Skip to content

Commit

Permalink
Add reporting on-demand menu items back in notebooks (#229)
Browse files Browse the repository at this point in the history
Signed-off-by: Joshua Li <joshuali925@gmail.com>
  • Loading branch information
joshuali925 committed Feb 14, 2023
1 parent ad44980 commit 0527730
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 94 deletions.
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

0 comments on commit 0527730

Please sign in to comment.