Skip to content

Commit

Permalink
Merge pull request #197 from xMort/pdo-workday-poc
Browse files Browse the repository at this point in the history
spreadjs - Dashboard Plugin
  • Loading branch information
BugsBunny338 authored Sep 2, 2022
2 parents 00bbfaf + 2878e53 commit ca9cd0d
Show file tree
Hide file tree
Showing 32 changed files with 2,355 additions and 0 deletions.
28 changes: 28 additions & 0 deletions spreadjs-plugin/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
##
## This .env file will be picked up by the Dashboard Plugin development harness; the values contained herein
## will be used only while developing the plugin.
##
## They will never make their way into a production build of the plugin itself. Plugins developed in a generic
## way can work out of the box on any backend, workspace or dashboard.
##
## Note: you should keep any secrets away from this file and ideally commit to VCS.
##

# Specify backend URL including protocol. The Analytical Backend used by the development harness will connect
# to this backend via proxy.
#
# If you do not provide backend URL, the harness will connect to public server used by GD.UI Examples Gallery
#
# Note: If you do provide backend URL, then you must also specify custom workspace and dashboard because the default
# workspace and the default dashboard are only available on the public server.
BACKEND_URL=https://workday.poc.gooddata.com/

# Specify workspace that contains dashboard against which you want to develop and test the plugin.
#
# If you do not provide workspace, the harness will use default workspace used by GD.UI Examples Gallery
WORKSPACE=horr0bfqz3g2tez1ej2jt6r11ajz4q90

# Specify dashboard against which you want to develop and test the plugin.
#
# If you do not provide dashboard, the harness will use a nice dashboard from GD.UI Examples Gallery
DASHBOARD_ID=ade3VUaz5U6s
7 changes: 7 additions & 0 deletions spreadjs-plugin/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// (C) 2020 GoodData Corporation
module.exports = {
parser: "@typescript-eslint/parser",
plugins: ["react-hooks", "prettier"],
extends: ["plugin:react/recommended", "plugin:import/errors", "plugin:import/typescript"],
parserOptions: { tsconfigRootDir: __dirname },
};
4 changes: 4 additions & 0 deletions spreadjs-plugin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env.secrets
dist
node_modules
.idea
5 changes: 5 additions & 0 deletions spreadjs-plugin/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"printWidth": 110,
"tabWidth": 4,
"trailingComma": "all"
}
195 changes: 195 additions & 0 deletions spreadjs-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# SpreadJS dashboard plugin

