diff --git a/src/utils/data/record.test.ts b/src/utils/data/record.test.ts deleted file mode 100644 index 3373e6358..000000000 --- a/src/utils/data/record.test.ts +++ /dev/null @@ -1,409 +0,0 @@ -import { - FallbackConfig, FallbackConfigTransformProps, filterSelectedRecords, getValue, getValueFallback, hasValue, NOT_FOUND, Record, STRATEGY_DO_NOTHING, -} from './record'; - -describe('utils/record.ts', () => { - beforeEach(() => { - // Silent console log (used by logger.warn) - // @ts-ignore - global.console = { warn: jest.fn(), log: jest.fn() }; - }); - - describe('hasValue', () => { - test('should return false when the given value is not defined', async () => { - const item: Record = { - '': '', - emptyObject: {}, - emptyArray: [], - null: null, - notFound: NOT_FOUND, - htmlEmptyParagraph: '

', - }; - - // @ts-expect-error - expect(hasValue(item)).toEqual(false); - expect(hasValue(item, null)).toEqual(false); - expect(hasValue(item, undefined)).toEqual(false); - expect(hasValue(item, '')).toEqual(false); - expect(hasValue(item, 'emptyObject')).toEqual(false); - expect(hasValue(item, 'emptyArray')).toEqual(false); - expect(hasValue(item, 'null')).toEqual(false); - expect(hasValue(item, 'notFound')).toEqual(false); - expect(hasValue(item, 'htmlEmptyParagraph')).toEqual(false); - }); - - // Edge cases, not handled yet - test('should return true when the given value is defined', async () => { - const item: Record = { - string: 'string', - string2: '0', - string3: '-1', - object: { a: 0 }, - object2: { 0: 0 }, - object3: { '': 0 }, - array: [0], - array1: [1], - array2: [-1], - htmlParagraph: '

a

', - }; - - expect(hasValue(item, 'string')).toEqual(true); - expect(hasValue(item, 'string2')).toEqual(true); - expect(hasValue(item, 'string3')).toEqual(true); - expect(hasValue(item, 'object')).toEqual(true); - expect(hasValue(item, 'object2')).toEqual(true); - expect(hasValue(item, 'object3')).toEqual(true); - expect(hasValue(item, 'array')).toEqual(true); - expect(hasValue(item, 'array1')).toEqual(true); - expect(hasValue(item, 'array2')).toEqual(true); - expect(hasValue(item, 'htmlParagraph')).toEqual(true); - }); - }); - - describe('getValue', () => { - test('should return null when the given key is not defined without logging the error (with STRATEGY_DO_NOTHING)', async () => { - const item: Record = { - false: false, - true: true, - empty: '', - 0: 0, - }; - - expect(getValue(item, 'notThere', null, STRATEGY_DO_NOTHING)).toEqual(null); - expect(getValue(item, 'notThereEither', null, STRATEGY_DO_NOTHING)).toEqual(null); - expect(getValue(item, '1', null, STRATEGY_DO_NOTHING)).toEqual(null); - expect(getValue(item, '_', null, STRATEGY_DO_NOTHING)).toEqual(null); - expect(getValue(item, '', null, STRATEGY_DO_NOTHING)).toEqual(null); - expect(getValue(item, null, null, STRATEGY_DO_NOTHING)).toEqual(null); - // @ts-expect-error - expect(getValue(item, 50.5, null, STRATEGY_DO_NOTHING)).toEqual(null); - // @ts-expect-error - expect(getValue(item, {}, null, STRATEGY_DO_NOTHING)).toEqual(null); - // @ts-expect-error - expect(getValue(item, { 0: 0 }, null, STRATEGY_DO_NOTHING)).toEqual(null); - // @ts-expect-error - expect(getValue(item, 0, null, STRATEGY_DO_NOTHING)).toEqual(null); - expect(console.log).not.toBeCalled(); - }); - - test('should return null when the given key is not defined and log the error (without STRATEGY_DO_NOTHING)', async () => { - const item: Record = { - false: false, - true: true, - empty: '', - 0: 0, - }; - - expect(getValue(item, 'notThere')).toEqual(null); - expect(console.log).toBeCalled(); - expect(getValue(item, 'notThereEither')).toEqual(null); - expect(console.log).toBeCalled(); - expect(getValue(item, '1')).toEqual(null); - expect(console.log).toBeCalled(); - expect(getValue(item, '_')).toEqual(null); - expect(console.log).toBeCalled(); - expect(getValue(item, '')).toEqual(null); - expect(console.log).toBeCalled(); - expect(getValue(item, null)).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValue(item, 50.5)).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValue(item, {})).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValue(item, { 0: 0 })).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValue(item, 0)).toEqual(null); - expect(console.log).toBeCalled(); - }); - - test('should return the expected value when the given value is defined (with multi depths)', async () => { - const item: Record = { - string: 'string', - string2: '0', - string3: '-1', - object: { a: 0 }, - object2: { 0: 0 }, - object3: { '': 0 }, - array: [0], - array1: [1], - array2: [-1], - htmlParagraph: '

a

', - htmlParagraphEmpty: '

', - objectDeep1: { 'a': { a: 2 } }, - objectDeep2: { 1: { a: [0] } }, - objectDeep3: { '-99': { a: [{ b: { c: 5 } }] } }, - }; - - expect(getValue(item, 'string')).toEqual('string'); - expect(getValue(item, 'string2')).toEqual('0'); - expect(getValue(item, 'string3')).toEqual('-1'); - expect(getValue(item, 'object')).toMatchObject({ a: 0 }); - expect(getValue(item, 'object2')).toMatchObject({ 0: 0 }); - expect(getValue(item, 'object3')).toMatchObject({ '': 0 }); - expect(getValue(item, 'array')).toEqual([0]); - expect(getValue(item, 'array1')).toEqual([1]); - expect(getValue(item, 'array2')).toEqual([-1]); - expect(getValue(item, 'htmlParagraph')).toEqual('

a

'); - expect(getValue(item, 'htmlParagraphEmpty')).toEqual('

'); - expect(getValue(item, 'objectDeep1.a.a')).toEqual(2); - expect(getValue(item, 'objectDeep2.1.a')).toEqual([0]); - expect(getValue(item, 'objectDeep2.1.a[0]')).toEqual(0); - expect(getValue(item, 'objectDeep3.-99.a[0].b')).toMatchObject({ c: 5 }); - expect(getValue(item, 'objectDeep3.-99.a[0].b["c"]')).toEqual(5); - expect(console.log).not.toBeCalled(); - }); - }); - - describe('getValueFallback', () => { - test('should return the expected value when given a proper "fallbacks" to fallback from (1 fallbacks)', async () => { - const fallbacks: Array = [ - { - record: { - a: 1, // XXX Should be considered as valid value and stop the loop - }, - key: 'a', - }, - ]; - - expect(getValueFallback(fallbacks)).toEqual(1); - expect(console.log).not.toBeCalled(); - }); - - test('should return the expected value when given a proper "fallbacks" to fallback from (2 fallbacks)', async () => { - const fallbacks: Array = [ - { - record: { - a: null, // Should not be considered as a valid value (go to next next fallback) - }, - key: 'a', - }, - { - record: { - a: 1, // XXX Should be considered as valid value and stop the loop - }, - key: 'a', - }, - ]; - - expect(getValueFallback(fallbacks)).toEqual(1); - expect(console.log).not.toBeCalled(); - }); - - test('should return the expected value when given a proper "fallbacks" to fallback from (3 fallbacks)', async () => { - const fallbacks: Array = [ - { - record: { - a: null, // Should not be considered as a valid value (go to next next fallback) - }, - key: 'a', - }, - { - record: { - a: undefined, // Should not be considered as a valid value (go to next next fallback) - }, - key: 'a', - }, - { - record: { - a: 1, // XXX Should be considered as valid value and stop the loop - }, - key: 'a', - }, - ]; - - expect(getValueFallback(fallbacks)).toEqual(1); - expect(console.log).not.toBeCalled(); - }); - - test('should return the expected value when given a proper "fallbacks" to fallback from (4 fallbacks)', async () => { - const fallbacks: Array = [ - { - record: { - a: null, // Should not be considered as a valid value (go to next next fallback) - }, - key: 'a', - }, - { - record: { - a: undefined, // Should not be considered as a valid value (go to next next fallback) - }, - key: 'a', - }, - { - record: { - a: 1, // XXX Should be considered as valid value and stop the loop - }, - key: 'a', - }, - { - record: { - a: 2, // Shouldn't be tested against - }, - key: 'a', - }, - ]; - - expect(getValueFallback(fallbacks)).toEqual(1); - expect(console.log).not.toBeCalled(); - }); - - test('should return the expected value when given a proper "fallbacks" to fallback from (4 fallbacks bis)', async () => { - const fallbacks: Array = [ - { - record: { - a: '', // Should not be considered as a valid value (go to next next fallback) - }, - key: 'a', - }, - { - record: { - a: 0, // Should not be considered as a valid value (go to next next fallback) - }, - key: 'a', - }, - { - record: { - a: 1, // XXX Should be considered as valid value and stop the loop - }, - key: 'a', - }, - { - record: { - a: 2, // Shouldn't be tested against - }, - key: 'a', - }, - ]; - - expect(getValueFallback(fallbacks)).toEqual(1); - expect(console.log).not.toBeCalled(); - }); - - test('should return the default value when given an improper "fallbacks" to fallback from', async () => { - expect(getValueFallback(null)).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback(1)).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback(0)).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback('')).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback({})).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback({ a: 5 })).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback(-42.00)).toEqual(null); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback(NaN)).toEqual(null); - expect(console.log).toBeCalled(); - }); - - test('should return the expected value when given an improper "fallbacks" to fallback from', async () => { - expect(getValueFallback(null, 55)).toEqual(55); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback(1, 'fallbackValue')).toEqual('fallbackValue'); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback(0, true)).toEqual(true); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback('', false)).toEqual(false); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback({}, {})).toMatchObject({}); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback({ a: 5 }, { a: 5 })).toMatchObject({ a: 5 }); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback(-42.00, [6])).toEqual([6]); - expect(console.log).toBeCalled(); - // @ts-expect-error - expect(getValueFallback(NaN, NaN)).toEqual(NaN); - expect(console.log).toBeCalled(); - }); - - test('should log a warning when proper "fallbacks" to fallback from are provided but none matches', async () => { - const fallbacks: Array = [ - { - record: { - a: 50, // Should not be considered as a valid value (go to next next fallback) - }, - key: 'b', - }, - { - record: { - a: 51, // Should not be considered as a valid value (go to next next fallback), but aren't any left - }, - key: 'c', - }, - ]; - - expect(getValueFallback(fallbacks, 55)).toEqual(55); - expect(console.log).toBeCalled(); - }); - - test('should apply "transform" to the matching record and have expected props', async () => { - const fallbacks: Array = [ - { - record: { - a: 1, // XXX Should be considered as valid value and stop the loop - }, - key: 'a', - transform(value: string | object | any, props: FallbackConfigTransformProps): any { - return { - value: value + 'x', - ...props, - }; - }, - }, - ]; - - expect(getValueFallback(fallbacks, 55).value).toEqual('1x'); - expect(getValueFallback(fallbacks, 55).record).toMatchObject({ a: 1 }); - expect(getValueFallback(fallbacks, 55).fallbacks).toMatchObject(fallbacks); - expect(getValueFallback(fallbacks, 55).defaultValue).toEqual(55); - expect(getValueFallback(fallbacks, 55).key).toEqual('a'); - expect(console.log).not.toBeCalled(); - }); - }); - - describe('filterSelectedRecords', () => { - const allRecords: Record[] = [ - { id: '1' }, - { id: '2' }, - { id: '3' }, - ]; - - describe('should filter an array of records', () => { - test('when there is no selected records', async () => { - expect(filterSelectedRecords(allRecords, [])).toEqual(allRecords); - }); - - test('when there is one selected records', async () => { - expect(filterSelectedRecords(allRecords, [{ id: '1' }])).toEqual([{ id: '2' }, { id: '3' }]); - }); - - test('when there is many selected records', async () => { - expect(filterSelectedRecords(allRecords, [{ id: '1' }, { id: '2' }])).toEqual([{ id: '3' }]); - }); - - test('when all records are selected', async () => { - expect(filterSelectedRecords(allRecords, [{ id: '1' }, { id: '2' }, { id: '3' }])).toEqual([]); - }); - }); - }); - -}); diff --git a/src/utils/data/record.ts b/src/utils/data/record.ts deleted file mode 100644 index 55870f02d..000000000 --- a/src/utils/data/record.ts +++ /dev/null @@ -1,351 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { createLogger } from '@unly/utils-simple-logger'; -import cloneDeep from 'lodash.clonedeep'; -import find from 'lodash.find'; -import get from 'lodash.get'; -import isArray from 'lodash.isarray'; -import isEmpty from 'lodash.isempty'; -import map from 'lodash.map'; -import remove from 'lodash.remove'; -import xOrBy from 'lodash.xorby'; - -import { GraphCMSSystemFields } from '../../types/data/GraphCMSSystemFields'; -import { SerializedRecord } from '../../types/SerializedRecord'; - -const logger = createLogger({ - label: 'utils/data/record', -}); - -/** - * Represents a generic record. (from GraphCMS DB) - * - * Base type, meant to be extended to create specialized types. - */ -export type Record = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; // Allow any key - See https://stackoverflow.com/a/47572701/2391795 -} & GraphCMSSystemFields; - -/** - * When some queries rely on required variables, but no value is available, you can use this fake value to perform the query so it doesn't fail, but won't find any record - * Basically acts as an optional value, because GraphQL doesn't seem to handle null variables, even if optional, if they're used at the top level - * Can also be used for anything similar - * - * @type {string} - */ -export const NOT_FOUND = '__FAKE_VALUE_THAT_MUST_MATCH_NO_RECORD__'; -export const UNDEFINED_ITEM_NO_IDENTIFIER = 'UNDEFINED_ITEM_NO_IDENTIFIER'; -export const NULL_ITEM_NO_IDENTIFIER = 'NULL_ITEM_NO_IDENTIFIER'; -export const NO_TYPE_FOUND = 'NO_TYPE_FOUND'; - -/** - * Logging strategies - * - * Each strategy will cause a different behaviour (see "getValue" function) - */ -export enum LOGGING_STRATEGY { - LOG_ERROR = 'LOG_ERROR', - DO_NOTHING = 'DO_NOTHING', -} - -export const STRATEGY_LOG_ERROR: LOGGING_STRATEGY = LOGGING_STRATEGY.LOG_ERROR; -export const STRATEGY_DO_NOTHING: LOGGING_STRATEGY = LOGGING_STRATEGY.DO_NOTHING; - -/** - * Find a record using the given field:value set - * Searches into the "id" field by default - * - * @param {Array} records - * @param {string} value - * @param {string} field - * @return {T} - */ -export const findRecordByField = (records: Array, value: string, field = 'id'): T => { - return find(records, { [field]: value } as any); // eslint-disable-line @typescript-eslint/no-explicit-any -}; - -export type FallbackConfigTransformProps = { - record: any; // eslint-disable-line @typescript-eslint/no-explicit-any - fallbacks: Array; - defaultValue: any; // eslint-disable-line @typescript-eslint/no-explicit-any - key: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any -} - -/** - * Fallback record - * - * Meant to be used with "getValueFallback" - */ -export type FallbackConfig = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - record: Record; - key: string; // Key to find within the "record", same as lodash.get "path" - // eslint-disable-next-line @typescript-eslint/no-explicit-any - transform?(value: string | object | any, props: FallbackConfigTransformProps): any; // Transformation applied if that fallback is selected (key found in record) -} - -/** - * Attempts to resolve the type of a given record - * Checks for the native apollo field "__typename" first - * If no identifier found, fallback to stringify the given record to allow further debug - * - * @param record - * @returns {string} - */ -export const resolveRecordType = (record: Record): string => { - if (typeof record === 'undefined') { - return UNDEFINED_ITEM_NO_IDENTIFIER; - } - if (record === null) { - return NULL_ITEM_NO_IDENTIFIER; - } - - if (get(record, '__typename', false)) { - return record.__typename; - } else { - const warning = `No identifier found for ${JSON.stringify(record, null, 2)}, returning "${NO_TYPE_FOUND}"`; - logger.warn(warning, 'resolveRecordType'); - Sentry.captureMessage(warning, Sentry.Severity.Warning); - return NO_TYPE_FOUND; - } -}; - -/** - * Attempts to resolve the identifiable properties of a given record - * The goal is mostly to extract only values that can be used to identify the record, for debug purposes - * - * @param record - * @param applyFallbackIfNotFound - * @returns {*} - */ -export const resolvedIdentifiableProperties = (record: Record, applyFallbackIfNotFound = true): Record => { - const identifiableItem = {}; - // List of properties that are meaningful to developers and can help find "which data is causing issues" - const meaningfulProperties = [ - 'id', - 'name', - 'path', - 'kind', - 'label', - 'labelFR', - 'labelEN', - 'url', - 'fileName', - ]; - - map(meaningfulProperties, (prop) => { - if (typeof get(record, prop, undefined) !== 'undefined') { - identifiableItem[prop] = record[prop]; - } - }); - - if (applyFallbackIfNotFound && isEmpty(identifiableItem)) { - return record; // Fallback to full object if no meaningful property was found - } - - return identifiableItem; -}; - -/** - * Log a warning with details regarding the missing property - * - * @param {Record} record - * @param {string} propertyPath - * @param {string} caller - */ -export const warnMissingProp = (record: Record, propertyPath: string, caller: string): void => { - const errorMessage = `Unable to resolve the property "${propertyPath}" in GraphQL object "${resolveRecordType(record)}" for ${JSON.stringify(resolvedIdentifiableProperties(record), null, 2)}`; - logger.warn(errorMessage, caller); - - if (process.env.NODE_ENV !== 'test') { - Sentry.captureException(Error(errorMessage)); - } -}; - -/** - * Get a property's value of an record, using a given path, while ensuring it doesn't throw any Exception at runtime (safe) - * - * It is a much safer way of using a data's (sub)property, which assumes the property may not exist and will deal with it gracefully, with sane fallback value - * Alternatively, take a look at "hasValue" if you just want to know whether a value exists (is defined with proper value) - * - * @example Accessing "modelData.some.data" will fail hard (throw exception, that will likely break the whole app) when "modelData.some" is not defined - * Instead, using "getValue(modelData, 'some.data', 0)" will not break and will return "0" as fallback value - * - * If the property isn't found then apply a strategy behavior based on the given level - * STRATEGY_LOG_ERROR: The property should exist, the error is sent to Epsagon. This likely means there is a data misconfiguration - * STRATEGY_DO_NOTHING: The property is optional and it's not an issue if it's missing, nothing is logged - * - * @param record - * @param propertyPath - * @param defaultValue - * @param missingPropStrategy - * @returns {*} - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const getValue = (record: Record, propertyPath: string, defaultValue: null | any = null, missingPropStrategy: LOGGING_STRATEGY = STRATEGY_LOG_ERROR): string | object | any => { - if (typeof propertyPath !== 'string') { - propertyPath = null; // Force null if property path is not of the expected type to avoid edge cases (0 would be an edge case) - } - - let value = get(record, propertyPath, NOT_FOUND); - - // If returned value is null, then it's considered as it was not found - if (value === null) { - value = NOT_FOUND; - } - - if (value === NOT_FOUND) { - if (defaultValue !== null) { - value = defaultValue; - - } else { - value = null; // Clean value which has been changed to NOT_FOUND - } - - if (missingPropStrategy === STRATEGY_LOG_ERROR) { - warnMissingProp(record, propertyPath, 'getValue'); - } - } - - return value; -}; - -/** - * Check if the value is defined and not "empty" or "invalid" - * - * If the value is falsy then it's considered undefined, unless it's - * - "0" (number) - * - "false" (boolean) - * - * '

' is a special use case due to the nature of GraphCMS, which will store '

' in any RichText that has been "touched", even though it's technically "empty" - * It's therefore considered in our app as a "false positive", and thus treated as if it was empty - * - * @param record - * @param propertyPath - */ -export const hasValue = (record: Record, propertyPath: string): boolean => { - const value = get(record, propertyPath, NOT_FOUND); - - if ((value === NOT_FOUND || value === '' || value === '

' || (typeof value === 'object' && isEmpty(value))) && value !== 0 && value !== false) { - return false; - } else { - return !!value; - } -}; - -/** - * Similar to getValue, but handles an array of fallbacks instead of an object - * Returns the first record that has a valid value - * - * This is meant to be used instead of "ternary hell conditions" when trying to resolve a property from different data sources, while dealing with a "priority order" - * - * P.S: Function's name kinda sucks, don't hesitate to propose a better alternative - * - * @param {Array} fallbacks - * @param {any | null} defaultValue - * @param {string} missingPropStrategy - * @return {any} - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const getValueFallback = (fallbacks: Array, defaultValue: null | any = null, missingPropStrategy: LOGGING_STRATEGY = STRATEGY_LOG_ERROR): any => { - let value = defaultValue; - let foundFallback; - - if (isArray(fallbacks)) { - for (const fallback of fallbacks) { - const record = fallback.record; - const key = fallback.key; - const transform = fallback.transform || null; - - if (record !== null && record !== undefined && key !== null && key !== undefined) { - if (hasValue(record, key)) { - if (transform) { - value = transform(getValue(record, key, defaultValue), { fallbacks, record, key, defaultValue }); - } else { - value = getValue(record, key, defaultValue); - } - foundFallback = true; - break; - } - } else { - // Invalid fallback, try next one - } - } - - } else { - const errorMessage = `Unable to resolve the fallback value (expecting array, got "${typeof fallbacks}") for ${JSON.stringify(fallbacks, null, 2)}`; - logger.warn(errorMessage, 'getValueFallback'); - - if (process.env.NODE_ENV !== 'test') { - Sentry.captureException(Error(errorMessage)); - } - } - - if (!foundFallback && missingPropStrategy === STRATEGY_LOG_ERROR) { - const errorMessage = `Unable to resolve any fallback value for ${JSON.stringify(fallbacks, null, 2)}`; - logger.warn(errorMessage, 'getValueFallback'); - - if (process.env.NODE_ENV !== 'test') { - Sentry.captureException(Error(errorMessage)); - } - } - - return value; -}; - -/** - * Filters records that have been selected already. - * - * Used to filter a list of records without already-selected items, in order to avoid clutter from the list. - * - * The option "forceKeepRecord" is meant to use when we want to keep a record, even though it's present in the selected list. - * This option is meant to be used when an record is the currently selected record, and we still want to display it on the list instead of filtering it out. - * - * @param {EducationalProgram[]} allRecords - * @param {EducationalProgram[]} selectedRecords - * @param {EducationalProgram} forceKeepRecord - * @return {EducationalProgram[]} - */ -export const filterSelectedRecords = ( - allRecords: Record[], - selectedRecords: Record[], - forceKeepRecord?: Record, -): Record[] => { - const clonedSelectedRecords = cloneDeep(selectedRecords); - - // In case we want to force keep a record in the list, we remove it from the selected list so that it doesn't get removed by the xOr operation (kinda ignored) - if (forceKeepRecord) { - remove(clonedSelectedRecords, (record: Record) => { // XXX "remove" mutates the array - return record.id === forceKeepRecord.id; - }); - } - - return xOrBy(allRecords, clonedSelectedRecords || [], 'id'); -}; - -/** - * Transform a Record into a SerializedRecord, so that it can be forwarded on the network - * with minimal identifiable properties, thus ensuring it can be reconstituted eventually - * - * @param {T} record - * @param {string} field - * @return {SerializedRecord} - */ -export const serializeRecord = (record: T, field = 'name'): SerializedRecord => { - return getValue(record, field, undefined, STRATEGY_LOG_ERROR); -}; - -/** - * Deserialize a serialized record back to its original value - * - * Basically finds the serialized record within a set or records, using it's identifiable property field - * - * @param {SerializedRecord} serializedRecord - * @param {T[]} records - * @param {string} field - * @return {T} - */ -export const deserializeRecord = (serializedRecord: SerializedRecord, records: T[], field = 'name'): T => { - return findRecordByField(records, serializedRecord, field); -};