Skip to content

Commit f359e4d

Browse files
authored
W-17669669: add pseudo-type support to ComponentSetBuilder (#1494)
* feat: add pseudo-type support to ComponentSetBuilder * chore: bump core to 8.8.2 * fix: review updates * fix: query for BotVersions during org metadata resolution
1 parent 8781f0b commit f359e4d

File tree

4 files changed

+309
-60
lines changed

4 files changed

+309
-60
lines changed

src/collections/componentSetBuilder.ts

+93-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import * as path from 'node:path';
9-
import { Logger, Messages, SfError, StateAggregator } from '@salesforce/core';
9+
import { AuthInfo, Connection, Logger, Messages, SfError, StateAggregator, trimTo15 } from '@salesforce/core';
1010
import fs from 'graceful-fs';
1111
import { minimatch } from 'minimatch';
1212
import { MetadataComponent } from '../resolve/types';
@@ -78,6 +78,8 @@ const getLogger = (): Logger => {
7878
return logger;
7979
};
8080

81+
const PSEUDO_TYPES = { AGENT: 'Agent' };
82+
8183
export class ComponentSetBuilder {
8284
/**
8385
* Builds a ComponentSet that can be used for source conversion,
@@ -188,7 +190,7 @@ export class ComponentSetBuilder {
188190
// Resolve metadata entries with an org connection
189191
if (org) {
190192
componentSet ??= new ComponentSet(undefined, registry);
191-
const orgComponentSet = await this.resolveOrgComponents(registry, org, metadata);
193+
const orgComponentSet = await this.resolveOrgComponents(registry, options);
192194
orgComponentSet.toArray().map(addToComponentSet(componentSet));
193195
}
194196

@@ -204,14 +206,26 @@ export class ComponentSetBuilder {
204206

205207
private static async resolveOrgComponents(
206208
registry: RegistryAccess,
207-
org: OrgOption,
208-
metadata?: MetadataOption
209+
options: ComponentSetOptions
209210
): Promise<ComponentSet> {
211+
// Get a connection from the OrgOption
212+
const { apiversion, org, metadata } = options;
213+
if (!org) {
214+
throw SfError.create({ message: 'ComponentSetBuilder.resolveOrgComponents() requires an OrgOption' });
215+
}
216+
const username = (await StateAggregator.getInstance()).aliases.getUsername(org.username) ?? org.username;
217+
const connection = await Connection.create({ authInfo: await AuthInfo.create({ username }) });
218+
if (apiversion) {
219+
connection.setApiVersion(apiversion);
220+
}
221+
210222
let mdMap = new Map() as MetadataMap;
211-
let debugMsg = `Building ComponentSet from metadata in an org using targetUsername: ${org.username}`;
223+
let debugMsg = `Building ComponentSet from metadata in an org using targetUsername: ${username}`;
212224
if (metadata) {
213225
if (metadata.metadataEntries?.length) {
214226
debugMsg += ` filtering on metadata: ${metadata.metadataEntries.toString()}`;
227+
// Replace pseudo-types from the metadataEntries
228+
metadata.metadataEntries = await replacePseudoTypes(metadata.metadataEntries, connection);
215229
}
216230
if (metadata.excludedEntries?.length) {
217231
debugMsg += ` excluding metadata: ${metadata.excludedEntries.toString()}`;
@@ -221,7 +235,7 @@ export class ComponentSetBuilder {
221235
getLogger().debug(debugMsg);
222236

223237
return ComponentSet.fromConnection({
224-
usernameOrConnection: (await StateAggregator.getInstance()).aliases.getUsername(org.username) ?? org.username,
238+
usernameOrConnection: connection,
225239
componentFilter: getOrgComponentFilter(org, mdMap, metadata),
226240
metadataTypes: mdMap.size ? Array.from(mdMap.keys()) : undefined,
227241
registry,
@@ -369,3 +383,76 @@ const buildMapFromMetadata = (mdOption: MetadataOption, registry: RegistryAccess
369383

370384
return mdMap;
371385
};
386+
387+
// Replace pseudo types with actual types.
388+
const replacePseudoTypes = async (mdEntries: string[], connection: Connection): Promise<string[]> => {
389+
const pseudoEntries: string[][] = [];
390+
let replacedEntries: string[] = [];
391+
392+
mdEntries.map((rawEntry) => {
393+
const [typeName, ...name] = rawEntry.split(':');
394+
if (Object.values(PSEUDO_TYPES).includes(typeName)) {
395+
pseudoEntries.push([typeName, name.join(':').trim()]);
396+
} else {
397+
replacedEntries.push(rawEntry);
398+
}
399+
});
400+
401+
if (pseudoEntries.length) {
402+
await Promise.all(
403+
pseudoEntries.map(async (pseudoEntry) => {
404+
const pseudoType = pseudoEntry[0];
405+
const pseudoName = pseudoEntry[1] || '*';
406+
getLogger().debug(`Converting pseudo-type ${pseudoType}:${pseudoName}`);
407+
if (pseudoType === PSEUDO_TYPES.AGENT) {
408+
const agentMdEntries = await buildAgentMdEntries(pseudoName, connection);
409+
replacedEntries = [...replacedEntries, ...agentMdEntries];
410+
}
411+
})
412+
);
413+
}
414+
415+
return replacedEntries;
416+
};
417+
418+
// From a Bot developer name, get all related BotVersion, GenAiPlanner, and GenAiPlugin metadata.
419+
const buildAgentMdEntries = async (botName: string, connection: Connection): Promise<string[]> => {
420+
if (botName === '*') {
421+
// Get all Agent top level metadata
422+
return Promise.resolve(['Bot', 'BotVersion', 'GenAiPlanner', 'GenAiPlugin']);
423+
}
424+
425+
const mdEntries = [`Bot:${botName}`, `BotVersion:${botName}.v1`, `GenAiPlanner:${botName}`];
426+
427+
try {
428+
// Query for the GenAiPlannerId
429+
const genAiPlannerIdQuery = `SELECT Id FROM GenAiPlannerDefinition WHERE DeveloperName = '${botName}'`;
430+
const plannerId = (await connection.singleRecordQuery<{ Id: string }>(genAiPlannerIdQuery, { tooling: true })).Id;
431+
432+
if (plannerId) {
433+
const plannerId15 = trimTo15(plannerId);
434+
// Query for the GenAiPlugins associated with the 15 char GenAiPlannerId
435+
const genAiPluginNames = (
436+
await connection.tooling.query<{ DeveloperName: string }>(
437+
`SELECT DeveloperName FROM GenAiPluginDefinition WHERE DeveloperName LIKE 'p_${plannerId15}%'`
438+
)
439+
).records;
440+
if (genAiPluginNames.length) {
441+
genAiPluginNames.map((r) => mdEntries.push(`GenAiPlugin:${r.DeveloperName}`));
442+
} else {
443+
getLogger().debug(`No GenAiPlugin metadata matches for plannerId: ${plannerId15}`);
444+
}
445+
} else {
446+
getLogger().debug(`No GenAiPlanner metadata matches for Bot: ${botName}`);
447+
}
448+
} catch (err) {
449+
const wrappedErr = SfError.wrap(err);
450+
getLogger().debug(`Error when querying for GenAiPlugin by Bot name: ${botName}\n${wrappedErr.message}`);
451+
if (wrappedErr.stack) {
452+
getLogger().debug(wrappedErr.stack);
453+
}
454+
}
455+
456+
// Get specific Agent top level metadata.
457+
return Promise.resolve(mdEntries);
458+
};

src/resolve/connectionResolver.ts

+28
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,34 @@ const listMembers =
283283
return [];
284284
}
285285

286+
// Workaround because metadata.list({ type: 'BotVersion' }) returns [].
287+
if (mdType.name === 'BotVersion') {
288+
try {
289+
const botDefQuery = 'SELECT Id, DeveloperName FROM BotDefinition';
290+
const botVersionQuery = 'SELECT BotDefinitionId, DeveloperName FROM BotVersion';
291+
const botDefs = (await connection.query<{ Id: string; DeveloperName: string }>(botDefQuery)).records;
292+
const botVersionDefs = (
293+
await connection.query<{ BotDefinitionId: string; DeveloperName: string }>(botVersionQuery)
294+
).records;
295+
return botVersionDefs
296+
.map((bvd) => {
297+
const botName = botDefs.find((bd) => bd.Id === bvd.BotDefinitionId)?.DeveloperName;
298+
if (botName) {
299+
return {
300+
fullName: `${botName}.${bvd.DeveloperName}`,
301+
fileName: `bots/${bvd.DeveloperName}.botVersion`,
302+
type: 'BotVersion',
303+
};
304+
}
305+
})
306+
.filter((b) => !!b);
307+
} catch (error) {
308+
const err = SfError.wrap(error);
309+
getLogger().debug(`[${mdType.name}] ${err.message}`);
310+
return [];
311+
}
312+
}
313+
286314
try {
287315
requestCount++;
288316
getLogger().debug(`listMetadata for ${inspect(query)}`);

0 commit comments

Comments
 (0)