Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b723ecccc | ||
|
|
168938500f | ||
|
|
2bb7d1e4b2 | ||
|
|
c5c3b74728 |
+2
-1
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
+3
-1
@@ -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,
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user