Skip to content

Commit

Permalink
feat: support throttling of listMetadata calls and queries; include a…
Browse files Browse the repository at this point in the history
…nd exclude metadata
  • Loading branch information
shetzel committed Dec 19, 2024
1 parent 6fabd68 commit 56435ca
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 95 deletions.
131 changes: 90 additions & 41 deletions src/collections/componentSetBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,18 @@ export type ManifestOption = {
};

type MetadataOption = {
/**
* Array of metadata type:name pairs to include in the ComponentSet.
*/
metadataEntries: string[];
/**
* Array of filesystem directory paths to search for local metadata to include in the ComponentSet.
*/
directoryPaths: string[];
/**
* Array of metadata type:name pairs to exclude from the ComponentSet.
*/
excludedEntries?: string[];
/**
* Array of metadata type:name pairs to delete before the deploy. Use of wildcards is not allowed.
*/
Expand Down Expand Up @@ -60,6 +70,14 @@ export type ComponentSetOptions = {

type MetadataMap = Map<string, string[]>;

let logger: Logger;
const getLogger = (): Logger => {
if (!logger) {
logger = Logger.childFromRoot('ComponentSetBuilder');
}
return logger;
};

export class ComponentSetBuilder {
/**
* Builds a ComponentSet that can be used for source conversion,
Expand All @@ -71,14 +89,13 @@ export class ComponentSetBuilder {
*/

public static async build(options: ComponentSetOptions): Promise<ComponentSet> {
const logger = Logger.childFromRoot('componentSetBuilder');
let componentSet: ComponentSet | undefined;

const { sourcepath, manifest, metadata, packagenames, org } = options;
const registry = new RegistryAccess(undefined, options.projectDir);

if (sourcepath) {
logger.debug(`Building ComponentSet from sourcepath: ${sourcepath.join(', ')}`);
getLogger().debug(`Building ComponentSet from sourcepath: ${sourcepath.join(', ')}`);
const fsPaths = sourcepath.map(validateAndResolvePath);
componentSet = ComponentSet.fromSource({
fsPaths,
Expand All @@ -88,16 +105,16 @@ export class ComponentSetBuilder {

// Return empty ComponentSet and use packageNames in the connection via `.retrieve` options
if (packagenames) {
logger.debug(`Building ComponentSet for packagenames: ${packagenames.toString()}`);
getLogger().debug(`Building ComponentSet for packagenames: ${packagenames.toString()}`);
componentSet ??= new ComponentSet(undefined, registry);
}

// Resolve manifest with source in package directories.
if (manifest) {
logger.debug(`Building ComponentSet from manifest: ${manifest.manifestPath}`);
getLogger().debug(`Building ComponentSet from manifest: ${manifest.manifestPath}`);
assertFileExists(manifest.manifestPath);

logger.debug(`Searching in packageDir: ${manifest.directoryPaths.join(', ')} for matching metadata`);
getLogger().debug(`Searching in packageDir: ${manifest.directoryPaths.join(', ')} for matching metadata`);
componentSet = await ComponentSet.fromManifest({
manifestPath: manifest.manifestPath,
resolveSourcePaths: manifest.directoryPaths,
Expand All @@ -108,9 +125,10 @@ export class ComponentSetBuilder {
});
}

// Resolve metadata entries with source in package directories.
if (metadata) {
logger.debug(`Building ComponentSet from metadata: ${metadata.metadataEntries.toString()}`);
// Resolve metadata entries with source in package directories, unless we are building a ComponentSet
// from metadata in an org.
if (metadata && !org) {
getLogger().debug(`Building ComponentSet from metadata: ${metadata.metadataEntries.toString()}`);
const directoryPaths = metadata.directoryPaths;
componentSet ??= new ComponentSet(undefined, registry);
const componentSetFilter = new ComponentSet(undefined, registry);
Expand All @@ -122,7 +140,7 @@ export class ComponentSetBuilder {
.map(addToComponentSet(componentSet))
.map(addToComponentSet(componentSetFilter));

logger.debug(`Searching for matching metadata in directories: ${directoryPaths.join(', ')}`);
getLogger().debug(`Searching for matching metadata in directories: ${directoryPaths.join(', ')}`);

// add destructive changes if defined. Because these are deletes, all entries
// are resolved to SourceComponents
Expand Down Expand Up @@ -170,25 +188,8 @@ export class ComponentSetBuilder {
// Resolve metadata entries with an org connection
if (org) {
componentSet ??= new ComponentSet(undefined, registry);

logger.debug(
`Building ComponentSet from targetUsername: ${org.username} ${
metadata ? `filtered by metadata: ${metadata.metadataEntries.toString()}` : ''
}`
);

const mdMap = metadata
? buildMapFromComponents(metadata.metadataEntries.map(entryToTypeAndName(registry)))
: (new Map() as MetadataMap);

const fromConnection = await ComponentSet.fromConnection({
usernameOrConnection: (await StateAggregator.getInstance()).aliases.getUsername(org.username) ?? org.username,
componentFilter: getOrgComponentFilter(org, mdMap, metadata),
metadataTypes: mdMap.size ? Array.from(mdMap.keys()) : undefined,
registry,
});

fromConnection.toArray().map(addToComponentSet(componentSet));
const orgComponentSet = await this.resolveOrgComponents(registry, org, metadata);
orgComponentSet.toArray().map(addToComponentSet(componentSet));
}

// there should have been a componentSet created by this point.
Expand All @@ -197,9 +198,35 @@ export class ComponentSetBuilder {
componentSet.sourceApiVersion ??= options.sourceapiversion;
componentSet.projectDirectory = options.projectDir;

logComponents(logger, componentSet);
logComponents(componentSet);
return componentSet;
}

private static async resolveOrgComponents(
registry: RegistryAccess,
org: OrgOption,
metadata?: MetadataOption
): Promise<ComponentSet> {
let mdMap = new Map() as MetadataMap;
let debugMsg = `Building ComponentSet from metadata in an org using targetUsername: ${org.username}`;
if (metadata) {
if (metadata.metadataEntries?.length) {
debugMsg += ` filtering on metadata: ${metadata.metadataEntries.toString()}`;
}
if (metadata.excludedEntries?.length) {
debugMsg += ` excluding metadata: ${metadata.excludedEntries.toString()}`;
}
mdMap = buildMapFromMetadata(metadata, registry);
}
getLogger().debug(debugMsg);

return ComponentSet.fromConnection({
usernameOrConnection: (await StateAggregator.getInstance()).aliases.getUsername(org.username) ?? org.username,
componentFilter: getOrgComponentFilter(org, mdMap, metadata),
metadataTypes: mdMap.size ? Array.from(mdMap.keys()) : undefined,
registry,
});
}
}

const addToComponentSet =
Expand Down Expand Up @@ -234,27 +261,27 @@ const assertNoWildcardInDestructiveEntries = (mdEntry: MetadataTypeAndMetadataNa

/** This is only for debug output of matched files based on the command flags.
* It will log up to 20 file matches. */
const logComponents = (logger: Logger, componentSet: ComponentSet): void => {
logger.debug(`Matching metadata files (${componentSet.size}):`);
const logComponents = (componentSet: ComponentSet): void => {
getLogger().debug(`Matching metadata files (${componentSet.size}):`);

const components = componentSet.getSourceComponents().toArray();

components
.slice(0, 20)
.map((cmp) => cmp.content ?? cmp.xml ?? cmp.fullName)
.map((m) => logger.debug(m));
if (components.length > 20) logger.debug(`(showing 20 of ${componentSet.size} matches)`);
.map((m) => getLogger().debug(m));
if (components.length > 20) getLogger().debug(`(showing 20 of ${componentSet.size} matches)`);

logger.debug(`ComponentSet apiVersion = ${componentSet.apiVersion ?? '<not set>'}`);
logger.debug(`ComponentSet sourceApiVersion = ${componentSet.sourceApiVersion ?? '<not set>'}`);
getLogger().debug(`ComponentSet apiVersion = ${componentSet.apiVersion ?? '<not set>'}`);
getLogger().debug(`ComponentSet sourceApiVersion = ${componentSet.sourceApiVersion ?? '<not set>'}`);
};

const getOrgComponentFilter = (
org: OrgOption,
mdMap: MetadataMap,
metadata?: MetadataOption
): FromConnectionOptions['componentFilter'] =>
metadata
metadata?.metadataEntries?.length
? (component: Partial<FileProperties>): boolean => {
if (component.type && component.fullName) {
const mdMapEntry = mdMap.get(component.type);
Expand Down Expand Up @@ -312,11 +339,33 @@ const typeAndNameToMetadataComponents =
.filter((cs) => minimatch(cs.fullName, metadataName))
: [{ type, fullName: metadataName }];

// TODO: use Map.groupBy when it's available
const buildMapFromComponents = (components: MetadataTypeAndMetadataName[]): MetadataMap => {
const buildMapFromMetadata = (mdOption: MetadataOption, registry: RegistryAccess): MetadataMap => {
const mdMap: MetadataMap = new Map<string, string[]>();
components.map((cmp) => {
mdMap.set(cmp.type.name, [...(mdMap.get(cmp.type.name) ?? []), cmp.metadataName]);
});

// Add metadata type entries we were told to include
if (mdOption.metadataEntries?.length) {
mdOption.metadataEntries.map(entryToTypeAndName(registry)).map((cmp) => {
mdMap.set(cmp.type.name, [...(mdMap.get(cmp.type.name) ?? []), cmp.metadataName]);
});
}

// Build an array of excluded types from the options
if (mdOption.excludedEntries?.length) {
const excludedTypes: string[] = [];
mdOption.excludedEntries.map(entryToTypeAndName(registry)).map((cmp) => {
if (cmp.metadataName === '*') {
excludedTypes.push(cmp.type.name);
}
});
if (mdMap.size === 0) {
// we are excluding specific metadata types from all supported types
Object.values(registry.getRegistry().types).map((t) => {
if (!excludedTypes.includes(t.name)) {
mdMap.set(t.name, []);
}
});
}
}

return mdMap;
};
Loading

2 comments on commit 56435ca

@svc-cli-bot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 56435ca Previous: c48e6ba Ratio
eda-componentSetCreate-linux 215 ms 217 ms 0.99
eda-sourceToMdapi-linux 2000 ms 1973 ms 1.01
eda-sourceToZip-linux 1794 ms 1798 ms 1.00
eda-mdapiToSource-linux 2669 ms 2732 ms 0.98
lotsOfClasses-componentSetCreate-linux 428 ms 430 ms 1.00
lotsOfClasses-sourceToMdapi-linux 3596 ms 3637 ms 0.99
lotsOfClasses-sourceToZip-linux 2949 ms 2933 ms 1.01
lotsOfClasses-mdapiToSource-linux 3480 ms 3402 ms 1.02
lotsOfClassesOneDir-componentSetCreate-linux 751 ms 737 ms 1.02
lotsOfClassesOneDir-sourceToMdapi-linux 6423 ms 6210 ms 1.03
lotsOfClassesOneDir-sourceToZip-linux 5162 ms 5108 ms 1.01
lotsOfClassesOneDir-mdapiToSource-linux 6363 ms 6069 ms 1.05

This comment was automatically generated by workflow using github-action-benchmark.

@svc-cli-bot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 56435ca Previous: c48e6ba Ratio
eda-componentSetCreate-win32 695 ms 634 ms 1.10
eda-sourceToMdapi-win32 3988 ms 3693 ms 1.08
eda-sourceToZip-win32 3108 ms 2844 ms 1.09
eda-mdapiToSource-win32 6095 ms 5586 ms 1.09
lotsOfClasses-componentSetCreate-win32 1313 ms 1195 ms 1.10
lotsOfClasses-sourceToMdapi-win32 8217 ms 7576 ms 1.08
lotsOfClasses-sourceToZip-win32 5132 ms 4680 ms 1.10
lotsOfClasses-mdapiToSource-win32 8192 ms 7508 ms 1.09
lotsOfClassesOneDir-componentSetCreate-win32 2353 ms 2128 ms 1.11
lotsOfClassesOneDir-sourceToMdapi-win32 14768 ms 13362 ms 1.11
lotsOfClassesOneDir-sourceToZip-win32 8729 ms 8362 ms 1.04
lotsOfClassesOneDir-mdapiToSource-win32 13812 ms 13457 ms 1.03

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.