From f2e76fc9895efab2af840b459f486e23cbc0a129 Mon Sep 17 00:00:00 2001 From: MarkLark86 Date: Wed, 27 May 2020 17:01:21 +1000 Subject: [PATCH] [SDESK-5220] Modal to show Highcharts license (#127) * [SDESK-5220] Modal to show Highcharts license * Add client_config to server * Add config instructions to README * Update eslint package and config * Fix behave test * Improve highcharts client config structure --- .eslintrc.js | 64 +++++++++++- README.md | 16 +++ client/components/HighchartsLicense.tsx | 126 ++++++++++++++++++++++++ client/components/index.ts | 1 + client/index.js | 53 +++++++++- client/interfaces.ts | 14 +++ client/superdeskApi.ts | 9 ++ client/typings/refs.d.ts | 1 + client/views/analytics.html | 1 + package.json | 21 ++-- server/analytics/__init__.py | 10 ++ server/features/saved_reports.feature | 4 +- tsconfig.json | 4 +- 13 files changed, 309 insertions(+), 15 deletions(-) create mode 100644 client/components/HighchartsLicense.tsx create mode 100644 client/components/index.ts create mode 100644 client/interfaces.ts create mode 100644 client/superdeskApi.ts create mode 100644 client/typings/refs.d.ts diff --git a/.eslintrc.js b/.eslintrc.js index 39ab9e263..77b9c4650 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1 +1,63 @@ -module.exports = require('superdesk-code-style'); +const sharedConfigs = require('superdesk-code-style'); + +module.exports = Object.assign({}, sharedConfigs, { + rules: Object.assign(sharedConfigs.rules, { + 'no-nested-ternary': 0, + 'no-unused-vars': 0, // marks typescript interfaces as unused vars + 'no-undef': 0, // marks interface properties as usages of undeclared variables + + // field names from back-end use camel-case for naming. + // I'm not convinced it's worth using a bracket notation only to satisfy a lint rule + 'camelcase': 0, + + // doesn't make sense with many properties + // 10 may use shorthand and one may be inline or reference differently named variable + 'object-shorthand': 0, + + // can make functions harder to read; forces into rewriting the function to insert a debugger + 'arrow-body-style': 0, + + // leaving up to developers. I prefer to quote external strings like css names, + // but keep internal properties unquoted unless required + 'quote-props': 0, + + 'newline-per-chained-call': ["error", {"ignoreChainWithDepth": 3}], + }), + parser: '@typescript-eslint/parser', + overrides: [ + { + files: ['*.ts', '*.tsx'], + rules: { + 'react/prop-types': 0, // interfaces are used in TypeScript files + 'no-unused-vars': 0, + 'no-undef': 0, + 'comma-dangle': 0, + 'camelcase': 0, + 'object-shorthand': 0, + 'arrow-body-style': 0, + 'newline-per-chained-call': 0, + 'quote-props': 0, + 'arrow-body-style': 0, + 'max-len': 0, // handled by tslint + + "comma-dangle": ["error", { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", + "functions": "always-multiline" + }], + + + // allow calling hasOwnProperty + "no-prototype-builtins": 0, + }, + }, + { + files: ['*.d.ts'], + rules: { + 'spaced-comment': 0, + }, + }, + ], +}); diff --git a/README.md b/README.md index 6d85d8725..d747f63bd 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ The Highcharts JS library is used to render the charts. It is available under di * [Client](#client-enable-the-superdesk-analytics-module) * [Server](#server-load-the-superdesk-analytics-module) * [Development](#development-setup) +* [Highcharts License](#highcharts-license) * [Config Options](#config-options) * [Highcharts Export Server](#highcharts-export-server) * [Installing the Service](#installing-the-service) @@ -117,6 +118,21 @@ pip install -e /path/to/superdesk-analytics ``` +## Highcharts License +You must have a valid license for [Highcharts](https://www.highcharts.com/) JS v6.x to use this plugin. + +The Highcharts JS library is used to render the charts. It is available under different licenses depending on whether it is intended for commercial/government use, or for a personal or non-profit project. + +To have the license details available to the end user in the Analytics page of Superdesk, provide the following config options in your settings.py: + +* HIGHCHARTS_LICENSE_TYPE (High-Five, Develop or OEM) +* HIGHCHARTS_LICENSEE (Name of the entity that owns the license) +* HIGHCHARTS_LICENSEE_CONTACT (A contact email address for the licensee) +* HIGHCHARTS_LICENSE_ID (The license ID provided by Highsoft) +* HIGHCHARTS_LICENSE_CUSTOMER_ID (A custom license field to use an internal customer number, if required) +* HIGHCHARTS_LICENSE_EXPIRY (the expiry of the license) + + ## Config Options * HIGHCHARTS_SERVER_HOST (defaults to 'localhost') * HIGHCHARTS_SERVER_PORT (defaults to '6060') diff --git a/client/components/HighchartsLicense.tsx b/client/components/HighchartsLicense.tsx new file mode 100644 index 000000000..39f6dfd0d --- /dev/null +++ b/client/components/HighchartsLicense.tsx @@ -0,0 +1,126 @@ +/* eslint-disable react/no-multi-comp */ + +import React from 'react'; + +import {appConfig} from 'appConfig'; + +import hc from 'highcharts'; + +import {IAnalyticsConfig} from '../interfaces'; +import {superdeskApi} from '../superdeskApi'; + +interface IProps { + closeModal(): void +} + +class HighchartsLicenseModal extends React.PureComponent { + render() { + const { + Modal, + ModalBody, + ModalFooter, + } = superdeskApi.components; + const gettext = superdeskApi.localization.gettext; + const config: IAnalyticsConfig = appConfig as IAnalyticsConfig; + const highchartsVersionLink = `https://www.highcharts.com/blog/changelog/#highcharts-v${hc.version}`; + const license = config.highcharts?.license ?? {}; + const licenseType = license.type ?? gettext('OEM'); + + return ( + +
+ +

{gettext('Highcharts {{licenseType}} License', {licenseType})}

+
+ +
+

{gettext('The use of Highcharts is provided under a license with the following details:')}

+
+
+ + + + + + + {license.licensee && ( + + + + + )} + {license.contact && ( + + + + + )} + {license.id && ( + + + + + )} + {license.customer_id && ( + + + + + )} + {license.expiry && ( + + + + + )} + + + + + +
{gettext('License Type')}:{licenseType}
{gettext('Licensee:')}{license.licensee}
{gettext('Licensee Contact:')} + + {license.contact} + +
{gettext('License ID:')}{license.id}
{gettext('Customer Installation No.:')}{license.customer_id}
{gettext('Expiry Date:')}{license.expiry}
{gettext('Installed Version:')} + + v{hc.version} + +
+
+
+ + + +
+ ); + } +} + +function showHighchartsModal(): void { + superdeskApi.ui.showModal(HighchartsLicenseModal); +} + +export class HighchartsLicense extends React.PureComponent { + render() { + return ( + + ); + } +} diff --git a/client/components/index.ts b/client/components/index.ts new file mode 100644 index 000000000..3aad5828a --- /dev/null +++ b/client/components/index.ts @@ -0,0 +1 @@ +export {HighchartsLicense} from './HighchartsLicense'; diff --git a/client/index.js b/client/index.js index eb8e37d82..e3f29d720 100644 --- a/client/index.js +++ b/client/index.js @@ -8,11 +8,18 @@ * at https://www.sourcefabric.org/superdesk/license */ +import {reactToAngular1} from 'superdesk-ui-framework'; +import {getSuperdeskApiImplementation} from 'superdesk-core/scripts/core/get-superdesk-api-implementation'; +import {superdeskApi} from './superdeskApi'; +import {appConfig, extensions} from 'appConfig'; +import ng from 'superdesk-core/scripts/core/services/ng'; + import {gettext} from './utils'; import './styles/analytics.scss'; import * as svc from './services'; import * as directive from './directives'; +import {HighchartsLicense} from './components'; // Base services/directives import './charts'; @@ -73,6 +80,11 @@ export default angular.module('superdesk.analytics', [ .directive('sdaArchivePreviewProxy', directive.ArchivePreviewProxy) .directive('sdaRenditionsPreview', directive.RenditionsPreview) + .component( + 'sdHighchartsLicense', + reactToAngular1(HighchartsLicense, []) + ) + .service('reportConfigs', svc.ReportConfigService) .run(cacheIncludedTemplates) @@ -92,4 +104,43 @@ export default angular.module('superdesk.analytics', [ return reports.length > 0; }], }); - }]); + }]) + + .run([ + '$injector', + 'modal', + 'privileges', + 'lock', + 'session', + 'authoringWorkspace', + 'metadata', + ( + $injector, + modal, + privileges, + lock, + session, + authoringWorkspace, + metadata + ) => { + ng.register($injector); + + ng.waitForServicesToBeAvailable() + .then(() => { + Object.assign( + superdeskApi, + getSuperdeskApiImplementation( + null, + extensions, + modal, + privileges, + lock, + session, + authoringWorkspace, + metadata, + appConfig + ) + ); + }); + }, + ]); diff --git a/client/interfaces.ts b/client/interfaces.ts new file mode 100644 index 000000000..a73c427e7 --- /dev/null +++ b/client/interfaces.ts @@ -0,0 +1,14 @@ +import {ISuperdeskGlobalConfig} from 'superdesk-api'; + +export interface IAnalyticsConfig extends ISuperdeskGlobalConfig { + highcharts?: { + license?: { + id?: string; + type?: string; + licensee?: string; + contact?: string; + customer_id?: string; + expiry?: string; + }; + }; +} diff --git a/client/superdeskApi.ts b/client/superdeskApi.ts new file mode 100644 index 000000000..3490cdd52 --- /dev/null +++ b/client/superdeskApi.ts @@ -0,0 +1,9 @@ +import {ISuperdesk} from 'superdesk-api'; + +// will be set asynchronously on planning module start +// members can't be accessed in root module scope synchronously + +// DO NOT USE OUTSIDE .ts OR .tsx FILES +// because it would make it harder to find and update usages when API changes + +export const superdeskApi = {} as ISuperdesk; diff --git a/client/typings/refs.d.ts b/client/typings/refs.d.ts new file mode 100644 index 000000000..2f45a6cc3 --- /dev/null +++ b/client/typings/refs.d.ts @@ -0,0 +1 @@ +/// diff --git a/client/views/analytics.html b/client/views/analytics.html index 14d17ea0c..e86194139 100644 --- a/client/views/analytics.html +++ b/client/views/analytics.html @@ -41,6 +41,7 @@ Add New + diff --git a/package.json b/package.json index 2e82f4b0e..5f22d8463 100644 --- a/package.json +++ b/package.json @@ -21,15 +21,21 @@ "dependencies": { "highcharts": "^6.2.0", "twix": "^1.1.5", - "@types/lodash": "4.14.116", + "@types/lodash": "4.14.117", "ts-loader": "3.5.0" }, "devDependencies": { - "eslint": "^4.19.1", - "eslint-plugin-jasmine": "^1.8.1", + "@typescript-eslint/parser": "2.6.1", + "angular-embed": "github:superdesk/angular-embed#d75968e", + "angular-embedly": "github:Urigo/angular-embedly#0.0.8", + "eslint": "6.6.0", "eslint-plugin-angular": "^1.6.1", + "eslint-plugin-jasmine": "^1.8.1", + "eslint-plugin-react": "7.16.0", "jasmine-core": "^2.99.1", "jasmine-reporters": "^2.3.0", + "jquery": "^3.4.1", + "jquery.gridster": "github:dustmoo/gridster.js#c306335397816beceb74e4a176067baef34a0359", "karma": "^2.0.0", "karma-chrome-launcher": "^2.2.0", "karma-jasmine": "^1.1.1", @@ -38,13 +44,8 @@ "karma-webpack": "^2.0.13", "lodash": "^4.17.5", "moment-timezone": "^0.5.14", - "superdesk-code-style": "^1.2.0", - "superdesk-core": "github:superdesk/superdesk-client-core#release/1.33.1", - "angular-embed": "github:superdesk/angular-embed#d75968e", - "angular-embedly": "github:Urigo/angular-embedly#0.0.8", - "jquery": "^3.4.1", - "jquery.gridster": "github:dustmoo/gridster.js#c306335397816beceb74e4a176067baef34a0359", "rangy": "1.3.0", - "typescript-eslint-parser": "^18.0.0" + "superdesk-code-style": "^1.2.0", + "superdesk-core": "github:superdesk/superdesk-client-core#develop" } } diff --git a/server/analytics/__init__.py b/server/analytics/__init__.py index 1d14b8c79..d961dff91 100644 --- a/server/analytics/__init__.py +++ b/server/analytics/__init__.py @@ -106,6 +106,16 @@ def init_app(app): init_featuremedia_updates_report(app) init_update_time_report(app) + app.client_config.setdefault('highcharts', {}) + app.client_config['highcharts']['license'] = { + 'id': app.config.get('HIGHCHARTS_LICENSE_ID') or '', + 'type': app.config.get('HIGHCHARTS_LICENSE_TYPE') or 'OEM', + 'licensee': app.config.get('HIGHCHARTS_LICENSEE') or '', + 'contact': app.config.get('HIGHCHARTS_LICENSEE_CONTACT') or '', + 'customer_id': app.config.get('HIGHCHARTS_LICENSE_CUSTOMER_ID') or '', + 'expiry': app.config.get('HIGHCHARTS_LICENSE_EXPIRY') or '' + } + # If this app is for testing, then create an endpoint for the base reporting service # so the core searching/aggregation functionality can be tested if app.testing: diff --git a/server/features/saved_reports.feature b/server/features/saved_reports.feature index dfa6d3ad6..078c359c1 100644 --- a/server/features/saved_reports.feature +++ b/server/features/saved_reports.feature @@ -118,7 +118,7 @@ Feature: Saved Reports """ When we patch "/users/#CONTEXT_USER_ID#" """ - {"user_type": "user", "privileges": {"global_saved_reports": 1}} + {"user_type": "user", "privileges": {"global_saved_reports": 1, "saved_reports": 1}} """ Then we get OK response When we post to "/saved_reports" @@ -204,7 +204,7 @@ Feature: Saved Reports """ When we patch "/users/#CONTEXT_USER_ID#" """ - {"user_type": "user", "privileges": {"global_saved_reports": 1}} + {"user_type": "user", "privileges": {"global_saved_reports": 1, "saved_reports": 1}} """ Then we get OK response When we patch "/saved_reports/#REPORT1#" diff --git a/tsconfig.json b/tsconfig.json index 57d9f8a3c..5b3c4aad8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ ], "compilerOptions": { "baseUrl": "client", + "skipLibCheck": true, "sourceMap": true, "allowJs": true, "esModuleInterop": true, @@ -14,7 +15,8 @@ "target": "es5", "lib": [ "dom", - "es2015" + "es2017", + "es2018.promise" ], "jsx": "react" }