Skip to content

Commit

Permalink
DXCDT-382: Use resource identifiers in keyword preservation (2/2) (#757)
Browse files Browse the repository at this point in the history
* Renaming load to loadAssetsFromLocal

* Renaming load to loadAssetsFromAuth0

* integrating into export process

* Adding config interpreter

* Disabling keyword replacement in certain cases

* Fixing directory load

* More forgiving lookup logic if properties and/or addresses do not exist

* Fixing directory load again

* Adding case that would have previously tripped up process

* Adding e2e tests for keyword preservation

* Removing old tests, updating recordings, removing console. logs

* Commenting-out test to fix later on

* Fixing workdirectory for e2e tests

* Adding eventual cases for auxillary files

* Adding TODO

* Adding preqrequisite checks

* Fixing test

* Standardizing definitions of resource identifiers

* Removing incompatible id from email templates

* Readding identifiers field for resource server

* Fixing email templates identifier field

* Reformulating arguments into object

* Adding e2e case

---------

Co-authored-by: Will Vedder <will.vedder@okta.com>
  • Loading branch information
willvedd and willvedd authored Mar 1, 2023
1 parent 4dc4a3c commit 247193f
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 47 deletions.
9 changes: 5 additions & 4 deletions src/context/directory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,12 @@ export default class DirectoryContext {
//@ts-ignore
delete this['assets'];

this.assets = preserveKeywords(
this.assets = preserveKeywords({
localAssets,
auth0.assets,
this.config.AUTH0_KEYWORD_REPLACE_MAPPINGS || {}
);
remoteAssets: auth0.assets,
keywordMappings: this.config.AUTH0_KEYWORD_REPLACE_MAPPINGS || {},
auth0Handlers: auth0.handlers,
});
} else {
this.assets = auth0.assets;
}
Expand Down
9 changes: 5 additions & 4 deletions src/context/yaml/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,12 @@ export default class YAMLContext {
//@ts-ignore
delete this['assets'];

this.assets = preserveKeywords(
this.assets = preserveKeywords({
localAssets,
auth0.assets,
this.config.AUTH0_KEYWORD_REPLACE_MAPPINGS || {}
);
remoteAssets: auth0.assets,
keywordMappings: this.config.AUTH0_KEYWORD_REPLACE_MAPPINGS || {},
auth0Handlers: auth0.handlers,
});
} else {
this.assets = auth0.assets;
}
Expand Down
51 changes: 39 additions & 12 deletions src/keywordPreservation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { get as getByDotNotation, set as setByDotNotation } from 'dot-prop';
import { keywordReplace } from './tools/utils';
import { KeywordMappings } from './types';
import { AssetTypes, KeywordMappings } from './types';
import { keywordReplaceArrayRegExp, keywordReplaceStringRegExp } from './tools/utils';
import { cloneDeep } from 'lodash';
import APIHandler from './tools/auth0/handlers/default';

/*
RFC for Keyword Preservation: https://github.com/auth0/auth0-deploy-cli/issues/688
Expand All @@ -24,6 +25,7 @@ export const shouldFieldBePreserved = (
export const getPreservableFieldsFromAssets = (
asset: object,
keywordMappings: KeywordMappings,
resourceSpecificIdentifiers: Partial<{ [key in AssetTypes]: string }>,
address = ''
): string[] => {
if (typeof asset === 'string') {
Expand All @@ -38,16 +40,17 @@ export const getPreservableFieldsFromAssets = (
if (Array.isArray(asset)) {
return asset
.map((arrayItem) => {
// Using the `name` field as the primary unique identifier for array items
// TODO: expand the available identifier fields to encompass objects that lack name
const hasIdentifier = arrayItem.name !== undefined;
const resourceIdentifier = resourceSpecificIdentifiers[address];
if (resourceIdentifier === undefined) return []; // See if this specific resource type has an identifier

if (!hasIdentifier) return [];
const identifierFieldValue = arrayItem[resourceIdentifier];
if (identifierFieldValue === undefined) return []; // See if this specific array item possess the resource-specific identifier

return getPreservableFieldsFromAssets(
arrayItem,
keywordMappings,
`${address}${shouldRenderDot ? '.' : ''}[name=${arrayItem.name}]`
resourceSpecificIdentifiers,
`${address}${shouldRenderDot ? '.' : ''}[${resourceIdentifier}=${identifierFieldValue}]`
);
})
.flat();
Expand All @@ -62,6 +65,7 @@ export const getPreservableFieldsFromAssets = (
return getPreservableFieldsFromAssets(
value,
keywordMappings,
resourceSpecificIdentifiers,
`${address}${shouldRenderDot ? '.' : ''}${key}`
);
})
Expand Down Expand Up @@ -173,14 +177,37 @@ export const updateAssetsByAddress = (
// preserveKeywords is the function that ultimately gets executed during export
// to attempt to preserve keywords (ex: ##KEYWORD##) in local configuration files
// from getting overwritten by remote values during export.
export const preserveKeywords = (
localAssets: object,
remoteAssets: object,
keywordMappings: KeywordMappings
): object => {
export const preserveKeywords = ({
localAssets,
remoteAssets,
keywordMappings,
auth0Handlers,
}: {
localAssets: object;
remoteAssets: object;
keywordMappings: KeywordMappings;
auth0Handlers: Pick<APIHandler, 'id' | 'identifiers' | 'type'>[];
}): object => {
if (Object.keys(keywordMappings).length === 0) return remoteAssets;

const addresses = getPreservableFieldsFromAssets(localAssets, keywordMappings, '');
const resourceSpecificIdentifiers = auth0Handlers.reduce(
(acc, handler): Partial<{ [key in AssetTypes]: string }> => {
return {
...acc,
[handler.type]: handler.identifiers.filter((identifiers) => {
return identifiers !== handler.id;
})[0],
};
},
{}
);

const addresses = getPreservableFieldsFromAssets(
localAssets,
keywordMappings,
resourceSpecificIdentifiers,
''
);

let updatedRemoteAssets = cloneDeep(remoteAssets);

Expand Down
11 changes: 11 additions & 0 deletions test/e2e/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,10 @@ describe('keyword preservation', () => {
expect(yaml.tenant.friendly_name).to.equal('##TENANT_NAME##');
expect(yaml.tenant.support_email).to.equal('support@##DOMAIN##');
expect(yaml.tenant.support_url).to.equal('https://##DOMAIN##/support');
expect(
yaml.emailTemplates.find(({ template }) => template === 'welcome_email').resultUrl
).to.equal('https://##DOMAIN##/welcome');

// expect(yaml.tenant.enabled_locales).to.equal('@@LANGUAGES@@'); TODO: enable @@ARRAY@@ keyword preservation in yaml formats

// const emailTemplateHTML = fs
Expand Down Expand Up @@ -431,6 +435,13 @@ describe('keyword preservation', () => {
expect(json.support_email).to.equal('support@##DOMAIN##');
expect(json.support_url).to.equal('https://##DOMAIN##/support');

const emailTemplateJson = JSON.parse(
fs.readFileSync(path.join(workDirectory, 'emailTemplates', 'welcome_email.json')).toString()
);

expect(emailTemplateJson.resultUrl).to.equal('https://##DOMAIN##/welcome');
expect(emailTemplateJson.subject).to.not.equal('##THIS_SHOULD_NOT_BE_PRESERVED##');

// const emailTemplateHTML = fs
// .readFileSync(path.join(workDirectory, 'emailTemplates', 'welcome_email.html'))
// .toString();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"template": "welcome_email",
"body": "./welcome_email.html",
"from": "",
"resultUrl": "https://##DOMAIN##/welcome",
"subject": "Welcome",
"syntax": "liquid",
"urlLifetimeInSeconds": 3600,
"enabled": false
}

94 changes: 67 additions & 27 deletions test/keywordPreservation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,32 +52,31 @@ describe('#Keyword Preservation', () => {
it('should retrieve all preservable fields from assets tree', () => {
const fieldsToPreserve = getPreservableFieldsFromAssets(
{
object: {
tenant: {
friendly_name: 'Friendly name ##KEYWORD##',
notInKeywordMapping: '##NOT_IN_KEYWORD_MAPPING##',
number: 5,
boolean: true,
nested: {
nestedProperty: 'Nested property ##KEYWORD##',
},
nestedArray: [
{
name: 'nested-array-item-1',
value:
"Even with ##KEYWORD##, this won't get preserved because this nested array item does not have a registered resource identifier",
},
],
},
array: [
actions: [
{
name: 'array-item-1',
nestedArray: [
{
name: 'nested-array-item-1',
value: 'Nested array value 1 ##KEYWORD##',
},
{
name: 'nested-array-item-2',
value: 'Nested array value 2 ##KEYWORD##',
},
],
actionName: 'action-1',
value: 'Action 1 ##KEYWORD##',
notInKeywordMapping: '##NOT_IN_KEYWORD_MAPPING##',
nested: {
nestedProperty: 'Another nested array property ##KEYWORD##',
},
},
{
actionName: 'action-2',
value: 'Action 2 ##KEYWORD##',
},
],
arrayReplace: '@@ARRAY_REPLACE_KEYWORD@@',
Expand All @@ -87,15 +86,15 @@ describe('#Keyword Preservation', () => {
{
KEYWORD: 'Travel0',
ARRAY_REPLACE_KEYWORD: ['this value', 'that value'],
}
},
{ actions: 'actionName' }
);

expect(fieldsToPreserve).to.have.members([
'object.friendly_name',
'object.nested.nestedProperty',
'array.[name=array-item-1].nestedArray.[name=nested-array-item-1].value',
'array.[name=array-item-1].nestedArray.[name=nested-array-item-2].value',
'array.[name=array-item-1].nested.nestedProperty',
'tenant.friendly_name',
'tenant.nested.nestedProperty',
'actions.[actionName=action-1].value',
'actions.[actionName=action-2].value',
'arrayReplace',
]);
});
Expand Down Expand Up @@ -292,6 +291,12 @@ describe('preserveKeywords', () => {
},
},
],
emailTemplates: [
{
template: 'welcome',
body: '<html>Welcome to ##ENV## ##COMPANY_NAME## Tenant</html>',
},
],
actions: [
{
name: 'action-1',
Expand Down Expand Up @@ -326,13 +331,42 @@ describe('preserveKeywords', () => {
display_name: 'This action exists on remote but not local',
},
],
emailTemplates: [
{
template: 'welcome',
body: '<html>Welcome to Production Travel0 Tenant</html>',
},
],
};

const auth0Handlers = [
{
id: 'id',
identifiers: ['id', 'name'],
type: 'actions',
},
{
id: 'id',
identifiers: ['id', 'name'],
type: 'connections',
},
{
id: 'id',
identifiers: ['template'],
type: 'emailTemplates',
},
];

it('should preserve keywords when they correlate to keyword mappings', () => {
const preservedAssets = preserveKeywords(mockLocalAssets, mockRemoteAssets, {
COMPANY_NAME: 'Travel0',
ALLOWED_LOGOUT_URLS: ['localhost:3000/logout', 'https://travel0.com/logout'],
ENV: 'Production',
const preservedAssets = preserveKeywords({
localAssets: mockLocalAssets,
remoteAssets: mockRemoteAssets,
keywordMappings: {
COMPANY_NAME: 'Travel0',
ALLOWED_LOGOUT_URLS: ['localhost:3000/logout', 'https://travel0.com/logout'],
ENV: 'Production',
},
auth0Handlers,
});

expect(preservedAssets).to.deep.equal(
Expand All @@ -341,13 +375,19 @@ describe('preserveKeywords', () => {
//@ts-ignore
expected.tenant = mockLocalAssets.tenant;
expected.actions[0].display_name = '##ENV## Action 1';
expected.emailTemplates[0].body = '<html>Welcome to ##ENV## ##COMPANY_NAME## Tenant</html>';
return expected;
})()
);
});

it('should not preserve keywords when no keyword mappings', () => {
const preservedAssets = preserveKeywords(mockLocalAssets, mockRemoteAssets, {});
const preservedAssets = preserveKeywords({
localAssets: mockLocalAssets,
remoteAssets: mockRemoteAssets,
keywordMappings: {},
auth0Handlers,
});
expect(preservedAssets).to.deep.equal(mockRemoteAssets);
});
});

0 comments on commit 247193f

Please sign in to comment.