Dashboard plugin that does custom executions on
[Workday POC workspace](https://workday.poc.gooddata.com/dashboards/#/workspace/horr0bfqz3g2tez1ej2jt6r11ajz4q90)
and renders them into financial statement (the statement was re-created visually based on [this demo](https://www.grapecity.com/spreadjs/demos/showcase/income-statement/react)).

The plugin searches for insights with name that starts with `#spreadjs#` string and replaces them
with custom financial statement insight. The original insight is not used at all, not even its
execution result (this should be optimized somehow - for now this is a quick and dirty way how can the desired
custom insight can be placed anywhere on the dashboard).

[SpreadJS](https://www.grapecity.com/spreadjs) library was used for rendering of the visualization.

SpreadJS trial license is necessary for visualization to render when deployed anywhere else then on localhost.
The current license commit with the code is valid till 2022-09-30.

![screenshot 1](img/plugin1.png)

![screenshot 2](img/plugin2.png)


# GoodData.UI Dashboard Plugin project

This is a one stop project to help you develop, test and build your own dashboard plugin. Before you start, we
encourage you to learn more about plugins in [our documentation](https://sdk.gooddata.com/gooddata-ui/docs/about_gooddataui.html).

In case you don't feel like reading the documentation at this point, go at least through the following quick introduction.

## Quick Introduction into Dashboard Plugins

Dashboard Plugins (plugins) allow developers to create extensions that alter behavior and look and feel of the
vanilla GoodData KPI Dashboards (dashboards).

Plugins are registered into the dashboard engine used to render a concrete dashboard. At the registration time the
plugin code can use several customization APIs to:

- deliver new custom widgets to render on the dashboard
- alter how particular insights are rendered; this in effect allows you to inject custom data visualizations of
analytics computed by GoodData
- listen to events occurring on the dashboard

When developing your own plugin, you typically create custom React components and event handlers that interact with
the rendered dashboard using available APIs and then register those components and handlers using the customization APIs.

The infrastructure within this project allows you to develop and verify your new plugin against a live, existing dashboard
located either on GoodData platform or GoodData.CN.

Once you are happy with your new plugin you have to build it using scripts included in this project and then host
the built artifacts.

After that, you can register the plugin into one or more workspaces on GoodData platform and/or GoodData.CN and
then use the plugin on any number of dashboards

_Note: GoodData currently does not provide hosting for your plugin artifacts._

## Plugin development guide

Building a new plugin is easy. Before you start, ensure that your `.env` and `.env.secrets` files are set up correctly.

0. (Optional) Export catalog: `npm run refresh-md`

To make referencing various metadata objects easier in your plugin, you can use the [Export catalog](https://sdk.gooddata.com/gooddata-ui/docs/export_catalog.html) feature to get a easy-to-use list of the various MD objects in your workspace (insights, dashboards, attributes, etc.).
For convenience, this was integrated to your plugin, just run `npm run refresh-md`.
This will connect to the workspace specified in the `.env` file using the credentials from `.env.secrets`
and populate the file `src/md/full.ts` with information about the metadata objects available in the specified workspace.
See the [Export catalog](https://sdk.gooddata.com/gooddata-ui/docs/export_catalog.html) documentation page for more information.

1. Start the development server: `npm start`

To verify everything works correctly, navigate to `https://127.0.0.1:3001`. You should see your existing
dashboard with a new empty section added at the end. The section will be titled 'Added from a plugin'.

Note: you can use `PORT` env variable to specify different port number.

2. Develop your plugin code in `src/dp_workday_poc`

The `src/dp_workday_poc/Plugin.tsx` is the main plugin file where you have to register all
your custom content. However, you can create as many new files as you want under the `src/dp_workday_poc`
directory. Just make sure to never place your custom code outside of this directory.

Note: we recommend to write your plugin in TypeScript and to use a modern IDE. This way you can conveniently
explore the plugin customization APIs from the comfort of your development environment.

3. Build the plugin: `npm run build-plugin`

This will build plugin artifacts under `dist/dashboardPlugin`.

4. Upload plugin artifacts to your hosting

It is paramount that you upload all files from the `dist/dashboardPlugin`.

_IMPORTANT_: your hosting must support https and your GoodData domain must include the hosting location in the list
of allowed hosts from where GoodData will load plugins. You should create a [support ticket](https://support.gooddata.com/hc/en-us/requests/new?ticket_form_id=582387) to explicitly allow the hosting
location before we will load any plugins from it. You may host multiple plugins in separate directories within
the allowed hosting location.

_GOOD IDEA_: treat plugin builds immutably. Never overwrite an already uploaded plugin artifacts. Organize your hosting
location so there is always unique directory that contains all plugin artifacts. This is a corner-stone of controlled,
phased rollout of the plugin.

_BAD IDEA_: overwriting existing plugin artifacts will immediately impact all dashboards that use the plugin, possibly
breaking them if you did not have chance to fully test the plugin.

5. Add plugin to one or more workspaces: `npm run add-plugin -- <url>`

Once your plugin is uploaded to public hosting location, you can add it into your workspace. You can achieve this
using the same CLI tool that you have used to create this plugin project. For convenience, this project contains
the tool among the devDependencies together with convenience script to add plugin to either workspace specified
in your `.env` file (default) or another workspace that you specify on the command line.

Run the `npm run add-plugin -- "https://your.hosting/pluginDirOfYourChoice/dp_workday_poc.js"` to
create a new dashboard plugin object in the workspace specified in the `.env` file. The created dashboard object
point to the URL of the built plugin.

After successful creation of the plugin object, the tool will print plugin object identifier. You will need this
identifier later to link dashboard(s) with the plugin.

Note: the CLI tool has options that allow you to add plugin to different backends and/or different workspaces. Check out
`npm run gdc-plugins -- --help` to learn more about the tool's commands and options.

6. Use plugin on a dashboard: `npm run link-plugin -- <plugin-object-id>`

Now that you have created a plugin object in your workspace, you can link it with one or more dashboards. The
`link-plugin` script in package.json is a shortcut to link plugin with dashboard specified in your `.env` file.

If your plugin supports parameterization (see [src/dp_workday_poc](./src/dp_workday_poc/Plugin.tsx)) and
you want to specify parameters for the link between dashboard the plugin, you can run `npm run link-plugin -- <plugin-object-id> --with-parameters`
and the tool will open an editor for you to enter the parameters.

Note: the CLI tool has options that allow you to link plugin to different backends and/or different workspaces. Check out
`npm run gdc-plugins -- --help` to learn more about the tool's commands and options.

_TIP_: you can use the `unlink` command to remove the link between dashboard and the plugin.

## Authentication & secrets

Your plugin does not have to concern itself with the authentication against GoodData backend. When the plugin runs
in context of GoodData KPI Dashboards, it is the application that takes care of all the authentication and ensures
that the plugin executes in an authenticated environment.

The authentication credentials that are required to start the development harness included in this project are used
only during development because the harness needs to provide authenticated environment to the plugin as well.

In order to provide credentials to the development harness, you can use either the `.env.secrets` file or export the
necessary environment variables before starting the harness.

The contents of `.env.secrets` will never make their way into plugin build artifacts, they are loaded only when starting
the development harness. Check out the webpack.config.js if you would like to double-check this.

_IMPORTANT: Never include credentials and secrets in your plugin source code or other assets that your plugin requires.
All this data will be available in the publicly hosted plugin artifacts and can also be found through the browser developer console._

## FAQ

### What's up with the directories in src? Can I rename them?

Do not rename or otherwise refactor any of the directories that were created during this project initialization.
The structure and naming are essential for the build and the runtime loading of your plugin to work properly.

This project is setup so that all your custom code must be self-contained in the [src/dp_workday_poc](./src/dp_workday_poc) directory.

The [src/dp_workday_poc\_engine](./src/dp_workday_poc_engine) and [src/dp_workday_poc\_entry](./src/dp_workday_poc_entry) directories contain essential plugin boilerplate.
You should not modify these directories or their contents unless you are 100% sure what you are doing.

The [src/harness] directory contains code for plugin development harness; it is used only during plugin development and the
code in this directory will not be part of the plugin build. You can start the harness using `npm start`.
You should have no need to modify the code in the harness. We anticipate that at times you may need to tweak Analytical Backend setup
that is contained in the [src/harness/backend.ts](src/harness/backend.ts) - this is a safe change.

### How can I setup compatibility of the plugin?

You can modify minEngineVersion and maxEngineVersion properties in `src/dp_workday_poc\_entry/index`.
By default, we guarantee that plugin will be compatible only with the exact version of the dashboard engine used during its build (`"bundled"` option). But if you are sure, that plugin is compatible also with the other engine versions, you can set concrete range of the versions (e.g. `"minEngineVersion": "8.8.0", "maxEngineVersion": "8.9.0"`). Note that combining multiple plugins created before version `8.8.0` may not work.

### How do plugin dependencies work?

Your plugin can depend on arbitrary third party packages at your discretion with one exception: the packages
specified as `peerDependencies` in this project's package.json. Packages that are listed as `peerDependencies`
will be _provided_ by the runtime environment.

### Can I modify webpack config?

This is generally not recommended and if needed should be approached by expert users only. In general, adding new
loaders and _extending_ the resolve section are the safer types of changes. However we strongly discourage making
modifications to other parts of the webpack config: changes to how the `dashboardPlugin` is built can break your
plugin and prevent it from loading correctly.

### How about Internet Explorer?

GoodData applications [do not support Internet Explorer](https://help.gooddata.com/pages/viewpage.action?pageId=86775029) as of November 19th 2021.
The plugin artifacts created during the plugin build are not compatible with Internet Explorer.

### How about Safari?

GoodData applications do support Safari, however currently it's not possible to run this boilerplate locally with GoodData.CN backend running on https protocol, due to the fact how Safari is handling authentication in backend redirects.
Binary file added spreadjs-plugin/img/plugin1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spreadjs-plugin/img/plugin2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions spreadjs-plugin/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// (C) 2019 GoodData Corporation
module.exports = {
preset: "ts-jest",
testRegex: "(\\.test)\\.(tsx?)$",
collectCoverage: false,
moduleFileExtensions: ["ts", "js", "tsx"],
moduleNameMapper: {
"^[./a-zA-Z0-9$_-]+\\.svg$": "<rootDir>/__mocks__/jestSvgStub.js",
"\\.(css|less|sass|scss)$": "<rootDir>/__mocks__/styleMock.ts",
},
testPathIgnorePatterns: ["/node_modules/", "/dist/"],
testEnvironment: "jsdom",
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts", "jest-enzyme"],
};
11 changes: 11 additions & 0 deletions spreadjs-plugin/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// (C) 2019 GoodData Corporation
/* eslint-disable @typescript-eslint/no-var-requires */
import "jest-enzyme";
import * as raf from "raf";

const enzyme = require("enzyme");
const Adapter = require("@wojtekmaj/enzyme-adapter-react-17");

enzyme.configure({ adapter: new Adapter() });

raf.polyfill();
120 changes: 120 additions & 0 deletions spreadjs-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
"name": "workday-poc",
"version": "1.0.0",
"description": "POC that uses SpreadJS library to create a dashboard widget",
"author": "mort",
"private": true,
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"sideEffects": [
"*.css",
"*.svg"
],
"files": [
"dist/**/*.js",
"dist/**/*.json",
"dist/**/*.d.ts",
"dist/**/*.map",
"dist/**/*.svg"
],
"scripts": {
"clean": "rm -rf ci dist esm coverage *.log styles/css && jest --clearCache",
"build": "webpack --mode production --config-name harness",
"build-plugin": "webpack --mode production --config-name dashboardPlugin",
"build-plugin-debug": "webpack --mode production --config-name dashboardPlugin --analyze",
"gdc-plugins": "gdc-plugins",
"add-plugin": "gdc-plugins dashboard-plugin add",
"link-plugin": "gdc-plugins dashboard-plugin link",
"unlink-plugin": "gdc-plugins dashboard-plugin unlink",
"start": "webpack-cli serve --config-name harness",
"test": "jest --watch --passWithNoTests",
"test-once": "jest --maxWorkers=${JEST_MAX_WORKERS:-'45%'} --passWithNoTests",
"eslint": "eslint -c .eslintrc.js --ext ts,tsx src/",
"prettier-check": "prettier --check \"{src,test}/**/*.{ts,tsx,json,scss,md,yaml,html}\"",
"prettier-write": "prettier --write \"{src,test}/**/*.{ts,tsx,json,scss,md,yaml,html}\"",
"refresh-md": "node ./scripts/refresh-md.js"
},
"dependencies": {
"@gooddata/sdk-ui-dashboard": "^8.10.0",
"@grapecity/spread-sheets": "^15.2.0",
"@grapecity/spread-sheets-react": "^15.2.0",
"json-stable-stringify": "^1.0.1",
"lodash": "^4.17.19",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@gooddata/sdk-backend-bear": "^8.10.0",
"@gooddata/sdk-backend-spi": "^8.10.0",
"@gooddata/sdk-model": "^8.10.0",
"@gooddata/sdk-ui": "^8.10.0",
"@gooddata/sdk-ui-charts": "^8.10.0",
"@gooddata/sdk-ui-ext": "^8.10.0",
"@gooddata/sdk-ui-geo": "^8.10.0",
"@gooddata/sdk-ui-pivot": "^8.10.0",
"react": "^16.10.0 || ^17.0.0",
"react-dom": "^16.10.0 || ^17.0.0"
},
"devDependencies": {
"@babel/core": "^7.7.2",
"@babel/preset-env": "^7.7.2",
"@babel/preset-react": "^7.7.2",
"@gooddata/catalog-export": "^8.10.0",
"@gooddata/plugin-toolkit": "^8.10.0",
"@gooddata/sdk-backend-bear": "^8.10.0",
"@gooddata/sdk-backend-spi": "^8.10.0",
"@gooddata/sdk-model": "^8.10.0",
"@gooddata/sdk-ui": "^8.10.0",
"@gooddata/sdk-ui-charts": "^8.10.0",
"@gooddata/sdk-ui-ext": "^8.10.0",
"@gooddata/sdk-ui-loaders": "^8.10.0",
"@types/enzyme": "^3.10.3",
"@types/jest": "^27.0.1",
"@types/json-stable-stringify": "^1.0.32",
"@types/lodash": "^4.14.158",
"@types/raf": "^3.4.0",
"@types/react": "^17.0.34",
"@types/react-dom": "^17.0.11",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.0",
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.5",
"babel-loader": "^8.0.5",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"concurrently": "^6.0.2",
"css-loader": "^6.7.1",
"dotenv": "^10.0.0",
"dotenv-webpack": "^7.0.2",
"enzyme": "^3.10.0",
"eslint": "^8.3.0",
"eslint-plugin-header": "^3.0.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^25.3.0",
"eslint-plugin-no-only-tests": "^2.4.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.20.5",
"eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-regexp": "^1.7.0",
"eslint-plugin-tsdoc": "^0.2.14",
"html-webpack-plugin": "^5.3.1",
"jest": "^27.5.1",
"jest-enzyme": "^7.1.2",
"jest-junit": "^3.0.0",
"lodash": "^4.17.19",
"prettier": "~2.5.0",
"process": "^0.11.10",
"raf": "^3.4.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-intl": "^5.23.0",
"source-map-loader": "^2.0.2",
"style-loader": "^3.3.1",
"ts-jest": "^27.0.5",
"ts-loader": "^8.3.0",
"typescript": "4.0.2",
"util": "^0.12.3",
"webpack": "^5.58.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.1"
}
}
Loading

0 comments on commit ca9cd0d

Please sign in to comment.