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

feat: [ZNO-2045] Download dashboard in PDF and Word(doc) formats #7

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ea0a437
chore: [ZNO-2072] Adds package pdf2docx
NikolayBorovenskiy Sep 25, 2023
8d7e286
style: [ZNO-2072] Adds styles to improve dashboard print.
NikolayBorovenskiy Sep 25, 2023
9e89af8
feat: [ZNO-2072] Extends dashboard context menu to doc, pdf exports.
NikolayBorovenskiy Sep 25, 2023
0c66571
feat: [ZNO-2072] Implements PDF dashboard screenshot driver.
NikolayBorovenskiy Sep 25, 2023
f187b4e
feat: [ZNO-2072] Implements dashboard data rest api
NikolayBorovenskiy Sep 25, 2023
6495d34
feat: [ZNO-2072] Implements PDF and Word export command
NikolayBorovenskiy Sep 25, 2023
8ba54f5
feat: [ZNO-2072] Makes stateless service Superset pdf report to gener…
NikolayBorovenskiy Sep 25, 2023
6ab62cd
feat: [ZNO-2072] Extends superset config by PDF_GENERATOR_BASEURL
NikolayBorovenskiy Sep 25, 2023
f49164f
chore: [ZNO-2072] Adds superset pdf report to docker compose
NikolayBorovenskiy Sep 25, 2023
56572ab
style: [ZNO-2072] Fix frontend-build CI
NikolayBorovenskiy Sep 27, 2023
802964b
style: [ZNO-2072] Improves python's code style
NikolayBorovenskiy Sep 27, 2023
fd2a24b
tests: [ZNO-2072] Fixed tests for HeaderActionsDropdown component
NikolayBorovenskiy Sep 27, 2023
01baacd
style: [ZNO-2072] Deletes Unnecessary else after raise
NikolayBorovenskiy Sep 27, 2023
4ec514b
tests: [ZNO-2072] Fixes Open API tests
NikolayBorovenskiy Sep 28, 2023
20a91e4
style: [ZNO-2072] Fixes pre-commit checkers
NikolayBorovenskiy Sep 28, 2023
cc6c40f
style: [ZNO-2072] Pylint fixes
NikolayBorovenskiy Sep 28, 2023
4486959
style: [ZNO-2072] Fixes pre-commit checkers
NikolayBorovenskiy Sep 28, 2023
f8b501d
fix: [ZNO-2072] Fixed TriedOptionsAndFailed exception implementation
NikolayBorovenskiy Sep 29, 2023
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
21 changes: 21 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,27 @@ services:
- REDIS_PORT=6379
- REDIS_SSL=false

superset-pdf-report:
container_name: superset_pdf_report
build:
target: development_build
context: .
dockerfile: ./superset-pdf-report/Dockerfile
args:
NODE_ENV: development
image: superset-pdf-report
ports:
- 8890:8890
depends_on: *superset-depends-on
volumes:
- ./superset-pdf-report:/app
- /app/node_modules
command: npm run start:dev
environment:
- PORT=8890
stdin_open: true
tty: true

superset-init:
image: *superset-image
container_name: superset_init
Expand Down
1 change: 1 addition & 0 deletions docker/pythonpath_dev/superset_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class CeleryConfig:
FEATURE_FLAGS = {"ALERT_REPORTS": True}
ALERT_REPORTS_NOTIFICATION_DRY_RUN = True
WEBDRIVER_BASEURL = "http://superset:8088/"
PDF_GENERATOR_BASEURL = "http://superset-pdf-report:8890/"
# The base URL for the email report hyperlinks.
WEBDRIVER_BASEURL_USER_FRIENDLY = WEBDRIVER_BASEURL

Expand Down
2 changes: 2 additions & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ paramiko==2.11.0
# via sshtunnel
parsedatetime==2.6
# via apache-superset
pdf2docx==0.5.6
# via apache-superset
pgsanity==0.2.9
# via apache-superset
polyline==2.0.0
Expand Down
14 changes: 14 additions & 0 deletions superset-frontend/src/assets/stylesheets/less/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,17 @@ header {
display: flex;
flex-direction: column;
}

