Skip to content

Commit

Permalink
fix: append forward slash to folder types manifest entries (#1407)
Browse files Browse the repository at this point in the history
* chore: lint fixes

* fix: update manifest resolver to identify InFolder parents with trailing slashes

* fix: only reports and dashboards have this behavior

* fix: add EmailTemplateFolder

* chore: bump core dep

* chore: update yarn.lock
  • Loading branch information
shetzel authored Sep 9, 2024
1 parent 40dda6e commit 4d35ce3
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 16 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"node": ">=18.0.0"
},
"dependencies": {
"@salesforce/core": "^8.5.2",
"@salesforce/core": "^8.5.4",
"@salesforce/kit": "^3.2.2",
"@salesforce/ts-types": "^2.0.12",
"fast-levenshtein": "^3.0.0",
Expand Down
17 changes: 12 additions & 5 deletions src/collections/componentSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,11 +443,7 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
addToTypeMap({
typeMap,
type: type.folderContentType ? this.registry.getTypeByName(type.folderContentType) : type,
fullName:
this.registry.getParentType(type.name)?.strategies?.recomposition === 'startEmpty' && fullName.includes('.')
? // they're reassembled like CustomLabels.MyLabel
fullName.split('.')[1]
: fullName,
fullName: constructFullName(this.registry, type, fullName),
destructiveType,
});
});
Expand Down Expand Up @@ -738,6 +734,17 @@ const splitOnFirstDelimiter = (input: string): [string, string] => {
return [input.substring(0, indexOfSplitChar), input.substring(indexOfSplitChar + 1)];
};

const constructFullName = (registry: RegistryAccess, type: MetadataType, fullName: string): string =>
// Some InFolder types are different. e.g., Report/ReportFolder & Dashboard/DashboardFolder.
// ReportFolders are deployed/retrieved as Reports. If a ReportFolder is being added append
// a "/" so the metadata API can identify it as a folder.
['DashboardFolder', 'ReportFolder', 'EmailTemplateFolder'].includes(type.name) && !fullName.endsWith('/')
? `${fullName}/`
: registry.getParentType(type.name)?.strategies?.recomposition === 'startEmpty' && fullName.includes('.')
? // they're reassembled like CustomLabels.MyLabel
fullName.split('.')[1]
: fullName;

/** side effect: mutates the typeMap property */
const addToTypeMap = ({
typeMap,
Expand Down
36 changes: 29 additions & 7 deletions src/resolve/manifestResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export class ManifestResolver {
const type = this.registry.getTypeByName(typeMembers.name);
const parentType = type.folderType ? this.registry.getTypeByName(type.folderType) : undefined;
return ensureArray(typeMembers.members).map((fullName, _index, members) => ({
fullName,
type: parentType && isMemberNestedInFolder(fullName, type, parentType, members) ? parentType : type,
fullName: resolveFullName(fullName, parentType),
type: !parentType ? type : resolveType(fullName, type, members, parentType),
}));
});

Expand Down Expand Up @@ -113,6 +113,33 @@ const getValidatedType =
return typeMembers;
};

// Mostly for parents of InFolder types to strip off trailing "/" characters
// in fullNames. Otherwise just returns the fullName.
const resolveFullName = (fullName: string, parentType?: MetadataType): string =>
parentType?.folderContentType && fullName.endsWith('/') ? fullName.substring(0, fullName.length - 1) : fullName;

// Resolve the correct metadata type from metadata entries in a manifest.
// Parents of InFolder types can be detected by looking for a trailing "/"
// character.
const resolveType = (
fullName: string,
type: MetadataType,
members: string[],
parentType?: MetadataType
): MetadataType => {
// Quick short-circuit for non-parent types and non-folderTypes
if (!parentType || !type.folderType) {
return type;
}

// Detect parents of InFolder types by looking for a trailing slash on InFolder types
if (parentType?.folderContentType && fullName.endsWith('/')) {
return parentType;
}

return isMemberNestedInFolder(fullName, type, parentType, members) ? parentType : type;
};

// Use the folderType instead of the type from the manifest when:
// 1. InFolder types: (report, dashboard, emailTemplate, document)
// 1a. type.inFolder === true (from metadataRegistry.json) AND
Expand All @@ -129,11 +156,6 @@ const isMemberNestedInFolder = (
parentType: MetadataType,
members: string[]
): boolean => {
// Quick short-circuit for non-folderTypes
if (!type.folderType) {
return false;
}

const isInFolderType = type.inFolder;
const isNestedInFolder = !fullName.includes('/') || members.some((m) => m.includes(`${fullName}/`));
const isNonMatchingFolder = parentType && parentType.folderType !== parentType.id;
Expand Down
58 changes: 58 additions & 0 deletions test/resolve/manifestResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,64 @@ describe('ManifestResolver', () => {
expect(result.components).to.deep.equal(expected);
});

it('should resolve nested InFolder types with trailing slashes', async () => {
const registry = new RegistryAccess();
const reportType = registry.getTypeByName('report');
const reportFolderType = registry.getTypeByName('reportFolder');
const folderManifest: VirtualFile = {
name: 'reports-package.xml',
data: Buffer.from(`<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>foo/</members>
<members>foo/subfoo/</members>
<members>foo/subfoo/MySubFooReport1</members>
<members>foo/subfoo/MySubFooReport2</members>
<members>bar/MyBarReport1</members>
<members>bar/MyBarReport2</members>
<name>Report</name>
</types>
<version>52.0</version>
</Package>\n`),
};
const tree = new VirtualTreeContainer([
{
dirPath: '.',
children: [folderManifest],
},
]);
const resolver = new ManifestResolver(tree);
const result = await resolver.resolve(folderManifest.name);
const expected: MetadataComponent[] = [
{
fullName: 'foo',
type: reportFolderType,
},
{
fullName: 'foo/subfoo',
type: reportFolderType,
},
{
fullName: 'foo/subfoo/MySubFooReport1',
type: reportType,
},
{
fullName: 'foo/subfoo/MySubFooReport2',
type: reportType,
},
{
fullName: 'bar/MyBarReport1',
type: reportType,
},
{
fullName: 'bar/MyBarReport2',
type: reportType,
},
];

expect(result.components).to.deep.equal(expected);
});

it('should resolve folderType types (Territory2*)', async () => {
const registry = new RegistryAccess();
const t2ModelType = registry.getTypeByName('Territory2Model');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<name>EmailTemplate</name>
</types>
<types>
<members>TopFolder</members>
<members>TopFolder/ChildFolder</members>
<members>TopFolder/</members>
<members>TopFolder/ChildFolder/</members>
<members>TopFolder/ChildFolder/Report_in_Child_Folder_qz4</members>
<members>TopFolder/Copy_of_Top_level_report_DOj</members>
<members>unfiled$public/Top_level_report_cZJ</members>
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@
strip-ansi "6.0.1"
ts-retry-promise "^0.8.1"

"@salesforce/core@^8.5.2", "@salesforce/core@^8.5.4":
"@salesforce/core@^8.5.4":
version "8.5.4"
resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-8.5.4.tgz#1cdd669462d2c2859b72135d1138a1b790b1fbcc"
integrity sha512-dO8tzFxq811qNPeKPPO2OA2KPYW5rO0YRinW/+7zmRJW3EtNpe93dsQVGwBSAAYrSbYeBwiKdliNqNTN7tKJ0A==
Expand Down

0 comments on commit 4d35ce3

Please sign in to comment.