Skip to content

Commit

Permalink
DXCDT-374: Update field by address for keyword preservation (#744)
Browse files Browse the repository at this point in the history
* Adding shouldFieldBePreserved function

* Adding getPreservableFieldsFromAssets function with test

* Adding array keyword replace case

* Adding undefined and null check

* Adding address notation to traversal

* Adding address notation lookup

* Adding stronger tests, cleaning up code

* Adding more context

* Removing unused package

* Adding more tests

* Adding more tests

* Fixing test

---------

Co-authored-by: Will Vedder <[email protected]>
  • Loading branch information
willvedd and willvedd authored Feb 21, 2023
1 parent c2ac9f4 commit e7521f3
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 1 deletion.
60 changes: 59 additions & 1 deletion src/keywordPreservation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { get as getByDotNotation } from 'dot-prop';
import { get as getByDotNotation, set as setByDotNotation } from 'dot-prop';
import { KeywordMappings } from './types';
import { keywordReplaceArrayRegExp, keywordReplaceStringRegExp } from './tools/utils';

Expand Down Expand Up @@ -102,3 +102,61 @@ export const getAssetsValueByAddress = (address: string, assets: any): any => {
getByDotNotation(assets, directions[0])
);
};

// convertAddressToDotNotation will convert the proprietary address into conventional
// JS object notation. Performing this conversion simplifies the process
// of updating a specific property for a given asset tree using the dot-prop library
export const convertAddressToDotNotation = (
assets: any,
address: string,
finalAddressTrail = ''
): string => {
const directions = address.split('.');

if (directions[0] === '') return finalAddressTrail;

if (directions[0].charAt(0) === '[') {
const identifier = directions[0].substring(1, directions[0].length - 1).split('=')[0];
const identifierValue = directions[0].substring(1, directions[0].length - 1).split('=')[1];

let targetIndex = -1;

assets.forEach((item: any, index: number) => {
if (item[identifier] === identifierValue) {
targetIndex = index;
}
});

if (targetIndex === -1)
throw new Error(`Cannot find ${directions[0]} in ${JSON.stringify(assets)}`);

return convertAddressToDotNotation(
assets[targetIndex],
directions.slice(1).join('.'),
`${finalAddressTrail}.${targetIndex}`
);
}

return convertAddressToDotNotation(
getByDotNotation(assets, directions[0]),
directions.slice(1).join('.'),
finalAddressTrail === '' ? directions[0] : `${finalAddressTrail}.${directions[0]}`
);
};

export const updateAssetsByAddress = (
assets: object,
address: string,
newValue: string
): object => {
const dotNotationAddress = convertAddressToDotNotation(assets, address);

const doesPropertyExist = getByDotNotation(assets, dotNotationAddress) !== undefined;

if (!doesPropertyExist) {
throw new Error(`cannot update assets by address: ${address} because it does not exist.`);
}

setByDotNotation(assets, dotNotationAddress, newValue);
return assets;
};
129 changes: 129 additions & 0 deletions test/keywordPreservation.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { expect } from 'chai';
import { get as getDotNotation } from 'dot-prop';
import {
shouldFieldBePreserved,
getPreservableFieldsFromAssets,
getAssetsValueByAddress,
convertAddressToDotNotation,
updateAssetsByAddress,
} from '../src/keywordPreservation';

describe('#Keyword Preservation', () => {
Expand Down Expand Up @@ -148,3 +151,129 @@ describe('getAssetsValueByAddress', () => {
);
});
});

describe('convertAddressToDotNotation', () => {
const mockAssets = {
tenant: {
friendly_name: 'Friendly Tenant Name',
},
actions: [
{
name: 'action-1',
code: "window.alert('Foo')",
},
{
name: 'action-2',
nestedProperty: {
array: [
{
name: 'foo',
},
{
name: 'bar',
arrayProperty: 'baz',
},
],
},
},
],
};

it('should convert proprietary address to conventional JS object notation (aka "dot notation")', () => {
expect(convertAddressToDotNotation(mockAssets, 'tenant.friendly_name')).to.equal(
'tenant.friendly_name'
);
expect(getDotNotation(mockAssets, 'tenant.friendly_name')).to.equal(
mockAssets.tenant.friendly_name
);

expect(convertAddressToDotNotation(mockAssets, 'actions.[name=action-1].code')).to.equal(
'actions.0.code'
);
expect(getDotNotation(mockAssets, 'actions.0.code')).to.equal(mockAssets.actions[0].code);

expect(
convertAddressToDotNotation(
mockAssets,
'actions.[name=action-2].nestedProperty.array.[name=bar].arrayProperty'
)
).to.equal('actions.1.nestedProperty.array.1.arrayProperty');

expect(getDotNotation(mockAssets, 'actions.1.nestedProperty.array.1.arrayProperty')).to.equal(
mockAssets.actions[1].nestedProperty?.array[1].arrayProperty
);
});

it('should throw if provided address is invalid', () => {
expect(() =>
convertAddressToDotNotation(mockAssets, 'actions.[name=this-action-does-not-exist].code')
).to.throw(
`Cannot find [name=this-action-does-not-exist] in [{"name":"action-1","code":"window.alert('Foo')"},{"name":"action-2","nestedProperty":{"array":[{"name":"foo"},{"name":"bar","arrayProperty":"baz"}]}}]`
);
});
});

describe('updateAssetsByAddress', () => {
const mockAssetTree = {
tenant: {
display_name: 'This is my tenant display name',
},
clients: [
{
name: 'client-1',
display_name: 'Some Display Name',
},
{
name: 'client-2',
display_name: 'This is the target value',
},
{
name: 'client-3',
connections: [
{
connection_name: 'connection-1',
display_name: 'My connection display name',
},
],
},
],
};
it('should update an specific asset field for a provided address', () => {
expect(
updateAssetsByAddress(
mockAssetTree,
'clients.[name=client-3].connections.[connection_name=connection-1].display_name',
'New connection display name'
)
).to.deep.equal(
(() => {
const newAssets = mockAssetTree;
//@ts-ignore because we know this value is defined
newAssets.clients[2].connections[0].display_name = 'New connection display name';
return newAssets;
})()
);

expect(
updateAssetsByAddress(mockAssetTree, 'tenant.display_name', 'This is the new display name')
).to.deep.equal(
(() => {
const newAssets = mockAssetTree;
newAssets.tenant.display_name = 'This is the new display name';
return newAssets;
})()
);
});

it('should throw errors if invalid addresses provided', () => {
expect(() =>
updateAssetsByAddress(mockAssetTree, 'clients.[name=this-client-does-not-exist]', '_')
).to.throw();

expect(() =>
updateAssetsByAddress(mockAssetTree, 'tenant.this_property_does_not_exist', '_')
).to.throw(
'cannot update assets by address: tenant.this_property_does_not_exist because it does not exist.'
);
});
});

0 comments on commit e7521f3

Please sign in to comment.