UNPKG

unomi-sdk-node

Version:

Node module to interact with unomi.

700 lines (699 loc) 40.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateSegmentCountObject = exports.isValidDateExpression = exports.reformatSegment = exports.formatProfileSegmentSubCondition = exports.formatProfilePropertySubCondition = exports.formatSubConditions = exports.validateRequiredProps = exports.validateSegmentObject = exports.validateSegmentsProperty = exports.validateMatchType = exports.validateComparisonOperatorUsage = exports.validatePropertyValue = exports.setExpectedValue = exports.checkExpectedValue = exports.formatPropertyName = exports.validatePropertyName = void 0; const helpers_1 = require("../utils/helpers"); /** * @function validatePropertyName * @param {Array<any>} parameterValuesObject */ function validatePropertyName(parameterValuesObject) { if (!parameterValuesObject.propertyName.startsWith("properties.")) { // check if "properties." is missing parameterValuesObject.propertyName = "properties." + parameterValuesObject.propertyName; // add "properties." in front of property name } return parameterValuesObject; // return validated object } exports.validatePropertyName = validatePropertyName; function formatPropertyName(subCondition) { if (subCondition.parameterValues.propertyName.startsWith("properties.")) { // check if "properties." is not missing subCondition.parameterValues.propertyName = subCondition.parameterValues.propertyName.replace("properties.", ""); // remove it from property name } return subCondition; // return formatted subcondition } exports.formatPropertyName = formatPropertyName; function checkExpectedValue(propertyName) { // note: CDP front-end sends all profile property values values as strings (except booleans), but some profile properties // like "age" only work with integers so they have to be changed to the right type to be used in unomi segments let noPrefixProperties = [ "age", "firstVisit", "lastVisit", "nbOfVisits", "previousVisit" //date ]; let words = propertyName.split("."); // split property name on dot // eg: properties.capture_properties.event.eventData.target.size.do__height // will become: [ 'properties', 'capture_properties', 'event', 'eventData', 'target', 'size', 'do__height' ] let property = words[words.length - 1]; // get property name let expectedType = "string"; // default expected type if (noPrefixProperties.includes(property)) { // check if profile property is a default property of an unomi profile switch (property) { // check which property it is // these properties have integer values case "age": case "nbOfVisits": expectedType = "number"; // expect integer/float value break; // these properties have date values case "firstVisit": case "lastVisit": case "previousVisit": expectedType = "string"; // expect string value break; } } else { // it's a profile property with a prefix let prefix = property.substring(0, 2); // get prefix in property name switch (prefix) { // check which prefix case "te": // text (saved as string in unomi) case "lt": // long text (saved as string in unomi) case "da": // date (saved as string in unomi) case "bo": // boolean (saved as string in unomi) expectedType = "string"; // expect string value break; case "lo": // long (saved as integer in unomi) case "do": // double (saved as float in unomi) expectedType = "number"; // expect integer/float value break; } } return expectedType; // return expected type for property } exports.checkExpectedValue = checkExpectedValue; function setExpectedValue(expectedType, propertyValue) { if (typeof (propertyValue) === "object") { // check if array, since array is treated as object by typeof // note: CDP front-end sends all profile property values values as strings (except booleans), but some profile properties // like "age" only work with integers so they have to be changed to the right type to be used in unomi segments // note for Number(): https://gomakethings.com/converting-strings-to-numbers-with-vanilla-javascript/ if (helpers_1.allSameType(propertyValue)) { // check if all values in array are of the same type switch (expectedType) { // change type based on expected value check case "string": for (let index = 0; index < propertyValue.length; index++) { // go through array propertyValue[index] = propertyValue[index].toString(); // convert all values to string } break; case "number": for (let index = 0; index < propertyValue.length; index++) { // go through array propertyValue[index] = Number(propertyValue[index]); // convert all values to integer/float } break; } } } else { // value is not an array switch (expectedType) { // change type based on expected value check case "string": propertyValue = propertyValue.toString(); // convert value to string break; case "number": propertyValue = Number(propertyValue); // convert value to integer/float break; } } return propertyValue; // return property value in expected type } exports.setExpectedValue = setExpectedValue; /** * @function validatePropertyValue * @param {PropertiesDefault} parameterValuesObject */ function validatePropertyValue(parameterValuesObject) { let isValidPropertyValueUsage = false; // default validation status let validatedParameterValuesObject = { propertyName: parameterValuesObject.propertyName, comparisonOperator: parameterValuesObject.comparisonOperator, }; let validationError = ""; let indicesDate = [4, 7]; // indices for the '-' character in yyyy-mm-dd (works with ISO format too, eg: 2020-03-20T14:28:23.382748) let indices; // array for storing indeces let expectedType = checkExpectedValue(parameterValuesObject.propertyName); // get expected type for profile property in current subcondition parameterValuesObject.propertyValue = setExpectedValue(expectedType, parameterValuesObject.propertyValue); // convert property value to expected type switch (typeof (parameterValuesObject.propertyValue)) { // check type of given propertyValue case "boolean": Object.assign(validatedParameterValuesObject, { propertyValue: parameterValuesObject.propertyValue.toString() }); // add string property value to validated object isValidPropertyValueUsage = true; // set subcondition as valid break; case "number": Object.assign(validatedParameterValuesObject, { propertyValueInteger: parameterValuesObject.propertyValue }); // add integer/float property value to validated object isValidPropertyValueUsage = true; // set subcondition as valid break; case "string": indices = helpers_1.findAllIndicesOfCharacter(parameterValuesObject.propertyValue, "-"); // find all instances of "-" in property value if (helpers_1.areArraysEqual(indicesDate, indices)) { // if indeces in arrays are equal, then property value is a date Object.assign(validatedParameterValuesObject, { propertyValueDate: parameterValuesObject.propertyValue }); // add date property value to validated object isValidPropertyValueUsage = true; // set subcondition as valid } else { if (parameterValuesObject.propertyValue.toString().startsWith("dateExpr::")) { // if value starts with 'dateExpr::', then property is a date expression let isValidDateExpr = isValidDateExpression(parameterValuesObject.propertyValue.replace("dateExpr::", "")); if (isValidDateExpr) { Object.assign(validatedParameterValuesObject, { propertyValueDateExpr: parameterValuesObject.propertyValue.replace("dateExpr::", "") }); // add date expression property value to validated object isValidPropertyValueUsage = true; // set subcondition as valid } else { isValidPropertyValueUsage = false; // set subcondition as invalid validationError = "Date expression " + parameterValuesObject.propertyValue.replace("dateExpr::", "") + " is invalid."; // set error message } } else { Object.assign(validatedParameterValuesObject, { propertyValue: parameterValuesObject.propertyValue }); // add string property value to validated object isValidPropertyValueUsage = true; // set subcondition as valid } } break; case "object": if (parameterValuesObject.propertyValue == undefined || parameterValuesObject.propertyValue == null) { // check if null or undefined, since null is treated as object by typeof isValidPropertyValueUsage = true; // set subcondition as valid } if (parameterValuesObject.propertyValue instanceof Array) { // check if array, since array is treated as object by typeof if (helpers_1.allSameType(parameterValuesObject.propertyValue)) { // check if all array values are of the same type switch (typeof (parameterValuesObject.propertyValue[0])) { // check type of first element in array (rest of elements should be same value!) case "number": Object.assign(validatedParameterValuesObject, { propertyValuesInteger: parameterValuesObject.propertyValue }); // add integer/float property values to validated object isValidPropertyValueUsage = true; // set subcondition as valid break; case "string": indices = helpers_1.findAllIndicesOfCharacter(parameterValuesObject.propertyValue[0], "-"); // find all instances of "-" in property value if (helpers_1.areArraysEqual(indicesDate, indices)) { // if indeces in arrays are equal, then property value is a date Object.assign(validatedParameterValuesObject, { propertyValuesDate: parameterValuesObject.propertyValue }); // add date property value to validated object isValidPropertyValueUsage = true; // set subcondition as valid } else { if (parameterValuesObject.propertyValue[0].toString().startsWith("dateExpr::")) { // if value starts with 'dateExpr::', then property is a date expression let values = []; for (let index = 0; index < parameterValuesObject.propertyValue.length; index++) { // go through property values values.push(parameterValuesObject.propertyValue[index].replace("dateExpr::", "")); // remove 'dateExpr::' prefix from property value and add to new list } Object.assign(validatedParameterValuesObject, { propertyValuesDateExpr: values }); // add date expression property value to validated object isValidPropertyValueUsage = true; // set subcondition as valid } else { Object.assign(validatedParameterValuesObject, { propertyValues: parameterValuesObject.propertyValue }); // add string property value to validated object isValidPropertyValueUsage = true; // set subcondition as valid } } break; } } } break; } return { isValidPropertyValueUsage: isValidPropertyValueUsage, validatedParameterValuesObject: validatedParameterValuesObject, validationError: validationError }; } exports.validatePropertyValue = validatePropertyValue; /** * @function validateComparisonOperatorUsage * @param {PropertiesPossible} parameterValuesObject */ function validateComparisonOperatorUsage(parameterValuesObject) { let isValidComparisonOperatorUsage = false; // default validation status const comparisonOperators = helpers_1.getProfilePropertyComparisonOperators(); // get an array of comparison operators const comparisonOperatorExists = comparisonOperators.includes(parameterValuesObject.comparisonOperator); // check if comparison operator in subcondition exists if (comparisonOperatorExists) { // check if comparison operator exists const parameterValuesObjectKeys = Object.keys(parameterValuesObject); // get an array with the names of the subcondition properties const propertyNameExists = parameterValuesObjectKeys.includes("propertyName"); // check if property name exists // check which property value keys exist const propertyValueExists = parameterValuesObjectKeys.includes("propertyValue"); const propertyValueIntegerExists = parameterValuesObjectKeys.includes("propertyValueInteger"); const propertyValueDateExists = parameterValuesObjectKeys.includes("propertyValueDate"); const propertyValueDateExprExists = parameterValuesObjectKeys.includes("propertyValueDateExpr"); const propertyValuesExists = parameterValuesObjectKeys.includes("propertyValues"); const propertyValuesIntegerExists = parameterValuesObjectKeys.includes("propertyValuesInteger"); const propertyValuesDateExists = parameterValuesObjectKeys.includes("propertyValuesDate"); const propertyValuesDateExprExists = parameterValuesObjectKeys.includes("propertyValuesDateExpr"); switch (parameterValuesObject.comparisonOperator) { // check which comparison operator // these comparisonOperators do not accept arrays as propertyValue case "equals": case "notEquals": case "greaterThan": case "greaterThanOrEqualTo": case "lessThan": case "lessThanOrEqualTo": if (propertyValueExists || propertyValueIntegerExists || propertyValueDateExists || propertyValueDateExprExists) { isValidComparisonOperatorUsage = true; // set comparison operator choice as valid } break; // these comparisonOperators only accept a single string as propertyValue case "contains": case "notContains": case "startsWith": case "endsWith": if (propertyValueExists) { isValidComparisonOperatorUsage = true; // set comparison operator choice as valid } break; // these comparisonOperators do not need a propertyValue case "exists": case "missing": if (parameterValuesObjectKeys.length === 2 && comparisonOperatorExists && propertyNameExists) { isValidComparisonOperatorUsage = true; // set comparison operator choice as valid } break; // these comparisonOperators do not accept single values as propertyValue case "between": case "in": case "notIn": case "all": case "hasSomeOf": case "hasNoneOf": if (propertyValuesExists || propertyValuesIntegerExists || propertyValuesDateExists || propertyValuesDateExprExists) { isValidComparisonOperatorUsage = true; // set comparison operator choice as valid } break; } } return isValidComparisonOperatorUsage; // return validation status of comparison operator } exports.validateComparisonOperatorUsage = validateComparisonOperatorUsage; /** * @function validateMatchType * @param {PropertiesDefault} subConditionObject */ function validateMatchType(subConditionObject) { let isValidMatchType = false; // default validation status; const matchTypes = helpers_1.getProfileSegmentMatchTypes(); // get an array of match types const matchTypeExists = matchTypes.includes(subConditionObject.matchType); // check if match type in subcondition exists if (matchTypeExists) { // check if comparison operator exists isValidMatchType = true; } return isValidMatchType; // return validation status of comparison operator } exports.validateMatchType = validateMatchType; /** * @function validateSegmentsProperty * @param {PropertiesDefault} subConditionObject */ function validateSegmentsProperty(subConditionObject) { let isValidSegments = true; // default validation status; let errors = new Array; // array to store validation errors const regex = /[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}/; // regex to validate the segment uuid against: https://stackoverflow.com/a/18516125 for (let index = 0; index < subConditionObject.segments.length; index++) { // validate every segment uuid const segment = subConditionObject.segments[index]; if (!regex.test(segment)) { // if the segment uuid is not a valid UUID4 isValidSegments = false; // segemnt uuid is not valid errors.push({ value: "Segment " + segment + " is not a valid segment uuid. Segment uuids have to comply with the UUID4 specifications." }); } } return [isValidSegments, errors]; // return validation status & errors } exports.validateSegmentsProperty = validateSegmentsProperty; /** * @function validateSegmentObject * @param {UsedProperties} params * @param {boolean} segmentUpdate */ function validateSegmentObject(params, segmentUpdate) { let subConditions = new Array; // array for storing subconditions let id; // segment (uu)id let errors = new Array; // array to store errors let areSubConditionsValid = true; // default validation status for (let index = 0; index < params.subConditions.length; index++) { // go through subconditions const subCondition = params.subConditions[index]; // get current subcondition // right now we only support 2 conditionTypes: profilePropertyCondition & profileSegmentCondition if (subCondition.conditionType == "profilePropertyCondition") { const validationPropertyNameResponse = validatePropertyName(Object.assign({}, subCondition)); // use deepcopy of subCondition for rest of validation const validationPropertyValueResponse = validatePropertyValue(validationPropertyNameResponse); // validate property value of subcondition if (validationPropertyValueResponse.isValidPropertyValueUsage) { // check if valid property value const isValidComparisonOperatorUsage = validateComparisonOperatorUsage(validationPropertyValueResponse.validatedParameterValuesObject); // validate comparison operator usage in subcondition if (!isValidComparisonOperatorUsage) { // used propertyValue(s) and comparisonOperator combination is not valid areSubConditionsValid = false; // set validation status as invalid if (validationPropertyValueResponse.validationError == "") { // if no specific error errors.push({ value: "comparisonOperator and propertyValue do not work together for property " + subCondition.propertyName }); } else { // if there is a specific error errors.push({ value: validationPropertyValueResponse.validationError }); } } } else { // used propertyValue(s) is not valid areSubConditionsValid = false; // set validation status as invalid if (validationPropertyValueResponse.validationError == "") { // if no specific error errors.push({ value: "propertyValue is not allowed for property " + subCondition.propertyName }); } else { // if there is a specific error errors.push({ value: validationPropertyValueResponse.validationError }); } } subConditions.push({ type: subCondition.conditionType, parameterValues: validationPropertyValueResponse.validatedParameterValuesObject }); } else if (subCondition.conditionType == "profileSegmentCondition") { let validatedSubCondition = {}; const isValidMatchType = validateMatchType(Object.assign({}, subCondition)); // use deepcopy of subCondition if (isValidMatchType) { // check if valid matchType validatedSubCondition.matchType = subCondition.matchType; } else { // used matchType is not valid areSubConditionsValid = false; errors.push({ value: "The matchType is not valid: " + subCondition.matchType }); } const [isValidSegments, segmentvalidationerrors] = validateSegmentsProperty(Object.assign({}, subCondition)); // use deepcopy of subCondition if (isValidSegments) { // check if valid matchType validatedSubCondition.segments = subCondition.segments; } else { // used matchType is not valid areSubConditionsValid = false; errors.push({ value: "The segments are not valid: " + subCondition.segments }); if (segmentvalidationerrors.length > 0) { // if there is at least 1 error for (let index in segmentvalidationerrors) { errors.push(segmentvalidationerrors[index]); // add validation errors to array } } } subConditions.push({ type: subCondition.conditionType, parameterValues: validatedSubCondition }); } else { // if there's a conditionType we don't support yet areSubConditionsValid = false; errors.push({ value: "The selected condition type is not supported: " + subCondition.conditionType }); } } ; // if not valid subconditions, then return the user's original subconditions so the mistake(s) can be seen and corrected if (!areSubConditionsValid) subConditions = params.subConditions; if (segmentUpdate) id = params.id; // check if segment update function else id = helpers_1.generateUuid(); // create segment function const segmentObject = { metadata: { id: id, name: params.name, description: params.description, scope: params.scope, tags: params.tags, systemTags: params.systemTags }, condition: { type: "booleanCondition", parameterValues: { operator: params.operator, subConditions: subConditions } } }; const validatedSegmentObject = { segmentObject: segmentObject, errors: errors, areSubConditionsValid: areSubConditionsValid }; return validatedSegmentObject; // return validated segment } exports.validateSegmentObject = validateSegmentObject; /** * @function validateRequiredProps * @param {string[]} required * @param {{[key: string]: any}} props */ function validateRequiredProps(required, props) { let missing = []; for (const prop of required) { if (!Object.keys(props).includes(prop) || props[prop] === null || props[prop] === undefined) { missing.push(prop); } } ; return { valid: !missing.length, missing, }; } exports.validateRequiredProps = validateRequiredProps; function formatSubConditions(subConditions) { let formattedSubConditions = new Array; // array to store formatted subconditions for (let index = 0; index < subConditions.length; index++) { // go through all subconditions let subCondition = subConditions[index]; // get current subcondition if (subCondition.type == "booleanCondition") { // check if current subcondition is a nested subcondition let _formattedSubConditions = formatSubConditions(subCondition.parameterValues.subConditions); // summon this function again to loop over nested subconditions formattedSubConditions.push({ "operator": subCondition.parameterValues.operator, "subConditions": _formattedSubConditions }); } else { // current subcondition is a normal subcondition // right now we only support 2 conditionTypes: profilePropertyCondition & profileSegmentCondition if (subCondition.type === "profilePropertyCondition") { subCondition = formatPropertyName(subCondition); // format subcondition formattedSubConditions.push(formatProfilePropertySubCondition(subCondition)); // add formatted subcondition to array } else if (subCondition.type === "profileSegmentCondition") { formattedSubConditions.push(formatProfileSegmentSubCondition(subCondition)); // add formatted subcondition to array } } } return formattedSubConditions; // return formatted subcondition } exports.formatSubConditions = formatSubConditions; function formatProfilePropertySubCondition(subCondition) { // note: CDP front-end expects all subcondition property values (except booleans) as type string, // because it sends them as string too and the validation happens in the unomi-sdk const parameterValuesObjectKeys = Object.keys(subCondition.parameterValues); // get an array with the names of the subcondition properties // check which property value keys exist const propertyValueExists = parameterValuesObjectKeys.includes("propertyValue"); const propertyValueIntegerExists = parameterValuesObjectKeys.includes("propertyValueInteger"); const propertyValueDateExists = parameterValuesObjectKeys.includes("propertyValueDate"); const propertyValueDateExprExists = parameterValuesObjectKeys.includes("propertyValueDateExpr"); const propertyValuesExists = parameterValuesObjectKeys.includes("propertyValues"); const propertyValuesIntegerExists = parameterValuesObjectKeys.includes("propertyValuesInteger"); const propertyValuesDateExists = parameterValuesObjectKeys.includes("propertyValuesDate"); const propertyValuesDateExprExists = parameterValuesObjectKeys.includes("propertyValuesDateExpr"); let formattedSubCondition = { propertyName: subCondition.parameterValues.propertyName, comparisonOperator: subCondition.parameterValues.comparisonOperator, conditionType: "profilePropertyCondition" }; if (propertyValueExists) { // check if string property value in subcondition if (subCondition.parameterValues.propertyValue === "true" || subCondition.parameterValues.propertyValue === "false") { // check if originally boolean subCondition.parameterValues.propertyValue = JSON.parse(subCondition.parameterValues.propertyValue.toLowerCase()); // convert to boolean } Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValue }); // add formatted property value } else if (propertyValueIntegerExists) { // check if integer/float property value in subcondition Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValueInteger.toString() }); // convert to string and add formatted property value } else if (propertyValueDateExists) { // check if date property value in subcondition Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValueDate.toString() }); // convert to string and add formatted property value } else if (propertyValueDateExprExists) { // check if date expression property value in subcondition Object.assign(formattedSubCondition, { propertyValue: "dateExpr::" + subCondition.parameterValues.propertyValueDateExpr.toString() }); // convert to string and add formatted property value } else if (propertyValuesExists) { // check if string property values in subcondition for (let index = 0; index < subCondition.parameterValues.propertyValues.length; index++) { // go through each element in property values if (subCondition.parameterValues.propertyValues[index] === "true" || subCondition.parameterValues.propertyValues[index] === "false") { // check if originally boolean subCondition.parameterValues.propertyValues[index] = JSON.parse(subCondition.parameterValues.propertyValues[index].toLowerCase()); // convert to boolean } else { // not originally boolean subCondition.parameterValues.propertyValues[index] = subCondition.parameterValues.propertyValues[index].toString(); // convert to string } } Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValues }); // add formatted property value } else if (propertyValuesIntegerExists) { // check if integer/float property values in subcondition for (let index = 0; index < subCondition.parameterValues.propertyValuesInteger.length; index++) { // go through each element in property values subCondition.parameterValues.propertyValuesInteger[index] = subCondition.parameterValues.propertyValuesInteger[index].toString(); // convert to string } Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValuesInteger }); // add formatted property value } else if (propertyValuesDateExists) { // check if date property values in subcondition for (let index = 0; index < subCondition.parameterValues.propertyValuesDate.length; index++) { // go through each element in property values subCondition.parameterValues.propertyValuesDate[index] = subCondition.parameterValues.propertyValuesDate[index].toString(); // convert to string } Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValuesDate }); // add formatted property value } else if (propertyValuesDateExprExists) { // check if date expression property values in subcondition for (let index = 0; index < subCondition.parameterValues.propertyValuesDateExpr.length; index++) { // go through each element in property values subCondition.parameterValues.propertyValuesDateExpr[index] = "dateExpr::" + subCondition.parameterValues.propertyValuesDateExpr[index].toString(); // convert to string } Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValuesDateExpr }); // add formatted property value | add dateExpr prefix so front-end knows it's a date expression and not a timestamp } else { Object.assign(formattedSubCondition, { propertyValue: null }); // add formatted property value } return formattedSubCondition; // return formatted subcondition } exports.formatProfilePropertySubCondition = formatProfilePropertySubCondition; function formatProfileSegmentSubCondition(subCondition) { let formattedSubCondition = { matchType: subCondition.parameterValues.matchType, segments: subCondition.parameterValues.segments, conditionType: "profileSegmentCondition" }; return formattedSubCondition; // return formatted subcondition } exports.formatProfileSegmentSubCondition = formatProfileSegmentSubCondition; function reformatSegment(sdkObject, segment) { // Sometimes segments exist where the condition is different from what is by default expected // example of a normal segment condition: // "condition": { // "type": "booleanCondition", // "parameterValues": { // "operator": "or", // "subConditions": [ // { // "parameterValues": { // "matchType": "in", // "segments": ["9a3066ac-9259-42c9-b289-89d68fe12827"] // }, // "type": "profileSegmentCondition" // }, // { // "parameterValues": { // "propertyName": "properties.capture_properties.te__domain", // "comparisonOperator": "equals", // "propertyValue": "example.com" // }, // "type": "profilePropertyCondition" // } // ] // } // } // example of the different segment condition: // "condition": { // "parameterValues": { // "propertyName": "properties.capture_properties.te__domain", // "comparisonOperator": "equals", // "propertyValue": "example.com" // }, // "type": "profilePropertyCondition" // } // This happens when the condition had a profileSegmentCondition on a segment (let's say segment-001), but then that segment (segment-001) got delete // When that segment (segment-001) gets deleted, the profileSegmentCondition on that segment also gets deleted // then if after that, the original segment only has 1 subCondition left, then you get the 2nd format (even though that's not how you're supposed to make segment in Unomi, so seems like a Unomi bug somewhere, maybe) // so, just because it can happen, we need to be able to also show these segments in the frontend instead of getting an error during the formatting of the segment for the frontend // so we first check if either propertyName or matchType exists in the condition object, to see if it's a normal condition, or the specific case described above // if it's the 2nd case, then we build our own object for the condition, the way it is supposed to be, and then our object gets validated and sent to the frontend if ("propertyName" in segment.condition.parameterValues) { segment.condition = { "type": "booleanCondition", "parameterValues": { "operator": "and", "subConditions": [ { "parameterValues": { "propertyName": segment.condition.parameterValues.propertyName, "propertyValue": segment.condition.parameterValues.propertyValue, "comparisonOperator": segment.condition.parameterValues.comparisonOperator }, "type": segment.condition.type } ] } }; } if ("matchType" in segment.condition.parameterValues) { segment.condition = { "type": "booleanCondition", "parameterValues": { "operator": "and", "subConditions": [ { "parameterValues": { "matchType": segment.condition.parameterValues.matchType, "segments": segment.condition.parameterValues.segments, }, "type": segment.condition.type } ] } }; } let formattedSubConditions = formatSubConditions(segment.condition.parameterValues.subConditions); // format subconditions in segment const segmentObject = { id: segment.metadata.id, name: segment.metadata.name, description: segment.metadata.description, scope: segment.metadata.scope, tags: segment.metadata.tags, systemTags: segment.metadata.systemTags, operator: segment.condition.parameterValues.operator, subConditions: formattedSubConditions }; sdkObject.responseData = segmentObject; // set object as response data return sdkObject; // return unomi-sdk response object } exports.reformatSegment = reformatSegment; /** * @function isValidDateExpression * @param {string} dateExpr */ function isValidDateExpression(dateExpr) { // examples of what date expressions this regex allows // * now+1y // * now-22M // * now // * now+1y/y // * now+2d+1w // * now/d // * now+1y+2M+3w+4d+5H+6m+7s // * nowww // * now+1y // * now/d // // examples of what the regex does NOT allow // * now+7d/d+89w/M/H/s // * now+7lekkerkoek // * nowww // * no const regex = /^now(|\/[yMwdhHms]|([+-][0-9]+[yMwdhHms](\/[yMwdhHms])?)+)$/; // regex to check valid date expressions const found = dateExpr.match(regex); // check if the dateExpr meets the regex conditions (null if it doesn't, a list with the groups if it does) var isValid = false; // default value if (found != null) { isValid = true; // if the dateExpr meets the regex conditions, then it is a valid date expression } return isValid; // return whether the dateExpr is valid or not } exports.isValidDateExpression = isValidDateExpression; /** * @function validateSegmentCountObject * @param {SegmentProfileCountProperties} params */ function validateSegmentCountObject(params) { let segments = new Array; // array for storing validated segments let validatedOperator; // to store the validated operator in the end let errors = new Array; // array to store validation errors let isValid = true; // valid by default const operator = params.operator; // get the operator from the object to validate const regex = /[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}/; // regex to validate the segment uuid against: https://stackoverflow.com/a/18516125 for (let index = 0; index < params.segments.length; index++) { // validate every segment uuid const segment = params.segments[index]; if (regex.test(segment)) { // if the segment uuid is a valid UUID4 segments.push(segment); } else { // if the segment uuid is not a valid UUID4 isValid = false; errors.push({ value: "Segment " + segment + " is not a valid segment uuid. Segment uuids have to comply with the UUID4 specifications." }); } } if (operator.toUpperCase() === "OR" || operator.toUpperCase() === "AND") { // validate the operator, options we support are 'OR' and 'AND' validatedOperator = operator.toUpperCase(); } else { isValid = false; errors.push({ value: "Operator " + operator + " is not a valid operator. Valid operators are 'OR' & 'AND'." }); } const validatedSegmentCountObject = { segments: segments, operator: validatedOperator, isValid: isValid, errors: errors }; return validatedSegmentCountObject; // return validated object } exports.validateSegmentCountObject = validateSegmentCountObject;