From fbbd8faf69bc809ef629e73ce64a974bf439f5ec Mon Sep 17 00:00:00 2001 From: Brian Date: Thu, 29 Nov 2018 17:43:54 -0500 Subject: [PATCH 1/5] WIP Initial changes for PNG as panel action --- .../panel_options_menu_container.ts | 1 + .../build_eui_context_menu_panels.ts | 19 +++- .../embeddable/context_menu_actions/types.ts | 2 + src/ui/public/embeddable/embeddable.ts | 6 +- src/ui/public/embeddable/types.ts | 4 + x-pack/plugins/reporting/index.js | 3 + .../panel_actions/get_report_panel_action.tsx | 92 +++++++++++++++++++ 7 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts index 478c59847fe3a4..54c4328cd84444 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts @@ -195,6 +195,7 @@ const mergeProps = ( actions, embeddable: ownProps.embeddable, containerState, + closeContextMenu: closeMyContextMenuPanel, }); } diff --git a/src/ui/public/embeddable/context_menu_actions/build_eui_context_menu_panels.ts b/src/ui/public/embeddable/context_menu_actions/build_eui_context_menu_panels.ts index 600aacc4224596..8dc71497964e72 100644 --- a/src/ui/public/embeddable/context_menu_actions/build_eui_context_menu_panels.ts +++ b/src/ui/public/embeddable/context_menu_actions/build_eui_context_menu_panels.ts @@ -47,17 +47,19 @@ function buildEuiContextMenuPanelItemsAndChildPanels({ actions, embeddable, containerState, + closeContextMenu, }: { contextMenuPanelId: string; actions: ContextMenuAction[]; embeddable?: Embeddable; containerState: ContainerState; + closeContextMenu: any; }) { const items: EuiContextMenuPanelItemDescriptor[] = []; const childPanels: EuiContextMenuPanelDescriptor[] = []; const actionsForPanel = getActionsForPanel(contextMenuPanelId, actions); actionsForPanel.forEach(action => { - const isVisible = action.isVisible({ embeddable, containerState }); + const isVisible = action.isVisible({ embeddable, containerState, closeContextMenu }); if (!isVisible) { return; } @@ -69,6 +71,7 @@ function buildEuiContextMenuPanelItemsAndChildPanels({ actions, embeddable, containerState, + closeContextMenu, }) ); } @@ -78,6 +81,7 @@ function buildEuiContextMenuPanelItemsAndChildPanels({ action, containerState, embeddable, + closeContextMenu, }) ); }); @@ -99,17 +103,19 @@ export function buildEuiContextMenuPanels({ actions, embeddable, containerState, + closeContextMenu, }: { contextMenuPanel: ContextMenuPanel; actions: ContextMenuAction[]; embeddable?: Embeddable; containerState: ContainerState; + closeContextMenu: any; }): EuiContextMenuPanelDescriptor[] { const euiContextMenuPanel: EuiContextMenuPanelDescriptor = { id: contextMenuPanel.id, title: contextMenuPanel.title, items: [], - content: contextMenuPanel.getContent({ embeddable, containerState }), + content: contextMenuPanel.getContent({ embeddable, containerState, closeContextMenu }), }; const contextMenuPanels = [euiContextMenuPanel]; @@ -118,6 +124,7 @@ export function buildEuiContextMenuPanels({ actions, embeddable, containerState, + closeContextMenu, }); euiContextMenuPanel.items = items; @@ -135,29 +142,31 @@ function convertPanelActionToContextMenuItem({ action, containerState, embeddable, + closeContextMenu, }: { action: ContextMenuAction; containerState: ContainerState; embeddable?: Embeddable; + closeContextMenu: any; }): EuiContextMenuPanelItemDescriptor { const menuPanelItem: EuiContextMenuPanelItemDescriptor = { name: action.displayName, icon: action.icon, panel: _.get(action, 'childContextMenuPanel.id'), - disabled: action.isDisabled({ embeddable, containerState }), + disabled: action.isDisabled({ embeddable, containerState, closeContextMenu }), 'data-test-subj': `dashboardPanelAction-${action.id}`, }; if (action.onClick) { menuPanelItem.onClick = () => { if (action.onClick) { - action.onClick({ embeddable, containerState }); + action.onClick({ embeddable, containerState, closeContextMenu }); } }; } if (action.getHref) { - menuPanelItem.href = action.getHref({ embeddable, containerState }); + menuPanelItem.href = action.getHref({ embeddable, containerState, closeContextMenu }); } return menuPanelItem; diff --git a/src/ui/public/embeddable/context_menu_actions/types.ts b/src/ui/public/embeddable/context_menu_actions/types.ts index a35c1f5b4480ee..7c194053d72182 100644 --- a/src/ui/public/embeddable/context_menu_actions/types.ts +++ b/src/ui/public/embeddable/context_menu_actions/types.ts @@ -33,4 +33,6 @@ export interface PanelActionAPI { * Information about the current state of the panel and dashboard. */ containerState: ContainerState; + + closeContextMenu(): void; } diff --git a/src/ui/public/embeddable/embeddable.ts b/src/ui/public/embeddable/embeddable.ts index 7689c0c2989959..d0a3e66d130040 100644 --- a/src/ui/public/embeddable/embeddable.ts +++ b/src/ui/public/embeddable/embeddable.ts @@ -19,7 +19,7 @@ import * as PropTypes from 'prop-types'; import { Adapters } from 'ui/inspector'; -import { ContainerState } from './types'; +import { ContainerState, Filters, SavedVisualization, TimeRange } from './types'; // TODO: we'll be able to get rid of this shape once all of dashboard is typescriptified too. export const embeddableShape = PropTypes.shape({ @@ -63,6 +63,10 @@ interface EmbeddableOptions { export abstract class Embeddable { public readonly metadata: EmbeddableMetadata = {}; + public readonly filters: Filters = []; + public readonly timeRange: TimeRange = { to: '', from: '' }; + public readonly savedVisualization: SavedVisualization = { id: '' }; + // TODO: Make title and editUrl required and move out of options parameter. constructor(options: EmbeddableOptions = {}) { this.metadata = options.metadata || {}; diff --git a/src/ui/public/embeddable/types.ts b/src/ui/public/embeddable/types.ts index 683e3471df6f62..f2e801bf3544fd 100644 --- a/src/ui/public/embeddable/types.ts +++ b/src/ui/public/embeddable/types.ts @@ -17,6 +17,10 @@ * under the License. */ +export interface SavedVisualization { + id?: string; + title?: string; +} export interface TimeRange { to: string; from: string; diff --git a/x-pack/plugins/reporting/index.js b/x-pack/plugins/reporting/index.js index 1ef8edbc5f921b..66056d80148b87 100644 --- a/x-pack/plugins/reporting/index.js +++ b/x-pack/plugins/reporting/index.js @@ -38,6 +38,9 @@ export const reporting = (kibana) => { 'plugins/reporting/share_context_menu/register_csv_reporting', 'plugins/reporting/share_context_menu/register_reporting', ], + contextMenuActions: [ + 'plugins/reporting/panel_actions/get_report_panel_action', + ], hacks: ['plugins/reporting/hacks/job_completion_notifier'], home: ['plugins/reporting/register_feature'], managementSections: ['plugins/reporting/views/management'], diff --git a/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx new file mode 100644 index 00000000000000..22a84f6608551c --- /dev/null +++ b/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx @@ -0,0 +1,92 @@ +/* + * 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 { set } from 'lodash'; +import rison from 'rison-node'; +import { ContextMenuAction, ContextMenuActionsRegistryProvider, Embeddable } from 'ui/embeddable'; +import { kfetch } from 'ui/kfetch'; +import { toastNotifications } from 'ui/notify'; +import { StateProvider } from 'ui/state_management/state'; +import { jobCompletionNotifications } from '../lib/job_completion_notifications'; + +class GetReportPanelAction extends ContextMenuAction { + private state: any; + constructor(state: any) { + super( + { + displayName: i18n.translate('kbn.dashboard.panel.reportPanel.displayName', { + defaultMessage: 'PNG Report', + }), + id: 'openReport', + parentPanelId: 'mainMenu', + }, + { + icon: 'document', + } + ); + this.state = state; + } + + public async onClick({ + embeddable, + closeContextMenu, + }: { + embeddable: Embeddable; + closeContextMenu: any; + }) { + if (!embeddable) { + return; + } + + closeContextMenu(); + + // Remove panels state since this report is for only one visualization panel + set(this.state, 'panels', true); + + const visualizationURL = + `/app/kibana#/visualize/edit/${ + embeddable.savedVisualization.id + }?_g=(refreshInterval:(pause:!f,value:900000),time:(from:${ + embeddable.timeRange.from + },mode:quick,to:${embeddable.timeRange.to}))` + + '&_a=' + + this.state.toQueryParam(); + + const reportconfig = { + browserTimezone: 'America/Phoenix', + layout: { + dimensions: { + width: 960, + height: 720, + }, + }, + objectType: 'visualization', + relativeUrl: visualizationURL, + title: embeddable.savedVisualization.title, + }; + + const query = { + jobParams: rison.encode(reportconfig), + visualization: JSON.stringify(embeddable.filters), + }; + const API_BASE_URL = '/api/reporting/generate'; + toastNotifications.addSuccess({ + title: `Queued report for ${reportconfig.objectType}`, + text: 'Track its progress in Management', + 'data-test-subj': 'queueReportSuccess', + }); + + const resp = await kfetch({ method: 'POST', pathname: `${API_BASE_URL}/png`, query }); + jobCompletionNotifications.add(resp.job.id); + } +} +export function createReportActionProvider(Private: any) { + const State = Private(StateProvider); + const state = new State('_a'); + return new GetReportPanelAction(state); +} +ContextMenuActionsRegistryProvider.register(createReportActionProvider); From 09bd4e49fe90103822bf7d8a6a298940f3a086be Mon Sep 17 00:00:00 2001 From: Brian Date: Fri, 30 Nov 2018 11:03:04 -0500 Subject: [PATCH 2/5] Fixed dup panel action menus and added code for adding app state to URL --- .../public/dashboard/store/panel_actions_store.ts | 1 + .../public/panel_actions/get_report_panel_action.tsx | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/store/panel_actions_store.ts b/src/legacy/core_plugins/kibana/public/dashboard/store/panel_actions_store.ts index 449125d0ecfa4d..b4c3730d7f5ba5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/store/panel_actions_store.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/store/panel_actions_store.ts @@ -27,6 +27,7 @@ class PanelActionsStore { * @type {IndexedArray} panelActionsRegistry */ public initializeFromRegistry(panelActionsRegistry: ContextMenuAction[]) { + this.actions = []; panelActionsRegistry.forEach(panelAction => { this.actions.push(panelAction); }); diff --git a/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx index 22a84f6608551c..efd43e00592cbf 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx @@ -30,6 +30,15 @@ class GetReportPanelAction extends ContextMenuAction { ); this.state = state; } + public isVisible({ embeddable }: { embeddable: Embeddable }): boolean { + if (!embeddable) { + return false; + } + if (!embeddable.savedVisualization.id) { + return false; + } + return true; + } public async onClick({ embeddable, @@ -41,6 +50,9 @@ class GetReportPanelAction extends ContextMenuAction { if (!embeddable) { return; } + if (!embeddable.savedVisualization.id) { + return; + } closeContextMenu(); From e732e6e5b35b8c64e2143eadb88b7d36b91082ee Mon Sep 17 00:00:00 2001 From: Brian Date: Fri, 30 Nov 2018 16:19:04 -0500 Subject: [PATCH 3/5] Changes to handle moment timeranges and added customizations to URL --- .../panel_actions/get_report_panel_action.tsx | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx index efd43e00592cbf..68762fbbb0f8f1 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { set } from 'lodash'; +import moment from 'moment'; import rison from 'rison-node'; import { ContextMenuAction, ContextMenuActionsRegistryProvider, Embeddable } from 'ui/embeddable'; import { kfetch } from 'ui/kfetch'; @@ -59,14 +60,29 @@ class GetReportPanelAction extends ContextMenuAction { // Remove panels state since this report is for only one visualization panel set(this.state, 'panels', true); + // Adds UI state for modified colors and other customizations + set(this.state, 'uiState', embeddable.customization); + + const fromtime = moment.isMoment(embeddable.timeRange.from) + ? embeddable.timeRange.from.toISOString() + : embeddable.timeRange.from; + const totime = moment.isMoment(embeddable.timeRange.to) + ? embeddable.timeRange.to.toISOString() + : embeddable.timeRange.to; + + let appquerystring = this.state.toQueryParam(); + + // Colors have a # that is not handled correctly so we encode it here + appquerystring = appquerystring.replace(new RegExp('#', 'g'), '%23'); + + // mode of quick is hard coded and works ok even if time is relative or absolute and + // refreshinterval is not used in the report const visualizationURL = `/app/kibana#/visualize/edit/${ embeddable.savedVisualization.id - }?_g=(refreshInterval:(pause:!f,value:900000),time:(from:${ - embeddable.timeRange.from - },mode:quick,to:${embeddable.timeRange.to}))` + + }?_g=(refreshInterval:(pause:!f,value:900000),time:(from:'${fromtime}',mode:quick,to:'${totime}'))` + '&_a=' + - this.state.toQueryParam(); + appquerystring; const reportconfig = { browserTimezone: 'America/Phoenix', @@ -83,9 +99,10 @@ class GetReportPanelAction extends ContextMenuAction { const query = { jobParams: rison.encode(reportconfig), - visualization: JSON.stringify(embeddable.filters), }; + const API_BASE_URL = '/api/reporting/generate'; + toastNotifications.addSuccess({ title: `Queued report for ${reportconfig.objectType}`, text: 'Track its progress in Management', @@ -93,6 +110,7 @@ class GetReportPanelAction extends ContextMenuAction { }); const resp = await kfetch({ method: 'POST', pathname: `${API_BASE_URL}/png`, query }); + jobCompletionNotifications.add(resp.job.id); } } From 713d59cf184ebcc05a8a26950bca25b3d762f537 Mon Sep 17 00:00:00 2001 From: Brian Date: Wed, 5 Dec 2018 11:21:50 -0500 Subject: [PATCH 4/5] Moved report link generation into embeddable --- .../discover/embeddable/search_embeddable.js | 28 +++++ .../embeddable/visualize_embeddable.ts | 32 ++++++ src/ui/public/embeddable/embeddable.ts | 6 +- src/ui/public/embeddable/types.ts | 4 - src/ui/public/visualize/loader/types.ts | 1 + x-pack/plugins/reporting/index.js | 3 +- .../get_csv_report_panel_action.tsx | 101 ++++++++++++++++++ ...on.tsx => get_png_report_panel_action.tsx} | 65 ++++------- 8 files changed, 187 insertions(+), 53 deletions(-) create mode 100644 x-pack/plugins/reporting/public/panel_actions/get_csv_report_panel_action.tsx rename x-pack/plugins/reporting/public/panel_actions/{get_report_panel_action.tsx => get_png_report_panel_action.tsx} (50%) diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.js b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.js index 5a138f0897b881..16214d65330b6c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.js +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.js @@ -17,12 +17,14 @@ * under the License. */ +import url from 'url'; import angular from 'angular'; import { Embeddable } from 'ui/embeddable'; import searchTemplate from './search_template.html'; import * as columnActions from 'ui/doc_table/actions/columns'; import { getTime } from 'ui/timefilter/get_time'; import { RequestAdapter } from 'ui/inspector/adapters'; +import rison from 'rison-node'; export class SearchEmbeddable extends Embeddable { constructor({ onEmbeddableStateChanged, savedSearch, editUrl, loader, $rootScope, $compile }) { @@ -58,6 +60,32 @@ export class SearchEmbeddable extends Embeddable { }; } + /** + * Generates an access URL that will be used in creating a PNG report. + */ + generateAccessLink(containerState) { + + const id = this.savedSearch.id; + + const appState = rison.encode({ filters: containerState.filters, + uiState: containerState.embeddableCustomization, + query: containerState.query, + }); + + const globalState = rison.encode({ time: containerState.timeRange }); + + // Use a url service to ensure everything is escaped properly + const myurl = `/app/kibana#` + url.format({ + pathname: '/visualize/edit/' + id, + query: { + '_g': globalState, + '_a': appState, + } + }); + + return myurl.toString(); + } + pushContainerStateParamsToScope() { // If there is column or sort data on the panel, that means the original columns or sort settings have // been overridden in a dashboard. 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 b78c7ac41ac3f2..f461e67269cb30 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,6 +18,8 @@ */ import _ from 'lodash'; +// @ts-ignore ts knows this shouldn't be possible, but just making sure +import rison from 'rison-node'; import { ContainerState, Embeddable } from 'ui/embeddable'; import { OnEmbeddableStateChanged } from 'ui/embeddable/embeddable_factory'; import { Filters, Query, TimeRange } from 'ui/embeddable/types'; @@ -29,6 +31,8 @@ import { VisualizeLoaderParams, VisualizeUpdateParams, } from 'ui/visualize/loader/types'; +import url from 'url'; +import { VisualizeConstants } from '../visualize_constants'; export interface VisualizeEmbeddableConfiguration { onEmbeddableStateChanged: OnEmbeddableStateChanged; @@ -108,6 +112,34 @@ export class VisualizeEmbeddable extends Embeddable { } } + /** + * Generates an access URL that will be used in creating a PNG report. + */ + public generateAccessLink(containerState: ContainerState) { + const id = this.savedVisualization.id; + + const appState = rison.encode({ + filters: containerState.filters, + uiState: containerState.embeddableCustomization, + query: containerState.query, + }); + + const globalState = rison.encode({ time: containerState.timeRange }); + + // Use a url service to ensure everything is escaped properly + const myurl = + `/app/kibana#` + + url.format({ + pathname: VisualizeConstants.EDIT_PATH + '/' + id, + query: { + _g: globalState, + _a: appState, + }, + }); + + return myurl.toString(); + } + public onContainerStateChanged(containerState: ContainerState) { this.transferCustomizationsToUiState(containerState); diff --git a/src/ui/public/embeddable/embeddable.ts b/src/ui/public/embeddable/embeddable.ts index d0a3e66d130040..7689c0c2989959 100644 --- a/src/ui/public/embeddable/embeddable.ts +++ b/src/ui/public/embeddable/embeddable.ts @@ -19,7 +19,7 @@ import * as PropTypes from 'prop-types'; import { Adapters } from 'ui/inspector'; -import { ContainerState, Filters, SavedVisualization, TimeRange } from './types'; +import { ContainerState } from './types'; // TODO: we'll be able to get rid of this shape once all of dashboard is typescriptified too. export const embeddableShape = PropTypes.shape({ @@ -63,10 +63,6 @@ interface EmbeddableOptions { export abstract class Embeddable { public readonly metadata: EmbeddableMetadata = {}; - public readonly filters: Filters = []; - public readonly timeRange: TimeRange = { to: '', from: '' }; - public readonly savedVisualization: SavedVisualization = { id: '' }; - // TODO: Make title and editUrl required and move out of options parameter. constructor(options: EmbeddableOptions = {}) { this.metadata = options.metadata || {}; diff --git a/src/ui/public/embeddable/types.ts b/src/ui/public/embeddable/types.ts index f2e801bf3544fd..683e3471df6f62 100644 --- a/src/ui/public/embeddable/types.ts +++ b/src/ui/public/embeddable/types.ts @@ -17,10 +17,6 @@ * under the License. */ -export interface SavedVisualization { - id?: string; - title?: string; -} export interface TimeRange { to: string; from: string; diff --git a/src/ui/public/visualize/loader/types.ts b/src/ui/public/visualize/loader/types.ts index 79e6133dbcad86..8d8f2d39b92769 100644 --- a/src/ui/public/visualize/loader/types.ts +++ b/src/ui/public/visualize/loader/types.ts @@ -49,6 +49,7 @@ export interface Query { } export interface VisSavedObject { + id: string; vis: Vis; description?: string; searchSource: SearchSource; diff --git a/x-pack/plugins/reporting/index.js b/x-pack/plugins/reporting/index.js index 66056d80148b87..197f01136a3bd9 100644 --- a/x-pack/plugins/reporting/index.js +++ b/x-pack/plugins/reporting/index.js @@ -39,7 +39,8 @@ export const reporting = (kibana) => { 'plugins/reporting/share_context_menu/register_reporting', ], contextMenuActions: [ - 'plugins/reporting/panel_actions/get_report_panel_action', + 'plugins/reporting/panel_actions/get_png_report_panel_action', + 'plugins/reporting/panel_actions/get_csv_report_panel_action', ], hacks: ['plugins/reporting/hacks/job_completion_notifier'], home: ['plugins/reporting/register_feature'], diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_report_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_csv_report_panel_action.tsx new file mode 100644 index 00000000000000..9e1684778242ff --- /dev/null +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_report_panel_action.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 { i18n } from '@kbn/i18n'; + +import rison from 'rison-node'; +import { + ContainerState, + ContextMenuAction, + ContextMenuActionsRegistryProvider, + Embeddable, +} from 'ui/embeddable'; +import { kfetch } from 'ui/kfetch'; +import { toastNotifications } from 'ui/notify'; +import { SearchEmbeddable } from '../../../../../src/core_plugins/kibana/public/discover/embeddable/search_embeddable'; +import { jobCompletionNotifications } from '../lib/job_completion_notifications'; + +class GetCsvReportPanelAction extends ContextMenuAction { + constructor() { + super( + { + displayName: i18n.translate('kbn.dashboard.panel.reportPanel.displayName', { + defaultMessage: 'CSV Report', + }), + id: 'openReport', + parentPanelId: 'mainMenu', + }, + { + icon: 'document', + } + ); + } + public isVisible({ embeddable }: { embeddable: Embeddable }): boolean { + if (!embeddable) { + return false; + } + if (!(embeddable instanceof SearchEmbeddable)) { + return false; + } + return true; + } + + public async onClick({ + embeddable, + containerState, + closeContextMenu, + }: { + embeddable: Embeddable; + closeContextMenu: any; + containerState: ContainerState; + }) { + if (!embeddable) { + return; + } + if (!(embeddable instanceof SearchEmbeddable)) { + return; + } + const searchEmbeddable = embeddable as SearchEmbeddable; + + if (!searchEmbeddable.savedSearch.id) { + return; + } + + closeContextMenu(); + + const searchURL = searchEmbeddable.generateAccessLink(containerState); + + const reportconfig = { + browserTimezone: 'America/Phoenix', + layout: { + dimensions: { + width: 960, + height: 720, + }, + }, + objectType: 'search', + relativeUrl: searchURL, + title: 'searchEmbeddable.getPanelTitle(containerState)', + }; + + const query = { + jobParams: rison.encode(reportconfig), + }; + + const API_BASE_URL = '/api/reporting/generate'; + + toastNotifications.addSuccess({ + title: `Queued report for ${reportconfig.objectType}`, + text: 'Track its progress in Management', + 'data-test-subj': 'queueReportSuccess', + }); + + const resp = await kfetch({ method: 'POST', pathname: `${API_BASE_URL}/csv`, query }); + + jobCompletionNotifications.add(resp.job.id); + } +} + +ContextMenuActionsRegistryProvider.register(() => new GetCsvReportPanelAction()); diff --git a/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_png_report_panel_action.tsx similarity index 50% rename from x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx rename to x-pack/plugins/reporting/public/panel_actions/get_png_report_panel_action.tsx index 68762fbbb0f8f1..d870505b93006c 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_report_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_png_report_panel_action.tsx @@ -5,18 +5,20 @@ */ import { i18n } from '@kbn/i18n'; -import { set } from 'lodash'; -import moment from 'moment'; import rison from 'rison-node'; -import { ContextMenuAction, ContextMenuActionsRegistryProvider, Embeddable } from 'ui/embeddable'; +import { + ContainerState, + ContextMenuAction, + ContextMenuActionsRegistryProvider, + Embeddable, +} from 'ui/embeddable'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; -import { StateProvider } from 'ui/state_management/state'; +import { VisualizeEmbeddable } from '../../../../../src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable'; import { jobCompletionNotifications } from '../lib/job_completion_notifications'; -class GetReportPanelAction extends ContextMenuAction { - private state: any; - constructor(state: any) { +class GetPngReportPanelAction extends ContextMenuAction { + constructor() { super( { displayName: i18n.translate('kbn.dashboard.panel.reportPanel.displayName', { @@ -29,13 +31,12 @@ class GetReportPanelAction extends ContextMenuAction { icon: 'document', } ); - this.state = state; } public isVisible({ embeddable }: { embeddable: Embeddable }): boolean { if (!embeddable) { return false; } - if (!embeddable.savedVisualization.id) { + if (!(embeddable instanceof VisualizeEmbeddable)) { return false; } return true; @@ -43,46 +44,28 @@ class GetReportPanelAction extends ContextMenuAction { public async onClick({ embeddable, + containerState, closeContextMenu, }: { embeddable: Embeddable; closeContextMenu: any; + containerState: ContainerState; }) { if (!embeddable) { return; } - if (!embeddable.savedVisualization.id) { + if (!(embeddable instanceof VisualizeEmbeddable)) { return; } + const visualizeEmbeddable = embeddable as VisualizeEmbeddable; - closeContextMenu(); - - // Remove panels state since this report is for only one visualization panel - set(this.state, 'panels', true); - - // Adds UI state for modified colors and other customizations - set(this.state, 'uiState', embeddable.customization); - - const fromtime = moment.isMoment(embeddable.timeRange.from) - ? embeddable.timeRange.from.toISOString() - : embeddable.timeRange.from; - const totime = moment.isMoment(embeddable.timeRange.to) - ? embeddable.timeRange.to.toISOString() - : embeddable.timeRange.to; - - let appquerystring = this.state.toQueryParam(); + if (!visualizeEmbeddable.savedVisualization.id) { + return; + } - // Colors have a # that is not handled correctly so we encode it here - appquerystring = appquerystring.replace(new RegExp('#', 'g'), '%23'); + closeContextMenu(); - // mode of quick is hard coded and works ok even if time is relative or absolute and - // refreshinterval is not used in the report - const visualizationURL = - `/app/kibana#/visualize/edit/${ - embeddable.savedVisualization.id - }?_g=(refreshInterval:(pause:!f,value:900000),time:(from:'${fromtime}',mode:quick,to:'${totime}'))` + - '&_a=' + - appquerystring; + const visualizationURL = visualizeEmbeddable.generateAccessLink(containerState); const reportconfig = { browserTimezone: 'America/Phoenix', @@ -94,7 +77,7 @@ class GetReportPanelAction extends ContextMenuAction { }, objectType: 'visualization', relativeUrl: visualizationURL, - title: embeddable.savedVisualization.title, + title: visualizeEmbeddable.getPanelTitle(containerState), }; const query = { @@ -114,9 +97,5 @@ class GetReportPanelAction extends ContextMenuAction { jobCompletionNotifications.add(resp.job.id); } } -export function createReportActionProvider(Private: any) { - const State = Private(StateProvider); - const state = new State('_a'); - return new GetReportPanelAction(state); -} -ContextMenuActionsRegistryProvider.register(createReportActionProvider); + +ContextMenuActionsRegistryProvider.register(() => new GetPngReportPanelAction()); From 17756db4fba7d969b14eb8ea74b47839f6125588 Mon Sep 17 00:00:00 2001 From: Brian Date: Mon, 17 Dec 2018 16:27:07 -0500 Subject: [PATCH 5/5] changes for csv exports --- .../discover/embeddable/search_embeddable.js | 30 +--------- .../embeddable/visualize_embeddable.ts | 5 +- .../get_csv_report_panel_action.tsx | 56 ++++++++++++------- .../get_png_report_panel_action.tsx | 8 +-- 4 files changed, 41 insertions(+), 58 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.js b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.js index 16214d65330b6c..7ee981e4f56f03 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.js +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.js @@ -17,14 +17,12 @@ * under the License. */ -import url from 'url'; import angular from 'angular'; import { Embeddable } from 'ui/embeddable'; import searchTemplate from './search_template.html'; import * as columnActions from 'ui/doc_table/actions/columns'; import { getTime } from 'ui/timefilter/get_time'; import { RequestAdapter } from 'ui/inspector/adapters'; -import rison from 'rison-node'; export class SearchEmbeddable extends Embeddable { constructor({ onEmbeddableStateChanged, savedSearch, editUrl, loader, $rootScope, $compile }) { @@ -60,32 +58,6 @@ export class SearchEmbeddable extends Embeddable { }; } - /** - * Generates an access URL that will be used in creating a PNG report. - */ - generateAccessLink(containerState) { - - const id = this.savedSearch.id; - - const appState = rison.encode({ filters: containerState.filters, - uiState: containerState.embeddableCustomization, - query: containerState.query, - }); - - const globalState = rison.encode({ time: containerState.timeRange }); - - // Use a url service to ensure everything is escaped properly - const myurl = `/app/kibana#` + url.format({ - pathname: '/visualize/edit/' + id, - query: { - '_g': globalState, - '_a': appState, - } - }); - - return myurl.toString(); - } - pushContainerStateParamsToScope() { // If there is column or sort data on the panel, that means the original columns or sort settings have // been overridden in a dashboard. @@ -196,4 +168,4 @@ export class SearchEmbeddable extends Embeddable { delete this.searchScope; } } -} +} \ No newline at end of file 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 f461e67269cb30..7a5cd935b16585 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 @@ -112,8 +112,9 @@ export class VisualizeEmbeddable extends Embeddable { } } - /** - * Generates an access URL that will be used in creating a PNG report. + /* + * Generates an access URL that can be used to open up the visualization directly in the Visualize App, + with the settings applied from the given containerState. */ public generateAccessLink(containerState: ContainerState) { const id = this.savedVisualization.id; diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_report_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_csv_report_panel_action.tsx index 9e1684778242ff..0802adab2e50a5 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_report_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_report_panel_action.tsx @@ -14,7 +14,7 @@ import { } from 'ui/embeddable'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; -import { SearchEmbeddable } from '../../../../../src/core_plugins/kibana/public/discover/embeddable/search_embeddable'; +import { SearchEmbeddable } from '../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable'; import { jobCompletionNotifications } from '../lib/job_completion_notifications'; class GetCsvReportPanelAction extends ContextMenuAction { @@ -32,6 +32,34 @@ class GetCsvReportPanelAction extends ContextMenuAction { } ); } + + public async generateJobParams({ searchEmbeddable }: { searchEmbeddable: SearchEmbeddable }) { + const adapters = searchEmbeddable.getInspectorAdapters(); + if (!adapters) { + return ''; + } + if (adapters.requests.requests.length === 0) { + return ''; + } + const body = await searchEmbeddable.searchScope.searchSource.getSearchRequestBody(); + const timeFieldName = searchEmbeddable.metadata.indexPattern.timeFieldName; + const fields = timeFieldName + ? [timeFieldName, ...searchEmbeddable.savedSearch.columns] + : searchEmbeddable.savedSearch.columns; + + const jobParams = rison.encode({ + conflictedTypesFields: [], + fields, + indexPatternId: searchEmbeddable.metadata.indexPattern.id, + metaFields: searchEmbeddable.metadata.indexPattern.metaFields, + searchRequest: { body }, + title: searchEmbeddable.savedSearch.title, + type: 'search', + }); + + return jobParams; + } + public isVisible({ embeddable }: { embeddable: Embeddable }): boolean { if (!embeddable) { return false; @@ -59,35 +87,21 @@ class GetCsvReportPanelAction extends ContextMenuAction { } const searchEmbeddable = embeddable as SearchEmbeddable; - if (!searchEmbeddable.savedSearch.id) { - return; - } - closeContextMenu(); - const searchURL = searchEmbeddable.generateAccessLink(containerState); - - const reportconfig = { - browserTimezone: 'America/Phoenix', - layout: { - dimensions: { - width: 960, - height: 720, - }, - }, - objectType: 'search', - relativeUrl: searchURL, - title: 'searchEmbeddable.getPanelTitle(containerState)', - }; + const jobParams = await this.generateJobParams({ searchEmbeddable }); + if (jobParams === '') { + return; + } const query = { - jobParams: rison.encode(reportconfig), + jobParams, }; const API_BASE_URL = '/api/reporting/generate'; toastNotifications.addSuccess({ - title: `Queued report for ${reportconfig.objectType}`, + title: `Queued report for CSV`, text: 'Track its progress in Management', 'data-test-subj': 'queueReportSuccess', }); diff --git a/x-pack/plugins/reporting/public/panel_actions/get_png_report_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_png_report_panel_action.tsx index d870505b93006c..5999576c7bb70d 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_png_report_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_png_report_panel_action.tsx @@ -14,7 +14,7 @@ import { } from 'ui/embeddable'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; -import { VisualizeEmbeddable } from '../../../../../src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable'; +import { VisualizeEmbeddable } from '../../../../../src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable'; import { jobCompletionNotifications } from '../lib/job_completion_notifications'; class GetPngReportPanelAction extends ContextMenuAction { @@ -59,10 +59,6 @@ class GetPngReportPanelAction extends ContextMenuAction { } const visualizeEmbeddable = embeddable as VisualizeEmbeddable; - if (!visualizeEmbeddable.savedVisualization.id) { - return; - } - closeContextMenu(); const visualizationURL = visualizeEmbeddable.generateAccessLink(containerState); @@ -77,7 +73,7 @@ class GetPngReportPanelAction extends ContextMenuAction { }, objectType: 'visualization', relativeUrl: visualizationURL, - title: visualizeEmbeddable.getPanelTitle(containerState), + title: visualizeEmbeddable.metadata.title, }; const query = {