From 14ed5b540e1eac561d183718535c464b5c22c136 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Fri, 12 Jul 2019 14:32:47 -0400 Subject: [PATCH 01/13] Per panel time range --- .i18nrc.json | 37 ++- .../actions/expand_panel_action.test.tsx | 2 +- ...anel_action.tsx => expand_panel_action.ts} | 6 +- .../embeddable_api/public/actions/action.ts | 6 +- .../build_eui_context_menu_panels.ts | 2 +- .../embeddable_api/public/index.ts | 1 + .../public/panel/embeddable_panel.tsx | 30 ++- .../add_panel/add_panel_action.test.tsx | 2 +- ...d_panel_action.tsx => add_panel_action.ts} | 8 +- ...l_action.tsx => customize_panel_action.ts} | 6 +- ..._panel_action.tsx => edit_panel_action.ts} | 6 +- .../inspect_panel_action.test.tsx | 2 +- ...nel_action.tsx => inspect_panel_action.ts} | 8 +- .../remove_panel_action.test.tsx | 2 +- ...anel_action.tsx => remove_panel_action.ts} | 6 +- .../panel/panel_header/panel_header.tsx | 33 ++- .../embeddable_api/public/triggers/index.ts | 8 + .../public/discover/embeddable/constants.ts | 20 ++ .../public/discover/embeddable/index.ts | 1 + .../discover/embeddable/search_embeddable.ts | 3 +- .../embeddable/search_embeddable_factory.ts | 3 +- .../embeddable/visualize_embeddable.ts | 18 +- x-pack/index.js | 2 + .../plugins/advanced_ui_actions/index.ts | 16 ++ .../public/can_inherit_time_range.test.ts | 40 +++ .../public/can_inherit_time_range.ts | 27 ++ .../public/custom_time_range_action.test.ts | 240 ++++++++++++++++++ .../public/custom_time_range_action.tsx | 95 +++++++ .../public/custom_time_range_badge.test.ts | 202 +++++++++++++++ .../public/custom_time_range_badge.tsx | 101 ++++++++ .../public/customize_time_range_modal.tsx | 176 +++++++++++++ .../public/does_inherit_time_range.ts | 23 ++ .../advanced_ui_actions/public/index.ts | 5 + .../advanced_ui_actions/public/shim/index.ts | 20 ++ .../advanced_ui_actions/public/shim/plugin.ts | 46 ++++ .../advanced_ui_actions/public/shim/types.ts | 15 ++ .../public/test_helpers/index.ts | 8 + .../test_helpers/time_range_container.ts | 42 +++ .../test_helpers/time_range_embeddable.ts | 31 +++ .../time_range_embeddable_factory.ts | 33 +++ ...nel_action.tsx => get_csv_panel_action.ts} | 6 +- 41 files changed, 1277 insertions(+), 61 deletions(-) rename src/legacy/core_plugins/dashboard_embeddable_container/public/actions/{expand_panel_action.tsx => expand_panel_action.ts} (93%) rename src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/{add_panel_action.tsx => add_panel_action.ts} (92%) rename src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/customize_title/{customize_panel_action.tsx => customize_panel_action.ts} (94%) rename src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/{edit_panel_action.tsx => edit_panel_action.ts} (95%) rename src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/{inspect_panel_action.tsx => inspect_panel_action.ts} (94%) rename src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/{remove_panel_action.tsx => remove_panel_action.ts} (94%) create mode 100644 src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/index.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/can_inherit_time_range.test.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/can_inherit_time_range.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/does_inherit_time_range.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/index.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/shim/index.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/shim/types.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/index.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_container.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable.ts create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts rename x-pack/legacy/plugins/reporting/public/panel_actions/{get_csv_panel_action.tsx => get_csv_panel_action.ts} (97%) diff --git a/.i18nrc.json b/.i18nrc.json index bcc1fe70ab1e7..3be42bfcc9554 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -24,7 +24,42 @@ "timelion": "src/legacy/core_plugins/timelion", "tagCloud": "src/legacy/core_plugins/tagcloud", "tsvb": "src/legacy/core_plugins/metrics", - "kbnESQuery": "packages/kbn-es-query" + "kbnESQuery": "packages/kbn-es-query", + "xpack.advancedUiActions": "x-pack/legacy/plugins/advanced_ui_actions", + "xpack.actions": "x-pack/legacy/plugins/actions", + "xpack.alerting": "x-pack/legacy/plugins/alerting", + "xpack.apm": "x-pack/legacy/plugins/apm", + "xpack.beatsManagement": "x-pack/legacy/plugins/beats_management", + "xpack.canvas": "x-pack/legacy/plugins/canvas", + "xpack.code": "x-pack/legacy/plugins/code", + "xpack.crossClusterReplication": "x-pack/legacy/plugins/cross_cluster_replication", + "xpack.dashboardMode": "x-pack/legacy/plugins/dashboard_mode", + "xpack.fileUpload": "x-pack/legacy/plugins/file_upload", + "xpack.graph": "x-pack/legacy/plugins/graph", + "xpack.grokDebugger": "x-pack/legacy/plugins/grokdebugger", + "xpack.idxMgmt": "x-pack/legacy/plugins/index_management", + "xpack.indexLifecycleMgmt": "x-pack/legacy/plugins/index_lifecycle_management", + "xpack.infra": "x-pack/legacy/plugins/infra", + "xpack.kueryAutocomplete": "x-pack/legacy/plugins/kuery_autocomplete", + "xpack.licenseMgmt": "x-pack/legacy/plugins/license_management", + "xpack.maps": "x-pack/legacy/plugins/maps", + "xpack.ml": "x-pack/legacy/plugins/ml", + "xpack.logstash": "x-pack/legacy/plugins/logstash", + "xpack.main": "x-pack/legacy/plugins/xpack_main", + "xpack.telemetry": "x-pack/legacy/plugins/telemetry", + "xpack.monitoring": "x-pack/legacy/plugins/monitoring", + "xpack.remoteClusters": "x-pack/legacy/plugins/remote_clusters", + "xpack.reporting": "x-pack/legacy/plugins/reporting", + "xpack.rollupJobs": "x-pack/legacy/plugins/rollup", + "xpack.searchProfiler": "x-pack/legacy/plugins/searchprofiler", + "xpack.siem": "x-pack/legacy/plugins/siem", + "xpack.security": "x-pack/legacy/plugins/security", + "xpack.server": "x-pack/legacy/server", + "xpack.snapshotRestore": "x-pack/legacy/plugins/snapshot_restore", + "xpack.spaces": "x-pack/legacy/plugins/spaces", + "xpack.upgradeAssistant": "x-pack/legacy/plugins/upgrade_assistant", + "xpack.uptime": "x-pack/legacy/plugins/uptime", + "xpack.watcher": "x-pack/legacy/plugins/watcher" }, "exclude": ["src/legacy/ui/ui_render/ui_render_mixin.js"], "translations": [] diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/actions/expand_panel_action.test.tsx b/src/legacy/core_plugins/dashboard_embeddable_container/public/actions/expand_panel_action.test.tsx index 40e5ee2cb776a..11570fe6d1be9 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/actions/expand_panel_action.test.tsx +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/actions/expand_panel_action.test.tsx @@ -99,5 +99,5 @@ test('Returns title', async () => { test('Returns an icon', async () => { const action = new ExpandPanelAction(); - expect(action.getIcon({ embeddable })).toBeDefined(); + expect(action.getIconType({ embeddable })).toBeDefined(); }); diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/actions/expand_panel_action.tsx b/src/legacy/core_plugins/dashboard_embeddable_container/public/actions/expand_panel_action.ts similarity index 93% rename from src/legacy/core_plugins/dashboard_embeddable_container/public/actions/expand_panel_action.tsx rename to src/legacy/core_plugins/dashboard_embeddable_container/public/actions/expand_panel_action.ts index 7a0187066534c..597c1e82280e6 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/actions/expand_panel_action.tsx +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/actions/expand_panel_action.ts @@ -17,9 +17,7 @@ * under the License. */ -import { EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; import { Action, IEmbeddable, @@ -72,12 +70,12 @@ export class ExpandPanelAction extends Action { ); } - public getIcon({ embeddable }: ActionContext) { + public getIconType({ embeddable }: ActionContext) { if (!embeddable.parent || !isDashboard(embeddable.parent)) { throw new IncompatibleActionError(); } // TODO: use 'minimize' when an eui-icon of such is available. - return ; + return isExpanded(embeddable) ? 'expand' : 'expand'; } public async isCompatible({ embeddable }: ActionContext) { diff --git a/src/legacy/core_plugins/embeddable_api/public/actions/action.ts b/src/legacy/core_plugins/embeddable_api/public/actions/action.ts index 81ceea6e76e6f..49246dd3410aa 100644 --- a/src/legacy/core_plugins/embeddable_api/public/actions/action.ts +++ b/src/legacy/core_plugins/embeddable_api/public/actions/action.ts @@ -17,8 +17,6 @@ * under the License. */ -import { EuiContextMenuItemIcon } from '@elastic/eui'; - import { IEmbeddable } from '../embeddables'; export interface ActionContext< @@ -45,9 +43,7 @@ export abstract class Action< /** * Optional icon that can be displayed along with the title. */ - public getIcon( - context: ActionContext - ): EuiContextMenuItemIcon | undefined { + public getIconType(context: ActionContext): string | undefined { return undefined; } diff --git a/src/legacy/core_plugins/embeddable_api/public/context_menu_actions/build_eui_context_menu_panels.ts b/src/legacy/core_plugins/embeddable_api/public/context_menu_actions/build_eui_context_menu_panels.ts index 283ca4cf13002..b51a40a3ef2d9 100644 --- a/src/legacy/core_plugins/embeddable_api/public/context_menu_actions/build_eui_context_menu_panels.ts +++ b/src/legacy/core_plugins/embeddable_api/public/context_menu_actions/build_eui_context_menu_panels.ts @@ -99,7 +99,7 @@ function convertPanelActionToContextMenuItem({ }): EuiContextMenuPanelItemDescriptor { const menuPanelItem: EuiContextMenuPanelItemDescriptor = { name: action.getDisplayName(actionContext), - icon: action.getIcon(actionContext), + icon: action.getIconType(actionContext), panel: _.get(action, 'childContextMenuPanel.id'), 'data-test-subj': `embeddablePanelAction-${action.id}`, }; diff --git a/src/legacy/core_plugins/embeddable_api/public/index.ts b/src/legacy/core_plugins/embeddable_api/public/index.ts index 10dc81817c86e..6d9f538618f27 100644 --- a/src/legacy/core_plugins/embeddable_api/public/index.ts +++ b/src/legacy/core_plugins/embeddable_api/public/index.ts @@ -38,6 +38,7 @@ export { triggerRegistry, executeTriggerActions, CONTEXT_MENU_TRIGGER, + PANEL_BADGE_TRIGGER, attachAction, } from './triggers'; diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/embeddable_panel.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/embeddable_panel.tsx index 015d2a49af0e6..77b333391023e 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/embeddable_panel.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/embeddable_panel.tsx @@ -22,7 +22,7 @@ import React from 'react'; import { Subscription } from 'rxjs'; import { buildContextMenuForActions } from '../context_menu_actions'; -import { CONTEXT_MENU_TRIGGER, triggerRegistry } from '../triggers'; +import { CONTEXT_MENU_TRIGGER, triggerRegistry, PANEL_BADGE_TRIGGER } from '../triggers'; import { IEmbeddable } from '../embeddables/i_embeddable'; import { ViewMode } from '../types'; @@ -30,7 +30,7 @@ import { RemovePanelAction } from './panel_header/panel_actions'; import { AddPanelAction } from './panel_header/panel_actions/add_panel/add_panel_action'; import { CustomizePanelTitleAction } from './panel_header/panel_actions/customize_title/customize_panel_action'; import { PanelHeader } from './panel_header/panel_header'; -import { actionRegistry } from '../actions'; +import { actionRegistry, Action } from '../actions'; import { InspectPanelAction } from './panel_header/panel_actions/inspect_panel_action'; import { EditPanelAction } from './panel_header/panel_actions/edit_panel_action'; import { getActionsForTrigger } from '../get_actions_for_trigger'; @@ -45,6 +45,7 @@ interface State { viewMode: ViewMode; hidePanelTitles: boolean; closeContextMenu: boolean; + badges: Action[]; } export class EmbeddablePanel extends React.Component { @@ -67,12 +68,13 @@ export class EmbeddablePanel extends React.Component { viewMode, hidePanelTitles, closeContextMenu: false, + badges: [], }; this.embeddableRoot = React.createRef(); } - public componentWillMount() { + public async componentWillMount() { this.mounted = true; const { embeddable } = this.props; const { parent } = embeddable; @@ -82,6 +84,7 @@ export class EmbeddablePanel extends React.Component { this.setState({ viewMode: embeddable.getInput().viewMode ? embeddable.getInput().viewMode : ViewMode.EDIT, }); + this.refreshBadges(); } }); @@ -91,11 +94,29 @@ export class EmbeddablePanel extends React.Component { this.setState({ hidePanelTitles: Boolean(parent.getInput().hidePanelTitles), }); + this.refreshBadges(); } }); } } + private async refreshBadges() { + const badges = await getActionsForTrigger( + actionRegistry, + triggerRegistry, + PANEL_BADGE_TRIGGER, + { + embeddable: this.props.embeddable, + } + ); + + if (this.mounted) { + this.setState({ + badges, + }); + } + } + public componentWillUnmount() { this.mounted = false; if (this.subscription) { @@ -122,6 +143,7 @@ export class EmbeddablePanel extends React.Component { const classes = classNames('embPanel', { 'embPanel--editing': !viewOnlyMode, }); + const title = this.props.embeddable.getTitle(); return ( @@ -131,6 +153,8 @@ export class EmbeddablePanel extends React.Component { isViewMode={viewOnlyMode} closeContextMenu={this.state.closeContextMenu} title={title} + badges={this.state.badges} + embeddable={this.props.embeddable} />
diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx index 6e2331ca922e2..617bd831ad80a 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx @@ -116,5 +116,5 @@ test('Returns title', async () => { test('Returns an icon', async () => { const action = new AddPanelAction(); - expect(action.getIcon()).toBeDefined(); + expect(action.getIconType()).toBeDefined(); }); diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/add_panel_action.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/add_panel_action.ts similarity index 92% rename from src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/add_panel_action.tsx rename to src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/add_panel_action.ts index 5f0571f3d140c..9ab33d58fd640 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/add_panel_action.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/add_panel/add_panel_action.ts @@ -16,10 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - -import { EuiIcon } from '@elastic/eui'; -import React from 'react'; - import { i18n } from '@kbn/i18n'; import { ViewMode } from '../../../../types'; import { Action, ActionContext } from '../../../../actions'; @@ -40,8 +36,8 @@ export class AddPanelAction extends Action { }); } - public getIcon() { - return ; + public getIconType() { + return 'plusInCircleFilled'; } public async isCompatible({ embeddable }: ActionContext) { diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/customize_title/customize_panel_action.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts similarity index 94% rename from src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/customize_title/customize_panel_action.tsx rename to src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts index d208b941ee57c..37034bd7ad7a6 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/customize_title/customize_panel_action.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts @@ -17,8 +17,6 @@ * under the License. */ -import { EuiIcon } from '@elastic/eui'; -import React from 'react'; import { i18n } from '@kbn/i18n'; import { Action, ActionContext } from '../../../../actions'; import { ViewMode } from '../../../../types'; @@ -45,8 +43,8 @@ export class CustomizePanelTitleAction extends Action { }); } - public getIcon() { - return ; + public getIconType() { + return 'pencil'; } public async isCompatible({ embeddable }: ActionContext) { diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/edit_panel_action.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/edit_panel_action.ts similarity index 95% rename from src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/edit_panel_action.tsx rename to src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/edit_panel_action.ts index e64a793295b35..bad2b95e75f0c 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/edit_panel_action.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/edit_panel_action.ts @@ -17,9 +17,7 @@ * under the License. */ -import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiIcon } from '@elastic/eui'; import { embeddableFactories } from '../../../embeddables/embeddable_factories_registry'; import { Action, ActionContext } from '../../../actions'; @@ -48,8 +46,8 @@ export class EditPanelAction extends Action { }); } - getIcon() { - return ; + getIconType() { + return 'pencil'; } public async isCompatible({ embeddable }: ActionContext) { diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/inspect_panel_action.test.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/inspect_panel_action.test.tsx index 595870a4aa6b2..8386cd13c7a98 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/inspect_panel_action.test.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/inspect_panel_action.test.tsx @@ -125,5 +125,5 @@ test('Returns title', async () => { test('Returns an icon', async () => { const inspectAction = new InspectPanelAction(); - expect(inspectAction.getIcon()).toBeDefined(); + expect(inspectAction.getIconType()).toBeDefined(); }); diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/inspect_panel_action.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/inspect_panel_action.ts similarity index 94% rename from src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/inspect_panel_action.tsx rename to src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/inspect_panel_action.ts index 2346dd76a69ce..0689c5a248923 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/inspect_panel_action.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/inspect_panel_action.ts @@ -16,10 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - -import { EuiIcon } from '@elastic/eui'; -import React from 'react'; - import { i18n } from '@kbn/i18n'; import { Inspector } from 'ui/inspector'; import { Action, ActionContext } from '../../../actions'; @@ -39,8 +35,8 @@ export class InspectPanelAction extends Action { }); } - public getIcon() { - return ; + public getIconType() { + return 'inspect'; } public async isCompatible({ embeddable }: ActionContext) { diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/remove_panel_action.test.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/remove_panel_action.test.tsx index 53c6bf59ab9a5..177a56288291e 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/remove_panel_action.test.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/remove_panel_action.test.tsx @@ -103,5 +103,5 @@ test('Returns title', async () => { test('Returns an icon', async () => { const action = new RemovePanelAction(); - expect(action.getIcon()).toBeDefined(); + expect(action.getIconType()).toBeDefined(); }); diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/remove_panel_action.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/remove_panel_action.ts similarity index 94% rename from src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/remove_panel_action.tsx rename to src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/remove_panel_action.ts index 04dcb3d132ec0..e7c5fda4e734c 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/remove_panel_action.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_actions/remove_panel_action.ts @@ -16,9 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiIcon } from '@elastic/eui'; import { ContainerInput, IContainer } from '../../../containers'; import { ViewMode } from '../../../types'; import { Action, ActionContext, IncompatibleActionError } from '../../../actions'; @@ -49,8 +47,8 @@ export class RemovePanelAction extends Action { }); } - public getIcon() { - return ; + public getIconType() { + return 'trash'; } public async isCompatible({ embeddable }: ActionContext) { diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx index 3d6f82b94693d..7e5aa00bf78f6 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx @@ -17,11 +17,13 @@ * under the License. */ -import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; +import { EuiContextMenuPanelDescriptor, EuiBadge } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React from 'react'; import { PanelOptionsMenu } from './panel_options_menu'; +import { Action } from '../../actions'; +import { IEmbeddable } from '../../embeddables'; export interface PanelHeaderProps { title?: string; @@ -29,12 +31,29 @@ export interface PanelHeaderProps { hidePanelTitles: boolean; getActionContextMenuPanel: () => Promise; closeContextMenu: boolean; + badges: Action[]; + embeddable: IEmbeddable; } interface PanelHeaderUiProps extends PanelHeaderProps { intl: InjectedIntl; } +function renderBadges(badges: Action[], embeddable: IEmbeddable) { + return badges.map(badge => ( + badge.execute({ embeddable })} + iconOnClickAriaLabel={badge.getDisplayName({ embeddable })} + onClick={() => badge.execute({ embeddable })} + onClickAriaLabel={badge.getDisplayName({ embeddable })} + > + {badge.getDisplayName({ embeddable })} + + )); +} + function PanelHeaderUi({ title, isViewMode, @@ -42,12 +61,17 @@ function PanelHeaderUi({ getActionContextMenuPanel, intl, closeContextMenu, + badges, + embeddable, }: PanelHeaderUiProps) { const classes = classNames('embPanel__header', { - 'embPanel__header--floater': !title || hidePanelTitles, + 'embPanel__header--floater': (!title || hidePanelTitles) && badges.length === 0, }); - if (isViewMode && (!title || hidePanelTitles)) { + const showTitle = !isViewMode || (title && !hidePanelTitles); + const showPanelBar = badges.length > 0 || showTitle; + + if (!showPanelBar) { return (
- {hidePanelTitles ? '' : title} + {showTitle ? title : ''} + {renderBadges(badges, embeddable)}
(); +triggerRegistry.set(PANEL_BADGE_TRIGGER, { + id: PANEL_BADGE_TRIGGER, + title: 'Panel badges', + actionIds: [], +}); + triggerRegistry.set(CONTEXT_MENU_TRIGGER, { id: CONTEXT_MENU_TRIGGER, title: 'Context menu', diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts new file mode 100644 index 0000000000000..cb12d74140641 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const SEARCH_EMBEDDABLE_TYPE = 'search'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts index 3138008f3e3a0..2a8fe206fb858 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts @@ -20,3 +20,4 @@ export * from './types'; export * from './search_embeddable_factory'; export * from './search_embeddable'; +export * from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts index 8591bb0ed7137..c3d45b75a7e5a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts @@ -39,6 +39,7 @@ import * as columnActions from '../doc_table/actions/columns'; import { SavedSearch } from '../types'; import searchTemplate from './search_template.html'; import { ISearchEmbeddable, SearchInput, SearchOutput } from './types'; +import { SEARCH_EMBEDDABLE_TYPE } from './constants'; interface SearchScope extends ng.IScope { columns?: string[]; @@ -77,8 +78,6 @@ interface SearchEmbeddableConfig { queryFilter: unknown; } -export const SEARCH_EMBEDDABLE_TYPE = 'search'; - export class SearchEmbeddable extends Embeddable implements ISearchEmbeddable { private readonly savedSearch: SavedSearch; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts index 01c2d96f853de..092729449b22b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts @@ -31,8 +31,9 @@ import { Container, } from '../../../../embeddable_api/public/index'; import { SavedSearchLoader } from '../types'; -import { SearchEmbeddable, SEARCH_EMBEDDABLE_TYPE } from './search_embeddable'; +import { SearchEmbeddable } from './search_embeddable'; import { SearchInput, SearchOutput } from './types'; +import { SEARCH_EMBEDDABLE_TYPE } from './constants'; export class SearchEmbeddableFactory extends EmbeddableFactory< SearchInput, diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index a59029fc36ef1..5986a5b22a52e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -18,14 +18,6 @@ */ import _ from 'lodash'; -import { - APPLY_FILTER_TRIGGER, - Embeddable, - EmbeddableInput, - EmbeddableOutput, - Trigger, - Container, -} from 'plugins/embeddable_api'; import { StaticIndexPattern } from 'ui/index_patterns'; import { PersistedState } from 'ui/persisted_state'; import { VisualizeLoader } from 'ui/visualize/loader'; @@ -40,6 +32,14 @@ import * as Rx from 'rxjs'; import { TimeRange } from 'ui/timefilter/time_history'; import { Query } from 'src/legacy/core_plugins/data/public'; import { Filter } from '@kbn/es-query'; +import { + APPLY_FILTER_TRIGGER, + Embeddable, + EmbeddableInput, + EmbeddableOutput, + Trigger, + Container, +} from '../../../../embeddable_api/public'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; const getKeys = (o: T): Array => Object.keys(o) as Array; @@ -65,6 +65,7 @@ export interface VisualizeOutput extends EmbeddableOutput { editUrl: string; indexPatterns?: StaticIndexPattern[]; savedObjectId: string; + visTypeName: string; } export class VisualizeEmbeddable extends Embeddable { @@ -98,6 +99,7 @@ export class VisualizeEmbeddable extends Embeddable + new kibana.Plugin({ + id: 'advanced_ui_actions', + publicDir: resolve(__dirname, 'public'), + uiExports: { + hacks: 'plugins/advanced_ui_actions/shim/index', + }, + }); diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/can_inherit_time_range.test.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/can_inherit_time_range.test.ts new file mode 100644 index 0000000000000..c8c3a5db5b54d --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/can_inherit_time_range.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { canInheritTimeRange } from './can_inherit_time_range'; +import { + HelloWorldEmbeddable, + HelloWorldContainer, +} from '../../../../../src/legacy/core_plugins/embeddable_api/public/test_samples'; +import { embeddableFactories } from '../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { TimeRangeEmbeddable, TimeRangeContainer } from './test_helpers'; + +test('canInheritTimeRange returns false if embeddable is inside container without a time range', () => { + const embeddable = new TimeRangeEmbeddable( + { id: '1234', timeRange: { from: 'noxw-15m', to: 'now' } }, + new HelloWorldContainer({ id: '123', panels: {} }, embeddableFactories) + ); + expect(canInheritTimeRange(embeddable)).toBe(false); +}); + +test('canInheritTimeRange returns false if embeddable is without a time range', () => { + const embeddable = new HelloWorldEmbeddable( + { id: '1234' }, + new HelloWorldContainer({ id: '123', panels: {} }, embeddableFactories) + ); + // @ts-ignore + expect(canInheritTimeRange(embeddable)).toBe(false); +}); + +test('canInheritTimeRange returns true if embeddable is inside a container with a time range', () => { + const embeddable = new TimeRangeEmbeddable( + { id: '1234', timeRange: { from: 'noxw-15m', to: 'now' } }, + new TimeRangeContainer( + { id: '123', panels: {}, timeRange: { from: 'noxw-15m', to: 'now' } }, + embeddableFactories + ) + ); + expect(canInheritTimeRange(embeddable)).toBe(true); +}); diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/can_inherit_time_range.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/can_inherit_time_range.ts new file mode 100644 index 0000000000000..cde841a3081ab --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/can_inherit_time_range.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TimeRange } from 'ui/timefilter/time_history'; +import { + Embeddable, + IContainer, + ContainerInput, +} from '../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { TimeRangeInput } from './custom_time_range_action'; + +interface ContainerTimeRangeInput extends ContainerInput { + timeRange: TimeRange; +} + +export function canInheritTimeRange(embeddable: Embeddable) { + if (!embeddable.parent) { + return false; + } + + const parent = embeddable.parent; + + return (parent as IContainer).getInput().timeRange !== undefined; +} diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts new file mode 100644 index 0000000000000..590bc70bb6748 --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { npStart } from 'ui/new_platform'; + +jest.mock('ui/new_platform', () => + require('ui/new_platform/__mocks__/helpers').createUiNewPlatformMock() +); + +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; +import { skip } from 'rxjs/operators'; +import * as Rx from 'rxjs'; +import { EmbeddableFactory } from '../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers'; +import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable_factory'; +import { CustomTimeRangeAction } from './custom_time_range_action'; +import { mount } from 'enzyme'; + +test('Custom time range action prevents embeddable from using container time', async done => { + const embeddableFactories = new Map(); + embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory()); + + const container = new TimeRangeContainer( + { + timeRange: { from: 'now-15m', to: 'now' }, + panels: { + '1': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '1', + }, + }, + '2': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '2', + }, + }, + }, + id: '123', + }, + embeddableFactories + ); + + await container.untilEmbeddableLoaded('1'); + await container.untilEmbeddableLoaded('2'); + + const child1 = container.getChild('1'); + expect(child1).toBeDefined(); + expect(child1.getInput().timeRange).toEqual({ from: 'now-15m', to: 'now' }); + + const child2 = container.getChild('2'); + expect(child2).toBeDefined(); + expect(child2.getInput().timeRange).toEqual({ from: 'now-15m', to: 'now' }); + + const overlayMock = npStart.core.overlays; + (overlayMock.openModal as any).mockClear(); + new CustomTimeRangeAction({ openModal: npStart.core.overlays.openModal }).execute({ + embeddable: child1, + }); + + const openModal = (overlayMock.openModal as any).mock.calls[0][0]; + + const wrapper = mount(openModal); + wrapper.setState({ timeRange: { from: 'now-30days', to: 'now-29days' } }); + + findTestSubject(wrapper, 'addPerPanelTimeRangeButton').simulate('click'); + + const subscription = Rx.merge(container.getOutput$(), container.getInput$()) + .pipe(skip(2)) + .subscribe(() => { + expect(child1.getInput().timeRange).toEqual({ from: 'now-30days', to: 'now-29days' }); + expect(child2.getInput().timeRange).toEqual({ from: 'now-30m', to: 'now-1m' }); + subscription.unsubscribe(); + done(); + }); + + container.updateInput({ timeRange: { from: 'now-30m', to: 'now-1m' } }); +}); + +test('Removing custom time range action resets embeddable back to container time', async done => { + const embeddableFactories = new Map(); + embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory()); + + const container = new TimeRangeContainer( + { + timeRange: { from: 'now-15m', to: 'now' }, + panels: { + '1': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '1', + }, + }, + '2': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '2', + }, + }, + }, + id: '123', + }, + embeddableFactories + ); + + await container.untilEmbeddableLoaded('1'); + await container.untilEmbeddableLoaded('2'); + + const child1 = container.getChild('1'); + const child2 = container.getChild('2'); + + const overlayMock = npStart.core.overlays; + (overlayMock.openModal as any).mockClear(); + new CustomTimeRangeAction({ openModal: npStart.core.overlays.openModal }).execute({ + embeddable: child1, + }); + + const openModal = (overlayMock.openModal as any).mock.calls[0][0]; + + const wrapper = mount(openModal); + wrapper.setState({ timeRange: { from: 'now-30days', to: 'now-29days' } }); + + findTestSubject(wrapper, 'addPerPanelTimeRangeButton').simulate('click'); + + container.updateInput({ timeRange: { from: 'now-30m', to: 'now-1m' } }); + + new CustomTimeRangeAction({ openModal: npStart.core.overlays.openModal }).execute({ + embeddable: child1, + }); + + const openModal2 = (overlayMock.openModal as any).mock.calls[1][0]; + + const wrapper2 = mount(openModal2); + findTestSubject(wrapper2, 'removePerPanelTimeRangeButton').simulate('click'); + + const subscription = Rx.merge(container.getOutput$(), container.getInput$()) + .pipe(skip(2)) + .subscribe(() => { + expect(child1.getInput().timeRange).toEqual({ from: 'now-10m', to: 'now-5m' }); + expect(child2.getInput().timeRange).toEqual({ from: 'now-10m', to: 'now-5m' }); + subscription.unsubscribe(); + done(); + }); + + container.updateInput({ timeRange: { from: 'now-10m', to: 'now-5m' } }); +}); + +test('Cancelling custom time range action leaves state alone', async done => { + const embeddableFactories = new Map(); + embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory()); + + const container = new TimeRangeContainer( + { + timeRange: { from: 'now-15m', to: 'now' }, + panels: { + '1': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '1', + timeRange: { to: '2', from: '1' }, + }, + }, + '2': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '2', + }, + }, + }, + id: '123', + }, + embeddableFactories + ); + + await container.untilEmbeddableLoaded('1'); + await container.untilEmbeddableLoaded('2'); + + const child1 = container.getChild('1'); + const child2 = container.getChild('2'); + + const overlayMock = npStart.core.overlays; + (overlayMock.openModal as any).mockClear(); + new CustomTimeRangeAction({ openModal: npStart.core.overlays.openModal }).execute({ + embeddable: child1, + }); + + const openModal = (overlayMock.openModal as any).mock.calls[0][0]; + + const wrapper = mount(openModal); + wrapper.setState({ timeRange: { from: 'now-300m', to: 'now-400m' } }); + + findTestSubject(wrapper, 'cancelPerPanelTimeRangeButton').simulate('click'); + + const subscription = Rx.merge(container.getOutput$(), container.getInput$()) + .pipe(skip(2)) + .subscribe(() => { + expect(child1.getInput().timeRange).toEqual({ from: '1', to: '2' }); + expect(child2.getInput().timeRange).toEqual({ from: 'now-30m', to: 'now-1m' }); + subscription.unsubscribe(); + done(); + }); + + container.updateInput({ timeRange: { from: 'now-30m', to: 'now-1m' } }); +}); + +test(`badge is compatible with embeddable that inherits from parent`, async () => { + const embeddableFactories = new Map(); + embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory()); + const container = new TimeRangeContainer( + { + timeRange: { from: 'now-15m', to: 'now' }, + panels: { + '1': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '1', + }, + }, + }, + id: '123', + }, + embeddableFactories + ); + + await container.untilEmbeddableLoaded('1'); + + const child = container.getChild('1'); + + const compatible = await new CustomTimeRangeAction({ + openModal: npStart.core.overlays.openModal, + }).isCompatible({ + embeddable: child, + }); + expect(compatible).toBe(true); +}); diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx new file mode 100644 index 0000000000000..70e25a90eeb97 --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { TimeRange } from 'ui/timefilter/time_history'; +import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/constants'; +import { + Action, + IEmbeddable, + ActionContext, + IncompatibleActionError, + Embeddable, + EmbeddableInput, +} from '../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { VisualizeEmbeddable } from '../../../../../src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../src/legacy/core_plugins/kibana/public/visualize/embeddable/constants'; + +import { CustomizeTimeRangeModal } from './customize_time_range_modal'; +import { OpenModal } from './shim/types'; + +export const CUSTOM_TIME_RANGE = 'CUSTOM_TIME_RANGE'; + +export interface TimeRangeInput extends EmbeddableInput { + timeRange: TimeRange; +} + +function hasTimeRange( + embeddable: IEmbeddable | Embeddable +): embeddable is Embeddable { + return (embeddable as Embeddable).getInput().timeRange !== undefined; +} + +function isVisualizeEmbeddable( + embeddable: IEmbeddable | VisualizeEmbeddable +): embeddable is VisualizeEmbeddable { + return embeddable.type === VISUALIZE_EMBEDDABLE_TYPE; +} + +export class CustomTimeRangeAction extends Action { + public readonly type = CUSTOM_TIME_RANGE; + private openModal: OpenModal; + private dateFormat: string; + + constructor({ openModal, dateFormat }: { openModal: OpenModal; dateFormat: string }) { + super(CUSTOM_TIME_RANGE); + this.order = 7; + this.openModal = openModal; + this.dateFormat = dateFormat; + } + + public getDisplayName() { + return i18n.translate('xpack.advancedUiActions.customizeTimeRangeMenuItem.displayName', { + defaultMessage: 'Customize time range', + }); + } + + public getIconType() { + return 'calendar'; + } + + public async isCompatible({ embeddable }: ActionContext) { + const isInputControl = + isVisualizeEmbeddable(embeddable) && embeddable.getOutput().visTypeName === 'inputControl'; + + return Boolean( + embeddable && + hasTimeRange(embeddable) && + // Saved searches don't listen to the time range from the container that is passed down to them so it + // won't work without a fix. For now, just leave them out. + embeddable.type !== SEARCH_EMBEDDABLE_TYPE && + !isInputControl + ); + } + + public execute({ embeddable }: ActionContext) { + if (!this.isCompatible({ embeddable })) { + throw new IncompatibleActionError(); + } + + // Only here for typescript + if (hasTimeRange(embeddable)) { + const modalSession = this.openModal( + modalSession.close()} + embeddable={embeddable} + dateFormat={this.dateFormat} + /> + ); + } + } +} diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts new file mode 100644 index 0000000000000..48c6315f7a224 --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { npStart } from 'ui/new_platform'; + +jest.mock('ui/new_platform', () => + require('ui/new_platform/__mocks__/helpers').createUiNewPlatformMock() +); + +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; +import { skip } from 'rxjs/operators'; +import * as Rx from 'rxjs'; +import { EmbeddableFactory } from '../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers'; +import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable_factory'; +import { CustomTimeRangeBadge } from './custom_time_range_badge'; +import { mount } from 'enzyme'; + +test('Custom time range action prevents embeddable from using container time', async done => { + const embeddableFactories = new Map(); + embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory()); + + const container = new TimeRangeContainer( + { + timeRange: { from: 'now-15m', to: 'now' }, + panels: { + '1': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '1', + }, + }, + '2': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '2', + }, + }, + }, + id: '123', + }, + embeddableFactories + ); + + await container.untilEmbeddableLoaded('1'); + await container.untilEmbeddableLoaded('2'); + + const child1 = container.getChild('1'); + expect(child1).toBeDefined(); + expect(child1.getInput().timeRange).toEqual({ from: 'now-15m', to: 'now' }); + + const child2 = container.getChild('2'); + expect(child2).toBeDefined(); + expect(child2.getInput().timeRange).toEqual({ from: 'now-15m', to: 'now' }); + + const overlayMock = npStart.core.overlays; + (overlayMock.openModal as any).mockClear(); + new CustomTimeRangeBadge({ openModal: npStart.core.overlays.openModal }).execute({ + embeddable: child1, + }); + + const openModal = (overlayMock.openModal as any).mock.calls[0][0]; + + const wrapper = mount(openModal); + wrapper.setState({ timeRange: { from: 'now-30days', to: 'now-29days' } }); + + findTestSubject(wrapper, 'addPerPanelTimeRangeButton').simulate('click'); + + const subscription = Rx.merge(container.getOutput$(), container.getInput$()) + .pipe(skip(2)) + .subscribe(() => { + expect(child1.getInput().timeRange).toEqual({ from: 'now-30days', to: 'now-29days' }); + expect(child2.getInput().timeRange).toEqual({ from: 'now-30m', to: 'now-1m' }); + subscription.unsubscribe(); + done(); + }); + + container.updateInput({ timeRange: { from: 'now-30m', to: 'now-1m' } }); +}); + +test('Removing custom time range action resets embeddable back to container time', async done => { + const embeddableFactories = new Map(); + embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory()); + + const container = new TimeRangeContainer( + { + timeRange: { from: 'now-15m', to: 'now' }, + panels: { + '1': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '1', + timeRange: { from: '1', to: '2' }, + }, + }, + '2': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '2', + }, + }, + }, + id: '123', + }, + embeddableFactories + ); + + await container.untilEmbeddableLoaded('1'); + await container.untilEmbeddableLoaded('2'); + + const child1 = container.getChild('1'); + const child2 = container.getChild('2'); + + const overlayMock = npStart.core.overlays; + (overlayMock.openModal as any).mockClear(); + new CustomTimeRangeBadge({ openModal: npStart.core.overlays.openModal }).execute({ + embeddable: child1, + }); + + const openModal = (overlayMock.openModal as any).mock.calls[0][0]; + + const wrapper = mount(openModal); + findTestSubject(wrapper, 'removePerPanelTimeRangeButton').simulate('click'); + + const subscription = Rx.merge(child1.getInput$(), container.getOutput$(), container.getInput$()) + .pipe(skip(4)) + .subscribe(() => { + expect(child1.getInput().timeRange).toEqual({ from: 'now-10m', to: 'now-5m' }); + expect(child2.getInput().timeRange).toEqual({ from: 'now-10m', to: 'now-5m' }); + subscription.unsubscribe(); + done(); + }); + + container.updateInput({ timeRange: { from: 'now-10m', to: 'now-5m' } }); +}); + +test(`badge is not compatible with embeddable that inherits from parent`, async () => { + const embeddableFactories = new Map(); + embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory()); + const container = new TimeRangeContainer( + { + timeRange: { from: 'now-15m', to: 'now' }, + panels: { + '1': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '1', + }, + }, + }, + id: '123', + }, + embeddableFactories + ); + + await container.untilEmbeddableLoaded('1'); + + const child = container.getChild('1'); + + const compatible = await new CustomTimeRangeBadge({ + openModal: npStart.core.overlays.openModal, + }).isCompatible({ + embeddable: child, + }); + expect(compatible).toBe(false); +}); + +test(`badge is compatible with embeddable that has custom time range`, async () => { + const embeddableFactories = new Map(); + embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory()); + const container = new TimeRangeContainer( + { + timeRange: { from: 'now-15m', to: 'now' }, + panels: { + '1': { + type: TIME_RANGE_EMBEDDABLE, + explicitInput: { + id: '1', + timeRange: { to: '123', from: '456' }, + }, + }, + }, + id: '123', + }, + embeddableFactories + ); + + await container.untilEmbeddableLoaded('1'); + + const child = container.getChild('1'); + + const compatible = await new CustomTimeRangeBadge({ + openModal: npStart.core.overlays.openModal, + }).isCompatible({ + embeddable: child, + }); + expect(compatible).toBe(true); +}); diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx new file mode 100644 index 0000000000000..7aed2894d63ab --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { TimeRange } from 'ui/timefilter/time_history'; +import { prettyDuration, commonDurationRanges } from '@elastic/eui'; + +import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/constants'; +import { + Action, + IEmbeddable, + ActionContext, + IncompatibleActionError, + Embeddable, + EmbeddableInput, +} from '../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { VisualizeEmbeddable } from '../../../../../src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../src/legacy/core_plugins/kibana/public/visualize/embeddable/constants'; + +import { CustomizeTimeRangeModal } from './customize_time_range_modal'; +import { doesInheritTimeRange } from './does_inherit_time_range'; +import { OpenModal } from './shim/types'; + +export const CUSTOM_TIME_RANGE_BADGE = 'CUSTOM_TIME_RANGE_BADGE'; + +export interface TimeRangeInput extends EmbeddableInput { + timeRange: TimeRange; +} + +function hasTimeRange( + embeddable: IEmbeddable | Embeddable +): embeddable is Embeddable { + return (embeddable as Embeddable).getInput().timeRange !== undefined; +} + +function isVisualizeEmbeddable( + embeddable: IEmbeddable | VisualizeEmbeddable +): embeddable is VisualizeEmbeddable { + return embeddable.type === VISUALIZE_EMBEDDABLE_TYPE; +} + +export class CustomTimeRangeBadge extends Action { + public readonly type = CUSTOM_TIME_RANGE_BADGE; + private openModal: OpenModal; + private dateFormat: string; + + constructor({ openModal, dateFormat }: { openModal: OpenModal; dateFormat: string }) { + super(CUSTOM_TIME_RANGE_BADGE); + this.order = 7; + this.openModal = openModal; + this.dateFormat = dateFormat; + } + + public getDisplayName({ embeddable }: ActionContext>) { + return prettyDuration( + embeddable.getInput().timeRange.from, + embeddable.getInput().timeRange.to, + commonDurationRanges, + this.dateFormat + ); + } + + public getIconType() { + return 'calendar'; + } + + public async isCompatible({ embeddable }: ActionContext) { + const isInputControl = + isVisualizeEmbeddable(embeddable) && embeddable.getOutput().visTypeName === 'inputControl'; + + return Boolean( + embeddable && + hasTimeRange(embeddable) && + // Saved searches don't listen to the time range from the container that is passed down to them so it + // won't work without a fix. For now, just leave them out. + embeddable.type !== SEARCH_EMBEDDABLE_TYPE && + !isInputControl && + !doesInheritTimeRange(embeddable) + ); + } + + public execute({ embeddable }: ActionContext) { + if (!this.isCompatible({ embeddable })) { + throw new IncompatibleActionError(); + } + + // Only here for typescript + if (hasTimeRange(embeddable)) { + const modalSession = this.openModal( + modalSession.close()} + embeddable={embeddable} + dateFormat={this.dateFormat} + /> + ); + } + } +} diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx new file mode 100644 index 0000000000000..d43054a5796ba --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx @@ -0,0 +1,176 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component } from 'react'; + +import { + EuiFormRow, + EuiButton, + EuiButtonEmpty, + EuiModalHeader, + EuiModalFooter, + EuiModalBody, + EuiModalHeaderTitle, + EuiSuperDatePicker, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import { TimeRange } from 'ui/timefilter/time_history'; +import { i18n } from '@kbn/i18n'; +import { Embeddable } from '../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { TimeRangeInput } from './custom_time_range_action'; +import { doesInheritTimeRange } from './does_inherit_time_range'; + +interface CustomizeTimeRangeProps { + embeddable: Embeddable; + onClose: () => void; + dateFormat?: string; +} + +interface State { + timeRange?: TimeRange; + inheritTimeRange: boolean; +} + +export class CustomizeTimeRangeModal extends Component { + constructor(props: CustomizeTimeRangeProps) { + super(props); + this.state = { + timeRange: props.embeddable.getInput().timeRange, + inheritTimeRange: doesInheritTimeRange(props.embeddable), + }; + } + + onTimeChange = ({ start, end }: { start: string; end: string }) => { + this.setState({ timeRange: { from: start, to: end } }); + }; + + cancel = () => { + this.props.onClose(); + }; + + onInheritToggle = () => { + this.setState(prevState => ({ + inheritTimeRange: !prevState.inheritTimeRange, + })); + }; + + addToPanel = () => { + const { embeddable } = this.props; + + embeddable.updateInput({ timeRange: this.state.timeRange }); + + this.props.onClose(); + }; + + inheritFromParent = () => { + const { embeddable } = this.props; + const parent = embeddable.parent; + const parentPanels = parent!.getInput().panels; + + // Remove any explicit input to this child from the parent. + parent!.updateInput({ + panels: { + ...parentPanels, + [embeddable.id]: { + ...parentPanels[embeddable.id], + explicitInput: { + ...parentPanels[embeddable.id].explicitInput, + timeRange: undefined, + }, + }, + }, + }); + + this.props.onClose(); + }; + + public render() { + return ( + + + + {i18n.translate('xpack.advancedUiActions.customizeTimeRange.modal.headerTitle', { + defaultMessage: 'Customize panel time range', + })} + + + + + {' '} + + + + + + + + + + {' '} + {i18n.translate( + 'xpack.advancedUiActions.customizePanelTimeRange.modal.removeButtonTitle', + { + defaultMessage: 'Remove', + } + )} + + + + + + + {' '} + {i18n.translate( + 'xpack.advancedUiActions.customizePanelTimeRange.modal.cancelButtonTitle', + { + defaultMessage: 'Cancel', + } + )} + + + + {i18n.translate( + 'xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle', + { + defaultMessage: 'Add to panel', + } + )} + + + + + + + ); + } +} diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/does_inherit_time_range.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/does_inherit_time_range.ts new file mode 100644 index 0000000000000..72b8e771beb68 --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/does_inherit_time_range.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Embeddable } from '../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { TimeRangeInput } from './custom_time_range_action'; + +export function doesInheritTimeRange(embeddable: Embeddable) { + if (!embeddable.parent) { + return false; + } + + const parent = embeddable.parent; + + // Note: this logic might not work in a container nested world... the explicit input + // may be on the root... or any of the interim parents. + + // If there is no explicit input defined on the parent then this embeddable inherits the + // time range from whatever the time range of the parent is. + return parent.getInput().panels[embeddable.id].explicitInput.timeRange === undefined; +} diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/index.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/index.ts new file mode 100644 index 0000000000000..41bc2aa258807 --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/index.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/shim/index.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/shim/index.ts new file mode 100644 index 0000000000000..f36d9041722ba --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/shim/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { npStart } from 'ui/new_platform'; +import { PluginInitializerContext } from 'src/core/public'; +import { embeddablePlugin } from '../../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { Plugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + const advancedUiActions = new Plugin(initializerContext); + + advancedUiActions.start(npStart.core, { + embeddable: embeddablePlugin, + }); +} + +plugin({} as any); diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts new file mode 100644 index 0000000000000..dad8d2b413a33 --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext, CoreStart } from 'src/core/public'; +import { + EmbeddablePlugin, + CONTEXT_MENU_TRIGGER, + PANEL_BADGE_TRIGGER, +} from '../../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { CUSTOM_TIME_RANGE, CustomTimeRangeAction } from '../custom_time_range_action'; + +import { CUSTOM_TIME_RANGE_BADGE, CustomTimeRangeBadge } from '../custom_time_range_badge'; + +export class Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public start(core: CoreStart, plugins: { embeddable: EmbeddablePlugin }) { + plugins.embeddable.addAction( + new CustomTimeRangeAction({ + openModal: core.overlays.openModal, + dateFormat: core.uiSettings.get('dateFormat'), + }) + ); + + plugins.embeddable.attachAction({ + triggerId: CONTEXT_MENU_TRIGGER, + actionId: CUSTOM_TIME_RANGE, + }); + + plugins.embeddable.addAction( + new CustomTimeRangeBadge({ + openModal: core.overlays.openModal, + dateFormat: core.uiSettings.get('dateFormat'), + }) + ); + plugins.embeddable.attachAction({ + triggerId: PANEL_BADGE_TRIGGER, + actionId: CUSTOM_TIME_RANGE_BADGE, + }); + } + + public stop() {} +} diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/shim/types.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/shim/types.ts new file mode 100644 index 0000000000000..c7607ac67f9fc --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/shim/types.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { OverlayRef } from 'src/core/public'; + +export type OpenModal = ( + modalChildren: React.ReactNode, + modalProps?: { + closeButtonAriaLabel?: string; + 'data-test-subj'?: string; + } +) => OverlayRef; diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/index.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/index.ts new file mode 100644 index 0000000000000..8d33b8344ff56 --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { TimeRangeEmbeddable, TIME_RANGE_EMBEDDABLE } from './time_range_embeddable'; +export { TimeRangeContainer } from './time_range_container'; diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_container.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_container.ts new file mode 100644 index 0000000000000..e096e784c2861 --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_container.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TimeRange } from 'ui/timefilter/time_history'; +import { + ContainerInput, + Container, + ContainerOutput, + EmbeddableFactory, +} from '../../../../../../src/legacy/core_plugins/embeddable_api/public'; + +interface ContainerTimeRangeInput extends ContainerInput { + timeRange: TimeRange; +} + +const TIME_RANGE_CONTAINER = 'TIME_RANGE_CONTAINER'; + +export class TimeRangeContainer extends Container< + { timeRange: TimeRange }, + ContainerTimeRangeInput, + ContainerOutput +> { + public readonly type = TIME_RANGE_CONTAINER; + constructor( + initialInput: ContainerTimeRangeInput, + embeddableFactories: Map, + parent?: Container + ) { + super(initialInput, { embeddableLoaded: {} }, embeddableFactories, parent); + } + + public getInheritedInput() { + return { timeRange: this.input.timeRange }; + } + + public render() {} + + public reload() {} +} diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable.ts new file mode 100644 index 0000000000000..81d2fd45722e7 --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TimeRange } from 'ui/timefilter/time_history'; +import { + EmbeddableOutput, + Embeddable, + EmbeddableInput, + IContainer, +} from '../../../../../../src/legacy/core_plugins/embeddable_api/public'; + +interface EmbeddableTimeRangeInput extends EmbeddableInput { + timeRange: TimeRange; +} + +export const TIME_RANGE_EMBEDDABLE = 'TIME_RANGE_EMBEDDABLE'; + +export class TimeRangeEmbeddable extends Embeddable { + public readonly type = TIME_RANGE_EMBEDDABLE; + + constructor(initialInput: EmbeddableTimeRangeInput, parent?: IContainer) { + super(initialInput, {}, parent); + } + + public render() {} + + public reload() {} +} diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.ts new file mode 100644 index 0000000000000..a4cee9f57a224 --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/test_helpers/time_range_embeddable_factory.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TimeRange } from 'ui/timefilter/time_history'; +import { + EmbeddableInput, + IContainer, + EmbeddableFactory, +} from '../../../../../../src/legacy/core_plugins/embeddable_api/public'; +import { TIME_RANGE_EMBEDDABLE, TimeRangeEmbeddable } from './time_range_embeddable'; + +interface EmbeddableTimeRangeInput extends EmbeddableInput { + timeRange: TimeRange; +} + +export class TimeRangeEmbeddableFactory extends EmbeddableFactory { + public readonly type = TIME_RANGE_EMBEDDABLE; + + public isEditable() { + return true; + } + + public async create(initialInput: EmbeddableTimeRangeInput, parent?: IContainer) { + return new TimeRangeEmbeddable(initialInput, parent); + } + + public getDisplayName() { + return 'time range'; + } +} diff --git a/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx b/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.ts similarity index 97% rename from x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx rename to x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.ts index b30e0cdac58d4..d11abcfd9a69d 100644 --- a/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx +++ b/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.ts @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import dateMath from '@elastic/datemath'; import { i18n } from '@kbn/i18n'; import moment from 'moment-timezone'; @@ -11,7 +10,6 @@ import moment from 'moment-timezone'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; import chrome from 'ui/chrome'; -import { EuiIcon } from '@elastic/eui'; import { ISearchEmbeddable, SEARCH_EMBEDDABLE_TYPE, @@ -49,8 +47,8 @@ class GetCsvReportPanelAction extends Action { this.isDownloading = false; } - public getIcon() { - return ; + public getIconType() { + return 'document'; } public getDisplayName() { From 5906fb6ef1cd9f14b64c563af74a7bb655b1f1ff Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Wed, 24 Jul 2019 11:45:28 -0400 Subject: [PATCH 02/13] fix typescript --- .../public/custom_time_range_action.tsx | 4 ++-- .../public/custom_time_range_badge.test.ts | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx index 70e25a90eeb97..c4fd56adac492 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx @@ -43,9 +43,9 @@ function isVisualizeEmbeddable( export class CustomTimeRangeAction extends Action { public readonly type = CUSTOM_TIME_RANGE; private openModal: OpenModal; - private dateFormat: string; + private dateFormat?: string; - constructor({ openModal, dateFormat }: { openModal: OpenModal; dateFormat: string }) { + constructor({ openModal, dateFormat }: { openModal: OpenModal; dateFormat?: string }) { super(CUSTOM_TIME_RANGE); this.order = 7; this.openModal = openModal; diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts index 48c6315f7a224..a3921a858e1af 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts @@ -59,7 +59,10 @@ test('Custom time range action prevents embeddable from using container time', a const overlayMock = npStart.core.overlays; (overlayMock.openModal as any).mockClear(); - new CustomTimeRangeBadge({ openModal: npStart.core.overlays.openModal }).execute({ + new CustomTimeRangeBadge({ + openModal: npStart.core.overlays.openModal, + dateFormat: 'MM YYYY', + }).execute({ embeddable: child1, }); @@ -117,7 +120,10 @@ test('Removing custom time range action resets embeddable back to container time const overlayMock = npStart.core.overlays; (overlayMock.openModal as any).mockClear(); - new CustomTimeRangeBadge({ openModal: npStart.core.overlays.openModal }).execute({ + new CustomTimeRangeBadge({ + openModal: npStart.core.overlays.openModal, + dateFormat: 'MM YYYY', + }).execute({ embeddable: child1, }); @@ -163,6 +169,7 @@ test(`badge is not compatible with embeddable that inherits from parent`, async const compatible = await new CustomTimeRangeBadge({ openModal: npStart.core.overlays.openModal, + dateFormat: 'MM YYYY', }).isCompatible({ embeddable: child, }); @@ -195,6 +202,7 @@ test(`badge is compatible with embeddable that has custom time range`, async () const compatible = await new CustomTimeRangeBadge({ openModal: npStart.core.overlays.openModal, + dateFormat: 'MM YYYY', }).isCompatible({ embeddable: child, }); From d16646da67f563b8e57bb5fa2d7b32cc08f42df4 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Thu, 25 Jul 2019 12:55:36 -0400 Subject: [PATCH 03/13] add space between badge and title --- .../panel/panel_header/panel_header.tsx | 1 + .../public/customize_time_range_modal.tsx | 38 +++++++++++-------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx index 7e5aa00bf78f6..82a6405c7c412 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx @@ -103,6 +103,7 @@ function PanelHeaderUi({ )} > {showTitle ? title : ''} +   {renderBadges(badges, embeddable)}
diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx index d43054a5796ba..582f88a354fd3 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx @@ -121,8 +121,8 @@ export class CustomizeTimeRangeModal extends Component - - + + - - - - + + + + - + + - {i18n.translate( - 'xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle', - { - defaultMessage: 'Add to panel', - } - )} + {this.state.inheritTimeRange + ? i18n.translate( + 'xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle', + { + defaultMessage: 'Add to panel', + } + ) + : i18n.translate( + 'xpack.advancedUiActions.customizePanelTimeRange.modal.updatePanelTimeRangeButtonTitle', + { + defaultMessage: 'Update', + } + )} - - + + From c6cd8de37f36f065e80674f8da16f3b8f576c3a7 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Thu, 25 Jul 2019 12:57:01 -0400 Subject: [PATCH 04/13] remove unneccessary iconOnClick* fns --- .../embeddable_api/public/panel/panel_header/panel_header.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx index 82a6405c7c412..f0d1f564a3e0d 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx @@ -44,8 +44,6 @@ function renderBadges(badges: Action[], embeddable: IEmbeddable) { badge.execute({ embeddable })} - iconOnClickAriaLabel={badge.getDisplayName({ embeddable })} onClick={() => badge.execute({ embeddable })} onClickAriaLabel={badge.getDisplayName({ embeddable })} > From d699c58f37582ff575694789a46830ba9387a964 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Thu, 25 Jul 2019 15:06:43 -0400 Subject: [PATCH 05/13] Use common time ranges from advanced settings --- .../public/custom_time_range_action.tsx | 14 +++++++++++++- .../public/custom_time_range_badge.test.ts | 4 ++++ .../public/custom_time_range_badge.tsx | 14 +++++++++++++- .../public/customize_time_range_modal.tsx | 11 +++++++++++ .../advanced_ui_actions/public/shim/plugin.ts | 8 ++++++-- .../plugins/advanced_ui_actions/public/types.ts | 11 +++++++++++ 6 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/types.ts diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx index c4fd56adac492..c25ab2a77b6a8 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx @@ -21,6 +21,7 @@ import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../src/legacy/core_plugin import { CustomizeTimeRangeModal } from './customize_time_range_modal'; import { OpenModal } from './shim/types'; +import { CommonlyUsedRange } from './types'; export const CUSTOM_TIME_RANGE = 'CUSTOM_TIME_RANGE'; @@ -44,12 +45,22 @@ export class CustomTimeRangeAction extends Action { public readonly type = CUSTOM_TIME_RANGE; private openModal: OpenModal; private dateFormat?: string; + private commonlyUsedRanges: CommonlyUsedRange[]; - constructor({ openModal, dateFormat }: { openModal: OpenModal; dateFormat?: string }) { + constructor({ + openModal, + dateFormat, + commonlyUsedRanges, + }: { + openModal: OpenModal; + dateFormat: string; + commonlyUsedRanges: CommonlyUsedRange[]; + }) { super(CUSTOM_TIME_RANGE); this.order = 7; this.openModal = openModal; this.dateFormat = dateFormat; + this.commonlyUsedRanges = commonlyUsedRanges; } public getDisplayName() { @@ -88,6 +99,7 @@ export class CustomTimeRangeAction extends Action { onClose={() => modalSession.close()} embeddable={embeddable} dateFormat={this.dateFormat} + commonlyUsedRanges={this.commonlyUsedRanges} /> ); } diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts index a3921a858e1af..f4a4fe81bdaed 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts @@ -62,6 +62,7 @@ test('Custom time range action prevents embeddable from using container time', a new CustomTimeRangeBadge({ openModal: npStart.core.overlays.openModal, dateFormat: 'MM YYYY', + commonlyUsedRanges: [], }).execute({ embeddable: child1, }); @@ -123,6 +124,7 @@ test('Removing custom time range action resets embeddable back to container time new CustomTimeRangeBadge({ openModal: npStart.core.overlays.openModal, dateFormat: 'MM YYYY', + commonlyUsedRanges: [], }).execute({ embeddable: child1, }); @@ -170,6 +172,7 @@ test(`badge is not compatible with embeddable that inherits from parent`, async const compatible = await new CustomTimeRangeBadge({ openModal: npStart.core.overlays.openModal, dateFormat: 'MM YYYY', + commonlyUsedRanges: [], }).isCompatible({ embeddable: child, }); @@ -203,6 +206,7 @@ test(`badge is compatible with embeddable that has custom time range`, async () const compatible = await new CustomTimeRangeBadge({ openModal: npStart.core.overlays.openModal, dateFormat: 'MM YYYY', + commonlyUsedRanges: [], }).isCompatible({ embeddable: child, }); diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx index 7aed2894d63ab..fcac999abb24a 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx @@ -23,6 +23,7 @@ import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../src/legacy/core_plugin import { CustomizeTimeRangeModal } from './customize_time_range_modal'; import { doesInheritTimeRange } from './does_inherit_time_range'; import { OpenModal } from './shim/types'; +import { CommonlyUsedRange } from './types'; export const CUSTOM_TIME_RANGE_BADGE = 'CUSTOM_TIME_RANGE_BADGE'; @@ -46,12 +47,22 @@ export class CustomTimeRangeBadge extends Action { public readonly type = CUSTOM_TIME_RANGE_BADGE; private openModal: OpenModal; private dateFormat: string; + private commonlyUsedRanges: CommonlyUsedRange[]; - constructor({ openModal, dateFormat }: { openModal: OpenModal; dateFormat: string }) { + constructor({ + openModal, + dateFormat, + commonlyUsedRanges, + }: { + openModal: OpenModal; + dateFormat: string; + commonlyUsedRanges: CommonlyUsedRange[]; + }) { super(CUSTOM_TIME_RANGE_BADGE); this.order = 7; this.openModal = openModal; this.dateFormat = dateFormat; + this.commonlyUsedRanges = commonlyUsedRanges; } public getDisplayName({ embeddable }: ActionContext>) { @@ -94,6 +105,7 @@ export class CustomTimeRangeBadge extends Action { onClose={() => modalSession.close()} embeddable={embeddable} dateFormat={this.dateFormat} + commonlyUsedRanges={this.commonlyUsedRanges} /> ); } diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx index 582f88a354fd3..35d2c22add5bc 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx @@ -23,11 +23,13 @@ import { i18n } from '@kbn/i18n'; import { Embeddable } from '../../../../../src/legacy/core_plugins/embeddable_api/public'; import { TimeRangeInput } from './custom_time_range_action'; import { doesInheritTimeRange } from './does_inherit_time_range'; +import { CommonlyUsedRange } from './types'; interface CustomizeTimeRangeProps { embeddable: Embeddable; onClose: () => void; dateFormat?: string; + commonlyUsedRanges: CommonlyUsedRange[]; } interface State { @@ -116,6 +118,15 @@ export class CustomizeTimeRangeModal extends Component { + return { + start: from, + end: to, + label: display, + }; + } + )} /> diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts index dad8d2b413a33..2fc88a5b636f5 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts @@ -18,10 +18,13 @@ export class Plugin { constructor(initializerContext: PluginInitializerContext) {} public start(core: CoreStart, plugins: { embeddable: EmbeddablePlugin }) { + const dateFormat = core.uiSettings.get('dateFormat'); + const commonlyUsedRanges = core.uiSettings.get('timepicker:quickRanges') as CommonlyUsedRange[]; plugins.embeddable.addAction( new CustomTimeRangeAction({ openModal: core.overlays.openModal, - dateFormat: core.uiSettings.get('dateFormat'), + dateFormat, + commonlyUsedRanges, }) ); @@ -33,7 +36,8 @@ export class Plugin { plugins.embeddable.addAction( new CustomTimeRangeBadge({ openModal: core.overlays.openModal, - dateFormat: core.uiSettings.get('dateFormat'), + dateFormat, + commonlyUsedRanges, }) ); plugins.embeddable.attachAction({ diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/types.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/types.ts new file mode 100644 index 0000000000000..2f7e941dc8c9b --- /dev/null +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/types.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; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface CommonlyUsedRange { + from: string; + to: string; + display: string; +} From 7f3d40c401b61157d78d42544bc6e16739513bf4 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Thu, 25 Jul 2019 15:07:04 -0400 Subject: [PATCH 06/13] Only add space after title if title is showing --- .../embeddable_api/public/panel/panel_header/panel_header.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx index f0d1f564a3e0d..6659cbca329c0 100644 --- a/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/panel/panel_header/panel_header.tsx @@ -100,8 +100,7 @@ function PanelHeaderUi({ } )} > - {showTitle ? title : ''} -   + {showTitle ? `${title} ` : ''} {renderBadges(badges, embeddable)} From a874332cff917d0b93d8a4d1fd9b97ad52eb9434 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Thu, 25 Jul 2019 15:56:01 -0400 Subject: [PATCH 07/13] fix: type errors --- .../public/custom_time_range_action.test.ts | 26 ++++++++++++++++--- .../advanced_ui_actions/public/shim/plugin.ts | 3 ++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts index 590bc70bb6748..65f09035d09ad 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts @@ -59,7 +59,11 @@ test('Custom time range action prevents embeddable from using container time', a const overlayMock = npStart.core.overlays; (overlayMock.openModal as any).mockClear(); - new CustomTimeRangeAction({ openModal: npStart.core.overlays.openModal }).execute({ + new CustomTimeRangeAction({ + openModal: npStart.core.overlays.openModal, + commonlyUsedRanges: [], + dateFormat: 'MM YYY', + }).execute({ embeddable: child1, }); @@ -116,7 +120,11 @@ test('Removing custom time range action resets embeddable back to container time const overlayMock = npStart.core.overlays; (overlayMock.openModal as any).mockClear(); - new CustomTimeRangeAction({ openModal: npStart.core.overlays.openModal }).execute({ + new CustomTimeRangeAction({ + openModal: npStart.core.overlays.openModal, + commonlyUsedRanges: [], + dateFormat: 'MM YYY', + }).execute({ embeddable: child1, }); @@ -129,7 +137,11 @@ test('Removing custom time range action resets embeddable back to container time container.updateInput({ timeRange: { from: 'now-30m', to: 'now-1m' } }); - new CustomTimeRangeAction({ openModal: npStart.core.overlays.openModal }).execute({ + new CustomTimeRangeAction({ + openModal: npStart.core.overlays.openModal, + commonlyUsedRanges: [], + dateFormat: 'MM YYY', + }).execute({ embeddable: child1, }); @@ -185,7 +197,11 @@ test('Cancelling custom time range action leaves state alone', async done => { const overlayMock = npStart.core.overlays; (overlayMock.openModal as any).mockClear(); - new CustomTimeRangeAction({ openModal: npStart.core.overlays.openModal }).execute({ + new CustomTimeRangeAction({ + openModal: npStart.core.overlays.openModal, + commonlyUsedRanges: [], + dateFormat: 'MM YYY', + }).execute({ embeddable: child1, }); @@ -233,6 +249,8 @@ test(`badge is compatible with embeddable that inherits from parent`, async () = const compatible = await new CustomTimeRangeAction({ openModal: npStart.core.overlays.openModal, + commonlyUsedRanges: [], + dateFormat: 'MM YYY', }).isCompatible({ embeddable: child, }); diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts index 2fc88a5b636f5..d7bc1d45a6406 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/shim/plugin.ts @@ -13,12 +13,13 @@ import { import { CUSTOM_TIME_RANGE, CustomTimeRangeAction } from '../custom_time_range_action'; import { CUSTOM_TIME_RANGE_BADGE, CustomTimeRangeBadge } from '../custom_time_range_badge'; +import { CommonlyUsedRange } from '../types'; export class Plugin { constructor(initializerContext: PluginInitializerContext) {} public start(core: CoreStart, plugins: { embeddable: EmbeddablePlugin }) { - const dateFormat = core.uiSettings.get('dateFormat'); + const dateFormat = core.uiSettings.get('dateFormat') as string; const commonlyUsedRanges = core.uiSettings.get('timepicker:quickRanges') as CommonlyUsedRange[]; plugins.embeddable.addAction( new CustomTimeRangeAction({ From 9ccd0338aa3f57435b5e918df2cc5560a8be57fb Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Thu, 25 Jul 2019 16:03:31 -0400 Subject: [PATCH 08/13] put xpack i18n links back in right place --- .i18nrc.json | 37 +------------------------------------ x-pack/.i18nrc.json | 1 + 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/.i18nrc.json b/.i18nrc.json index 3be42bfcc9554..bcc1fe70ab1e7 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -24,42 +24,7 @@ "timelion": "src/legacy/core_plugins/timelion", "tagCloud": "src/legacy/core_plugins/tagcloud", "tsvb": "src/legacy/core_plugins/metrics", - "kbnESQuery": "packages/kbn-es-query", - "xpack.advancedUiActions": "x-pack/legacy/plugins/advanced_ui_actions", - "xpack.actions": "x-pack/legacy/plugins/actions", - "xpack.alerting": "x-pack/legacy/plugins/alerting", - "xpack.apm": "x-pack/legacy/plugins/apm", - "xpack.beatsManagement": "x-pack/legacy/plugins/beats_management", - "xpack.canvas": "x-pack/legacy/plugins/canvas", - "xpack.code": "x-pack/legacy/plugins/code", - "xpack.crossClusterReplication": "x-pack/legacy/plugins/cross_cluster_replication", - "xpack.dashboardMode": "x-pack/legacy/plugins/dashboard_mode", - "xpack.fileUpload": "x-pack/legacy/plugins/file_upload", - "xpack.graph": "x-pack/legacy/plugins/graph", - "xpack.grokDebugger": "x-pack/legacy/plugins/grokdebugger", - "xpack.idxMgmt": "x-pack/legacy/plugins/index_management", - "xpack.indexLifecycleMgmt": "x-pack/legacy/plugins/index_lifecycle_management", - "xpack.infra": "x-pack/legacy/plugins/infra", - "xpack.kueryAutocomplete": "x-pack/legacy/plugins/kuery_autocomplete", - "xpack.licenseMgmt": "x-pack/legacy/plugins/license_management", - "xpack.maps": "x-pack/legacy/plugins/maps", - "xpack.ml": "x-pack/legacy/plugins/ml", - "xpack.logstash": "x-pack/legacy/plugins/logstash", - "xpack.main": "x-pack/legacy/plugins/xpack_main", - "xpack.telemetry": "x-pack/legacy/plugins/telemetry", - "xpack.monitoring": "x-pack/legacy/plugins/monitoring", - "xpack.remoteClusters": "x-pack/legacy/plugins/remote_clusters", - "xpack.reporting": "x-pack/legacy/plugins/reporting", - "xpack.rollupJobs": "x-pack/legacy/plugins/rollup", - "xpack.searchProfiler": "x-pack/legacy/plugins/searchprofiler", - "xpack.siem": "x-pack/legacy/plugins/siem", - "xpack.security": "x-pack/legacy/plugins/security", - "xpack.server": "x-pack/legacy/server", - "xpack.snapshotRestore": "x-pack/legacy/plugins/snapshot_restore", - "xpack.spaces": "x-pack/legacy/plugins/spaces", - "xpack.upgradeAssistant": "x-pack/legacy/plugins/upgrade_assistant", - "xpack.uptime": "x-pack/legacy/plugins/uptime", - "xpack.watcher": "x-pack/legacy/plugins/watcher" + "kbnESQuery": "packages/kbn-es-query" }, "exclude": ["src/legacy/ui/ui_render/ui_render_mixin.js"], "translations": [] diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index f8c37c792d51c..5c90b60ca9044 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -1,6 +1,7 @@ { "prefix": "xpack", "paths": { + "xpack.advancedUiActions": "legacy/plugins/advanced_ui_actions", "xpack.actions": "legacy/plugins/actions", "xpack.alerting": "legacy/plugins/alerting", "xpack.apm": "legacy/plugins/apm", From bbf4b8d913938dc84bd2d360c8471107ec8c11f7 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Mon, 29 Jul 2019 10:07:24 -0400 Subject: [PATCH 09/13] Fix flex items, remove spaces --- .../public/customize_time_range_modal.tsx | 100 ++++++++---------- 1 file changed, 43 insertions(+), 57 deletions(-) diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx index 35d2c22add5bc..9d07c88070b44 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx @@ -102,7 +102,6 @@ export class CustomizeTimeRangeModal extends Component - {' '} - - - - - {' '} - {i18n.translate( - 'xpack.advancedUiActions.customizePanelTimeRange.modal.removeButtonTitle', - { - defaultMessage: 'Remove', - } - )} - - - - - - - {' '} - {i18n.translate( - 'xpack.advancedUiActions.customizePanelTimeRange.modal.cancelButtonTitle', - { - defaultMessage: 'Cancel', - } - )} - - - - - {this.state.inheritTimeRange - ? i18n.translate( - 'xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle', - { - defaultMessage: 'Add to panel', - } - ) - : i18n.translate( - 'xpack.advancedUiActions.customizePanelTimeRange.modal.updatePanelTimeRangeButtonTitle', - { - defaultMessage: 'Update', - } - )} - - - + + + + {i18n.translate( + 'xpack.advancedUiActions.customizePanelTimeRange.modal.removeButtonTitle', + { + defaultMessage: 'Remove', + } + )} + + + + + {i18n.translate( + 'xpack.advancedUiActions.customizePanelTimeRange.modal.cancelButtonTitle', + { + defaultMessage: 'Cancel', + } + )} + + + + + {this.state.inheritTimeRange + ? i18n.translate( + 'xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle', + { + defaultMessage: 'Add to panel', + } + ) + : i18n.translate( + 'xpack.advancedUiActions.customizePanelTimeRange.modal.updatePanelTimeRangeButtonTitle', + { + defaultMessage: 'Update', + } + )} + + From 36c3542facadd86af6908aa6680dfd617fe61aca Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Mon, 29 Jul 2019 10:07:36 -0400 Subject: [PATCH 10/13] use right string for detecting input control vis types --- .../advanced_ui_actions/public/custom_time_range_action.tsx | 3 ++- .../advanced_ui_actions/public/custom_time_range_badge.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx index c25ab2a77b6a8..811d62e816fef 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx @@ -75,7 +75,8 @@ export class CustomTimeRangeAction extends Action { public async isCompatible({ embeddable }: ActionContext) { const isInputControl = - isVisualizeEmbeddable(embeddable) && embeddable.getOutput().visTypeName === 'inputControl'; + isVisualizeEmbeddable(embeddable) && + embeddable.getOutput().visTypeName === 'input_control_vis'; return Boolean( embeddable && diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx index fcac999abb24a..510756ebe0601 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx @@ -80,7 +80,8 @@ export class CustomTimeRangeBadge extends Action { public async isCompatible({ embeddable }: ActionContext) { const isInputControl = - isVisualizeEmbeddable(embeddable) && embeddable.getOutput().visTypeName === 'inputControl'; + isVisualizeEmbeddable(embeddable) && + embeddable.getOutput().visTypeName === 'input_control_vis'; return Boolean( embeddable && From f47c8a4128fcc0379aafb3eb799c504fded1809e Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Mon, 5 Aug 2019 09:16:01 -0400 Subject: [PATCH 11/13] delete empty file --- x-pack/legacy/plugins/advanced_ui_actions/public/index.ts | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 x-pack/legacy/plugins/advanced_ui_actions/public/index.ts diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/index.ts b/x-pack/legacy/plugins/advanced_ui_actions/public/index.ts deleted file mode 100644 index 41bc2aa258807..0000000000000 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ From b72106bdf7e004b6f84d277b20df215873273ea7 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Mon, 5 Aug 2019 09:16:17 -0400 Subject: [PATCH 12/13] remove unneccessary checks --- .../public/custom_time_range_badge.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx index 510756ebe0601..eca9aa5e93104 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_badge.tsx @@ -79,19 +79,7 @@ export class CustomTimeRangeBadge extends Action { } public async isCompatible({ embeddable }: ActionContext) { - const isInputControl = - isVisualizeEmbeddable(embeddable) && - embeddable.getOutput().visTypeName === 'input_control_vis'; - - return Boolean( - embeddable && - hasTimeRange(embeddable) && - // Saved searches don't listen to the time range from the container that is passed down to them so it - // won't work without a fix. For now, just leave them out. - embeddable.type !== SEARCH_EMBEDDABLE_TYPE && - !isInputControl && - !doesInheritTimeRange(embeddable) - ); + return Boolean(embeddable && hasTimeRange(embeddable) && !doesInheritTimeRange(embeddable)); } public execute({ embeddable }: ActionContext) { From ace925305295b637f6ab72517a41af613b499140 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Mon, 5 Aug 2019 09:16:35 -0400 Subject: [PATCH 13/13] hide time range override action for markdown vis --- .../advanced_ui_actions/public/custom_time_range_action.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx index 811d62e816fef..6dc4f37293f37 100644 --- a/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx +++ b/x-pack/legacy/plugins/advanced_ui_actions/public/custom_time_range_action.tsx @@ -78,13 +78,17 @@ export class CustomTimeRangeAction extends Action { isVisualizeEmbeddable(embeddable) && embeddable.getOutput().visTypeName === 'input_control_vis'; + const isMarkdown = + isVisualizeEmbeddable(embeddable) && embeddable.getOutput().visTypeName === 'markdown'; + return Boolean( embeddable && hasTimeRange(embeddable) && // Saved searches don't listen to the time range from the container that is passed down to them so it // won't work without a fix. For now, just leave them out. embeddable.type !== SEARCH_EMBEDDABLE_TYPE && - !isInputControl + !isInputControl && + !isMarkdown ); }