@@ -8,7 +8,7 @@ import type {
88 CheckDatasetsForPublicationResponse ,
99 GetEntryMetaStatusResponse ,
1010} from '../../types' ;
11- import type { EntryFields } from '../../us/types' ;
11+ import type { EntryFields , GetEntriesEntryResponse } from '../../us/types' ;
1212
1313export function filterEntirsForCheck ( entries : Pick < EntryFields , 'entryId' | 'scope' > [ ] ) {
1414 const datasetIds : string [ ] = [ ] ;
@@ -116,3 +116,157 @@ export function getEntryMetaStatusByError(errorWrapper: unknown): GetEntryMetaSt
116116 return { code : 'UNHANDLED' } ;
117117 }
118118}
119+
120+ export async function collectAllRelatedEntryIds ( {
121+ entryId,
122+ typedApi,
123+ ctx,
124+ } : {
125+ entryId : string ;
126+ typedApi : TypedApi ;
127+ ctx ?: { logError : ( message : string , error : Error ) => void } ;
128+ } ) : Promise < Set < string > > {
129+ const allRelatedEntryIds = new Set < string > ( ) ;
130+ const visited = new Set < string > ( ) ;
131+
132+ async function collectRelations ( currentEntryId : string ) : Promise < void > {
133+ if ( visited . has ( currentEntryId ) ) {
134+ return ;
135+ }
136+ visited . add ( currentEntryId ) ;
137+
138+ try {
139+ const relations = await typedApi . us . getRelations ( {
140+ entryId : currentEntryId ,
141+ direction : 'parent' ,
142+ } ) ;
143+
144+ for ( const relation of relations ) {
145+ allRelatedEntryIds . add ( relation . entryId ) ;
146+ await collectRelations ( relation . entryId ) ;
147+ }
148+ } catch ( error ) {
149+ ctx ?. logError ( `Error getting relations for entry ${ currentEntryId } ` , error as Error ) ;
150+ }
151+ }
152+
153+ await collectRelations ( entryId ) ;
154+
155+ return allRelatedEntryIds ;
156+ }
157+
158+ export async function fetchEntriesWithLinks ( {
159+ entryIds,
160+ typedApi,
161+ ctx,
162+ } : {
163+ entryIds : string [ ] ;
164+ typedApi : TypedApi ;
165+ ctx ?: { logError : ( message : string , error : Error ) => void } ;
166+ } ) : Promise < ( GetEntriesEntryResponse | null ) [ ] > {
167+ if ( entryIds . length === 0 ) {
168+ return [ ] ;
169+ }
170+
171+ const results : ( GetEntriesEntryResponse | null ) [ ] = [ ] ;
172+
173+ // Process in batches of 50 to avoid URL length limitations
174+ for ( let i = 0 ; i < entryIds . length ; i += 50 ) {
175+ const batchIds = entryIds . slice ( i , i + 50 ) ;
176+
177+ try {
178+ const entriesResult = await typedApi . us . getEntries ( {
179+ ids : batchIds ,
180+ includeLinks : true ,
181+ } ) ;
182+
183+ // Create map for quick lookup
184+ const entriesMap = new Map (
185+ entriesResult . entries . map ( ( entry ) => [ entry . entryId , entry ] ) ,
186+ ) ;
187+
188+ // Preserve order from batchIds
189+ batchIds . forEach ( ( id ) => {
190+ const entry = entriesMap . get ( id ) ;
191+ results . push ( entry || null ) ;
192+ } ) ;
193+ } catch ( error ) {
194+ ctx ?. logError ( `Error fetching entries batch (${ batchIds . length } IDs)` , error as Error ) ;
195+ results . push ( ...new Array ( batchIds . length ) . fill ( null ) ) ;
196+ }
197+ }
198+
199+ return results ;
200+ }
201+
202+ export function buildEnrichedLinksTree ( {
203+ entriesData,
204+ annotations,
205+ } : {
206+ entriesData : ( GetEntriesEntryResponse | null ) [ ] ;
207+ annotations ?: Array < {
208+ entryId : string ;
209+ result ?: {
210+ scope ?: EntryScope ;
211+ type ?: string ;
212+ annotation ?: {
213+ description ?: string ;
214+ } ;
215+ } ;
216+ error ?: {
217+ code ?: string ;
218+ message ?: string ;
219+ details ?: unknown ;
220+ } ;
221+ } > ;
222+ } ) {
223+ const linksTree : Record < string , { description ?: string ; links : Record < string , any > } > = { } ;
224+
225+ // Build basic tree structure
226+ for ( const entry of entriesData ) {
227+ if ( ! entry ) continue ;
228+
229+ linksTree [ entry . entryId ] = {
230+ links : { } ,
231+ } ;
232+
233+ if ( 'links' in entry && entry . links ) {
234+ for ( const [ _linkId , linkEntryId ] of Object . entries ( entry . links ) ) {
235+ if ( typeof linkEntryId === 'string' ) {
236+ linksTree [ entry . entryId ] . links [ linkEntryId ] = {
237+ entryId : linkEntryId ,
238+ links : { } ,
239+ } ;
240+ }
241+ }
242+ }
243+ }
244+
245+ // Enrich with annotations if provided
246+ if ( annotations ) {
247+ const annotationsMap = new Map (
248+ annotations
249+ . filter (
250+ ( ann ) : ann is typeof ann & { result : NonNullable < typeof ann . result > } =>
251+ 'result' in ann && Boolean ( ann . result ) ,
252+ )
253+ . map ( ( ann ) => [ ann . entryId , ann . result . annotation ?. description ] ) ,
254+ ) ;
255+
256+ for ( const [ currentEntryId , node ] of Object . entries ( linksTree ) ) {
257+ const description = annotationsMap . get ( currentEntryId ) ;
258+ if ( description ) {
259+ node . description = description ;
260+ }
261+
262+ for ( const [ linkId , linkNode ] of Object . entries ( node . links ) ) {
263+ const linkDescription = annotationsMap . get ( linkId ) ;
264+ if ( linkDescription ) {
265+ linkNode . description = linkDescription ;
266+ }
267+ }
268+ }
269+ }
270+
271+ return linksTree ;
272+ }
0 commit comments