Skip to content

Commit

Permalink
Moved report link generation into embeddable
Browse files Browse the repository at this point in the history
  • Loading branch information
bgaddis56 committed Dec 5, 2018
1 parent e732e6e commit 713d59c
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) {
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
6 changes: 1 addition & 5 deletions src/ui/public/embeddable/embeddable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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 || {};
Expand Down
4 changes: 0 additions & 4 deletions src/ui/public/embeddable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@
* under the License.
*/

export interface SavedVisualization {
id?: string;
title?: string;
}
export interface TimeRange {
to: string;
from: string;
Expand Down
1 change: 1 addition & 0 deletions src/ui/public/visualize/loader/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface Query {
}

export interface VisSavedObject {
id: string;
vis: Vis;
description?: string;
searchSource: SearchSource;
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/reporting/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand All @@ -29,60 +31,41 @@ 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;
}

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',
Expand All @@ -94,7 +77,7 @@ class GetReportPanelAction extends ContextMenuAction {
},
objectType: 'visualization',
relativeUrl: visualizationURL,
title: embeddable.savedVisualization.title,
title: visualizeEmbeddable.getPanelTitle(containerState),
};

const query = {
Expand All @@ -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());

0 comments on commit 713d59c

Please sign in to comment.