@@ -12,7 +12,9 @@ const _ = require('#helpers/lodash');
1212const config = require ( '#config' ) ;
1313const isRetryableError = require ( '#helpers/is-retryable-error' ) ;
1414const logger = require ( '#helpers/logger' ) ;
15- const retryRequest = require ( '#helpers/retry-request' ) ;
15+ const retryLaunchpadRequest = require ( '#helpers/retry-launchpad-request' ) ;
16+
17+ const { LAUNCHPAD_ADDRESS_FAMILY } = retryLaunchpadRequest ;
1618
1719const PAGE_RETRIES = 2 ;
1820
@@ -54,8 +56,8 @@ function addToSet(entries, set) {
5456 }
5557}
5658
57- async function fetchPage ( url , resolver , name , pageCount ) {
58- return pRetry ( ( ) => retryRequest ( url , { resolver } ) , {
59+ async function fetchPage ( url , name , pageCount ) {
60+ return pRetry ( ( ) => retryLaunchpadRequest ( url ) , {
5961 retries : PAGE_RETRIES ,
6062 async onFailedAttempt ( err ) {
6163 if ( ! isRetryableError ( err ) ) throw err ;
@@ -64,74 +66,139 @@ async function fetchPage(url, resolver, name, pageCount) {
6466 err,
6567 url,
6668 attemptNumber : err . attemptNumber ,
67- retriesLeft : err . retriesLeft
69+ retriesLeft : err . retriesLeft ,
70+ launchpadAddressFamily : LAUNCHPAD_ADDRESS_FAMILY
6871 } ) ;
6972 }
7073 } ) ;
7174}
7275
73- async function getUbuntuMembersMap ( resolver ) {
76+ async function getUbuntuMembersMap ( ) {
7477 const map = new Map ( ) ;
7578
7679 // set a date so we can use it for cache checks
7780 map [ Symbol . for ( 'createdAt' ) ] = new Date ( ) ;
7881
7982 await pMapSeries ( Object . keys ( config . ubuntuTeamMapping ) , async ( name ) => {
8083 const set = new Set ( ) ;
84+ const teamPath = config . ubuntuTeamMapping [ name ] ;
85+ const totalSizes = new Set ( ) ;
86+ const seenUrls = new Set ( ) ;
8187
8288 // Initialize pagination variables
83- let url = `https://api.launchpad.net/1.0/${ config . ubuntuTeamMapping [ name ] } /participants` ;
89+ let url = `https://api.launchpad.net/1.0/${ teamPath } /participants` ;
8490 let totalProcessed = 0 ;
8591 let pageCount = 0 ;
8692 const maxPages = 1000 ; // Safety limit to prevent infinite loops
8793
94+ logger . debug ( `starting ubuntu membership fetch for ${ name } ` , {
95+ team : name ,
96+ teamPath,
97+ url,
98+ launchpadAddressFamily : LAUNCHPAD_ADDRESS_FAMILY
99+ } ) ;
100+
88101 // Paginate through all results using next_collection_link
89102 while ( url && pageCount < maxPages ) {
90- pageCount ++ ;
91- logger . debug (
92- `${ name } fetching page ${ pageCount } , processed ${ totalProcessed } entries`
93- ) ;
94-
95- const response = await fetchPage ( url , resolver , name , pageCount ) ;
96-
97- const json = await response . body . json ( ) ;
98-
99- if ( ! Number . isFinite ( json . total_size ) || json . total_size < 0 )
100- throw new TypeError ( 'Property "total_size" was invalid' ) ;
101-
102- // Add entries from current page
103- addToSet ( json . entries , set ) ;
104- totalProcessed += json . entries . length ;
103+ const currentUrl = url ;
104+ if ( seenUrls . has ( currentUrl ) ) {
105+ const err = new TypeError ( 'Property "next_collection_link" repeated' ) ;
106+ err . team = name ;
107+ err . teamPath = teamPath ;
108+ err . pageCount = pageCount ;
109+ err . totalProcessed = totalProcessed ;
110+ err . url = currentUrl ;
111+ throw err ;
112+ }
105113
106- // Check if there's a next page
107- if ( json . next_collection_link && isURL ( json . next_collection_link ) ) {
108- // Safeguard - ensure it's a valid Launchpad API URL
109- if ( ! json . next_collection_link . startsWith ( 'https://api.launchpad.net/' ) )
110- throw new TypeError (
111- 'Property "next_collection_link" is not a valid API link'
112- ) ;
114+ seenUrls . add ( currentUrl ) ;
115+ pageCount ++ ;
116+ logger . debug ( `${ name } fetching page ${ pageCount } ` , {
117+ team : name ,
118+ teamPath,
119+ pageCount,
120+ totalProcessed,
121+ url : currentUrl ,
122+ launchpadAddressFamily : LAUNCHPAD_ADDRESS_FAMILY
123+ } ) ;
113124
114- url = json . next_collection_link ;
115- } else {
116- url = null ; // No more pages
125+ try {
126+ const response = await fetchPage ( currentUrl , name , pageCount ) ;
127+ const json = await response . body . json ( ) ;
128+
129+ if ( ! Number . isFinite ( json . total_size ) || json . total_size < 0 )
130+ throw new TypeError ( 'Property "total_size" was invalid' ) ;
131+
132+ totalSizes . add ( json . total_size ) ;
133+
134+ // Add entries from current page
135+ addToSet ( json . entries , set ) ;
136+ totalProcessed += json . entries . length ;
137+
138+ // Check if there's a next page
139+ if ( json . next_collection_link && isURL ( json . next_collection_link ) ) {
140+ // Safeguard - ensure it's a valid Launchpad API URL
141+ if (
142+ ! json . next_collection_link . startsWith ( 'https://api.launchpad.net/' )
143+ )
144+ throw new TypeError (
145+ 'Property "next_collection_link" is not a valid API link'
146+ ) ;
147+
148+ url = json . next_collection_link ;
149+ } else {
150+ url = null ; // No more pages
151+ }
152+
153+ logger . debug ( `${ name } page ${ pageCount } fetched` , {
154+ team : name ,
155+ teamPath,
156+ pageCount,
157+ entries : json . entries . length ,
158+ totalProcessed,
159+ totalSize : json . total_size ,
160+ nextCollectionLink : url ,
161+ uniqueMembers : set . size ,
162+ launchpadAddressFamily : LAUNCHPAD_ADDRESS_FAMILY
163+ } ) ;
164+ } catch ( err ) {
165+ err . team = name ;
166+ err . teamPath = teamPath ;
167+ err . pageCount = pageCount ;
168+ err . totalProcessed = totalProcessed ;
169+ err . url = currentUrl ;
170+ err . isRetryable = isRetryableError ( err ) ;
171+ throw err ;
117172 }
118-
119- // Log progress and detect total_size changes
120- logger . debug (
121- `${ name } page ${ pageCount } : processed ${ json . entries . length } entries, total: ${ totalProcessed } , API total_size: ${ json . total_size } `
122- ) ;
123173 }
124174
125175 // Warn if we hit the safety limit
126176 if ( pageCount >= maxPages ) {
127- logger . warn (
177+ await logger . warn (
128178 `${ name } hit maximum page limit (${ maxPages } ), may have incomplete data`
129179 ) ;
130180 }
131181
132- logger . debug (
133- `${ name } completed: ${ totalProcessed } total entries processed across ${ pageCount } pages`
134- ) ;
182+ if ( totalSizes . size > 1 ) {
183+ await logger . warn ( `${ name } total_size changed during pagination` , {
184+ team : name ,
185+ teamPath,
186+ totalSizes : [ ...totalSizes ] ,
187+ pages : pageCount ,
188+ totalProcessed,
189+ uniqueMembers : set . size
190+ } ) ;
191+ }
192+
193+ logger . debug ( `${ name } completed ubuntu membership fetch` , {
194+ team : name ,
195+ teamPath,
196+ totalProcessed,
197+ uniqueMembers : set . size ,
198+ pages : pageCount ,
199+ totalSizes : [ ...totalSizes ] ,
200+ launchpadAddressFamily : LAUNCHPAD_ADDRESS_FAMILY
201+ } ) ;
135202 map . set ( name , set ) ;
136203 } ) ;
137204
0 commit comments