Compare commits

...
10 changed files with 114 additions and 8 deletions
@@ -89,6 +89,7 @@ import { filterAttributeGroups } from './advancedFilterItems';
import filterMixin from 'shared/mixins/filterMixin';
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
import cloneObject from 'dashboard/helpers/clone';
export default {
components: {
@@ -351,7 +352,7 @@ export default {
if (this.$v.$invalid) return;
this.$store.dispatch(
'setConversationFilters',
JSON.parse(JSON.stringify(this.appliedFilters))
cloneObject(this.appliedFilters)
);
this.$emit('applyFilter', this.appliedFilters);
this.$track(CONVERSATION_EVENTS.APPLY_FILTER, {
@@ -1,3 +1,5 @@
import cloneObject from 'dashboard/helpers/clone';
const allElementsString = arr => {
return arr.every(elem => typeof elem === 'string');
};
@@ -27,7 +29,7 @@ const generatePayloadForObject = item => {
};
const generatePayload = data => {
const actions = JSON.parse(JSON.stringify(data));
const actions = cloneObject(data);
let payload = actions.map(item => {
if (Array.isArray(item.action_params)) {
item.action_params = formatArray(item.action_params);
@@ -5,6 +5,8 @@ import {
} from 'dashboard/routes/dashboard/settings/automation/operators';
import filterQueryGenerator from './filterQueryGenerator';
import actionQueryGenerator from './actionQueryGenerator';
import cloneObject from 'dashboard/helpers/clone';
const MESSAGE_CONDITION_VALUES = [
{
id: 'incoming',
@@ -255,7 +257,7 @@ export const getStandardAttributeInputType = (automationTypes, event, key) => {
};
export const generateAutomationPayload = payload => {
const automation = JSON.parse(JSON.stringify(payload));
const automation = cloneObject(payload);
automation.conditions[automation.conditions.length - 1].query_operator = null;
automation.conditions = filterQueryGenerator(automation.conditions).payload;
automation.actions = actionQueryGenerator(automation.actions);
+33
View File
@@ -0,0 +1,33 @@
/**
* Creates a deep clone of the provided object.
*
* This function attempts to use the `structuredClone` method if available.
* If `structuredClone` is not supported, it falls back to using
* `JSON.parse(JSON.stringify())`.
*
* @param {*} obj - The object to be cloned. Can be of any type.
* @returns {*} A deep clone of the input object.
*
* @throws {TypeError} If the object contains values that JSON cannot serialize
* (e.g., functions, undefined) when falling back to
* JSON methods.
*
* @example
* const original = { a: 1, b: { c: 2 } };
* const clone = cloneObject(original);
* console.log(clone); // { a: 1, b: { c: 2 } }
* console.log(original === clone); // false
*/
export function cloneObject(obj) {
if (typeof structuredClone === 'function') {
return structuredClone(obj);
}
// The JSON method doesn't handle all JavaScript types correctly and may cause unexpected behavior.
// At the moment structuredClone has good adoption across browsers https://caniuse.com/mdn-api_structuredclone
// and is the preferred method for cloning objects.
//
// We can consider implementing a more robust fallback method in the future if we find users are running into issues.
// Ref: https://github.com/lukeed/klona
return JSON.parse(JSON.stringify(obj));
}
@@ -1,3 +1,5 @@
import cloneObject from 'dashboard/helpers/clone';
const setArrayValues = item => {
return item.values[0]?.id ? item.values.map(val => val.id) : item.values;
};
@@ -21,7 +23,7 @@ const generateValues = item => {
const generatePayload = data => {
// Make a copy of data to avoid vue data reactivity issues
const filters = JSON.parse(JSON.stringify(data));
const filters = cloneObject(data);
let payload = filters.map(item => {
// If item key is content, we will split it using comma and return as array
// FIX ME: Make this generic option instead of using the key directly here
@@ -0,0 +1,61 @@
import { describe, it, expect, vi } from 'vitest';
import { cloneObject } from '../clone'; // Update this path
describe('cloneObject', () => {
it('should clone a simple object', () => {
const original = { a: 1, b: 'string', c: true };
const cloned = cloneObject(original);
expect(cloned).toEqual(original);
expect(cloned).not.toBe(original);
});
it('should clone a nested object', () => {
const original = { a: 1, b: { c: 2, d: { e: 3 } } };
const cloned = cloneObject(original);
expect(cloned).toEqual(original);
expect(cloned.b).not.toBe(original.b);
expect(cloned.b.d).not.toBe(original.b.d);
});
it('should clone an array', () => {
const original = [1, 2, [3, 4]];
const cloned = cloneObject(original);
expect(cloned).toEqual(original);
expect(cloned).not.toBe(original);
expect(cloned[2]).not.toBe(original[2]);
});
it('should clone a Date object', () => {
const original = new Date();
const cloned = cloneObject(original);
expect(cloned).toEqual(original);
expect(cloned).not.toBe(original);
});
it('should use structuredClone when available', () => {
const structuredCloneSpy = vi.fn(x => x);
global.structuredClone = structuredCloneSpy;
const obj = { a: 1 };
cloneObject(obj);
expect(structuredCloneSpy).toHaveBeenCalledWith(obj);
delete global.structuredClone;
});
it('should fall back to JSON methods when structuredClone is not available', () => {
const jsonParseSpy = vi.spyOn(JSON, 'parse');
const jsonStringifySpy = vi.spyOn(JSON, 'stringify');
const original = { a: 1 };
global.structuredClone = undefined;
cloneObject(original);
expect(jsonStringifySpy).toHaveBeenCalledWith(original);
expect(jsonParseSpy).toHaveBeenCalled();
jsonParseSpy.mockRestore();
jsonStringifySpy.mockRestore();
});
});
@@ -98,6 +98,8 @@ import { filterAttributeGroups } from '../contactFilterItems';
import filterMixin from 'shared/mixins/filterMixin';
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
import cloneObject from 'dashboard/helpers/clone';
export default {
components: {
FilterInputBox,
@@ -318,7 +320,7 @@ export default {
if (this.$v.$invalid) return;
this.$store.dispatch(
'contacts/setContactFilters',
JSON.parse(JSON.stringify(this.appliedFilters))
cloneObject(this.appliedFilters)
);
this.$emit('applyFilter', this.appliedFilters);
this.$track(CONTACTS_EVENTS.APPLY_FILTER, {
@@ -149,6 +149,7 @@ import automationMethodsMixin from 'dashboard/mixins/automations/methodsMixin';
import automationValidationsMixin from 'dashboard/mixins/automations/validationsMixin';
import filterInputBox from 'dashboard/components/widgets/FilterInput/Index.vue';
import automationActionInput from 'dashboard/components/widgets/AutomationActionInput.vue';
import cloneObject from 'dashboard/helpers/clone';
import {
AUTOMATION_RULE_EVENTS,
@@ -170,7 +171,7 @@ export default {
data() {
return {
automationTypes: JSON.parse(JSON.stringify(AUTOMATIONS)),
automationTypes: cloneObject(AUTOMATIONS),
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
automationRuleEvents: AUTOMATION_RULE_EVENTS,
automationMutated: false,
@@ -147,6 +147,7 @@ import automationMethodsMixin from 'dashboard/mixins/automations/methodsMixin';
import automationValidationsMixin from 'dashboard/mixins/automations/validationsMixin';
import filterInputBox from 'dashboard/components/widgets/FilterInput/Index.vue';
import automationActionInput from 'dashboard/components/widgets/AutomationActionInput.vue';
import cloneObject from 'dashboard/helpers/clone';
import {
AUTOMATION_RULE_EVENTS,
@@ -172,7 +173,7 @@ export default {
},
data() {
return {
automationTypes: JSON.parse(JSON.stringify(AUTOMATIONS)),
automationTypes: cloneObject(AUTOMATIONS),
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
automationRuleEvents: AUTOMATION_RULE_EVENTS,
automationMutated: false,
@@ -19,6 +19,7 @@ import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables';
import actionQueryGenerator from 'dashboard/helper/actionQueryGenerator.js';
import macrosMixin from 'dashboard/mixins/macrosMixin';
import cloneObject from 'dashboard/helpers/clone';
export default {
components: {
@@ -128,7 +129,7 @@ export default {
this.mode === 'EDIT'
? this.$t('MACROS.EDIT.API.SUCCESS_MESSAGE')
: this.$t('MACROS.ADD.API.SUCCESS_MESSAGE');
let serializedMacro = JSON.parse(JSON.stringify(macro));
let serializedMacro = cloneObject(macro);
serializedMacro.actions = actionQueryGenerator(serializedMacro.actions);
await this.$store.dispatch(action, serializedMacro);
useAlert(successMessage);