fix(itglue): list flexible assets per type to satisfy API 422 requirement
IT Glue's /flexible_assets endpoint refuses requests without a filter[flexible-asset-type-id] (returns 422 "Cannot index flexible assets without providing a flexible asset type ID filter"). The analyzer's Stage 2 search was caught and tolerated, but never returned docs. Added getFlexibleAssetsForOrganization(orgId) on ITGlueClient. It fetches the type list once per process (memoized), then fans out per-type fetches with Promise.allSettled so a permission-restricted type doesn't poison the whole org. Wired into itglue-search and aggregate-persistence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9acf48e78a
commit
a0a6e7f192
3 changed files with 48 additions and 2 deletions
|
|
@ -237,7 +237,7 @@ async function fetchITGlueDocTitles(
|
|||
try {
|
||||
const org = await client.findOrganizationByName(name);
|
||||
if (!org) continue;
|
||||
const docs = await client.getFlexibleAssets({ organizationId: org.id });
|
||||
const docs = await client.getFlexibleAssetsForOrganization(org.id);
|
||||
const titles = docs
|
||||
.map((d) => (d as { name?: string }).name)
|
||||
.filter((t): t is string => typeof t === 'string')
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export async function itglueSearch(
|
|||
|
||||
// Flexible assets — runbooks, integrations, app-specific docs.
|
||||
try {
|
||||
const flex = await client.getFlexibleAssets({ organizationId: resolved.org_id });
|
||||
const flex = await client.getFlexibleAssetsForOrganization(resolved.org_id);
|
||||
for (const a of flex) {
|
||||
if (docs.length >= MAX_DOCS_RETURNED) break;
|
||||
const dedupeKey = `flex:${a.id}`;
|
||||
|
|
|
|||
|
|
@ -286,6 +286,52 @@ export class ITGlueClient {
|
|||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached, per-instance fetch of enabled flexible asset type ids. IT Glue's
|
||||
* /flexible_assets endpoint requires a flexibleAssetTypeId filter (otherwise
|
||||
* 422). This caches the type list so callers don't pay the lookup on every
|
||||
* call. Cache lives for the life of the process — types rarely change.
|
||||
*/
|
||||
private flexibleAssetTypesCache: Promise<ITGlueFlexibleAssetType[]> | null = null;
|
||||
private async cachedFlexibleAssetTypes(): Promise<ITGlueFlexibleAssetType[]> {
|
||||
if (!this.flexibleAssetTypesCache) {
|
||||
this.flexibleAssetTypesCache = this.getFlexibleAssetTypes().catch((err) => {
|
||||
// Re-throw next call so a transient failure isn't sticky.
|
||||
this.flexibleAssetTypesCache = null;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return this.flexibleAssetTypesCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get every flexible asset for a given organization across all enabled types.
|
||||
* IT Glue's /flexible_assets endpoint requires a per-type filter (the
|
||||
* `getFlexibleAssets` raw call returns 422 otherwise), so this helper
|
||||
* enumerates types and fans out per-type requests in parallel. Per-type
|
||||
* failures are tolerated so a single bad type doesn't poison the whole org.
|
||||
*/
|
||||
async getFlexibleAssetsForOrganization(
|
||||
organizationId: number | string
|
||||
): Promise<ITGlueFlexibleAsset[]> {
|
||||
const types = await this.cachedFlexibleAssetTypes();
|
||||
const enabled = types.filter((t) => t.enabled);
|
||||
const results = await Promise.allSettled(
|
||||
enabled.map((t) =>
|
||||
this.getFlexibleAssets({
|
||||
organizationId,
|
||||
flexibleAssetTypeId: t.id,
|
||||
})
|
||||
)
|
||||
);
|
||||
const out: ITGlueFlexibleAsset[] = [];
|
||||
for (const r of results) {
|
||||
if (r.status === 'fulfilled') out.push(...r.value);
|
||||
// Tolerate per-type failures — common for permission-restricted types.
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ─── Configurations ───────────────────────────────────────────────────────
|
||||
|
||||
private mapConfiguration(item: any): ITGlueConfiguration {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue