From 592a2dbe1e723b5f15927247224430e57db54bf6 Mon Sep 17 00:00:00 2001 From: Stephen Stone Date: Fri, 21 Aug 2020 11:17:58 -0500 Subject: [PATCH 1/2] fix(cardutilityfunctions): don't stringify react symbols --- src/utils/cardUtilityFunctions.js | 109 +++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 25 deletions(-) diff --git a/src/utils/cardUtilityFunctions.js b/src/utils/cardUtilityFunctions.js index d1620aa835..19e7aad0f7 100644 --- a/src/utils/cardUtilityFunctions.js +++ b/src/utils/cardUtilityFunctions.js @@ -1,5 +1,7 @@ +import React from 'react'; import warning from 'warning'; import isNil from 'lodash/isNil'; +import mapValues from 'lodash/mapValues'; import { CARD_SIZES } from '../constants/LayoutConstants'; @@ -183,59 +185,116 @@ export const formatNumberWithPrecision = (value, precision = 0, locale = 'en') = }; /** - * Find variables in a string that are identified by surrounding curly braces - * @param {string} value - A string with variables, i.e. `{manufacturer} acceleration over the last {sensor} hours` - * @return {array} variables - an array of variables, i.e. ['manufacturer', 'sensor'] + * Reusable function to check if a string contains variables identified by surrounding curly braces i.e. {deviceid} + * @param {string} value A string with variables, i.e. `{manufacturer} acceleration over the last {sensor} hours` + * @returns {Array} an array of variables, i.e. ['manufacturer', 'sensor'] */ export const getVariables = value => { - // an array of instances of a substring surrounded by curly braces - const variables = value && typeof value === 'string' ? value.match(/{[a-zA-Z0-9_-]+}/g) : null; - // if there are variables found, trim the curly braces from each and return - return variables ? variables.map(variable => variable.replace(/[{}]/g, '')) : null; + let variables = value && typeof value === 'string' ? value.match(/{[a-zA-Z0-9_-]+}/g) : null; + variables = variables?.map(variable => variable.replace(/[{}]/g, '')); + return variables; }; /** * Replace variables from the list of variables that are found on the target with their corresponding value - * @param {array} variables - Array of variables to be replaced - * @param {object} cardVariables - Object with variable properties and replacement values, i.e. { manufacturer: 'Rentech', sensor: 3 } - * @param {string} target - The raw string to insert variable values into - * @return {array} updatedTarget - the new string with the updated variable values + * @param {Array} variables an array of variable strings + * @param {object} cardVariables an object with properties such that the key is a variable name and the value is the value to replace it with, i.e. { manufacturer: 'Rentech', sensor: 3 } + * @param {Object || String} target a card or string to replace variables on + * @returns {Object} a parsed object with all variables replaced with their corresponding values found on the values object */ export const replaceVariables = (variables, cardVariables, target) => { - let updatedTarget = JSON.stringify(target); - // Need to create a copy of cardVariables with all lower-case keys const insensitiveCardVariables = Object.keys(cardVariables).reduce((acc, variable) => { acc[variable.toLowerCase()] = cardVariables[variable]; return acc; }, {}); + // if it's an array then recursively place the variables in each element + if (Array.isArray(target)) { + return target.map(element => replaceVariables(variables, cardVariables, element)); + } + // variables.forEach(variable => { + // const insensitiveVariable = variable.toLowerCase(); + // const variableRegex = new RegExp(`{${variable}}`, 'g'); + // // Need to update the target with all lower-case variables for case-insesitivity + // updatedTarget = updatedTarget.replace(variableRegex, `{${insensitiveVariable}}`); + + // if (typeof insensitiveCardVariables[insensitiveVariable] === 'function') { + // const callback = insensitiveCardVariables[insensitiveVariable]; + // updatedTarget = callback(variable, target); + // } else { + // const insensitiveVariableRegex = new RegExp(`{${insensitiveVariable}}`, 'g'); + // updatedTarget = updatedTarget.replace( + // insensitiveVariableRegex, + // insensitiveCardVariables[insensitiveVariable] + // ); + // } + // if it's an object, then recursively replace each value unless it's a react element + if (typeof target === 'object') { + // if it's a react element, leave it alone + return React.isValidElement(target) + ? target + : mapValues(target, property => + replaceVariables(variables, insensitiveCardVariables, property) + ); + } + + // we can only replace on string targets at this point + if (typeof target !== 'string') { + return target; + } + let updatedTarget = target; variables.forEach(variable => { const insensitiveVariable = variable.toLowerCase(); const variableRegex = new RegExp(`{${variable}}`, 'g'); + const insensitiveVariableRegex = new RegExp(`{${insensitiveVariable}}`, 'g'); // Need to update the target with all lower-case variables for case-insesitivity updatedTarget = updatedTarget.replace(variableRegex, `{${insensitiveVariable}}`); - - if (typeof insensitiveCardVariables[insensitiveVariable] === 'function') { + const exactMatch = new RegExp(`^{${insensitiveVariable}}$`, 'g'); + // if we're an exact match on number then set to number (to support numeric thresholds) + if ( + exactMatch.test(target) && + typeof insensitiveCardVariables[insensitiveVariable] === 'number' + ) { + updatedTarget = insensitiveCardVariables[insensitiveVariable]; + } else if (typeof insensitiveCardVariables[insensitiveVariable] === 'function') { const callback = insensitiveCardVariables[insensitiveVariable]; updatedTarget = callback(variable, target); } else { - const insensitiveVariableRegex = new RegExp(`{${insensitiveVariable}}`, 'g'); - updatedTarget = updatedTarget.replace( - insensitiveVariableRegex, - insensitiveCardVariables[insensitiveVariable] - ); + // if the target is still a string then continue + updatedTarget = + typeof updatedTarget === 'string' && !isNil(insensitiveCardVariables[insensitiveVariable]) + ? updatedTarget.replace( + insensitiveVariableRegex, + insensitiveCardVariables[insensitiveVariable] + ) // otherwise do string replace + : updatedTarget; } }); - return JSON.parse(updatedTarget); + return updatedTarget; }; /** - * - * @param {object} card - * @returns {array} an array of unique variable values + * @param {Object} card + * @returns {Array} an array of unique variable values */ -export const getCardVariables = card => [...new Set(getVariables(JSON.stringify(card)))]; +export const getCardVariables = card => { + // for each + const propertyVariables = Object.values(card).reduce((acc, property) => { + if (typeof property === 'object' && !React.isValidElement(property)) { + // recursively search any objects for additional string properties + acc.push(...getCardVariables(property)); + } else if (typeof property === 'string') { + // if it's a string, look for variables + const detectedVariables = getVariables(property); + if (detectedVariables) { + acc.push(...detectedVariables); + } + } + return acc; + }, []); + return [...new Set(propertyVariables)]; +}; /** * Replace variables from the list of variables that are found on the target with their corresponding value From f2655ef4dcabfd235cfa2a41dde738e8b51bcb48 Mon Sep 17 00:00:00 2001 From: Stephen Stone Date: Fri, 21 Aug 2020 11:25:24 -0500 Subject: [PATCH 2/2] fix(cardutilityfunctions): export card variable util functions --- .../__snapshots__/Welcome.story.storyshot | 36 +++++++++++++++++++ src/index.js | 3 ++ .../__snapshots__/publicAPI.test.js.snap | 3 ++ src/utils/cardUtilityFunctions.js | 15 -------- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/.storybook/__snapshots__/Welcome.story.storyshot b/.storybook/__snapshots__/Welcome.story.storyshot index 43d64135c3..ebb471fb55 100644 --- a/.storybook/__snapshots__/Welcome.story.storyshot +++ b/.storybook/__snapshots__/Welcome.story.storyshot @@ -3364,6 +3364,42 @@ exports[`Storybook Snapshot tests and console checks Storyshots 0/Getting Starte formatNumberWithPrecision +
+
+
+ getVariables +
+
+
+
+
+ getCardVariables +
+
+
+
+
+ replaceVariables +
+
diff --git a/src/index.js b/src/index.js index a6ce462c3f..63b975f28f 100755 --- a/src/index.js +++ b/src/index.js @@ -271,6 +271,9 @@ export { determineMaxValueCardAttributeCount, compareGrains, formatNumberWithPrecision, + getVariables, + getCardVariables, + replaceVariables, } from './utils/cardUtilityFunctions'; export { csvDownloadHandler } from './utils/componentUtilityFunctions'; diff --git a/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap b/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap index 24ae2cc65a..b537a9f8c3 100644 --- a/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap +++ b/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap @@ -19809,6 +19809,9 @@ Map { "determineMaxValueCardAttributeCount" => Object {}, "compareGrains" => Object {}, "formatNumberWithPrecision" => Object {}, + "getVariables" => Object {}, + "getCardVariables" => Object {}, + "replaceVariables" => Object {}, "csvDownloadHandler" => Object {}, "tableActions" => Object { "TABLE_ACTION_APPLY": "TABLE_ACTION_APPLY", diff --git a/src/utils/cardUtilityFunctions.js b/src/utils/cardUtilityFunctions.js index 19e7aad0f7..c989fd9859 100644 --- a/src/utils/cardUtilityFunctions.js +++ b/src/utils/cardUtilityFunctions.js @@ -213,22 +213,7 @@ export const replaceVariables = (variables, cardVariables, target) => { if (Array.isArray(target)) { return target.map(element => replaceVariables(variables, cardVariables, element)); } - // variables.forEach(variable => { - // const insensitiveVariable = variable.toLowerCase(); - // const variableRegex = new RegExp(`{${variable}}`, 'g'); - // // Need to update the target with all lower-case variables for case-insesitivity - // updatedTarget = updatedTarget.replace(variableRegex, `{${insensitiveVariable}}`); - // if (typeof insensitiveCardVariables[insensitiveVariable] === 'function') { - // const callback = insensitiveCardVariables[insensitiveVariable]; - // updatedTarget = callback(variable, target); - // } else { - // const insensitiveVariableRegex = new RegExp(`{${insensitiveVariable}}`, 'g'); - // updatedTarget = updatedTarget.replace( - // insensitiveVariableRegex, - // insensitiveCardVariables[insensitiveVariable] - // ); - // } // if it's an object, then recursively replace each value unless it's a react element if (typeof target === 'object') { // if it's a react element, leave it alone