@media print {
a[href]::after {
content: none !important;
}

.header-controls {
display: none !important;
}

.grid-row--empty > div {
color: transparent !important;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ test('should render', () => {
test('should render the dropdown button', () => {
const mockedProps = createProps();
setup(mockedProps);
expect(screen.getByRole('button')).toBeInTheDocument();
const dropdownButtons = screen.getAllByRole('button');
expect(dropdownButtons[0]).toBeInTheDocument();
});

test('should render the menu items', async () => {
Expand All @@ -121,7 +122,7 @@ test('should render the menu items', async () => {
expect(screen.getAllByRole('menuitem')).toHaveLength(4);
expect(screen.getByText('Refresh dashboard')).toBeInTheDocument();
expect(screen.getByText('Set auto-refresh interval')).toBeInTheDocument();
expect(screen.getByText('Download as image')).toBeInTheDocument();
expect(screen.getByText('Download')).toBeInTheDocument();
expect(screen.getByText('Enter fullscreen')).toBeInTheDocument();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { isEmpty } from 'lodash';
import {
css,
isFeatureEnabled,
FeatureFlag,
SupersetClient,
Expand All @@ -29,6 +30,7 @@ import { Menu } from 'src/components/Menu';
import { URL_PARAMS } from 'src/constants';
import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems';
import CssEditor from 'src/dashboard/components/CssEditor';
import Icons from 'src/components/Icons';
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
import SaveModal from 'src/dashboard/components/SaveModal';
import HeaderReportDropdown from 'src/features/reports/ReportModal/HeaderReportDropdown';
Expand All @@ -40,6 +42,7 @@ import getDashboardUrl from 'src/dashboard/util/getDashboardUrl';
import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
import { getUrlParam } from 'src/utils/urlUtils';
import { LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_IMAGE } from 'src/logger/LogUtils';
import { exportDashboard } from 'src/explore/exploreUtils';

const propTypes = {
addSuccessToast: PropTypes.func.isRequired,
Expand Down Expand Up @@ -91,13 +94,24 @@ const MENU_KEYS = {
EDIT_PROPERTIES: 'edit-properties',
EDIT_CSS: 'edit-css',
DOWNLOAD_AS_IMAGE: 'download-as-image',
DOWNLOAD_AS_PDF_PORTRAIT: 'download-as-pdf-portrait',
DOWNLOAD_AS_PDF_LANDSCAPE: 'download-as-pdf-landscape',
DOWNLOAD_AS_DOC_PORTRAIT: 'download-as-doc-portrait',
DOWNLOAD_AS_DOC_LANDSCAPE: 'download-as-doc-landscape',
TOGGLE_FULLSCREEN: 'toggle-fullscreen',
MANAGE_EMBEDDED: 'manage-embedded',
MANAGE_EMAIL_REPORT: 'manage-email-report',
};

const SCREENSHOT_NODE_SELECTOR = '.dashboard';

const iconReset = css`
.ant-dropdown-menu-item > & > .anticon:first-child {
margin-right: 0;
vertical-align: 0;
}
`;

class HeaderActionsDropdown extends React.PureComponent {
static discardChanges() {
window.location.reload();
Expand Down Expand Up @@ -167,6 +181,38 @@ class HeaderActionsDropdown extends React.PureComponent {
case MENU_KEYS.EDIT_PROPERTIES:
this.props.showPropertiesModal();
break;
case MENU_KEYS.DOWNLOAD_AS_PDF_PORTRAIT: {
exportDashboard({
formData: { id: this.props.dashboardId },
resultFormat: 'pdf',
landscape: false,
});
break;
}
case MENU_KEYS.DOWNLOAD_AS_PDF_LANDSCAPE: {
exportDashboard({
formData: { id: this.props.dashboardId },
resultFormat: 'pdf',
landscape: true,
});
break;
}
case MENU_KEYS.DOWNLOAD_AS_DOC_PORTRAIT: {
exportDashboard({
formData: { id: this.props.dashboardId },
resultFormat: 'doc',
landscape: false,
});
break;
}
case MENU_KEYS.DOWNLOAD_AS_DOC_LANDSCAPE: {
exportDashboard({
formData: { id: this.props.dashboardId },
resultFormat: 'doc',
landscape: true,
});
break;
}
case MENU_KEYS.DOWNLOAD_AS_IMAGE: {
// menu closes with a delay, we need to hide it manually,
// so that we don't capture it on the screenshot
Expand Down Expand Up @@ -312,12 +358,53 @@ class HeaderActionsDropdown extends React.PureComponent {
</Menu.Item>
)}
{!editMode && (
<Menu.Item
key={MENU_KEYS.DOWNLOAD_AS_IMAGE}
onClick={this.handleMenuClick}
>
{t('Download as image')}
</Menu.Item>
<Menu.SubMenu title={t('Download')} key={MENU_KEYS.DOWNLOAD_SUBMENU}>
<Menu.SubMenu
title={t('Download as PDF')}
key={MENU_KEYS.DOWNLOAD_SUBMENU}
>
<Menu.Item
key={MENU_KEYS.DOWNLOAD_AS_PDF_PORTRAIT}
icon={<Icons.FilePdfOutlined css={iconReset} />}
onClick={this.handleMenuClick}
>
{t('Portrait')}
</Menu.Item>
<Menu.Item
key={MENU_KEYS.DOWNLOAD_AS_PDF_LANDSCAPE}
icon={<Icons.FilePdfOutlined css={iconReset} />}
onClick={this.handleMenuClick}
>
{t('Landscape')}
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu
title={t('Download as Doc')}
key={MENU_KEYS.DOWNLOAD_SUBMENU}
>
<Menu.Item
key={MENU_KEYS.DOWNLOAD_AS_DOC_PORTRAIT}
icon={<Icons.FileWordOutlined css={iconReset} />}
onClick={this.handleMenuClick}
>
{t('Portrait')}
</Menu.Item>
<Menu.Item
key={MENU_KEYS.DOWNLOAD_AS_DOC_LANDSCAPE}
icon={<Icons.FileWordOutlined css={iconReset} />}
onClick={this.handleMenuClick}
>
{t('Landscape')}
</Menu.Item>
</Menu.SubMenu>
<Menu.Item
key={MENU_KEYS.DOWNLOAD_AS_IMAGE}
icon={<Icons.FileOutlined css={iconReset} />}
onClick={this.handleMenuClick}
>
{t('Download as image')}
</Menu.Item>
</Menu.SubMenu>
)}
{userCanShare && (
<Menu.SubMenu
Expand Down
16 changes: 16 additions & 0 deletions superset-frontend/src/explore/exploreUtils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,22 @@ export const exportChart = ({
SupersetClient.postForm(url, { form_data: safeStringify(payload) });
};

export const exportDashboard = ({
formData,
resultFormat = 'pdf',
landscape = false,
}) => {
const url = '/api/v1/dashboard/data';

const payload = {
...formData,
result_format: resultFormat,
landscape,
};

SupersetClient.postForm(url, { form_data: safeStringify(payload) });
};

export const exploreChart = (formData, requestParams) => {
const url = getExploreUrl({
formData,
Expand Down
21 changes: 21 additions & 0 deletions superset-pdf-report/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"extends": [
"eslint:recommended",
"plugin:node/recommended"
],
"parserOptions": {
// Only ESLint 6.2.0 and later support ES2020.
"ecmaVersion": 2020
},
"rules": {
"node/exports-style": ["error", "module.exports"],
"node/file-extension-in-import": ["error", "always"],
"node/prefer-global/buffer": ["error", "always"],
"node/prefer-global/console": ["error", "always"],
"node/prefer-global/process": ["error", "always"],
"node/prefer-global/url-search-params": ["error", "always"],
"node/prefer-global/url": ["error", "always"],
"node/prefer-promises/dns": "error",
"node/prefer-promises/fs": "error"
}
}
46 changes: 46 additions & 0 deletions superset-pdf-report/.jscsrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"disallowKeywords": ["with"],
"disallowKeywordsOnNewLine": ["else"],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleVarDecl": false,
"disallowNewlineBeforeBlockStatements": true,
"disallowQuotedKeysInObjects": true,
"disallowSpaceAfterObjectKeys": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowSpacesInFunction": false,
"disallowSpacesInsideParentheses": true,
"disallowTrailingWhitespace": true,
"maximumLineLength": 120,
"requireCamelCaseOrUpperCaseIdentifiers": true,
"requireCapitalizedComments": true,
"requireCapitalizedConstructors": true,
"requireCurlyBraces": true,
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"case",
"return",
"try",
"catch",
"typeof"
],
"requireSpaceAfterLineComment": true,
"requireSpaceAfterBinaryOperators": true,
"requireSpaceBeforeBinaryOperators": true,
"requireSpaceBeforeBlockStatements": true,
"requireSpaceBeforeObjectValues": true,
"requireSpacesInFunction": {
"beforeOpeningCurlyBrace": true
},
"requireTrailingComma": {
"ignoreSingleLine": true
},
"requireEarlyReturn": true,
"validateIndentation": 4,
"validateLineBreaks": "LF",
"validateQuoteMarks": "'"
}
3 changes: 3 additions & 0 deletions superset-pdf-report/.jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"esversion": 9
}
43 changes: 43 additions & 0 deletions superset-pdf-report/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# This Dockerfile uses multi-stage build to customize DEV and PROD images:
# https://docs.docker.com/develop/develop-images/multistage-build/

FROM node:16 AS base

LABEL maintainer="[email protected]"
LABEL vendor="Prozori solutions"

# Install latest chrome dev package and fonts to support major
# charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version
# of Chromium that Puppeteer
# installs, work.
RUN apt-get update \
&& apt-get install -y wget gnupg \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update \
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-khmeros fonts-kacst fonts-freefont-ttf libxss1 \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*


## It's a good idea to use dumb-init to help prevent zombie chrome processes.
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init
RUN chmod +x /usr/local/bin/dumb-init

ARG NODE_ENV

ENV NODE_ENV=${NODE_ENV}

WORKDIR /app

COPY ./superset-pdf-report/package.json /app/package.json
COPY ./superset-pdf-report/package-lock.json /app/package-lock.json

FROM base as development_build
RUN npm install -g nodemon && npm install

FROM base as production_build
USER root
RUN npm ci
COPY . /app
Loading
Loading