Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: append forward slash to folder types manifest entries #1407

Merged
merged 8 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading