diff --git a/dev_docs/assets/ml_csv_upload.png b/dev_docs/assets/ml_csv_upload.png new file mode 100644 index 0000000000000..dd82e8d40fcc8 Binary files /dev/null and b/dev_docs/assets/ml_csv_upload.png differ diff --git a/dev_docs/assets/sample_data.png b/dev_docs/assets/sample_data.png new file mode 100644 index 0000000000000..69bf138f9a1d7 Binary files /dev/null and b/dev_docs/assets/sample_data.png differ diff --git a/dev_docs/tutorials/sample_data.mdx b/dev_docs/tutorials/sample_data.mdx new file mode 100644 index 0000000000000..75afaaaea6f32 --- /dev/null +++ b/dev_docs/tutorials/sample_data.mdx @@ -0,0 +1,33 @@ +--- +id: kibDevTutorialSampleData +slug: /kibana-dev-docs/tutorial/sample-data +title: Add sample data +summary: Learn how to add sample data to Kibana +date: 2021-04-26 +tags: ['kibana', 'onboarding', 'dev', 'architecture', 'tutorials'] +--- + +## Installation from the UI + +1. Navigate to the home page. +2. Click **Add data**. +3. Click on the **Sample data** tab. +4. Select a dataset by clicking on the **Add data** button. + +![Sample Data](../assets/sample_data.png) + +## CSV Upload + +1. Navigate to the **Machine Learning** application. +2. Click on the **Data Visualizer** tab. +3. Click on **Select file** in the **Import data** container. + +![CSV Upload](../assets/ml_csv_upload.png) + +## makelogs + +The makelogs script generates sample web server logs. Make sure Elasticsearch is running before running the script. + +```sh +node scripts/makelogs --auth : +``` \ No newline at end of file diff --git a/docs/user/dashboard/timelion.asciidoc b/docs/user/dashboard/timelion.asciidoc index ff71cd7b383bd..12d0169c13f66 100644 --- a/docs/user/dashboard/timelion.asciidoc +++ b/docs/user/dashboard/timelion.asciidoc @@ -4,7 +4,7 @@ Instead of using a visual editor to create charts, you define a graph by chaining functions together, using the *Timelion*-specific syntax. The syntax enables some features that classical point series charts don't offer, such as pulling data from different indices or data sources into one graph. -deprecated::[7.0.0,"*Timelion* is still supported. The *Timelion app* is deprecated in 7.0, replaced by dashboard features. In 8.0 and later, the *Timelion app* is removed from {kib}. To prepare for the removal of *Timelion app*, you must migrate *Timelion app* worksheets to a dashboard. For information on how to migrate *Timelion app* worksheets, refer to the link:https://www.elastic.co/guide/en/kibana/7.10/release-notes-7.10.0.html#deprecation-v7.10.0[7.10.0 Release Notes]."] +deprecated::[7.0.0,"*Timelion* is still supported. The *Timelion app* is deprecated in 7.0, replaced by dashboard features. In the last 7.x minor version and later, the *Timelion app* is removed from {kib}. To prepare for the removal of *Timelion app*, you must migrate *Timelion app* worksheets to a dashboard. For information on how to migrate *Timelion app* worksheets, refer to the link:https://www.elastic.co/guide/en/kibana/7.10/release-notes-7.10.0.html#deprecation-v7.10.0[7.10.0 Release Notes]."] [float] ==== Timelion expressions @@ -554,4 +554,4 @@ Save and add the panel to the dashboard. . Click *Save and return*. -For more information about *Timelion* conditions, refer to https://www.elastic.co/blog/timeseries-if-then-else-with-timelion[I have but one .condition()]. \ No newline at end of file +For more information about *Timelion* conditions, refer to https://www.elastic.co/blog/timeseries-if-then-else-with-timelion[I have but one .condition()]. diff --git a/package.json b/package.json index 8024ecafde769..57f03fe6f5b35 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set/npm_module", "@elastic/search-ui-app-search-connector": "^1.5.0", "@elastic/ui-ace": "0.2.3", + "@hapi/accept": "^5.0.2", "@hapi/boom": "^9.1.1", "@hapi/cookie": "^11.0.2", "@hapi/good-squeeze": "6.0.0", @@ -171,7 +172,6 @@ "JSONStream": "1.3.5", "abort-controller": "^3.0.0", "abortcontroller-polyfill": "^1.4.0", - "accept": "3.0.2", "ajv": "^6.12.4", "angular": "^1.8.0", "angular-aria": "^1.8.0", @@ -303,7 +303,6 @@ "object-hash": "^1.3.1", "object-path-immutable": "^3.1.1", "opn": "^5.5.0", - "oppsy": "^2.0.0", "p-limit": "^3.0.1", "p-map": "^4.0.0", "p-retry": "^4.2.0", @@ -489,7 +488,6 @@ "@testing-library/react": "^11.2.6", "@testing-library/react-hooks": "^5.1.1", "@testing-library/user-event": "^13.1.1", - "@types/accept": "3.1.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", "@types/archiver": "^5.1.0", diff --git a/packages/kbn-i18n/BUILD.bazel b/packages/kbn-i18n/BUILD.bazel index d71f7d78b1221..02f5874a69a83 100644 --- a/packages/kbn-i18n/BUILD.bazel +++ b/packages/kbn-i18n/BUILD.bazel @@ -85,10 +85,10 @@ ts_project( deps = DEPS, allow_js = True, declaration = True, - declaration_dir = "types", + declaration_dir = "target_types", declaration_map = True, incremental = True, - out_dir = "node", + out_dir = "target_node", source_map = True, root_dir = "src", tsconfig = ":tsconfig", @@ -102,38 +102,16 @@ ts_project( allow_js = True, declaration = False, incremental = True, - out_dir = "web", + out_dir = "target_web", source_map = True, root_dir = "src", tsconfig = ":tsconfig_browser", ) -filegroup( - name = "tsc_types", - srcs = [":tsc"], - output_group = "types", -) - -filegroup( - name = "target_files", - srcs = [ - ":tsc", - ":tsc_browser", - ":tsc_types", - ], -) - -pkg_npm( - name = "target", - deps = [ - ":target_files", - ], -) - js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = [":target"] + DEPS, + deps = [":tsc", ":tsc_browser"] + DEPS, package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) diff --git a/packages/kbn-i18n/angular/package.json b/packages/kbn-i18n/angular/package.json index 974058ec0ac91..11c842a9fc49b 100644 --- a/packages/kbn-i18n/angular/package.json +++ b/packages/kbn-i18n/angular/package.json @@ -1,5 +1,5 @@ { - "browser": "../target/web/angular", - "main": "../target/node/angular", - "types": "../target/types/angular/index.d.ts" + "browser": "../target_web/angular", + "main": "../target_node/angular", + "types": "../target_types/angular/index.d.ts" } \ No newline at end of file diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index 36b625b1097bf..d91b81a88e098 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -1,8 +1,8 @@ { "name": "@kbn/i18n", - "browser": "./target/web/browser.js", - "main": "./target/node/index.js", - "types": "./target/types/index.d.ts", + "browser": "./target_web/browser.js", + "main": "./target_node/index.js", + "types": "./target_types/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", "private": true diff --git a/packages/kbn-i18n/react/package.json b/packages/kbn-i18n/react/package.json index d4cf1a0a30f61..c29ddd45f084d 100644 --- a/packages/kbn-i18n/react/package.json +++ b/packages/kbn-i18n/react/package.json @@ -1,5 +1,5 @@ { - "browser": "../target/web/react", - "main": "../target/node/react", - "types": "../target/types/react/index.d.ts" + "browser": "../target_web/react", + "main": "../target_node/react", + "types": "../target_types/react/index.d.ts" } \ No newline at end of file diff --git a/packages/kbn-i18n/tsconfig.browser.json b/packages/kbn-i18n/tsconfig.browser.json index 9ee4aeed8da21..707e3294bf1e7 100644 --- a/packages/kbn-i18n/tsconfig.browser.json +++ b/packages/kbn-i18n/tsconfig.browser.json @@ -3,11 +3,12 @@ "compilerOptions": { "allowJs": true, "incremental": true, - "outDir": "./target/web", + "outDir": "./target_web", "declaration": false, "isolatedModules": true, "sourceMap": true, - "sourceRoot": "../../../../../packages/kbn-i18n/src" + "sourceRoot": "../../../../../packages/kbn-i18n/src", + "types": ["node"], }, "include": [ "src/**/*.ts", diff --git a/packages/kbn-i18n/tsconfig.json b/packages/kbn-i18n/tsconfig.json index ddb21915eac50..787e9b45123ac 100644 --- a/packages/kbn-i18n/tsconfig.json +++ b/packages/kbn-i18n/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "allowJs": true, "incremental": true, - "declarationDir": "./target/types", - "outDir": "./target/node", + "declarationDir": "./target_types", + "outDir": "./target_node", "declaration": true, "declarationMap": true, "sourceMap": true, diff --git a/src/core/server/core_app/bundle_routes/select_compressed_file.ts b/src/core/server/core_app/bundle_routes/select_compressed_file.ts index c7b071a9c3548..36c63f653618c 100644 --- a/src/core/server/core_app/bundle_routes/select_compressed_file.ts +++ b/src/core/server/core_app/bundle_routes/select_compressed_file.ts @@ -7,10 +7,10 @@ */ import { extname } from 'path'; -import Accept from 'accept'; +import Accept from '@hapi/accept'; import { open } from './fs'; -declare module 'accept' { +declare module '@hapi/accept' { // @types/accept does not include the `preferences` argument so we override the type to include it export function encodings(encodingHeader?: string, preferences?: string[]): string[]; } diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts index 0e51c886f7f30..cb7f5a000cefb 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts @@ -26,7 +26,8 @@ async function removeLogFile() { await asyncUnlink(logFilePath).catch(() => void 0); } -describe('migration from 7.7.2-xpack with 100k objects', () => { +// FAILING on 7.13: https://github.com/elastic/kibana/issues/96895 +describe.skip('migration from 7.7.2-xpack with 100k objects', () => { let esServer: kbnTestServer.TestElasticsearchUtils; let root: Root; let coreStart: InternalCoreStart; diff --git a/src/plugins/dashboard/common/index.ts b/src/plugins/dashboard/common/index.ts index 017b7d804c872..1ed5bfba3abb9 100644 --- a/src/plugins/dashboard/common/index.ts +++ b/src/plugins/dashboard/common/index.ts @@ -24,3 +24,7 @@ export { } from './types'; export { migratePanelsTo730 } from './migrate_to_730_panels'; + +export const UI_SETTINGS = { + ENABLE_LABS_UI: 'labs:dashboard:enable_ui', +}; diff --git a/src/plugins/dashboard/common/saved_dashboard_references.ts b/src/plugins/dashboard/common/saved_dashboard_references.ts index 9f0858759d0d9..9757415a7bc36 100644 --- a/src/plugins/dashboard/common/saved_dashboard_references.ts +++ b/src/plugins/dashboard/common/saved_dashboard_references.ts @@ -81,6 +81,9 @@ export function extractReferences( } const { panels, state } = dashboardAttributesToState(attributes); + if (!Array.isArray(panels)) { + return { attributes, references }; + } if (((panels as unknown) as Array>).some(isPre730Panel)) { return pre730ExtractReferences({ attributes, references }, deps); diff --git a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx index 1156bf8c6e0d1..0f7acfbb3f5f6 100644 --- a/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/add_to_library_action.test.tsx @@ -14,6 +14,7 @@ import { CoreStart } from 'kibana/public'; import { coreMock, uiSettingsServiceMock } from '../../../../../core/public/mocks'; import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; +import { getStubPluginServices } from '../../../../presentation_util/public'; import { EmbeddableInput, @@ -63,6 +64,7 @@ beforeEach(async () => { uiActions: {} as any, uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, + presentationUtil: getStubPluginServices(), }; container = new DashboardContainer(getSampleDashboardInput(), containerOptions); diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx index 5d3527431a048..08e115ffca908 100644 --- a/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.test.tsx @@ -21,6 +21,7 @@ import { CONTACT_CARD_EMBEDDABLE, } from '../../services/embeddable_test_samples'; import { ErrorEmbeddable, IContainer, isErrorEmbeddable } from '../../services/embeddable'; +import { getStubPluginServices } from '../../../../presentation_util/public'; const { setup, doStart } = embeddablePluginMock.createInstance(); setup.registerEmbeddableFactory( @@ -53,6 +54,7 @@ beforeEach(async () => { uiActions: {} as any, uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, + presentationUtil: getStubPluginServices(), }; const input = getSampleDashboardInput({ panels: { diff --git a/src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx index 280e56a230dac..48bb787116862 100644 --- a/src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx @@ -12,6 +12,8 @@ import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helper import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; import { isErrorEmbeddable } from '../../services/embeddable'; +import { getStubPluginServices } from '../../../../presentation_util/public'; + import { CONTACT_CARD_EMBEDDABLE, ContactCardEmbeddableFactory, @@ -45,6 +47,7 @@ beforeEach(async () => { uiActions: {} as any, uiSettings: uiSettingsServiceMock.createStartContract(), http: coreMock.createStart().http, + presentationUtil: getStubPluginServices(), }; const input = getSampleDashboardInput({ panels: { diff --git a/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx b/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx index d82e8cbe9d2e5..20144b47e474b 100644 --- a/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/export_csv_action.test.tsx @@ -24,6 +24,7 @@ import { embeddablePluginMock } from '../../../../embeddable/public/mocks'; import { DataPublicPluginStart } from '../../../../data/public/types'; import { dataPluginMock } from '../../../../data/public/mocks'; import { LINE_FEED_CHARACTER } from 'src/plugins/data/common/exports/export_csv'; +import { getStubPluginServices } from '../../../../presentation_util/public'; describe('Export CSV action', () => { const { setup, doStart } = embeddablePluginMock.createInstance(); @@ -59,6 +60,7 @@ describe('Export CSV action', () => { uiActions: {} as any, uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, + presentationUtil: getStubPluginServices(), }; const input = getSampleDashboardInput({ panels: { diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx index 5958563c35b15..3d001913f4c75 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_action.test.tsx @@ -27,6 +27,7 @@ import { ContactCardEmbeddableOutput, CONTACT_CARD_EMBEDDABLE, } from '../../services/embeddable_test_samples'; +import { getStubPluginServices } from '../../../../presentation_util/public'; const { setup, doStart } = embeddablePluginMock.createInstance(); setup.registerEmbeddableFactory( @@ -60,6 +61,7 @@ beforeEach(async () => { uiActions: {} as any, uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, + presentationUtil: getStubPluginServices(), }; container = new DashboardContainer(getSampleDashboardInput(), containerOptions); diff --git a/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx b/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx index 00de58f8954e0..3204a0b38fc84 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx @@ -27,6 +27,7 @@ import { ContactCardEmbeddableOutput, ContactCardEmbeddable, } from '../../services/embeddable_test_samples'; +import { getStubPluginServices } from '../../../../presentation_util/public'; describe('LibraryNotificationPopover', () => { const { setup, doStart } = embeddablePluginMock.createInstance(); @@ -55,6 +56,7 @@ describe('LibraryNotificationPopover', () => { uiActions: {} as any, uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, + presentationUtil: getStubPluginServices(), }; container = new DashboardContainer(getSampleDashboardInput(), containerOptions); diff --git a/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx index 600fca28eb815..c8fe39f63fa23 100644 --- a/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx @@ -21,6 +21,7 @@ import { ContactCardEmbeddableInput, ContactCardEmbeddableOutput, } from '../../services/embeddable_test_samples'; +import { getStubPluginServices } from '../../../../presentation_util/public'; const { setup, doStart } = embeddablePluginMock.createInstance(); setup.registerEmbeddableFactory( @@ -46,6 +47,7 @@ beforeEach(async () => { uiActions: {} as any, uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, + presentationUtil: getStubPluginServices(), }; const input = getSampleDashboardInput({ panels: { diff --git a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx index a119f3364df4f..daa21b034f7c2 100644 --- a/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/unlink_from_library_action.test.tsx @@ -29,6 +29,7 @@ import { ContactCardEmbeddableOutput, CONTACT_CARD_EMBEDDABLE, } from '../../services/embeddable_test_samples'; +import { getStubPluginServices } from '../../../../presentation_util/public'; const { setup, doStart } = embeddablePluginMock.createInstance(); setup.registerEmbeddableFactory( @@ -55,6 +56,7 @@ beforeEach(async () => { uiActions: {} as any, uiSettings: uiSettingsServiceMock.createStartContract(), http: coreStart.http, + presentationUtil: getStubPluginServices(), }; container = new DashboardContainer(getSampleDashboardInput(), containerOptions); diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index 9050aee5ce38c..d5eddf6bb4864 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -30,6 +30,7 @@ import { import { createKbnUrlStateStorage, withNotifyOnErrors } from '../services/kibana_utils'; import { KibanaContextProvider } from '../services/kibana_react'; + import { AppMountParameters, CoreSetup, @@ -81,6 +82,7 @@ export async function mountApp({ kibanaLegacy: { dashboardConfig }, savedObjectsTaggingOss, visualizations, + presentationUtil, } = pluginsStart; const spacesApi = pluginsStart.spacesOss?.isSpacesAvailable ? pluginsStart.spacesOss : undefined; @@ -208,22 +210,24 @@ export async function mountApp({ const app = ( - - - - - - - - - - + + + + + + + + + + + + ); diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx index 19c541303fd03..41054b377d22e 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx @@ -38,6 +38,9 @@ import { } from '../../../../../core/public/mocks'; import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { uiActionsPluginMock } from '../../../../ui_actions/public/mocks'; +import { getStubPluginServices } from '../../../../presentation_util/public'; + +const presentationUtil = getStubPluginServices(); const options: DashboardContainerServices = { application: {} as any, @@ -50,6 +53,7 @@ const options: DashboardContainerServices = { uiActions: {} as any, uiSettings: uiSettingsServiceMock.createStartContract(), http: coreMock.createStart().http, + presentationUtil, }; beforeEach(() => { @@ -233,17 +237,19 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => { const component = mount( - Promise.resolve([])} - getAllEmbeddableFactories={(() => []) as any} - getEmbeddableFactory={(() => null) as any} - notifications={{} as any} - application={options.application} - overlays={{} as any} - inspector={inspector} - SavedObjectFinder={() => null} - /> + + Promise.resolve([])} + getAllEmbeddableFactories={(() => []) as any} + getEmbeddableFactory={(() => null) as any} + notifications={{} as any} + application={options.application} + overlays={{} as any} + inspector={inspector} + SavedObjectFinder={() => null} + /> + ); diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 9013a832d27a4..92b0727d2458c 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -38,6 +38,7 @@ import { import { PLACEHOLDER_EMBEDDABLE } from './placeholder'; import { PanelPlacementMethod, IPanelPlacementArgs } from './panel/dashboard_panel_placement'; import { DashboardCapabilities } from '../types'; +import { PresentationUtilPluginStart } from '../../services/presentation_util'; export interface DashboardContainerInput extends ContainerInput { dashboardCapabilities?: DashboardCapabilities; @@ -68,6 +69,7 @@ export interface DashboardContainerServices { embeddable: EmbeddableStart; uiActions: UiActionsStart; http: CoreStart['http']; + presentationUtil: PresentationUtilPluginStart; } interface IndexSignature { @@ -245,7 +247,9 @@ export class DashboardContainer extends Container - + + + , dom diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx index 9c82d03396a48..991033b9a0d6a 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx @@ -22,8 +22,10 @@ import { ContactCardEmbeddableFactory, } from '../../../services/embeddable_test_samples'; import { coreMock, uiSettingsServiceMock } from '../../../../../../core/public/mocks'; +import { getStubPluginServices } from '../../../../../presentation_util/public'; let dashboardContainer: DashboardContainer | undefined; +const presentationUtil = getStubPluginServices(); function prepare(props?: Partial) { const { setup, doStart } = embeddablePluginMock.createInstance(); @@ -68,6 +70,7 @@ function prepare(props?: Partial) { } as any, uiSettings: uiSettingsServiceMock.createStartContract(), http: coreMock.createStart().http, + presentationUtil, }; dashboardContainer = new DashboardContainer(initialInput, options); const defaultTestProps: DashboardGridProps = { @@ -96,7 +99,9 @@ test('renders DashboardGrid', () => { const { props, options } = prepare(); const component = mountWithIntl( - + + + ); const panelElements = component.find('EmbeddableChildPanel'); @@ -107,7 +112,9 @@ test('renders DashboardGrid with no visualizations', () => { const { props, options } = prepare(); const component = mountWithIntl( - + + + ); @@ -120,7 +127,9 @@ test('DashboardGrid removes panel when removed from container', () => { const { props, options } = prepare(); const component = mountWithIntl( - + + + ); @@ -137,7 +146,9 @@ test('DashboardGrid renders expanded panel', () => { const { props, options } = prepare(); const component = mountWithIntl( - + + + ); @@ -163,7 +174,9 @@ test('DashboardGrid unmount unsubscribes', async (done) => { const { props, options } = prepare(); const component = mountWithIntl( - + + + ); 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 ee9b633dfdcbf..5d492c049fdad 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx @@ -19,11 +19,12 @@ import React from 'react'; import { Subscription } from 'rxjs'; import ReactGridLayout, { Layout } from 'react-grid-layout'; import { GridData } from '../../../../common'; -import { ViewMode, EmbeddableChildPanel } from '../../../services/embeddable'; +import { ViewMode } from '../../../services/embeddable'; import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants'; import { DashboardPanelState } from '../types'; import { withKibana } from '../../../services/kibana_react'; import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; +import { DashboardGridItem } from './dashboard_grid_item'; let lastValidGridSize = 0; @@ -123,9 +124,6 @@ interface PanelLayout extends Layout { class DashboardGridUi extends React.Component { private subscription?: Subscription; private mounted: boolean = false; - // A mapping of panelIndexes to grid items so we can set the zIndex appropriately on the last focused - // item. - private gridItems = {} as { [key: string]: HTMLDivElement | null }; constructor(props: DashboardGridProps) { super(props); @@ -222,13 +220,20 @@ class DashboardGridUi extends React.Component { } }; - public renderPanels() { - const { focusedPanelIndex, panels, expandedPanelId } = this.state; + public render() { + if (this.state.isLayoutInvalid) { + return null; + } + + const { container, kibana } = this.props; + const { focusedPanelIndex, panels, expandedPanelId, viewMode } = this.state; + const isViewMode = viewMode === ViewMode.VIEW; // Part of our unofficial API - need to render in a consistent order for plugins. const panelsInOrder = Object.keys(panels).map( (key: string) => panels[key] as DashboardPanelState ); + panelsInOrder.sort((panelA, panelB) => { if (panelA.gridData.y === panelB.gridData.y) { return panelA.gridData.x - panelB.gridData.x; @@ -237,55 +242,27 @@ class DashboardGridUi extends React.Component { } }); - return _.map(panelsInOrder, (panel) => { - const expandPanel = - expandedPanelId !== undefined && expandedPanelId === panel.explicitInput.id; - const hidePanel = expandedPanelId !== undefined && expandedPanelId !== panel.explicitInput.id; - const classes = classNames({ - // eslint-disable-next-line @typescript-eslint/naming-convention - 'dshDashboardGrid__item--expanded': expandPanel, - // eslint-disable-next-line @typescript-eslint/naming-convention - 'dshDashboardGrid__item--hidden': hidePanel, - }); - return ( -
{ - this.gridItems[panel.explicitInput.id] = reactGridItem; - }} - > - -
- ); - }); - } - - public render() { - if (this.state.isLayoutInvalid) { - return null; - } + const dashboardPanels = _.map(panelsInOrder, ({ explicitInput, type }) => ( + + )); - const { viewMode } = this.state; - const isViewMode = viewMode === ViewMode.VIEW; return ( - {this.renderPanels()} + {dashboardPanels} ); } diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid_item.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid_item.tsx new file mode 100644 index 0000000000000..2054db6836dd5 --- /dev/null +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid_item.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useRef, useEffect, FC } from 'react'; +import { EuiLoadingChart } from '@elastic/eui'; +import classNames from 'classnames'; + +import { EmbeddableChildPanel } from '../../../services/embeddable'; +import { useLabs } from '../../../services/presentation_util'; +import { DashboardPanelState } from '../types'; + +type PanelProps = Pick; +type DivProps = Pick, 'className' | 'style' | 'children'>; + +interface Props extends PanelProps, DivProps { + id: DashboardPanelState['explicitInput']['id']; + type: DashboardPanelState['type']; + focusedPanelId?: string; + expandedPanelId?: string; + key: string; + isRenderable?: boolean; +} + +const Item = React.forwardRef( + ( + { + container, + expandedPanelId, + focusedPanelId, + id, + PanelComponent, + type, + isRenderable = true, + // The props below are passed from ReactGridLayoutn and need to be merged with their counterparts. + // https://github.com/react-grid-layout/react-grid-layout/issues/1241#issuecomment-658306889 + children, + className, + style, + ...rest + }, + ref + ) => { + const expandPanel = expandedPanelId !== undefined && expandedPanelId === id; + const hidePanel = expandedPanelId !== undefined && expandedPanelId !== id; + const classes = classNames({ + // eslint-disable-next-line @typescript-eslint/naming-convention + 'dshDashboardGrid__item--expanded': expandPanel, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'dshDashboardGrid__item--hidden': hidePanel, + }); + + return ( +
+ {isRenderable ? ( + <> + + {children} + + ) : ( +
+ +
+ )} +
+ ); + } +); + +export const ObservedItem: FC = (props: Props) => { + const [intersection, updateIntersection] = useState(); + const [isRenderable, setIsRenderable] = useState(false); + const panelRef = useRef(null); + + const observerRef = useRef( + new window.IntersectionObserver(([value]) => updateIntersection(value), { + root: panelRef.current, + }) + ); + + useEffect(() => { + const { current: currentObserver } = observerRef; + currentObserver.disconnect(); + const { current } = panelRef; + + if (current) { + currentObserver.observe(current); + } + + return () => currentObserver.disconnect(); + }, [panelRef]); + + useEffect(() => { + if (intersection?.isIntersecting && !isRenderable) { + setIsRenderable(true); + } + }, [intersection, isRenderable]); + + return ; +}; + +export const DashboardGridItem: FC = (props: Props) => { + const { isProjectEnabled } = useLabs(); + const isEnabled = isProjectEnabled('labs:dashboard:deferBelowFold'); + + return isEnabled ? : ; +}; diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index 093f09efa3780..690d1b177cdb5 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -26,8 +26,10 @@ import { ContactCardEmbeddableFactory, CONTACT_CARD_EMBEDDABLE, } from '../../../../../embeddable/public/lib/test_samples'; +import { getStubPluginServices } from '../../../../../presentation_util/public'; let dashboardContainer: DashboardContainer | undefined; +const presentationUtil = getStubPluginServices(); const ExitFullScreenButton = () =>
EXIT
; @@ -61,6 +63,7 @@ function getProps( uiActions: { getTriggerCompatibleActions: (() => []) as any, } as any, + presentationUtil, }; const input = getSampleDashboardInput({ @@ -94,7 +97,9 @@ test('renders DashboardViewport', () => { const component = mount( - + + + ); @@ -108,7 +113,9 @@ test('renders DashboardViewport with no visualizations', () => { const component = mount( - + + + ); @@ -124,7 +131,9 @@ test('renders DashboardEmptyScreen', () => { const component = mount( - + + + ); @@ -140,7 +149,9 @@ test('renders exit full screen button when in full screen mode', async () => { const component = mount( - + + + ); @@ -166,7 +177,9 @@ test('renders exit full screen button when in full screen mode and empty screen' const component = mount( - + + + ); @@ -190,7 +203,9 @@ test('DashboardViewport unmount unsubscribes', async (done) => { const component = mount( - + + + ); diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index 02403999cd75c..e1a62fe980f55 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -13,6 +13,7 @@ import angular from 'angular'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import UseUnmount from 'react-use/lib/useUnmount'; +import { UI_SETTINGS } from '../../../common'; import { BaseVisType, VisTypeAlias } from '../../../../visualizations/public'; import { AddFromLibraryButton, @@ -30,6 +31,7 @@ import { SaveResult, showSaveModal, } from '../../services/saved_objects'; +import { LazyLabsFlyout, withSuspense } from '../../../../presentation_util/public'; import { NavAction } from '../../types'; import { DashboardSavedObject } from '../..'; @@ -76,6 +78,8 @@ export interface DashboardTopNavProps { viewMode: ViewMode; } +const Flyout = withSuspense(LazyLabsFlyout, null); + export function DashboardTopNav({ dashboardStateManager, clearUnsavedChanges, @@ -109,11 +113,13 @@ export function DashboardTopNav({ const [state, setState] = useState({ chromeIsVisible: false }); const [isSaveInProgress, setIsSaveInProgress] = useState(false); + const [isLabsShown, setIsLabsShown] = useState(false); const lensAlias = visualizations.getAliases().find(({ name }) => name === 'lens'); const quickButtonVisTypes = ['markdown', 'maps']; const stateTransferService = embeddable.getStateTransfer(); const IS_DARK_THEME = uiSettings.get('theme:darkMode'); + const isLabsEnabled = uiSettings.get(UI_SETTINGS.ENABLE_LABS_UI); const trackUiMetric = usageCollection?.reportUiCounter.bind( usageCollection, @@ -489,6 +495,12 @@ export function DashboardTopNav({ dashboardCapabilities, }); } + + if (isLabsEnabled) { + actions[TopNavIds.LABS] = () => { + setIsLabsShown(!isLabsShown); + }; + } return actions; }, [ dashboardCapabilities, @@ -499,6 +511,8 @@ export function DashboardTopNav({ runSave, runQuickSave, share, + isLabsEnabled, + isLabsShown, ]); UseUnmount(() => { @@ -528,6 +542,7 @@ export function DashboardTopNav({ isNewDashboard: !savedDashboard.id, isDirty: dashboardStateManager.getIsDirty(timefilter), isSaveInProgress, + isLabsEnabled, }); const badges = unsavedChanges @@ -620,6 +635,9 @@ export function DashboardTopNav({ return ( <> + {isLabsEnabled && isLabsShown ? ( + setIsLabsShown(false)} /> + ) : null} {viewMode !== ViewMode.VIEW ? ( <> diff --git a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts index da14f98468256..a47c32750fdb0 100644 --- a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts +++ b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts @@ -26,16 +26,20 @@ export function getTopNavConfig( isNewDashboard: boolean; isDirty: boolean; isSaveInProgress?: boolean; + isLabsEnabled?: boolean; } ) { + const labs = options.isLabsEnabled ? [getLabsConfig(actions[TopNavIds.LABS])] : []; switch (dashboardMode) { case ViewMode.VIEW: return options.hideWriteControls ? [ + ...labs, getFullScreenConfig(actions[TopNavIds.FULL_SCREEN]), getShareConfig(actions[TopNavIds.SHARE]), ] : [ + ...labs, getFullScreenConfig(actions[TopNavIds.FULL_SCREEN]), getShareConfig(actions[TopNavIds.SHARE]), getCloneConfig(actions[TopNavIds.CLONE]), @@ -44,6 +48,7 @@ export function getTopNavConfig( case ViewMode.EDIT: const disableButton = options.isSaveInProgress; const navItems: TopNavMenuData[] = [ + ...labs, getOptionsConfig(actions[TopNavIds.OPTIONS], disableButton), getShareConfig(actions[TopNavIds.SHARE], disableButton), ]; @@ -91,6 +96,20 @@ function getFullScreenConfig(action: NavAction) { }; } +function getLabsConfig(action: NavAction) { + return { + id: 'labs', + label: i18n.translate('dashboard.topNav.labsButtonAriaLabel', { + defaultMessage: 'labs', + }), + description: i18n.translate('dashboard.topNav.labsConfigDescription', { + defaultMessage: 'Labs', + }), + testId: 'dashboardLabs', + run: action, + }; +} + /** * @returns {kbnTopNavConfig} */ diff --git a/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts b/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts index ee3d08e2330ae..8f2f580dbdd3c 100644 --- a/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts +++ b/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts @@ -15,4 +15,5 @@ export const TopNavIds = { ENTER_EDIT_MODE: 'enterEditMode', CLONE: 'clone', FULL_SCREEN: 'fullScreenMode', + LABS: 'labs', }; diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 0c4ef8c58f949..230918399d88f 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -145,15 +145,7 @@ export class DashboardPlugin public setup( core: CoreSetup, - { - share, - uiActions, - embeddable, - home, - urlForwarding, - data, - usageCollection, - }: DashboardSetupDependencies + { share, embeddable, home, urlForwarding, data, usageCollection }: DashboardSetupDependencies ): DashboardSetup { this.dashboardFeatureFlagConfig = this.initializerContext.config.get(); const startServices = core.getStartServices(); @@ -208,6 +200,7 @@ export class DashboardPlugin inspector: deps.inspector, http: coreStart.http, ExitFullScreenButton, + presentationUtil: deps.presentationUtil, }; }; diff --git a/src/plugins/dashboard/public/services/presentation_util.ts b/src/plugins/dashboard/public/services/presentation_util.ts index d3e6c1ebe9eec..17bc97db9ac63 100644 --- a/src/plugins/dashboard/public/services/presentation_util.ts +++ b/src/plugins/dashboard/public/services/presentation_util.ts @@ -10,4 +10,5 @@ export { PresentationUtilPluginStart, LazyDashboardPicker, withSuspense, + useLabs, } from '../../../presentation_util/public'; diff --git a/src/plugins/dashboard/server/plugin.ts b/src/plugins/dashboard/server/plugin.ts index fbed98a882b0a..6c1eea29f5297 100644 --- a/src/plugins/dashboard/server/plugin.ts +++ b/src/plugins/dashboard/server/plugin.ts @@ -22,6 +22,7 @@ import { EmbeddableSetup } from '../../embeddable/server'; import { UsageCollectionSetup } from '../../usage_collection/server'; import { registerDashboardUsageCollector } from './usage/register_collector'; import { dashboardPersistableStateServiceFactory } from './embeddable/dashboard_container_embeddable_factory'; +import { getUISettings } from './ui_settings'; interface SetupDeps { embeddable: EmbeddableSetup; @@ -54,6 +55,8 @@ export class DashboardPlugin dashboardPersistableStateServiceFactory(plugins.embeddable) ); + core.uiSettings.register(getUISettings()); + return {}; } diff --git a/src/plugins/dashboard/server/ui_settings.ts b/src/plugins/dashboard/server/ui_settings.ts new file mode 100644 index 0000000000000..34cfff0e4ef47 --- /dev/null +++ b/src/plugins/dashboard/server/ui_settings.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { schema } from '@kbn/config-schema'; +import { SETTING_CATEGORY } from '../../../../src/plugins/presentation_util/server'; +import { UiSettingsParams } from '../../../../src/core/types'; +import { UI_SETTINGS } from '../common'; + +/** + * uiSettings definitions for Dashboard. + */ +export const getUISettings = (): Record> => ({ + [UI_SETTINGS.ENABLE_LABS_UI]: { + name: i18n.translate('dashboard.labs.enableUI', { + defaultMessage: 'Enable labs button in Dashboard', + }), + description: i18n.translate('dashboard.labs.enableUnifiedToolbarProjectDescription', { + defaultMessage: + 'This flag determines if the viewer has access to the Labs button, a quick way to enable and disable experimental features in Dashboard.', + }), + value: false, + type: 'boolean', + schema: schema.boolean(), + category: [SETTING_CATEGORY], + requiresPageReload: true, + }, +}); diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts index 58ddf1eb7ba25..1d6956fc80920 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts @@ -6,27 +6,17 @@ * Side Public License, v 1. */ -import { find, template } from 'lodash'; +import { find } from 'lodash'; import $ from 'jquery'; import openRowHtml from './table_row/open.html'; import detailsHtml from './table_row/details.html'; import { dispatchRenderComplete } from '../../../../../../kibana_utils/public'; import { DOC_HIDE_TIME_COLUMN_SETTING } from '../../../../../common'; -import cellTemplateHtml from '../components/table_row/cell.html'; -import truncateByHeightTemplateHtml from '../components/table_row/truncate_by_height.html'; import { getServices } from '../../../../kibana_services'; import { getContextUrl } from '../../../helpers/get_context_url'; import { formatRow, formatTopLevelObject } from '../../helpers'; - -const TAGS_WITH_WS = />\s+<'); -} +import { truncateByHeight } from './table_row/truncate_by_height'; +import { cell } from './table_row/cell'; // guesstimate at the minimum number of chars wide cells in the table should be const MIN_LINE_LENGTH = 20; @@ -37,9 +27,6 @@ interface LazyScope extends ng.IScope { } export function createTableRowDirective($compile: ng.ICompileService) { - const cellTemplate = template(noWhiteSpace(cellTemplateHtml)); - const truncateByHeightTemplate = template(noWhiteSpace(truncateByHeightTemplateHtml)); - return { restrict: 'A', scope: { @@ -133,7 +120,7 @@ export function createTableRowDirective($compile: ng.ICompileService) { const hideTimeColumn = getServices().uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false); if (indexPattern.timeFieldName && !hideTimeColumn) { newHtmls.push( - cellTemplate({ + cell({ timefield: true, formatted: _displayField(row, indexPattern.timeFieldName), filterable: mapping(indexPattern.timeFieldName).filterable && $scope.filter, @@ -146,7 +133,7 @@ export function createTableRowDirective($compile: ng.ICompileService) { const formatted = formatRow(row, indexPattern); newHtmls.push( - cellTemplate({ + cell({ timefield: false, sourcefield: true, formatted, @@ -164,7 +151,7 @@ export function createTableRowDirective($compile: ng.ICompileService) { }) ); newHtmls.push( - cellTemplate({ + cell({ timefield: false, sourcefield: true, formatted: formatTopLevelObject(row, innerColumns, indexPattern), @@ -174,7 +161,7 @@ export function createTableRowDirective($compile: ng.ICompileService) { ); } else { newHtmls.push( - cellTemplate({ + cell({ timefield: false, sourcefield: column === '_source', formatted: _displayField(row, column, true), @@ -191,8 +178,8 @@ export function createTableRowDirective($compile: ng.ICompileService) { const $cell = $cells.eq(i); if ($cell.data('discover:html') === html) return; - const reuse = find($cells.slice(i + 1), (cell) => { - return $.data(cell, 'discover:html') === html; + const reuse = find($cells.slice(i + 1), (c) => { + return $.data(c, 'discover:html') === html; }); const $target = reuse ? $(reuse).detach() : $(html); @@ -231,7 +218,7 @@ export function createTableRowDirective($compile: ng.ICompileService) { const text = indexPattern.formatField(row, fieldName); if (truncate && text.length > MIN_LINE_LENGTH) { - return truncateByHeightTemplate({ + return truncateByHeight({ body: text, }); } diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.test.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.test.ts new file mode 100644 index 0000000000000..c6d0d324b9bc2 --- /dev/null +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.test.ts @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { cell } from './cell'; + +describe('cell renderer', () => { + it('renders a cell without filter buttons if it is not filterable', () => { + expect( + cell({ + filterable: false, + column: 'foo', + timefield: true, + sourcefield: false, + formatted: 'formatted content', + }) + ).toMatchInlineSnapshot(` + "formatted content + " + `); + }); + + it('renders a cell with filter buttons if it is filterable', () => { + expect( + cell({ + filterable: true, + column: 'foo', + timefield: true, + sourcefield: false, + formatted: 'formatted content', + }) + ).toMatchInlineSnapshot(` + "formatted content + " + `); + }); + + it('renders a sourcefield', () => { + expect( + cell({ + filterable: false, + column: 'foo', + timefield: false, + sourcefield: true, + formatted: 'formatted content', + }) + ).toMatchInlineSnapshot(` + "formatted content + " + `); + }); + + it('renders a field that is neither a timefield or sourcefield', () => { + expect( + cell({ + filterable: false, + column: 'foo', + timefield: false, + sourcefield: false, + formatted: 'formatted content', + }) + ).toMatchInlineSnapshot(` + "formatted content + " + `); + }); + + it('renders the "formatted" contents without any manipulation', () => { + expect( + cell({ + filterable: false, + column: 'foo', + timefield: true, + sourcefield: false, + formatted: + '
 hey you can put HTML & stuff in here 
', + }) + ).toMatchInlineSnapshot(` + "
 hey you can put HTML & stuff in here 
+ " + `); + }); + + it('escapes the contents of "column" within the "data-column" attribute', () => { + expect( + cell({ + filterable: true, + column: '', + timefield: true, + sourcefield: false, + formatted: 'formatted content', + }) + ).toMatchInlineSnapshot(` + "formatted content + " + `); + }); +}); diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.ts new file mode 100644 index 0000000000000..8138e0f4a4fd8 --- /dev/null +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { escape } from 'lodash'; +import cellWithFilters from './cell_with_buttons.html'; +import cellWithoutFilters from './cell_without_buttons.html'; + +const TAGS_WITH_WS = />\s+<'); +} + +const cellWithFiltersTemplate = noWhiteSpace(cellWithFilters); +const cellWithoutFiltersTemplate = noWhiteSpace(cellWithoutFilters); + +interface CellProps { + timefield: boolean; + sourcefield?: boolean; + formatted: string; + filterable: boolean; + column: string; +} + +export const cell = (props: CellProps) => { + let classes = ''; + let extraAttrs = ''; + if (props.timefield) { + classes = 'eui-textNoWrap'; + extraAttrs = 'width="1%"'; + } else if (props.sourcefield) { + classes = 'eui-textBreakAll eui-textBreakWord'; + } else { + classes = 'kbnDocTableCell__dataField eui-textBreakAll eui-textBreakWord'; + } + + if (props.filterable) { + const escapedColumnContents = escape(props.column); + return cellWithFiltersTemplate + .replace('__classes__', classes) + .replace('__extraAttrs__', extraAttrs) + .replace('__column__', escapedColumnContents) + .replace('__column__', escapedColumnContents) + .replace('', props.formatted); + } + return cellWithoutFiltersTemplate + .replace('__classes__', classes) + .replace('__extraAttrs__', extraAttrs) + .replace('', props.formatted); +}; diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_with_buttons.html similarity index 71% rename from src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.html rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_with_buttons.html index 0d17c2ca94cac..99c65e6034013 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.html +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_with_buttons.html @@ -1,21 +1,10 @@ -<% -var attributes = ''; -if (timefield) { - attributes='class="eui-textNoWrap" width="1%"'; -} else if (sourcefield) { - attributes='class="eui-textBreakAll eui-textBreakWord"'; -} else { - attributes='class="kbnDocTableCell__dataField eui-textBreakAll eui-textBreakWord"'; -} -%> - data-test-subj="docTableField"> - <%= formatted %> + + - <% if (filterable) { %> - <% } %> diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_without_buttons.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_without_buttons.html new file mode 100644 index 0000000000000..8dc33cbfb8353 --- /dev/null +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_without_buttons.html @@ -0,0 +1,4 @@ + + + + diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.html deleted file mode 100644 index cf13f10e70060..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.html +++ /dev/null @@ -1,3 +0,0 @@ -
- <%= body %> -
\ No newline at end of file diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.test.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.test.ts new file mode 100644 index 0000000000000..70d8465589237 --- /dev/null +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { truncateByHeight } from './truncate_by_height'; + +describe('truncateByHeight', () => { + it('renders input without any formatting or escaping', () => { + expect( + truncateByHeight({ + body: + '
 hey you can put HTML & stuff in here 
', + }) + ).toMatchInlineSnapshot( + `"
 hey you can put HTML & stuff in here 
"` + ); + }); +}); diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.ts new file mode 100644 index 0000000000000..7eb31459eb4f5 --- /dev/null +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const truncateByHeight = ({ body }: { body: string }) => { + return `
${body}
`; +}; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 06d1cd290ffd5..f1592d5a8cf0b 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -444,4 +444,12 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'labs:dashboard:enable_ui': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, + 'labs:dashboard:deferBelowFold': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, }; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index dfbe6bd3e0485..570b52171be28 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -121,4 +121,6 @@ export interface UsageStats { 'labs:canvas:enable_ui': boolean; 'labs:canvas:useDataService': boolean; 'labs:presentation:timeToPresent': boolean; + 'labs:dashboard:enable_ui': boolean; + 'labs:dashboard:deferBelowFold': boolean; } diff --git a/src/plugins/presentation_util/common/labs.ts b/src/plugins/presentation_util/common/labs.ts index 902c22681e55e..7ca5272daa9c7 100644 --- a/src/plugins/presentation_util/common/labs.ts +++ b/src/plugins/presentation_util/common/labs.ts @@ -11,8 +11,9 @@ import { i18n } from '@kbn/i18n'; export const LABS_PROJECT_PREFIX = 'labs:'; export const USE_DATA_SERVICE = `${LABS_PROJECT_PREFIX}canvas:useDataService` as const; export const TIME_TO_PRESENT = `${LABS_PROJECT_PREFIX}presentation:timeToPresent` as const; +export const DEFER_BELOW_FOLD = `${LABS_PROJECT_PREFIX}dashboard:deferBelowFold` as const; -export const projectIDs = [TIME_TO_PRESENT, USE_DATA_SERVICE] as const; +export const projectIDs = [TIME_TO_PRESENT, USE_DATA_SERVICE, DEFER_BELOW_FOLD] as const; export const environmentNames = ['kibana', 'browser', 'session'] as const; export const solutionNames = ['canvas', 'dashboard', 'presentation'] as const; @@ -50,6 +51,20 @@ export const projects: { [ID in ProjectID]: ProjectConfig & { id: ID } } = { ), solutions: ['canvas'], }, + [DEFER_BELOW_FOLD]: { + id: DEFER_BELOW_FOLD, + isActive: false, + isDisplayed: true, + environments: ['kibana', 'browser', 'session'], + name: i18n.translate('presentationUtil.labs.enableDeferBelowFoldProjectName', { + defaultMessage: 'Defer loading below "the fold"', + }), + description: i18n.translate('presentationUtil.labs.enableDeferBelowFoldProjectDescription', { + defaultMessage: + 'Any Dashboard panels below the fold-- the area hidden beyond the bottom of the window, accessed by scrolling-- will not be loaded immediately, but only when they enter the viewport', + }), + solutions: ['dashboard'], + }, }; export type ProjectID = typeof projectIDs[number]; diff --git a/src/plugins/presentation_util/public/index.ts b/src/plugins/presentation_util/public/index.ts index aee3cff92438b..5ad81c7e759bc 100644 --- a/src/plugins/presentation_util/public/index.ts +++ b/src/plugins/presentation_util/public/index.ts @@ -12,6 +12,7 @@ export { PresentationCapabilitiesService, PresentationDashboardsService, PresentationLabsService, + getStubPluginServices, } from './services'; export { PresentationUtilPluginSetup, PresentationUtilPluginStart } from './types'; @@ -40,3 +41,7 @@ export { export function plugin() { return new PresentationUtilPlugin(); } + +import { pluginServices } from './services'; + +export const useLabs = () => (() => pluginServices.getHooks().labs.useService())(); diff --git a/src/plugins/presentation_util/public/services/index.ts b/src/plugins/presentation_util/public/services/index.ts index 30bab78aeb27b..d68779b129ca6 100644 --- a/src/plugins/presentation_util/public/services/index.ts +++ b/src/plugins/presentation_util/public/services/index.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +import { PresentationUtilPluginStart } from '../types'; import { PluginServices } from './create'; import { PresentationCapabilitiesService } from './capabilities'; import { PresentationDashboardsService } from './dashboards'; import { PresentationLabsService } from './labs'; +import { registry as stubRegistry } from './stub'; export { PresentationCapabilitiesService } from './capabilities'; export { PresentationDashboardsService } from './dashboards'; @@ -21,3 +23,11 @@ export interface PresentationUtilServices { } export const pluginServices = new PluginServices(); + +export const getStubPluginServices = (): PresentationUtilPluginStart => { + pluginServices.setRegistry(stubRegistry.start({})); + return { + ContextProvider: pluginServices.getContextProvider(), + labsService: pluginServices.getServices().labs, + }; +}; diff --git a/src/plugins/presentation_util/public/services/stub/labs.ts b/src/plugins/presentation_util/public/services/stub/labs.ts index aee7ce20bd86a..5192f5f090fec 100644 --- a/src/plugins/presentation_util/public/services/stub/labs.ts +++ b/src/plugins/presentation_util/public/services/stub/labs.ts @@ -20,10 +20,33 @@ import { PresentationLabsService, isEnabledByStorageValue, applyProjectStatus } export type LabsServiceFactory = PluginServiceFactory; +type Statuses = { + [id in ProjectID]: { + defaultValue: boolean; + session: boolean | null; + browser: boolean | null; + kibana: boolean; + }; +}; + export const labsServiceFactory: LabsServiceFactory = () => { + let statuses = {} as Statuses; + + const getProject = (id: ProjectID) => { + const project = projects[id]; + const value = statuses[id]; + const status = { + session: isEnabledByStorageValue(project, 'session', value.session), + browser: isEnabledByStorageValue(project, 'browser', value.browser), + kibana: isEnabledByStorageValue(project, 'kibana', value.kibana), + }; + + return applyProjectStatus(project, status); + }; + const reset = () => projectIDs.reduce((acc, id) => { - const project = getProject(id); + const project = projects[id]; const defaultValue = project.isActive; acc[id] = { @@ -33,9 +56,9 @@ export const labsServiceFactory: LabsServiceFactory = () => { kibana: defaultValue, }; return acc; - }, {} as { [id in ProjectID]: { defaultValue: boolean; session: boolean | null; browser: boolean | null; kibana: boolean } }); + }, {} as Statuses); - let statuses = reset(); + statuses = reset(); const getProjects = (solutions: SolutionName[] = []) => projectIDs.reduce((acc, id) => { @@ -49,21 +72,10 @@ export const labsServiceFactory: LabsServiceFactory = () => { return acc; }, {} as { [id in ProjectID]: Project }); - const getProject = (id: ProjectID) => { - const project = projects[id]; - const value = statuses[id]; - const status = { - session: isEnabledByStorageValue(project, 'session', value.session), - browser: isEnabledByStorageValue(project, 'browser', value.browser), - kibana: isEnabledByStorageValue(project, 'kibana', value.kibana), - }; - - return applyProjectStatus(project, status); - }; - const setProjectStatus = (id: ProjectID, env: EnvironmentName, value: boolean) => { statuses[id] = { ...statuses[id], [env]: value }; }; + const isProjectEnabled = (id: ProjectID) => getProject(id).status.isEnabled; return { diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 30706fb2bfc3c..230d2052f089e 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -8278,6 +8278,18 @@ "_meta": { "description": "Non-default value of setting." } + }, + "labs:dashboard:enable_ui": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, + "labs:dashboard:deferBelowFold": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } } } }, diff --git a/src/plugins/timelion/public/components/timelion_deprecation.tsx b/src/plugins/timelion/public/components/timelion_deprecation.tsx index 41ae09f305863..efcef88b3d0a2 100644 --- a/src/plugins/timelion/public/components/timelion_deprecation.tsx +++ b/src/plugins/timelion/public/components/timelion_deprecation.tsx @@ -19,7 +19,7 @@ export const TimelionDeprecation = ({ links }: DocLinksStart) => { title={ diff --git a/src/plugins/timelion/server/deprecations.ts b/src/plugins/timelion/server/deprecations.ts index 3d4e687f154cf..e65d72cb460df 100644 --- a/src/plugins/timelion/server/deprecations.ts +++ b/src/plugins/timelion/server/deprecations.ts @@ -30,7 +30,7 @@ export const showWarningMessageIfTimelionSheetWasFound = async ( const count = await getTimelionSheetsCount(savedObjectsClient); if (count > 0) { logger.warn( - 'Deprecated since 7.0, the Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard. See https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html.' + 'Deprecated since 7.0, the Timelion app will be removed in the last 7.x minor version. To continue using your Timelion worksheets, migrate them to a dashboard. See https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html.' ); } }; @@ -49,7 +49,7 @@ export async function getDeprecations({ if (count > 0) { deprecations.push({ - message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard.`, + message: `You have ${count} Timelion worksheets. The Timelion app will be removed in the last 7.x minor version. To continue using your Timelion worksheets, migrate them to a dashboard.`, documentationUrl: 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', level: 'warning', diff --git a/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap index c3ffc0dd08412..3ca2834a54fca 100644 --- a/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap +++ b/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap @@ -8,7 +8,7 @@ Object { "area", ], "visConfig": Array [ - "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", + "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"palette\\":{\\"name\\":\\"default\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", ], }, "getArgument": [Function], diff --git a/src/plugins/vis_type_vislib/public/vislib/partials/touchdown.tmpl.html b/src/plugins/vis_type_vislib/public/vislib/partials/touchdown.tmpl.html deleted file mode 100644 index ee95eef68f3b2..0000000000000 --- a/src/plugins/vis_type_vislib/public/vislib/partials/touchdown.tmpl.html +++ /dev/null @@ -1,7 +0,0 @@ -

- - - <%= wholeBucket ? 'Part of this bucket' : 'This area' %> - may contain partial data. The selected time range does not fully cover it. - -

diff --git a/src/plugins/vis_type_vislib/public/vislib/partials/touchdown_template.tsx b/src/plugins/vis_type_vislib/public/vislib/partials/touchdown_template.tsx new file mode 100644 index 0000000000000..55955da07ebdd --- /dev/null +++ b/src/plugins/vis_type_vislib/public/vislib/partials/touchdown_template.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import ReactDOM from 'react-dom/server'; + +interface Props { + wholeBucket: boolean; +} + +export const touchdownTemplate = ({ wholeBucket }: Props) => { + return ReactDOM.renderToStaticMarkup( +

+ + + {wholeBucket ? 'Part of this bucket' : 'This area'} may contain partial data. The selected + time range does not fully cover it. + +

+ ); +}; diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series.js index 28464009a8339..b4ab2ea2992c5 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series.js @@ -14,10 +14,10 @@ import { Tooltip } from '../components/tooltip'; import { Chart } from './_chart'; import { TimeMarker } from './time_marker'; import { seriesTypes } from './point_series/series_types'; -import touchdownTmplHtml from '../partials/touchdown.tmpl.html'; +import { touchdownTemplate } from '../partials/touchdown_template'; const seriTypes = seriesTypes; -const touchdownTmpl = _.template(touchdownTmplHtml); + /** * Line Chart Visualization * @@ -169,7 +169,7 @@ export class PointSeries extends Chart { } function textFormatter() { - return touchdownTmpl(callPlay(d3.event)); + return touchdownTemplate(callPlay(d3.event)); } const endzoneTT = new Tooltip('endzones', this.handler.el, textFormatter, null); diff --git a/src/plugins/vis_type_xy/common/index.ts b/src/plugins/vis_type_xy/common/index.ts index 903adb53eb403..a80946f7c62fa 100644 --- a/src/plugins/vis_type_xy/common/index.ts +++ b/src/plugins/vis_type_xy/common/index.ts @@ -6,17 +6,14 @@ * Side Public License, v 1. */ -import { $Values } from '@kbn/utility-types'; - /** * Type of charts able to render */ -export const ChartType = Object.freeze({ - Line: 'line' as const, - Area: 'area' as const, - Histogram: 'histogram' as const, -}); -export type ChartType = $Values; +export enum ChartType { + Line = 'line', + Area = 'area', + Histogram = 'histogram', +} /** * Type of xy visualizations diff --git a/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap index e6665c26a2815..7c21e699216bc 100644 --- a/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap +++ b/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap @@ -4,11 +4,70 @@ exports[`xy vis toExpressionAst function should match basic snapshot 1`] = ` Object { "addArgument": [Function], "arguments": Object { + "addLegend": Array [ + true, + ], + "addTimeMarker": Array [ + false, + ], + "addTooltip": Array [ + true, + ], + "categoryAxes": Array [ + Object { + "toAst": [Function], + }, + ], + "chartType": Array [ + "area", + ], + "gridCategoryLines": Array [ + false, + ], + "labels": Array [ + Object { + "toAst": [Function], + }, + ], + "legendPosition": Array [ + "top", + ], + "palette": Array [ + "default", + ], + "seriesDimension": Array [ + Object { + "toAst": [Function], + }, + ], + "seriesParams": Array [ + Object { + "toAst": [Function], + }, + ], + "thresholdLine": Array [ + Object { + "toAst": [Function], + }, + ], + "times": Array [], "type": Array [ "area", ], - "visConfig": Array [ - "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", + "valueAxes": Array [ + Object { + "toAst": [Function], + }, + ], + "xDimension": Array [ + Object { + "toAst": [Function], + }, + ], + "yDimension": Array [ + Object { + "toAst": [Function], + }, ], }, "getArgument": [Function], diff --git a/src/plugins/vis_type_xy/public/editor/common_config.tsx b/src/plugins/vis_type_xy/public/editor/common_config.tsx index 1e4ac7df0082c..1815d9cfc429d 100644 --- a/src/plugins/vis_type_xy/public/editor/common_config.tsx +++ b/src/plugins/vis_type_xy/public/editor/common_config.tsx @@ -9,11 +9,11 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { VisEditorOptionsProps } from '../../../visualizations/public'; +import type { VisEditorOptionsProps } from '../../../visualizations/public'; -import { VisParams } from '../types'; +import type { VisParams } from '../types'; import { MetricsAxisOptions, PointSeriesOptions } from './components/options'; -import { ValidationWrapper } from './components/common'; +import { ValidationWrapper } from './components/common/validation_wrapper'; export function getOptionTabs(showElasticChartsOptions = false) { return [ diff --git a/src/plugins/vis_type_xy/public/expression_functions/category_axis.ts b/src/plugins/vis_type_xy/public/expression_functions/category_axis.ts new file mode 100644 index 0000000000000..30215d8feb8a3 --- /dev/null +++ b/src/plugins/vis_type_xy/public/expression_functions/category_axis.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import type { + ExpressionFunctionDefinition, + Datatable, + ExpressionValueBoxed, +} from '../../../expressions/public'; +import type { CategoryAxis } from '../types'; +import type { ExpressionValueScale } from './vis_scale'; +import type { ExpressionValueLabel } from './label'; + +export interface Arguments extends Omit { + title?: string; + scale: ExpressionValueScale; + labels: ExpressionValueLabel; +} + +export type ExpressionValueCategoryAxis = ExpressionValueBoxed< + 'category_axis', + { + id: CategoryAxis['id']; + show: CategoryAxis['show']; + position: CategoryAxis['position']; + axisType: CategoryAxis['type']; + title: { + text?: string; + }; + labels: CategoryAxis['labels']; + scale: CategoryAxis['scale']; + } +>; + +export const categoryAxis = (): ExpressionFunctionDefinition< + 'categoryaxis', + Datatable | null, + Arguments, + ExpressionValueCategoryAxis +> => ({ + name: 'categoryaxis', + help: i18n.translate('visTypeXy.function.categoryAxis.help', { + defaultMessage: 'Generates category axis object', + }), + type: 'category_axis', + args: { + id: { + types: ['string'], + help: i18n.translate('visTypeXy.function.categoryAxis.id.help', { + defaultMessage: 'Id of category axis', + }), + required: true, + }, + show: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.categoryAxis.show.help', { + defaultMessage: 'Show the category axis', + }), + required: true, + }, + position: { + types: ['string'], + help: i18n.translate('visTypeXy.function.categoryAxis.position.help', { + defaultMessage: 'Position of the category axis', + }), + required: true, + }, + type: { + types: ['string'], + help: i18n.translate('visTypeXy.function.categoryAxis.type.help', { + defaultMessage: 'Type of the category axis. Can be category or value', + }), + required: true, + }, + title: { + types: ['string'], + help: i18n.translate('visTypeXy.function.categoryAxis.title.help', { + defaultMessage: 'Title of the category axis', + }), + }, + scale: { + types: ['vis_scale'], + help: i18n.translate('visTypeXy.function.categoryAxis.scale.help', { + defaultMessage: 'Scale config', + }), + }, + labels: { + types: ['label'], + help: i18n.translate('visTypeXy.function.categoryAxis.labels.help', { + defaultMessage: 'Axis label config', + }), + }, + }, + fn: (context, args) => { + return { + type: 'category_axis', + id: args.id, + show: args.show, + position: args.position, + axisType: args.type, + title: { + text: args.title, + }, + scale: { + ...args.scale, + type: args.scale.scaleType, + }, + labels: args.labels, + }; + }, +}); diff --git a/src/plugins/vis_type_xy/public/expression_functions/index.ts b/src/plugins/vis_type_xy/public/expression_functions/index.ts new file mode 100644 index 0000000000000..4e7db57ee65d7 --- /dev/null +++ b/src/plugins/vis_type_xy/public/expression_functions/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { visTypeXyVisFn } from './xy_vis_fn'; + +export { categoryAxis, ExpressionValueCategoryAxis } from './category_axis'; +export { timeMarker, ExpressionValueTimeMarker } from './time_marker'; +export { valueAxis, ExpressionValueValueAxis } from './value_axis'; +export { seriesParam, ExpressionValueSeriesParam } from './series_param'; +export { thresholdLine, ExpressionValueThresholdLine } from './threshold_line'; +export { label, ExpressionValueLabel } from './label'; +export { visScale, ExpressionValueScale } from './vis_scale'; +export { xyDimension, ExpressionValueXYDimension } from './xy_dimension'; diff --git a/src/plugins/vis_type_xy/public/expression_functions/label.ts b/src/plugins/vis_type_xy/public/expression_functions/label.ts new file mode 100644 index 0000000000000..934278d13cff0 --- /dev/null +++ b/src/plugins/vis_type_xy/public/expression_functions/label.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import type { Labels } from '../../../charts/public'; +import type { + ExpressionFunctionDefinition, + Datatable, + ExpressionValueBoxed, +} from '../../../expressions/public'; + +export type ExpressionValueLabel = ExpressionValueBoxed< + 'label', + { + color?: Labels['color']; + filter?: Labels['filter']; + overwriteColor?: Labels['overwriteColor']; + rotate?: Labels['rotate']; + show?: Labels['show']; + truncate?: Labels['truncate']; + } +>; + +export const label = (): ExpressionFunctionDefinition< + 'label', + Datatable | null, + Labels, + ExpressionValueLabel +> => ({ + name: 'label', + help: i18n.translate('visTypeXy.function.label.help', { + defaultMessage: 'Generates label object', + }), + type: 'label', + args: { + color: { + types: ['string'], + help: i18n.translate('visTypeXy.function.label.color.help', { + defaultMessage: 'Color of label', + }), + }, + filter: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.label.filter.help', { + defaultMessage: 'Hides overlapping labels and duplicates on axis', + }), + }, + overwriteColor: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.label.overwriteColor.help', { + defaultMessage: 'Overwrite color', + }), + }, + rotate: { + types: ['number'], + help: i18n.translate('visTypeXy.function.label.rotate.help', { + defaultMessage: 'Rotate angle', + }), + }, + show: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.label.show.help', { + defaultMessage: 'Show label', + }), + }, + truncate: { + types: ['number', 'null'], + help: i18n.translate('visTypeXy.function.label.truncate.help', { + defaultMessage: 'The number of symbols before truncating', + }), + }, + }, + fn: (context, args) => { + return { + type: 'label', + color: args.color, + filter: args.hasOwnProperty('filter') ? args.filter : undefined, + overwriteColor: args.overwriteColor, + rotate: args.rotate, + show: args.show, + truncate: args.truncate, + }; + }, +}); diff --git a/src/plugins/vis_type_xy/public/expression_functions/series_param.ts b/src/plugins/vis_type_xy/public/expression_functions/series_param.ts new file mode 100644 index 0000000000000..402187cea6586 --- /dev/null +++ b/src/plugins/vis_type_xy/public/expression_functions/series_param.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import type { + ExpressionFunctionDefinition, + Datatable, + ExpressionValueBoxed, +} from '../../../expressions/public'; +import type { SeriesParam } from '../types'; + +export interface Arguments extends Omit { + label: string; + id: string; +} + +export type ExpressionValueSeriesParam = ExpressionValueBoxed< + 'series_param', + { + data: { label: string; id: string }; + drawLinesBetweenPoints?: boolean; + interpolate?: SeriesParam['interpolate']; + lineWidth?: number; + mode: SeriesParam['mode']; + show: boolean; + showCircles: boolean; + seriesParamType: SeriesParam['type']; + valueAxis: string; + } +>; + +export const seriesParam = (): ExpressionFunctionDefinition< + 'seriesparam', + Datatable, + Arguments, + ExpressionValueSeriesParam +> => ({ + name: 'seriesparam', + help: i18n.translate('visTypeXy.function.seriesparam.help', { + defaultMessage: 'Generates series param object', + }), + type: 'series_param', + inputTypes: ['datatable'], + args: { + label: { + types: ['string'], + help: i18n.translate('visTypeXy.function.seriesParam.label.help', { + defaultMessage: 'Name of series param', + }), + required: true, + }, + id: { + types: ['string'], + help: i18n.translate('visTypeXy.function.seriesParam.id.help', { + defaultMessage: 'Id of series param', + }), + required: true, + }, + drawLinesBetweenPoints: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.seriesParam.drawLinesBetweenPoints.help', { + defaultMessage: 'Draw lines between points', + }), + }, + interpolate: { + types: ['string'], + help: i18n.translate('visTypeXy.function.seriesParam.interpolate.help', { + defaultMessage: 'Interpolate mode. Can be linear, cardinal or step-after', + }), + }, + show: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.seriesParam.show.help', { + defaultMessage: 'Show param', + }), + required: true, + }, + lineWidth: { + types: ['number'], + help: i18n.translate('visTypeXy.function.seriesParam.lineWidth.help', { + defaultMessage: 'Width of line', + }), + }, + mode: { + types: ['string'], + help: i18n.translate('visTypeXy.function.seriesParam.mode.help', { + defaultMessage: 'Chart mode. Can be stacked or percentage', + }), + }, + showCircles: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.seriesParam.showCircles.help', { + defaultMessage: 'Show circles', + }), + }, + type: { + types: ['string'], + help: i18n.translate('visTypeXy.function.seriesParam.type.help', { + defaultMessage: 'Chart type. Can be line, area or histogram', + }), + }, + valueAxis: { + types: ['string'], + help: i18n.translate('visTypeXy.function.seriesParam.valueAxis.help', { + defaultMessage: 'Name of value axis', + }), + }, + }, + fn: (context, args) => { + return { + type: 'series_param', + data: { label: args.label, id: args.id }, + drawLinesBetweenPoints: args.drawLinesBetweenPoints, + interpolate: args.interpolate, + lineWidth: args.lineWidth, + mode: args.mode, + show: args.show, + showCircles: args.showCircles, + seriesParamType: args.type, + valueAxis: args.valueAxis, + }; + }, +}); diff --git a/src/plugins/vis_type_xy/public/expression_functions/threshold_line.ts b/src/plugins/vis_type_xy/public/expression_functions/threshold_line.ts new file mode 100644 index 0000000000000..8c01e37503985 --- /dev/null +++ b/src/plugins/vis_type_xy/public/expression_functions/threshold_line.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import type { + ExpressionFunctionDefinition, + Datatable, + ExpressionValueBoxed, +} from '../../../expressions/public'; +import type { ThresholdLine } from '../types'; + +export type ExpressionValueThresholdLine = ExpressionValueBoxed< + 'threshold_line', + { + show: ThresholdLine['show']; + value: ThresholdLine['value']; + width: ThresholdLine['width']; + style: ThresholdLine['style']; + color: ThresholdLine['color']; + } +>; + +export const thresholdLine = (): ExpressionFunctionDefinition< + 'thresholdline', + Datatable | null, + ThresholdLine, + ExpressionValueThresholdLine +> => ({ + name: 'thresholdline', + help: i18n.translate('visTypeXy.function.thresholdLine.help', { + defaultMessage: 'Generates threshold line object', + }), + type: 'threshold_line', + args: { + show: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.thresholdLine.show.help', { + defaultMessage: 'Show threshould line', + }), + required: true, + }, + value: { + types: ['number', 'null'], + help: i18n.translate('visTypeXy.function.thresholdLine.value.help', { + defaultMessage: 'Threshold value', + }), + required: true, + }, + width: { + types: ['number', 'null'], + help: i18n.translate('visTypeXy.function.thresholdLine.width.help', { + defaultMessage: 'Width of threshold line', + }), + required: true, + }, + style: { + types: ['string'], + help: i18n.translate('visTypeXy.function.thresholdLine.style.help', { + defaultMessage: 'Style of threshold line. Can be full, dashed or dot-dashed', + }), + required: true, + }, + color: { + types: ['string'], + help: i18n.translate('visTypeXy.function.thresholdLine.color.help', { + defaultMessage: 'Color of threshold line', + }), + required: true, + }, + }, + fn: (context, args) => { + return { + type: 'threshold_line', + show: args.show, + value: args.value, + width: args.width, + style: args.style, + color: args.color, + }; + }, +}); diff --git a/src/plugins/vis_type_xy/public/expression_functions/time_marker.ts b/src/plugins/vis_type_xy/public/expression_functions/time_marker.ts new file mode 100644 index 0000000000000..3d9f609292c00 --- /dev/null +++ b/src/plugins/vis_type_xy/public/expression_functions/time_marker.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import type { + ExpressionFunctionDefinition, + Datatable, + ExpressionValueBoxed, +} from '../../../expressions/public'; +import type { TimeMarker } from '../types'; + +export type ExpressionValueTimeMarker = ExpressionValueBoxed< + 'time_marker', + { + time: string; + class?: string; + color?: string; + opacity?: number; + width?: number; + } +>; + +export const timeMarker = (): ExpressionFunctionDefinition< + 'timemarker', + Datatable | null, + TimeMarker, + ExpressionValueTimeMarker +> => ({ + name: 'timemarker', + help: i18n.translate('visTypeXy.function.timemarker.help', { + defaultMessage: 'Generates time marker object', + }), + type: 'time_marker', + args: { + time: { + types: ['string'], + help: i18n.translate('visTypeXy.function.timeMarker.time.help', { + defaultMessage: 'Exact Time', + }), + required: true, + }, + class: { + types: ['string'], + help: i18n.translate('visTypeXy.function.timeMarker.class.help', { + defaultMessage: 'Css class name', + }), + }, + color: { + types: ['string'], + help: i18n.translate('visTypeXy.function.timeMarker.color.help', { + defaultMessage: 'Color of time marker', + }), + }, + opacity: { + types: ['number'], + help: i18n.translate('visTypeXy.function.timeMarker.opacity.help', { + defaultMessage: 'Opacity of time marker', + }), + }, + width: { + types: ['number'], + help: i18n.translate('visTypeXy.function.timeMarker.width.help', { + defaultMessage: 'Width of time marker', + }), + }, + }, + fn: (context, args) => { + return { + type: 'time_marker', + time: args.time, + class: args.class, + color: args.color, + opacity: args.opacity, + width: args.width, + }; + }, +}); diff --git a/src/plugins/vis_type_xy/public/expression_functions/value_axis.ts b/src/plugins/vis_type_xy/public/expression_functions/value_axis.ts new file mode 100644 index 0000000000000..510ec9bc605d2 --- /dev/null +++ b/src/plugins/vis_type_xy/public/expression_functions/value_axis.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import type { ExpressionValueCategoryAxis } from './category_axis'; +import type { CategoryAxis } from '../types'; +import type { + ExpressionFunctionDefinition, + Datatable, + ExpressionValueBoxed, +} from '../../../expressions/public'; + +interface Arguments { + name: string; + axisParams: ExpressionValueCategoryAxis; +} + +export type ExpressionValueValueAxis = ExpressionValueBoxed< + 'value_axis', + { + name: string; + id: string; + show: boolean; + position: CategoryAxis['position']; + axisType: CategoryAxis['type']; + title: { + text?: string; + }; + labels: CategoryAxis['labels']; + scale: CategoryAxis['scale']; + } +>; + +export const valueAxis = (): ExpressionFunctionDefinition< + 'valueaxis', + Datatable | null, + Arguments, + ExpressionValueValueAxis +> => ({ + name: 'valueaxis', + help: i18n.translate('visTypeXy.function.valueaxis.help', { + defaultMessage: 'Generates value axis object', + }), + type: 'value_axis', + args: { + name: { + types: ['string'], + help: i18n.translate('visTypeXy.function.valueAxis.name.help', { + defaultMessage: 'Name of value axis', + }), + required: true, + }, + axisParams: { + types: ['category_axis'], + help: i18n.translate('visTypeXy.function.valueAxis.axisParams.help', { + defaultMessage: 'Value axis params', + }), + required: true, + }, + }, + fn: (context, args) => { + return { + type: 'value_axis', + name: args.name, + id: args.axisParams.id, + show: args.axisParams.show, + position: args.axisParams.position, + axisType: args.axisParams.axisType, + title: args.axisParams.title, + scale: args.axisParams.scale, + labels: args.axisParams.labels, + }; + }, +}); diff --git a/src/plugins/vis_type_xy/public/expression_functions/vis_scale.ts b/src/plugins/vis_type_xy/public/expression_functions/vis_scale.ts new file mode 100644 index 0000000000000..fadf3d80a6e81 --- /dev/null +++ b/src/plugins/vis_type_xy/public/expression_functions/vis_scale.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import type { + ExpressionFunctionDefinition, + Datatable, + ExpressionValueBoxed, +} from '../../../expressions/public'; +import type { Scale } from '../types'; + +export type ExpressionValueScale = ExpressionValueBoxed< + 'vis_scale', + { + boundsMargin?: Scale['boundsMargin']; + defaultYExtents?: Scale['defaultYExtents']; + max?: Scale['max']; + min?: Scale['min']; + mode?: Scale['mode']; + setYExtents?: Scale['setYExtents']; + scaleType: Scale['type']; + } +>; + +export const visScale = (): ExpressionFunctionDefinition< + 'visscale', + Datatable | null, + Scale, + ExpressionValueScale +> => ({ + name: 'visscale', + help: i18n.translate('visTypeXy.function.scale.help', { + defaultMessage: 'Generates scale object', + }), + type: 'vis_scale', + args: { + boundsMargin: { + types: ['number', 'string'], + help: i18n.translate('visTypeXy.function.scale.boundsMargin.help', { + defaultMessage: 'Margin of bounds', + }), + }, + defaultYExtents: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.scale.defaultYExtents.help', { + defaultMessage: 'Flag which allows to scale to data bounds', + }), + }, + setYExtents: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.scale.setYExtents.help', { + defaultMessage: 'Flag which allows to set your own extents', + }), + }, + max: { + types: ['number', 'null'], + help: i18n.translate('visTypeXy.function.scale.max.help', { + defaultMessage: 'Max value', + }), + }, + min: { + types: ['number', 'null'], + help: i18n.translate('visTypeXy.function.scale.min.help', { + defaultMessage: 'Min value', + }), + }, + mode: { + types: ['string'], + help: i18n.translate('visTypeXy.function.scale.mode.help', { + defaultMessage: 'Scale mode. Can be normal, percentage, wiggle or silhouette', + }), + }, + type: { + types: ['string'], + help: i18n.translate('visTypeXy.function.scale.type.help', { + defaultMessage: 'Scale type. Can be linear, log or square root', + }), + required: true, + }, + }, + fn: (context, args) => { + return { + type: 'vis_scale', + boundsMargin: args.boundsMargin, + defaultYExtents: args.defaultYExtents, + setYExtents: args.setYExtents, + max: args.max, + min: args.min, + mode: args.mode, + scaleType: args.type, + }; + }, +}); diff --git a/src/plugins/vis_type_xy/public/expression_functions/xy_dimension.ts b/src/plugins/vis_type_xy/public/expression_functions/xy_dimension.ts new file mode 100644 index 0000000000000..ecbc3640c035b --- /dev/null +++ b/src/plugins/vis_type_xy/public/expression_functions/xy_dimension.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import type { ExpressionValueVisDimension } from '../../../visualizations/public'; +import type { + ExpressionFunctionDefinition, + Datatable, + ExpressionValueBoxed, +} from '../../../expressions/public'; +import type { Dimension } from '../types'; + +interface Arguments { + visDimension: ExpressionValueVisDimension; + params: string; + aggType: string; + label: string; +} + +export type ExpressionValueXYDimension = ExpressionValueBoxed< + 'xy_dimension', + { + label: string; + aggType: string; + params: Dimension['params']; + accessor: number; + format: Dimension['format']; + } +>; + +export const xyDimension = (): ExpressionFunctionDefinition< + 'xydimension', + Datatable | null, + Arguments, + ExpressionValueXYDimension +> => ({ + name: 'xydimension', + help: i18n.translate('visTypeXy.function.xydimension.help', { + defaultMessage: 'Generates xy dimension object', + }), + type: 'xy_dimension', + args: { + visDimension: { + types: ['vis_dimension'], + help: i18n.translate('visTypeXy.function.xyDimension.visDimension.help', { + defaultMessage: 'Dimension object config', + }), + required: true, + }, + label: { + types: ['string'], + help: i18n.translate('visTypeXy.function.xyDimension.label.help', { + defaultMessage: 'Label', + }), + }, + aggType: { + types: ['string'], + help: i18n.translate('visTypeXy.function.xyDimension.aggType.help', { + defaultMessage: 'Aggregation type', + }), + }, + params: { + types: ['string'], + default: '"{}"', + help: i18n.translate('visTypeXy.function.xyDimension.params.help', { + defaultMessage: 'Params', + }), + }, + }, + fn: (context, args) => { + return { + type: 'xy_dimension', + label: args.label, + aggType: args.aggType, + params: JSON.parse(args.params!), + accessor: args.visDimension.accessor as number, + format: args.visDimension.format, + }; + }, +}); diff --git a/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts b/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts new file mode 100644 index 0000000000000..b8b8c0e8b8cca --- /dev/null +++ b/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts @@ -0,0 +1,273 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +import type { ExpressionFunctionDefinition, Datatable, Render } from '../../../expressions/public'; +import type { ChartType } from '../../common'; +import type { VisParams, XYVisConfig } from '../types'; + +export const visName = 'xy_vis'; +export interface RenderValue { + visData: Datatable; + visType: ChartType; + visConfig: VisParams; + syncColors: boolean; +} + +export type VisTypeXyExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof visName, + Datatable, + XYVisConfig, + Render +>; + +export const visTypeXyVisFn = (): VisTypeXyExpressionFunctionDefinition => ({ + name: visName, + type: 'render', + context: { + types: ['datatable'], + }, + help: i18n.translate('visTypeXy.functions.help', { + defaultMessage: 'XY visualization', + }), + args: { + type: { + types: ['string'], + default: '""', + help: 'xy vis type', + }, + chartType: { + types: ['string'], + help: i18n.translate('visTypeXy.function.args.args.chartType.help', { + defaultMessage: 'Type of a chart. Can be line, area or histogram', + }), + }, + addTimeMarker: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.args.addTimeMarker.help', { + defaultMessage: 'Show time marker', + }), + }, + addLegend: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.args.addLegend.help', { + defaultMessage: 'Show chart legend', + }), + }, + addTooltip: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.args.addTooltip.help', { + defaultMessage: 'Show tooltip on hover', + }), + }, + legendPosition: { + types: ['string'], + help: i18n.translate('visTypeXy.function.args.legendPosition.help', { + defaultMessage: 'Position the legend on top, bottom, left, right of the chart', + }), + }, + categoryAxes: { + types: ['category_axis'], + help: i18n.translate('visTypeXy.function.args.categoryAxes.help', { + defaultMessage: 'Category axis config', + }), + multi: true, + }, + thresholdLine: { + types: ['threshold_line'], + help: i18n.translate('visTypeXy.function.args.thresholdLine.help', { + defaultMessage: 'Threshold line config', + }), + }, + labels: { + types: ['label'], + help: i18n.translate('visTypeXy.function.args.labels.help', { + defaultMessage: 'Chart labels config', + }), + }, + orderBucketsBySum: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.args.orderBucketsBySum.help', { + defaultMessage: 'Order buckets by sum', + }), + }, + seriesParams: { + types: ['series_param'], + help: i18n.translate('visTypeXy.function.args.seriesParams.help', { + defaultMessage: 'Series param config', + }), + multi: true, + }, + valueAxes: { + types: ['value_axis'], + help: i18n.translate('visTypeXy.function.args.valueAxes.help', { + defaultMessage: 'Value axis config', + }), + multi: true, + }, + radiusRatio: { + types: ['number'], + help: i18n.translate('visTypeXy.function.args.radiusRatio.help', { + defaultMessage: 'Dot size ratio', + }), + }, + gridCategoryLines: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.args.gridCategoryLines.help', { + defaultMessage: 'Show grid category lines in chart', + }), + }, + gridValueAxis: { + types: ['string'], + help: i18n.translate('visTypeXy.function.args.gridValueAxis.help', { + defaultMessage: 'Name of value axis for which we show grid', + }), + }, + isVislibVis: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.args.isVislibVis.help', { + defaultMessage: + 'Flag to indicate old vislib visualizations. Used for backwards compatibility including colors', + }), + }, + detailedTooltip: { + types: ['boolean'], + help: i18n.translate('visTypeXy.function.args.detailedTooltip.help', { + defaultMessage: 'Show detailed tooltip', + }), + }, + fittingFunction: { + types: ['string'], + help: i18n.translate('visTypeXy.function.args.fittingFunction.help', { + defaultMessage: 'Name of fitting function', + }), + }, + times: { + types: ['time_marker'], + help: i18n.translate('visTypeXy.function.args.times.help', { + defaultMessage: 'Time marker config', + }), + multi: true, + }, + palette: { + types: ['string'], + help: i18n.translate('visTypeXy.function.args.palette.help', { + defaultMessage: 'Defines the chart palette name', + }), + }, + xDimension: { + types: ['xy_dimension', 'null'], + help: i18n.translate('visTypeXy.function.args.xDimension.help', { + defaultMessage: 'X axis dimension config', + }), + }, + yDimension: { + types: ['xy_dimension'], + help: i18n.translate('visTypeXy.function.args.yDimension.help', { + defaultMessage: 'Y axis dimension config', + }), + multi: true, + }, + zDimension: { + types: ['xy_dimension'], + help: i18n.translate('visTypeXy.function.args.zDimension.help', { + defaultMessage: 'Z axis dimension config', + }), + multi: true, + }, + widthDimension: { + types: ['xy_dimension'], + help: i18n.translate('visTypeXy.function.args.widthDimension.help', { + defaultMessage: 'Width dimension config', + }), + multi: true, + }, + seriesDimension: { + types: ['xy_dimension'], + help: i18n.translate('visTypeXy.function.args.seriesDimension.help', { + defaultMessage: 'Series dimension config', + }), + multi: true, + }, + splitRowDimension: { + types: ['xy_dimension'], + help: i18n.translate('visTypeXy.function.args.splitRowDimension.help', { + defaultMessage: 'Split by row dimension config', + }), + multi: true, + }, + splitColumnDimension: { + types: ['xy_dimension'], + help: i18n.translate('visTypeXy.function.args.splitColumnDimension.help', { + defaultMessage: 'Split by column dimension config', + }), + multi: true, + }, + }, + fn(context, args, handlers) { + const visType = args.chartType; + const visConfig = { + type: args.chartType, + addLegend: args.addLegend, + addTooltip: args.addTooltip, + legendPosition: args.legendPosition, + addTimeMarker: args.addTimeMarker, + categoryAxes: args.categoryAxes.map((categoryAxis) => ({ + ...categoryAxis, + type: categoryAxis.axisType, + })), + orderBucketsBySum: args.orderBucketsBySum, + labels: args.labels, + thresholdLine: args.thresholdLine, + valueAxes: args.valueAxes.map((valueAxis) => ({ ...valueAxis, type: valueAxis.axisType })), + grid: { + categoryLines: args.gridCategoryLines, + valueAxis: args.gridValueAxis, + }, + seriesParams: args.seriesParams.map((seriesParam) => ({ + ...seriesParam, + type: seriesParam.seriesParamType, + })), + radiusRatio: args.radiusRatio, + times: args.times, + isVislibVis: args.isVislibVis, + detailedTooltip: args.detailedTooltip, + palette: { + type: 'palette', + name: args.palette, + }, + fittingFunction: args.fittingFunction, + dimensions: { + x: args.xDimension, + y: args.yDimension, + z: args.zDimension, + width: args.widthDimension, + series: args.seriesDimension, + splitRow: args.splitRowDimension, + splitColumn: args.splitColumnDimension, + }, + } as VisParams; + + if (handlers?.inspectorAdapters?.tables) { + handlers.inspectorAdapters.tables.logDatatable('default', context); + } + + return { + type: 'render', + as: visName, + value: { + context, + visType, + visConfig, + visData: context, + syncColors: handlers?.isSyncColorsEnabled?.() ?? false, + }, + }; + }, +}); diff --git a/src/plugins/vis_type_xy/public/plugin.ts b/src/plugins/vis_type_xy/public/plugin.ts index d414da8f6dc97..7bdb4f78bc631 100644 --- a/src/plugins/vis_type_xy/public/plugin.ts +++ b/src/plugins/vis_type_xy/public/plugin.ts @@ -12,8 +12,6 @@ import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/p import { ChartsPluginSetup } from '../../charts/public'; import { DataPublicPluginStart } from '../../data/public'; import { UsageCollectionSetup } from '../../usage_collection/public'; - -import { createVisTypeXyVisFn } from './xy_vis_fn'; import { setDataActions, setFormatService, @@ -23,10 +21,13 @@ import { setPalettesService, setTrackUiMetric, } from './services'; + import { visTypesDefinitions } from './vis_types'; import { LEGACY_CHARTS_LIBRARY } from '../common'; import { xyVisRenderer } from './vis_renderer'; +import * as expressionFunctions from './expression_functions'; + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface VisTypeXyPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -66,8 +67,18 @@ export class VisTypeXyPlugin setUISettings(core.uiSettings); setThemeService(charts.theme); setPalettesService(charts.palettes); - [createVisTypeXyVisFn].forEach(expressions.registerFunction); + expressions.registerRenderer(xyVisRenderer); + expressions.registerFunction(expressionFunctions.visTypeXyVisFn); + expressions.registerFunction(expressionFunctions.categoryAxis); + expressions.registerFunction(expressionFunctions.timeMarker); + expressions.registerFunction(expressionFunctions.valueAxis); + expressions.registerFunction(expressionFunctions.seriesParam); + expressions.registerFunction(expressionFunctions.thresholdLine); + expressions.registerFunction(expressionFunctions.label); + expressions.registerFunction(expressionFunctions.visScale); + expressions.registerFunction(expressionFunctions.xyDimension); + visTypesDefinitions.forEach(visualizations.createBaseVisualization); } diff --git a/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts b/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts index c425eb71117e8..e15f9c4207702 100644 --- a/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts @@ -1414,6 +1414,9 @@ export const sampleAreaVis = { color: '#E7664C', }, labels: {}, + palette: { + name: 'default', + }, }, }, editorConfig: { @@ -1575,6 +1578,9 @@ export const sampleAreaVis = { style: 'full', color: '#E7664C', }, + palette: { + name: 'default', + }, labels: {}, dimensions: { x: { diff --git a/src/plugins/vis_type_xy/public/to_ast.test.ts b/src/plugins/vis_type_xy/public/to_ast.test.ts index 22e2d5f1cd9cc..4437986eff5f7 100644 --- a/src/plugins/vis_type_xy/public/to_ast.test.ts +++ b/src/plugins/vis_type_xy/public/to_ast.test.ts @@ -42,7 +42,7 @@ describe('xy vis toExpressionAst function', () => { it('should match basic snapshot', () => { toExpressionAst(vis, params); - const [, builtExpression] = (buildExpression as jest.Mock).mock.calls[0][0]; + const [, builtExpression] = (buildExpression as jest.Mock).mock.calls.pop()[0]; expect(builtExpression).toMatchSnapshot(); }); diff --git a/src/plugins/vis_type_xy/public/to_ast.ts b/src/plugins/vis_type_xy/public/to_ast.ts index 84331af3a5329..c0a0ee566a445 100644 --- a/src/plugins/vis_type_xy/public/to_ast.ts +++ b/src/plugins/vis_type_xy/public/to_ast.ts @@ -11,13 +11,122 @@ import moment from 'moment'; import { VisToExpressionAst, getVisSchemas } from '../../visualizations/public'; import { buildExpression, buildExpressionFunction } from '../../expressions/public'; import { BUCKET_TYPES } from '../../data/public'; +import { Labels } from '../../charts/public'; -import { DateHistogramParams, Dimensions, HistogramParams, VisParams } from './types'; -import { visName, VisTypeXyExpressionFunctionDefinition } from './xy_vis_fn'; +import { + DateHistogramParams, + Dimensions, + Dimension, + HistogramParams, + VisParams, + CategoryAxis, + SeriesParam, + ThresholdLine, + ValueAxis, + Scale, + TimeMarker, +} from './types'; +import { visName, VisTypeXyExpressionFunctionDefinition } from './expression_functions/xy_vis_fn'; import { XyVisType } from '../common'; import { getEsaggsFn } from './to_ast_esaggs'; import { TimeRangeBounds } from '../../data/common'; +const prepareLabel = (data: Labels) => { + const label = buildExpressionFunction('label', { + ...data, + }); + + return buildExpression([label]); +}; + +const prepareScale = (data: Scale) => { + const scale = buildExpressionFunction('visscale', { + ...data, + }); + + return buildExpression([scale]); +}; + +const prepareThresholdLine = (data: ThresholdLine) => { + const thresholdLine = buildExpressionFunction('thresholdline', { + ...data, + }); + + return buildExpression([thresholdLine]); +}; + +const prepareTimeMarker = (data: TimeMarker) => { + const timeMarker = buildExpressionFunction('timemarker', { + ...data, + }); + + return buildExpression([timeMarker]); +}; + +const prepareCategoryAxis = (data: CategoryAxis) => { + const categoryAxis = buildExpressionFunction('categoryaxis', { + id: data.id, + show: data.show, + position: data.position, + type: data.type, + title: data.title.text, + scale: prepareScale(data.scale), + labels: prepareLabel(data.labels), + }); + + return buildExpression([categoryAxis]); +}; + +const prepareValueAxis = (data: ValueAxis) => { + const categoryAxis = buildExpressionFunction('valueaxis', { + name: data.name, + axisParams: prepareCategoryAxis({ + ...data, + }), + }); + + return buildExpression([categoryAxis]); +}; + +const prepareSeriesParam = (data: SeriesParam) => { + const seriesParam = buildExpressionFunction('seriesparam', { + label: data.data.label, + id: data.data.id, + drawLinesBetweenPoints: data.drawLinesBetweenPoints, + interpolate: data.interpolate, + lineWidth: data.lineWidth, + mode: data.mode, + show: data.show, + showCircles: data.showCircles, + type: data.type, + valueAxis: data.valueAxis, + }); + + return buildExpression([seriesParam]); +}; + +const prepareVisDimension = (data: Dimension) => { + const visDimension = buildExpressionFunction('visdimension', { accessor: data.accessor }); + + if (data.format) { + visDimension.addArgument('format', data.format.id); + visDimension.addArgument('formatParams', JSON.stringify(data.format.params)); + } + + return buildExpression([visDimension]); +}; + +const prepareXYDimension = (data: Dimension) => { + const xyDimension = buildExpressionFunction('xydimension', { + params: JSON.stringify(data.params), + aggType: data.aggType, + label: data.label, + visDimension: prepareVisDimension(data), + }); + + return buildExpression([xyDimension]); +}; + export const toExpressionAst: VisToExpressionAst = async (vis, params) => { const schemas = getVisSchemas(vis, params); const dimensions: Dimensions = { @@ -62,15 +171,13 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params } } - const visConfig = { ...vis.params }; - (dimensions.y || []).forEach((yDimension) => { const yAgg = responseAggs[yDimension.accessor]; - const seriesParam = (visConfig.seriesParams || []).find( + const seriesParam = (vis.params.seriesParams || []).find( (param: any) => param.data.id === yAgg.id ); if (seriesParam) { - const usedValueAxis = (visConfig.valueAxes || []).find( + const usedValueAxis = (vis.params.valueAxes || []).find( (valueAxis: any) => valueAxis.id === seriesParam.valueAxis ); if (usedValueAxis?.scale.mode === 'percentage') { @@ -79,11 +186,34 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params } }); - visConfig.dimensions = dimensions; - const visTypeXy = buildExpressionFunction(visName, { type: vis.type.name as XyVisType, - visConfig: JSON.stringify(visConfig), + chartType: vis.params.type, + addTimeMarker: vis.params.addTimeMarker, + addLegend: vis.params.addLegend, + addTooltip: vis.params.addTooltip, + legendPosition: vis.params.legendPosition, + orderBucketsBySum: vis.params.orderBucketsBySum, + categoryAxes: vis.params.categoryAxes.map(prepareCategoryAxis), + valueAxes: vis.params.valueAxes.map(prepareValueAxis), + seriesParams: vis.params.seriesParams.map(prepareSeriesParam), + labels: prepareLabel(vis.params.labels), + thresholdLine: prepareThresholdLine(vis.params.thresholdLine), + gridCategoryLines: vis.params.grid.categoryLines, + gridValueAxis: vis.params.grid.valueAxis, + radiusRatio: vis.params.radiusRatio, + isVislibVis: vis.params.isVislibVis, + detailedTooltip: vis.params.detailedTooltip, + fittingFunction: vis.params.fittingFunction, + times: vis.params.times.map(prepareTimeMarker), + palette: vis.params.palette.name, + xDimension: dimensions.x ? prepareXYDimension(dimensions.x) : null, + yDimension: dimensions.y.map(prepareXYDimension), + zDimension: dimensions.z?.map(prepareXYDimension), + widthDimension: dimensions.width?.map(prepareXYDimension), + seriesDimension: dimensions.series?.map(prepareXYDimension), + splitRowDimension: dimensions.splitRow?.map(prepareXYDimension), + splitColumnDimension: dimensions.splitColumn?.map(prepareXYDimension), }); const ast = buildExpression([getEsaggsFn(vis), visTypeXy]); diff --git a/src/plugins/vis_type_xy/public/types/config.ts b/src/plugins/vis_type_xy/public/types/config.ts index d5c5bfe004191..f025a36a82410 100644 --- a/src/plugins/vis_type_xy/public/types/config.ts +++ b/src/plugins/vis_type_xy/public/types/config.ts @@ -20,7 +20,7 @@ import { YDomainRange, } from '@elastic/charts'; -import { Dimension, Scale, ThresholdLine } from './param'; +import type { Dimension, Scale, ThresholdLine } from './param'; export interface Column { id: string | null; diff --git a/src/plugins/vis_type_xy/public/types/constants.ts b/src/plugins/vis_type_xy/public/types/constants.ts index 5c2f23b76aa96..05ed0783d4c68 100644 --- a/src/plugins/vis_type_xy/public/types/constants.ts +++ b/src/plugins/vis_type_xy/public/types/constants.ts @@ -6,52 +6,43 @@ * Side Public License, v 1. */ -import { $Values } from '@kbn/utility-types'; - -export const ChartMode = Object.freeze({ - Normal: 'normal' as const, - Stacked: 'stacked' as const, -}); -export type ChartMode = $Values; - -export const InterpolationMode = Object.freeze({ - Linear: 'linear' as const, - Cardinal: 'cardinal' as const, - StepAfter: 'step-after' as const, -}); -export type InterpolationMode = $Values; - -export const AxisType = Object.freeze({ - Category: 'category' as const, - Value: 'value' as const, -}); -export type AxisType = $Values; - -export const ScaleType = Object.freeze({ - Linear: 'linear' as const, - Log: 'log' as const, - SquareRoot: 'square root' as const, -}); -export type ScaleType = $Values; - -export const AxisMode = Object.freeze({ - Normal: 'normal' as const, - Percentage: 'percentage' as const, - Wiggle: 'wiggle' as const, - Silhouette: 'silhouette' as const, -}); -export type AxisMode = $Values; - -export const ThresholdLineStyle = Object.freeze({ - Full: 'full' as const, - Dashed: 'dashed' as const, - DotDashed: 'dot-dashed' as const, -}); -export type ThresholdLineStyle = $Values; - -export const ColorMode = Object.freeze({ - Background: 'Background' as const, - Labels: 'Labels' as const, - None: 'None' as const, -}); -export type ColorMode = $Values; +export enum ChartMode { + Normal = 'normal', + Stacked = 'stacked', +} + +export enum InterpolationMode { + Linear = 'linear', + Cardinal = 'cardinal', + StepAfter = 'step-after', +} + +export enum AxisType { + Category = 'category', + Value = 'value', +} + +export enum ScaleType { + Linear = 'linear', + Log = 'log', + SquareRoot = 'square root', +} + +export enum AxisMode { + Normal = 'normal', + Percentage = 'percentage', + Wiggle = 'wiggle', + Silhouette = 'silhouette', +} + +export enum ThresholdLineStyle { + Full = 'full', + Dashed = 'dashed', + DotDashed = 'dot-dashed', +} + +export enum ColorMode { + Background = 'Background', + Labels = 'Labels', + None = 'None', +} diff --git a/src/plugins/vis_type_xy/public/types/index.ts b/src/plugins/vis_type_xy/public/types/index.ts index d612e9fcf5f6f..6abbdfabaa956 100644 --- a/src/plugins/vis_type_xy/public/types/index.ts +++ b/src/plugins/vis_type_xy/public/types/index.ts @@ -9,4 +9,4 @@ export * from './constants'; export * from './config'; export * from './param'; -export * from './vis_type'; +export type { VisTypeNames, XyVisTypeDefinition } from './vis_type'; diff --git a/src/plugins/vis_type_xy/public/types/param.ts b/src/plugins/vis_type_xy/public/types/param.ts index 69b6daf077a32..f90899620126a 100644 --- a/src/plugins/vis_type_xy/public/types/param.ts +++ b/src/plugins/vis_type_xy/public/types/param.ts @@ -6,13 +6,21 @@ * Side Public License, v 1. */ -import { Fit, Position } from '@elastic/charts'; - -import { Style, Labels, PaletteOutput } from '../../../charts/public'; -import { SchemaConfig } from '../../../visualizations/public'; - -import { ChartType } from '../../common'; -import { +import type { Fit, Position } from '@elastic/charts'; +import type { Style, Labels, PaletteOutput } from '../../../charts/public'; +import type { SchemaConfig } from '../../../visualizations/public'; +import type { ChartType, XyVisType } from '../../common'; +import type { + ExpressionValueCategoryAxis, + ExpressionValueSeriesParam, + ExpressionValueValueAxis, + ExpressionValueLabel, + ExpressionValueThresholdLine, + ExpressionValueTimeMarker, + ExpressionValueXYDimension, +} from '../expression_functions'; + +import type { ChartMode, AxisMode, AxisType, @@ -47,7 +55,7 @@ export interface CategoryAxis { * remove with vis_type_vislib * https://github.com/elastic/kibana/issues/56143 */ - style: Partial