Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reporting on-demand menu items back in notebooks #229

Merged
merged 2 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ coverage/
.cypress/screenshots
.cypress/videos
common/query_manager/antlr/output
.eslintcache
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import {
contextMenuCreateReportDefinition,
contextMenuViewReports,
generateInContextReport
generateInContextReport,
} from '../reporting_context_menu_helper';

describe('reporting_context_menu_helper tests', () => {
Expand Down Expand Up @@ -52,7 +52,8 @@ describe('reporting_context_menu_helper tests', () => {
global.fetch = jest.fn(() =>
Promise.resolve({
status,
json: () => Promise.resolve({ filename, fileFormat, data: 'test-data' }),
json: () =>
Promise.resolve({ filename, fileFormat, data: 'test-data', reportId: 'test-id' }),
text: () => Promise.resolve({ tenant }),
})
);
Expand All @@ -65,31 +66,23 @@ describe('reporting_context_menu_helper tests', () => {
const toggleReportingLoadingModal = jest.fn();
await generateReport(200, 'test.csv', '__user__', setToast, toggleReportingLoadingModal);
expect(toggleReportingLoadingModal).toBeCalledWith(true);
expect(setToast).toBeCalledWith('Successfully generated report.', 'success');
expect(setToast).toBeCalledWith('Please continue report generation in the new tab.', 'success');
});

it('generates pdf for global tenant', async () => {
const setToast = jest.fn();
const toggleReportingLoadingModal = jest.fn();
await generateReport(200, 'test.pdf', '', setToast, toggleReportingLoadingModal);
expect(toggleReportingLoadingModal).toBeCalledWith(true);
expect(setToast).toBeCalledWith('Successfully generated report.', 'success');
expect(setToast).toBeCalledWith('Please continue report generation in the new tab.', 'success');
});

it('generates png for custom tenant', async () => {
const setToast = jest.fn();
const toggleReportingLoadingModal = jest.fn();
await generateReport(200, 'test.png', 'custom_tenant', setToast, toggleReportingLoadingModal);
expect(toggleReportingLoadingModal).toBeCalledWith(true);
expect(setToast).toBeCalledWith('Successfully generated report.', 'success');
});

it('generates png for custom tenant', async () => {
const setToast = jest.fn();
const toggleReportingLoadingModal = jest.fn();
await generateReport(200, 'test.png', 'custom_tenant', setToast, toggleReportingLoadingModal);
expect(toggleReportingLoadingModal).toBeCalledWith(true);
expect(setToast).toBeCalledWith('Successfully generated report.', 'success');
expect(setToast).toBeCalledWith('Please continue report generation in the new tab.', 'success');
});

it('handles 404 error', async () => {
Expand Down Expand Up @@ -132,7 +125,11 @@ describe('reporting_context_menu_helper tests', () => {
global.fetch = jest.fn(() => Promise.reject({ status: 500 }));
const setToast = jest.fn();
const toggleReportingLoadingModal = jest.fn();
await generateInContextReport('csv', { setToast }, toggleReportingLoadingModal);
try {
await generateInContextReport('csv', { setToast }, toggleReportingLoadingModal);
} catch (error) {
expect(error.status).toEqual(500);
}
expect(toggleReportingLoadingModal).toBeCalledWith(true);
expect(setToast).toBeCalledWith('Tenant error', 'danger', 'Failed to get user tenant.');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { parse } from "url";
import { parse } from 'url';

const getReportSourceURL = (baseURI: string) => {
return baseURI.substr(baseURI.lastIndexOf('/') + 1, baseURI.length);
}
};

export const readDataReportToFile = async (
stream: string,
Expand All @@ -16,7 +16,7 @@ export const readDataReportToFile = async (
) => {
const blob = new Blob([stream]);
const url = URL.createObjectURL(blob);
let link = document.createElement('a');
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', fileName);
document.body.appendChild(link);
Expand All @@ -25,22 +25,18 @@ export const readDataReportToFile = async (
};

const getFileFormatPrefix = (fileFormat: string) => {
var fileFormatPrefix = 'data:' + fileFormat + ';base64,';
const fileFormatPrefix = 'data:' + fileFormat + ';base64,';
return fileFormatPrefix;
};

const readStreamToFile = async (
stream: string,
fileFormat: string,
fileName: string
) => {
let link = document.createElement('a');
const readStreamToFile = async (stream: string, fileFormat: string, fileName: string) => {
const link = document.createElement('a');
if (fileName.includes('csv')) {
readDataReportToFile(stream, fileFormat, fileName);
return;
}
let fileFormatPrefix = getFileFormatPrefix(fileFormat);
let url = fileFormatPrefix + stream;
const fileFormatPrefix = getFileFormatPrefix(fileFormat);
const url = fileFormatPrefix + stream;
if (typeof link.download !== 'string') {
window.open(url, '_blank');
return;
Expand Down Expand Up @@ -93,8 +89,7 @@ function addTenantToURL(url, userRequestedTenant) {
// build fake url from relative url
const fakeUrl = `http://opensearch.com${url}`;
const tenantKey = 'security_tenant';
const tenantKeyAndValue =
tenantKey + '=' + encodeURIComponent(userRequestedTenant);
const tenantKeyAndValue = tenantKey + '=' + encodeURIComponent(userRequestedTenant);

const { pathname, search } = parse(fakeUrl);
const queryDelimiter = !search ? '?' : '&';
Expand Down Expand Up @@ -127,15 +122,10 @@ export const generateInContextReport = async (
try {
const tenant = await getTenantInfoIfExists();
if (tenant) {
baseUrl = addTenantToURL(baseUrl, tenant)
baseUrl = addTenantToURL(baseUrl, tenant);
}
} catch (error) {
props.setToast(
'Tenant error',
'danger',
'Failed to get user tenant.'
);
console.log(`failed to get user tenant: ${error}`);
props.setToast('Tenant error', 'danger', 'Failed to get user tenant.');
}

const reportSource = 'Notebook';
Expand All @@ -151,7 +141,7 @@ export const generateInContextReport = async (
core_params: {
base_url: baseUrl,
report_format: fileFormat,
time_duration: 'PT30M', // time duration can be hard-coded
time_duration: 'PT30M', // time duration can be hard-coded
...rest,
},
},
Expand All @@ -166,71 +156,63 @@ export const generateInContextReport = async (
},
},
};
fetch(
'../api/reporting/generateReport',
{
headers: {
'Content-Type': 'application/json',
'osd-xsrf': 'true',
accept: '*/*',
'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.6',
pragma: 'no-cache',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
},
method: 'POST',
body: JSON.stringify(contextMenuOnDemandReport),
referrerPolicy: 'strict-origin-when-cross-origin',
mode: 'cors',
credentials: 'include',
}
)
.then((response) => {
toggleReportingLoadingModal(false);
if (response.status === 200) {
// success toast
props.setToast('Successfully generated report.', 'success');
} else {
if (response.status === 403) {
// permissions failure toast
props.setToast(
'Error generating report,',
'danger',
'Insufficient permissions. Reach out to your OpenSearch Dashboards administrator.'
);
} else if (response.status === 503) {
// timeout failure
props.setToast(
'Error generating report.',
'danger',
'Timed out generating on-demand report from notebook. Try again later.'
);
await fetch('../api/reporting/generateReport', {
headers: {
'Content-Type': 'application/json',
'osd-xsrf': 'true',
accept: '*/*',
'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.6',
pragma: 'no-cache',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
},
method: 'POST',
body: JSON.stringify(contextMenuOnDemandReport),
referrerPolicy: 'strict-origin-when-cross-origin',
mode: 'cors',
credentials: 'include',
})
.then(async (response) => [response.status, await response.json()])
.then(async ([status, data]) => {
toggleReportingLoadingModal(false);
if (status === 200) {
const a = document.createElement('a');
a.href = window.location.origin + `${data.queryUrl}&visualReportId=${data.reportId}`;
a.target = '_blank';
a.rel = 'noreferrer';
a.click();
// success toast
props.setToast('Please continue report generation in the new tab.', 'success');
} else {
// generic failure
props.setToast(
'Download error',
'danger',
'There was an error generating this report.'
);
if (status === 403) {
// permissions failure toast
props.setToast(
'Error generating report,',
'danger',
'Insufficient permissions. Reach out to your OpenSearch Dashboards administrator.'
);
} else if (status === 503) {
// timeout failure
props.setToast(
'Error generating report.',
'danger',
'Timed out generating on-demand report from notebook. Try again later.'
);
} else {
// generic failure
props.setToast('Download error', 'danger', 'There was an error generating this report.');
}
}
}
return response.json();
})
.then(async (data) => {
await readStreamToFile(data.data, fileFormat, data.filename);
})
}
});
};

export const contextMenuCreateReportDefinition = (baseURI: string) => {
const reportSourceId = getReportSourceURL(baseURI);
let reportSource = 'notebook:';

reportSource += reportSourceId.toString();
window.location.assign(
`reports-dashboards#/create?previous=${reportSource}?timeFrom=0?timeTo=0`
);
window.location.assign(`reports-dashboards#/create?previous=${reportSource}?timeFrom=0?timeTo=0`);
};

export const contextMenuViewReports = () =>
window.location.assign('reports-dashboards#/');
export const contextMenuViewReports = () => window.location.assign('reports-dashboards#/');
16 changes: 16 additions & 0 deletions public/components/notebooks/components/notebook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,22 @@ export class Notebook extends Component<NotebookProps, NotebookState> {
id: 0,
title: 'Reporting',
items: [
{
name: 'Download PDF',
icon: <EuiIcon type="download" />,
onClick: () => {
this.setState({ isReportingActionsPopoverOpen: false });
generateInContextReport('pdf', this.props, this.toggleReportingLoadingModal);
},
},
{
name: 'Download PNG',
icon: <EuiIcon type="download" />,
onClick: () => {
this.setState({ isReportingActionsPopoverOpen: false });
generateInContextReport('png', this.props, this.toggleReportingLoadingModal);
},
},
{
name: 'Create report definition',
icon: <EuiIcon type="calendar" />,
Expand Down