diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index e887f08ebdccb..98a773389d3c1 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -25827,12 +25827,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz", "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==" }, - "node_modules/currencyformatter.js": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/currencyformatter.js/-/currencyformatter.js-1.0.5.tgz", - "integrity": "sha1-+MbZRdzmtn70j0dRaGEajZXJx14=", - "peer": true - }, "node_modules/cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -56137,8 +56131,7 @@ "version": "0.18.25", "license": "Apache-2.0", "dependencies": { - "handlebars": "^4.7.7", - "just-handlebars-helpers": "^1.0.19" + "handlebars": "^4.7.7" }, "devDependencies": { "@types/jest": "^26.0.0", @@ -56156,23 +56149,6 @@ "react-dom": "^16.13.1" } }, - "plugins/plugin-chart-handlebars/node_modules/just-handlebars-helpers": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/just-handlebars-helpers/-/just-handlebars-helpers-1.0.19.tgz", - "integrity": "sha512-E+0eUn5xKfBAoU6mF3QbGZ939PZDw7RYI6AMTpRQtesRH2lZXjXaOqHzJ2nbHnDVmxNQM453sXFnMpd/uaLkKg==", - "peerDependencies": { - "currencyformatter.js": ">= 1.0.4 < 2", - "handlebars": ">= 3.*", - "moment": ">= 2.22.0 < 3", - "sprintf-js": ">= 1.1.1 < 2" - } - }, - "plugins/plugin-chart-handlebars/node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "peer": true - }, "plugins/plugin-chart-pivot-table": { "name": "@superset-ui/plugin-chart-pivot-table", "version": "0.18.25", @@ -70126,22 +70102,7 @@ "@types/jest": "^26.0.0", "@types/lodash": "^4.14.149", "handlebars": "^4.7.7", - "jest": "^26.0.1", - "just-handlebars-helpers": "^1.0.19" - }, - "dependencies": { - "just-handlebars-helpers": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/just-handlebars-helpers/-/just-handlebars-helpers-1.0.19.tgz", - "integrity": "sha512-E+0eUn5xKfBAoU6mF3QbGZ939PZDw7RYI6AMTpRQtesRH2lZXjXaOqHzJ2nbHnDVmxNQM453sXFnMpd/uaLkKg==", - "requires": {} - }, - "sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "peer": true - } + "jest": "^26.0.1" } }, "@superset-ui/plugin-chart-pivot-table": { @@ -76936,12 +76897,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz", "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==" }, - "currencyformatter.js": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/currencyformatter.js/-/currencyformatter.js-1.0.5.tgz", - "integrity": "sha1-+MbZRdzmtn70j0dRaGEajZXJx14=", - "peer": true - }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", diff --git a/superset-frontend/plugins/plugin-chart-handlebars/package.json b/superset-frontend/plugins/plugin-chart-handlebars/package.json index 38884b36df3ad..d861f0397fc2f 100644 --- a/superset-frontend/plugins/plugin-chart-handlebars/package.json +++ b/superset-frontend/plugins/plugin-chart-handlebars/package.json @@ -26,8 +26,7 @@ "access": "public" }, "dependencies": { - "handlebars": "^4.7.7", - "just-handlebars-helpers": "^1.0.19" + "handlebars": "^4.7.7" }, "peerDependencies": { "@superset-ui/chart-controls": "*", diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/components/Handlebars/HandlebarsViewer.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/components/Handlebars/HandlebarsViewer.tsx index 77ebe65a8043f..1a77dbc5cd733 100644 --- a/superset-frontend/plugins/plugin-chart-handlebars/src/components/Handlebars/HandlebarsViewer.tsx +++ b/superset-frontend/plugins/plugin-chart-handlebars/src/components/Handlebars/HandlebarsViewer.tsx @@ -21,7 +21,11 @@ import Handlebars from 'handlebars'; import moment from 'moment'; import React, { useMemo, useState } from 'react'; import { isPlainObject } from 'lodash'; -import Helpers from 'just-handlebars-helpers'; + +import * as html from '../../helpers/html'; +import * as math from '../../helpers/math'; +import * as strings from '../../helpers/strings'; +import * as conditionals from '../../helpers/conditionals'; export interface HandlebarsViewerProps { templateSource: string; @@ -75,4 +79,9 @@ Handlebars.registerHelper('stringify', (obj: any, obj2: any) => { return isPlainObject(obj) ? JSON.stringify(obj) : String(obj); }); -Helpers.registerHelpers(Handlebars); +[math, html, strings, conditionals].forEach(helper => { + // register all the helper functions to Handlebars + Object.keys(helper).forEach(name => { + Handlebars.registerHelper(name, helper[name]); + }); +}); diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/helpers/conditionals.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/helpers/conditionals.ts new file mode 100644 index 0000000000000..23927adb242af --- /dev/null +++ b/superset-frontend/plugins/plugin-chart-handlebars/src/helpers/conditionals.ts @@ -0,0 +1,299 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { isArray, isObject } from './utils'; + +/** + * Determine whether or not two values are equal (===). + * + * @example + * {{eq '3' 3}} => false + * + * @param {any} value1 + * @param {any} value2 + * @returns {boolean} + */ +export function eq(value1: any, value2: any) { + return value1 === value2; +} + +/** + * Determine whether or not two values are not equal (!==). + * + * @example + * {{neq 4 3}} => true + * + * @param {any} value1 + * @param {any} value2 + * @returns {boolean} + */ +export function neq(value1: any, value2: any) { + return value1 !== value2; +} + +/** + * Check for less than condition (a < b). + * + * @example + * {{lt 2 3}} => true + * + * @param {any} value1 + * @param {any} value2 + * @returns {boolean} + */ +export function lt(value1: any, value2: any) { + return value1 < value2; +} + +/** + * Check for less than or equals condition (a <= b). + * + * @example + * {{lte 2 3}} => true + * + * @param {any} value1 + * @param {any} value2 + * @returns {boolean} + */ +export function lte(value1: any, value2: any) { + return value1 <= value2; +} + +/** + * Check for greater than condition (a > b). + * + * @example + * {{gt 2 3}} => false + * + * @param {any} value1 + * @param {any} value2 + * @returns {boolean} + */ +export function gt(value1: any, value2: any) { + return value1 > value2; +} + +/** + * Check for greater than or equals condition (a >= b). + * + * @example + * {{gte 3 3}} => true + * + * @param {any} value1 + * @param {any} value2 + * @returns {boolean} + */ +export function gte(value1: any, value2: any) { + return value1 >= value2; +} + +/** + * Helper to imitate the ternary '?:' conditional operator. + * + * @example + * {{ifx true 'Foo' 'Bar'}} => Foo + * {{ifx false 'Foo' 'Bar'}} => Foo + * + * @param {boolean} condition + * @param {any} value1 Value to return when the condition holds true. + * @param {any} value2 Value to return when the condition is false (Optional). + * @returns {any} + */ +export function ifx(condition: boolean, value1: any, value2: any) { + // Check if user has omitted the last parameter + // if that's the case, it would be the Handlebars options object + // which it sends always as the last parameter. + if ( + isObject(value2) && + value2.name === 'ifx' && + Object.prototype.hasOwnProperty.call(value2, 'hash') + ) { + // This means the user has skipped the last parameter, + // so we should return an empty string ('') in the else case instead. + return condition ? value1 : ''; + } + + return condition ? value1 : value2; +} + +/** + * Logical NOT of any expression. + * + * @example + * {{not true}} => false + * {{not false}} => true + * + * @param {any} expression + * @returns {boolean} + */ +export function not(expression: any) { + return !expression; +} + +/** + * Check if an array is empty. + * + * @example + * {{empty array}} => true | false + * + * @param {any} array + * @returns {boolean} + */ +export function empty(array: any) { + if (!isArray(array)) { + return true; + } + + return array.length === 0; +} + +/** + * Determine the length of an array. + * + * @example + * {{count array}} => false | array.length + * + * @param {any} array + * @returns {boolean | number} + */ +export function count(array: any) { + if (!isArray(array)) { + return false; + } + + return array.length; +} + +/** + * Returns the boolean AND of two or more parameters passed i.e + * it is true iff all the parameters are true. + * + * @example + * var value1 = value2 = true; + * {{and value1 value2}} => true + * + * var value1 = false, value2 = true; + * {{and value1 value2}} => false + * + * @param {any} params + * @returns {boolean} + */ +export function and(...params: any[]) { + // Ignore the object appended by handlebars. + if (isObject(params[params.length - 1])) { + params.pop(); + } + + for (let i = 0; i < params.length; i += 1) { + if (!params[i]) { + return false; + } + } + + return true; +} + +/** + * Returns the boolean OR of two or more parameters passed i.e + * it is true if any of the parameters is true. + * + * @example + * var value1 = true, value2 = false; + * {{or value1 value2}} => true + * + * var value = value2 = false; + * {{or value1 value2}} => false + * + * @param {any} params + * @returns {boolean} + */ +export function or(...params: any[]) { + // Ignore the object appended by handlebars. + if (isObject(params[params.length - 1])) { + params.pop(); + } + + for (let i = 0; i < params.length; i += 1) { + if (params[i]) { + return true; + } + } + + return false; +} + +/** + * Returns the first non-falsy value from the parameter list. + * Works quite similar to the SQL's COALESCE() function, but unlike this + * checks for the first non-false parameter. + * + * @example + * var fullName = 'Foo Bar', nickName = 'foob'; + * {{coalesce fullName nickName 'Unknown'}} => 'Foo Bar' + * + * var fullName = '', nickName = 'foob'; + * {{coalesce fullName nickName 'Unknown'}} => 'foob' + * + * @param {any} params + * @returns {any} + */ +export function coalesce(...params: any[]) { + // Ignore the object appended by handlebars. + if (isObject(params[params.length - 1])) { + params.pop(); + } + + for (let i = 0; i < params.length; i += 1) { + if (params[i]) { + return params[i]; + } + } + + return params.pop(); +} + +/** + * Returns boolean if the array contains the element strictly or non-strictly. + * + * @example + * var array = [1, 2, 3, 4]; + * var value1 = 2, value2 = 10, value3 = '3'; + * {{includes array value1}} => true + * {{includes array value2}} => false + * {{includes array value3}} => false + * {{includes array value3 false}} => false + * + * @param {array} array + * @param {any} value + * @param {boolean} strict + * @returns {boolean} + */ +export function includes(array: any[], value: any, strict = true) { + if (!isArray(array) || array.length === 0) { + return false; + } + + for (let i = 0; i < array.length; i += 1) { + if ((strict && array[i] === value) || (!strict && array[i] === value)) { + return true; + } + } + + return false; +} diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/helpers/html.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/helpers/html.ts new file mode 100644 index 0000000000000..2d5f9b17f645d --- /dev/null +++ b/superset-frontend/plugins/plugin-chart-handlebars/src/helpers/html.ts @@ -0,0 +1,147 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * A showIf helper for showing any html element. + * + * @example + * {{showIf true}} => '' + * + * @param {any} expression + * @returns {string} + */ +export function showIf(expression: any) { + return expression ? '' : 'hidden'; +} + +/** + * A hideIf helper for hiding any html element. + * + * @example + * {{hideIf true}} => 'hidden' + * + * @param {any} expression + * @returns {string} + */ +export function hideIf(expression: any) { + return expression ? 'hidden' : ''; +} + +/** + * A selectedIf helper for dropdown and radio boxes. + * + * @example + * {{selectedIf true}} => 'selected' + * + * @param {any} expression + * @returns {string} + */ +export function selectedIf(expression: any) { + return expression ? 'selected' : ''; +} + +/** + * A checkedIf helper for checkboxes. + * + * @example + * {{checkedIf true}} => 'checked' + * + * @param {any} expression + * @returns {string} + */ +export function checkedIf(expression: any) { + return expression ? 'checked' : ''; +} + +/** + * An options helper for generating