diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8367670..e8b1260 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,6 +84,7 @@ jobs: no-commit: true show-skill-number: 8 show-event-badge: 'number' + show-badge: 'icon' - name: Print Output id: output diff --git a/__tests__/getstats.test.js b/__tests__/getstats.test.js index cf593a4..f0bb5e6 100644 --- a/__tests__/getstats.test.js +++ b/__tests__/getstats.test.js @@ -10,7 +10,7 @@ const { fetchTrailblazerEarnedStampsInfo } = require('./../src/getStats') -describe('fetchTrailblazerRankInfo', () => { +describe('fetches successfully data from the API', () => { afterEach(() => { jest.clearAllMocks() }) @@ -18,7 +18,9 @@ describe('fetchTrailblazerRankInfo', () => { it('fetches successfully data from the API: TrailblazerRank', async () => { const mockData = { data: { - /* mock response data */ + profile: { + /* mock profile data */ + } } } axios.post.mockResolvedValue(mockData) @@ -44,7 +46,12 @@ describe('fetchTrailblazerRankInfo', () => { it('fetches successfully data from the API: TrailblazerBadges', async () => { const mockData = { data: { - /* mock response data */ + profile: { + earnedAwards: { + edges: [], + pageInfo: null + } + } } } axios.post.mockResolvedValue(mockData) @@ -56,9 +63,8 @@ describe('fetchTrailblazerRankInfo', () => { { query: expect.any(String), variables: { - count: 10, + count: 100, after: null, - filter: null, hasSlug: true, slug: 'testUsername' } @@ -67,13 +73,18 @@ describe('fetchTrailblazerRankInfo', () => { headers: { 'Content-Type': 'application/json' } } ) - expect(response).toEqual(mockData.data) + expect(response).toEqual(mockData) }) it('fetches successfully data from the API: TrailblazerSuperBadges', async () => { const mockData = { data: { - /* mock response data */ + profile: { + earnedAwards: { + edges: [], + pageInfo: null + } + } } } axios.post.mockResolvedValue(mockData) @@ -96,13 +107,18 @@ describe('fetchTrailblazerRankInfo', () => { headers: { 'Content-Type': 'application/json' } } ) - expect(response).toEqual(mockData.data) + expect(response).toEqual(mockData) }) it('fetches successfully data from the API: TrailblazerEventBadges', async () => { const mockData = { data: { - /* mock response data */ + profile: { + earnedAwards: { + edges: [], + pageInfo: null + } + } } } axios.post.mockResolvedValue(mockData) @@ -125,13 +141,24 @@ describe('fetchTrailblazerRankInfo', () => { headers: { 'Content-Type': 'application/json' } } ) - expect(response).toEqual(mockData.data) + expect(response).toEqual(mockData) }) it('fetches successfully data from the API: TrailblazerCertifs', async () => { const mockData = { data: { - /* mock response data */ + data: { + profile: { + __typename: 'Profile', + id: '123', + credential: { + messages: ['message1', 'message2'], + messagesOnly: ['messageOnly1', 'messageOnly2'], + brands: ['brand1', 'brand2'], + certifications: ['certification1', 'certification2'] + } + } + } } } axios.post.mockResolvedValue(mockData) @@ -144,7 +171,8 @@ describe('fetchTrailblazerRankInfo', () => { query: expect.any(String), variables: { hasSlug: true, - slug: 'testUsername' + slug: 'testUsername', + count: 100 } }, { @@ -157,7 +185,11 @@ describe('fetchTrailblazerRankInfo', () => { it('fetches successfully data from the API: TrailblazerSkills', async () => { const mockData = { data: { - /* mock response data */ + data: { + profile: { + earnedSkills: [] + } + } } } axios.post.mockResolvedValue(mockData) @@ -183,7 +215,12 @@ describe('fetchTrailblazerRankInfo', () => { it('fetches successfully data from the API: EarnedStamps', async () => { const mockData = { data: { - /* mock response data */ + data: { + earnedStamps: { + edges: [], + pageInfo: null + } + } } } axios.post.mockResolvedValue(mockData) @@ -195,7 +232,10 @@ describe('fetchTrailblazerRankInfo', () => { { query: expect.any(String), variables: { - first: 10, + first: 100, + count: 100, + after: null, + hasSlug: true, slug: 'testUsername' } }, @@ -205,6 +245,120 @@ describe('fetchTrailblazerRankInfo', () => { ) expect(response).toEqual(mockData.data) }) +}) + +describe('fetches successfully data from the API in multiple pages', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + // it('fetches successfully data in multiple pages from the API: TrailblazerBadges', async () => { + // const mockDataPage1 = { + // data: { + // profile: { + // profileData : { + // __typename: null, + // trailheadStats: null + // }, + // earnedAwards: { + // edges: ['edge1'], + // pageInfo: { + // hasNextPage: true, + // endCursor: 'cursor1' + // } + // } + // } + // } + // } + + // const mockDataPage2 = { + // data: { + // profile: { + // profileData : { + // __typename: null, + // trailheadStats: null + // }, + // earnedAwards: { + // edges: ['edge2'], + // pageInfo: { + // hasNextPage: false, + // endCursor: 'cursor2' + // } + // } + // } + // } + // } + + // axios.post + // .mockResolvedValueOnce(mockDataPage1) + // .mockResolvedValueOnce(mockDataPage2) + + // const response = await fetchTrailblazerBadgesInfo('testUsername') + + // expect(axios.post).toHaveBeenCalledTimes(2) + // expect(response).toEqual({ + // data: { + // profile: { + // earnedAwards: { + // edges: ['edge1', 'edge2'], + // pageInfo: mockDataPage2.data.profile.earnedAwards.pageInfo + // } + // } + // } + // }) + // }) + + it('fetches successfully data in multiple pages from the API: EarnedStamps', async () => { + const mockDataPage1 = { + data: { + data: { + earnedStamps: { + edges: ['edge1'], + pageInfo: { + hasNextPage: true, + endCursor: 'cursor1' + } + } + } + } + } + + const mockDataPage2 = { + data: { + data: { + earnedStamps: { + edges: ['edge2'], + pageInfo: { + hasNextPage: false, + endCursor: 'cursor2' + } + } + } + } + } + + axios.post + .mockResolvedValueOnce(mockDataPage1) + .mockResolvedValueOnce(mockDataPage2) + + const response = await fetchTrailblazerEarnedStampsInfo('testUsername') + + expect(axios.post).toHaveBeenCalledTimes(2) + expect(response).toEqual({ + data: { + earnedStamps: { + edges: ['edge1', 'edge2'], + pageInfo: mockDataPage2.data.data.earnedStamps.pageInfo + } + } + }) + }) +}) + +describe('handles API error', () => { + afterEach(() => { + jest.clearAllMocks() + }) it('handles API error : TrailblazerRank', async () => { const errorMessage = 'Network Error' @@ -237,6 +391,9 @@ describe('fetchTrailblazerRankInfo', () => { const errorMessage = 'Network Error' axios.post.mockRejectedValue(new Error(errorMessage)) console.error = jest.fn() + const mockErrorResponse = { + data: { profile: { earnedAwards: { edges: [], pageInfo: null } } } + } const response = await fetchTrailblazerBadgesInfo('testUsername') @@ -245,9 +402,8 @@ describe('fetchTrailblazerRankInfo', () => { { query: expect.any(String), variables: { - count: 10, + count: 100, after: null, - filter: null, hasSlug: true, slug: 'testUsername' } @@ -256,7 +412,7 @@ describe('fetchTrailblazerRankInfo', () => { headers: { 'Content-Type': 'application/json' } } ) - expect(response).toBeNull() + expect(response).toEqual(mockErrorResponse) expect(console.error).toHaveBeenCalledWith( 'Error fetching data: ', expect.any(Error) @@ -267,6 +423,9 @@ describe('fetchTrailblazerRankInfo', () => { const errorMessage = 'Network Error' axios.post.mockRejectedValue(new Error(errorMessage)) console.error = jest.fn() + const mockErrorResponse = { + data: { profile: { earnedAwards: { edges: [], pageInfo: null } } } + } const response = await fetchTrailblazerSuperBadgesInfo('testUsername') @@ -286,7 +445,7 @@ describe('fetchTrailblazerRankInfo', () => { headers: { 'Content-Type': 'application/json' } } ) - expect(response).toBeNull() + expect(response).toEqual(mockErrorResponse) expect(console.error).toHaveBeenCalledWith( 'Error fetching data: ', expect.any(Error) @@ -297,6 +456,9 @@ describe('fetchTrailblazerRankInfo', () => { const errorMessage = 'Network Error' axios.post.mockRejectedValue(new Error(errorMessage)) console.error = jest.fn() + const mockErrorResponse = { + data: { profile: { earnedAwards: { edges: [], pageInfo: null } } } + } const response = await fetchTrailblazerEventBadgesInfo('testUsername') @@ -316,7 +478,7 @@ describe('fetchTrailblazerRankInfo', () => { headers: { 'Content-Type': 'application/json' } } ) - expect(response).toBeNull() + expect(response).toEqual(mockErrorResponse) expect(console.error).toHaveBeenCalledWith( 'Error fetching data: ', expect.any(Error) @@ -327,6 +489,15 @@ describe('fetchTrailblazerRankInfo', () => { const errorMessage = 'Network Error' axios.post.mockRejectedValue(new Error(errorMessage)) console.error = jest.fn() + const mockErrorResponse = { + data: { + profile: { + credential: { + certifications: [] + } + } + } + } const response = await fetchTrailblazerCertifsInfo('testUsername') @@ -336,14 +507,15 @@ describe('fetchTrailblazerRankInfo', () => { query: expect.any(String), variables: { hasSlug: true, - slug: 'testUsername' + slug: 'testUsername', + count: 100 } }, { headers: { 'Content-Type': 'application/json' } } ) - expect(response).toBeNull() + expect(response).toEqual(mockErrorResponse) expect(console.error).toHaveBeenCalledWith( 'Error fetching data: ', expect.any(Error) @@ -354,6 +526,9 @@ describe('fetchTrailblazerRankInfo', () => { const errorMessage = 'Network Error' axios.post.mockRejectedValue(new Error(errorMessage)) console.error = jest.fn() + const mockErrorResponse = { + data: { profile: { earnedSkills: [] } } + } const response = await fetchTrailblazerSkillsInfo('testUsername') @@ -370,7 +545,7 @@ describe('fetchTrailblazerRankInfo', () => { headers: { 'Content-Type': 'application/json' } } ) - expect(response).toBeNull() + expect(response).toEqual(mockErrorResponse) expect(console.error).toHaveBeenCalledWith( 'Error fetching data: ', expect.any(Error) @@ -381,6 +556,9 @@ describe('fetchTrailblazerRankInfo', () => { const errorMessage = 'Network Error' axios.post.mockRejectedValue(new Error(errorMessage)) console.error = jest.fn() + const mockErrorResponse = { + data: { earnedStamps: { edges: [], pageInfo: null } } + } const response = await fetchTrailblazerEarnedStampsInfo('testUsername') @@ -389,7 +567,10 @@ describe('fetchTrailblazerRankInfo', () => { { query: expect.any(String), variables: { - first: 10, + after: null, + count: 100, + first: 100, + hasSlug: true, slug: 'testUsername' } }, @@ -397,7 +578,7 @@ describe('fetchTrailblazerRankInfo', () => { headers: { 'Content-Type': 'application/json' } } ) - expect(response).toBeNull() + expect(response).toEqual(mockErrorResponse) expect(console.error).toHaveBeenCalledWith( 'Error fetching data: ', expect.any(Error) diff --git a/badges/coverage.svg b/badges/coverage.svg index 752a3d6..0fa4b81 100644 --- a/badges/coverage.svg +++ b/badges/coverage.svg @@ -1 +1 @@ -Coverage: 97.73%Coverage97.73% \ No newline at end of file +Coverage: 90.71%Coverage90.71% \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 77768fa..048312b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -292232,14 +292232,122 @@ const { GET_TRAILBLAZER_EARNED_STAMPS } = __nccwpck_require__(37056) -async function fetchTrailblazerRankInfo(trailheadUsername) { - const endpoint = 'https://profile.api.trailhead.com/graphql' +// Define the extractData function for each specific function +const extractRankData = (response, profileData, allEdges, pageInfo) => { + if (!profileData) { + profileData = { + __typename: response.data.data.profile.__typename, + trailheadStats: response.data.data.profile.trailheadStats + } + } - const graphqlQuery = { - query: GET_TRAILBLAZER_RANK, - variables: { - hasSlug: true, - slug: trailheadUsername + return { + newProfileData: profileData, + newAllEdges: allEdges, + newPageInfo: pageInfo + } +} + +const extractBadgesData = (response, profileData, allEdges, pageInfo) => { + if (!profileData) { + profileData = { + __typename: response.data.data.profile.__typename, + trailheadStats: response.data.data.profile.trailheadStats + } + } + + allEdges = allEdges.concat(response.data.data.profile.earnedAwards.edges) + pageInfo = response.data.data.profile.earnedAwards.pageInfo + + return { + newProfileData: profileData, + newAllEdges: allEdges, + newPageInfo: pageInfo + } +} + +const extractCertifsData = (response, profileData, allEdges, pageInfo) => { + if (!profileData) { + profileData = { + __typename: response.data.data.profile.__typename, + id: response.data.data.profile.id, + credential: { + messages: response.data.data.profile.credential.messages, + messagesOnly: response.data.data.profile.credential.messagesOnly, + brands: response.data.data.profile.credential.brands + } + } + } + + allEdges = allEdges.concat( + response.data.data.profile.credential.certifications + ) + + return { + newProfileData: profileData, + newAllEdges: allEdges, + newPageInfo: pageInfo + } +} + +const extractSkillsData = (response, profileData, allEdges, pageInfo) => { + if (!profileData) { + profileData = { + __typename: response.data.data.profile.__typename, + id: response.data.data.profile.id + } + } + + allEdges = allEdges.concat(response.data.data.profile.earnedSkills) + + return { + newProfileData: profileData, + newAllEdges: allEdges, + newPageInfo: pageInfo + } +} + +const extractStampsData = (response, profileData, allEdges, pageInfo) => { + allEdges = allEdges.concat(response.data.data.earnedStamps.edges) + pageInfo = response.data.data.earnedStamps.pageInfo + + return { + newProfileData: profileData, + newAllEdges: allEdges, + newPageInfo: pageInfo + } +} + +// Define the factorized fetchData function +async function fetchData( + endpoint, + graphqlQuery, + extractData, + maxPage = 100, + currentPage = 1, + profileData = null, + allEdges = [], + pageInfo = null, + maxRetry = 3, + currentRetry = 0 +) { + console.log('fetchData') + // console.log('endpoint: ', endpoint) + // console.log('graphqlQuery: ', graphqlQuery) + // console.log('maxPage: ', maxPage) + // console.log('currentPage: ', currentPage) + // console.log('profileData: ', profileData) + // console.log('allEdges: ', allEdges) + // console.log('pageInfo: ', pageInfo) + + if (currentPage > maxPage) { + return { + data: { + profile: { + ...profileData, + earnedAwards: { edges: allEdges, pageInfo } + } + } } } @@ -292250,20 +292358,80 @@ async function fetchTrailblazerRankInfo(trailheadUsername) { } }) - return response.data + const { newProfileData, newAllEdges, newPageInfo } = extractData( + response, + profileData, + allEdges, + pageInfo + ) + + if (newPageInfo != undefined && !newPageInfo.hasNextPage) { + console.log('newPageInfo: ', newPageInfo) + return { + data: { + profile: { + ...newProfileData, + earnedAwards: { edges: newAllEdges, pageInfo: newPageInfo } + } + } + } + } + + return await fetchData( + endpoint, + graphqlQuery, + extractData, + maxPage, + currentPage + 1, + newProfileData, + newAllEdges, + newPageInfo, + maxRetry, + currentRetry + ) } catch (error) { console.error('Error fetching data: ', error) - return null + if (error.response && error.response.status === 429) { + if (currentRetry < maxRetry) { + console.log('Too many requests, waiting for 1 minute before retrying...') + await new Promise(resolve => setTimeout(resolve, 60000)) // wait for 1 minute + return await fetchData( + endpoint, + graphqlQuery, + extractData, + maxPage, + currentPage, + profileData, + allEdges, + pageInfo, + maxRetry, + currentRetry + 1 + ) + } else { + console.error('Max retry limit reached') + throw error + } + } else { + return { + data: { + profile: { + ...profileData, + earnedAwards: { edges: allEdges, pageInfo } + } + } + } + } } } -async function fetchTrailblazerBadgesInfo(trailheadUsername) { +// Use the fetchData function in each specific function +async function fetchTrailblazerRankInfo(trailheadUsername) { + console.log('fetchTrailblazerRankInfo') const endpoint = 'https://profile.api.trailhead.com/graphql' - const graphqlQuery = { - query: GET_TRAILBLAZER_BADGES, + query: GET_TRAILBLAZER_RANK, variables: { - count: 10, + count: 100, after: null, filter: null, hasSlug: true, @@ -292271,23 +292439,29 @@ async function fetchTrailblazerBadgesInfo(trailheadUsername) { } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) + return await fetchData(endpoint, graphqlQuery, extractRankData, 2) +} - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null +async function fetchTrailblazerBadgesInfo(trailheadUsername) { + console.log('fetchTrailblazerBadgesInfo') + const endpoint = 'https://profile.api.trailhead.com/graphql' + const graphqlQuery = { + query: GET_TRAILBLAZER_BADGES, + variables: { + count: 100, + after: null, + filter: null, + hasSlug: true, + slug: trailheadUsername + } } + + return await fetchData(endpoint, graphqlQuery, extractBadgesData) } async function fetchTrailblazerSuperBadgesInfo(trailheadUsername) { + console.log('fetchTrailblazerSuperBadgesInfo') const endpoint = 'https://profile.api.trailhead.com/graphql' - const graphqlQuery = { query: GET_TRAILBLAZER_BADGES, variables: { @@ -292299,23 +292473,12 @@ async function fetchTrailblazerSuperBadgesInfo(trailheadUsername) { } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) - - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null - } + return await fetchData(endpoint, graphqlQuery, extractBadgesData) } async function fetchTrailblazerEventBadgesInfo(trailheadUsername) { + console.log('fetchTrailblazerEventBadgesInfo') const endpoint = 'https://profile.api.trailhead.com/graphql' - const graphqlQuery = { query: GET_TRAILBLAZER_BADGES, variables: { @@ -292327,48 +292490,27 @@ async function fetchTrailblazerEventBadgesInfo(trailheadUsername) { } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) - - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null - } + return await fetchData(endpoint, graphqlQuery, extractBadgesData) } async function fetchTrailblazerCertifsInfo(trailheadUsername) { + console.log('fetchTrailblazerCertifsInfo') const endpoint = 'https://profile.api.trailhead.com/graphql' - const graphqlQuery = { query: GET_TRAILBLAZER_CERTIFS, variables: { hasSlug: true, - slug: trailheadUsername + slug: trailheadUsername, + count: 100 } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) - - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null - } + return await fetchData(endpoint, graphqlQuery, extractCertifsData, 5) } async function fetchTrailblazerSkillsInfo(trailheadUsername) { + console.log('fetchTrailblazerSkillsInfo') const endpoint = 'https://profile.api.trailhead.com/graphql' - const graphqlQuery = { query: GET_TRAILBLAZER_SKILLS, variables: { @@ -292377,43 +292519,24 @@ async function fetchTrailblazerSkillsInfo(trailheadUsername) { } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) - - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null - } + return await fetchData(endpoint, graphqlQuery, extractSkillsData, 5) } async function fetchTrailblazerEarnedStampsInfo(trailheadUsername) { + console.log('fetchTrailblazerEarnedStampsInfo') const endpoint = 'https://mobile.api.trailhead.com/graphql' - const graphqlQuery = { query: GET_TRAILBLAZER_EARNED_STAMPS, variables: { - slug: trailheadUsername, - first: 10 + first: 100, + count: 100, + after: null, + hasSlug: true, + slug: trailheadUsername } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) - - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null - } + return await fetchData(endpoint, graphqlQuery, extractStampsData, 5) } module.exports = { diff --git a/src/getStats.js b/src/getStats.js index cd0d07c..44fd42f 100644 --- a/src/getStats.js +++ b/src/getStats.js @@ -7,14 +7,122 @@ const { GET_TRAILBLAZER_EARNED_STAMPS } = require('./queries') -async function fetchTrailblazerRankInfo(trailheadUsername) { - const endpoint = 'https://profile.api.trailhead.com/graphql' +// Define the extractData function for each specific function +const extractRankData = (response, profileData, allEdges, pageInfo) => { + if (!profileData) { + profileData = { + __typename: response.data.data.profile.__typename, + trailheadStats: response.data.data.profile.trailheadStats + } + } - const graphqlQuery = { - query: GET_TRAILBLAZER_RANK, - variables: { - hasSlug: true, - slug: trailheadUsername + return { + newProfileData: profileData, + newAllEdges: allEdges, + newPageInfo: pageInfo + } +} + +const extractBadgesData = (response, profileData, allEdges, pageInfo) => { + if (!profileData) { + profileData = { + __typename: response.data.data.profile.__typename, + trailheadStats: response.data.data.profile.trailheadStats + } + } + + allEdges = allEdges.concat(response.data.data.profile.earnedAwards.edges) + pageInfo = response.data.data.profile.earnedAwards.pageInfo + + return { + newProfileData: profileData, + newAllEdges: allEdges, + newPageInfo: pageInfo + } +} + +const extractCertifsData = (response, profileData, allEdges, pageInfo) => { + if (!profileData) { + profileData = { + __typename: response.data.data.profile.__typename, + id: response.data.data.profile.id, + credential: { + messages: response.data.data.profile.credential.messages, + messagesOnly: response.data.data.profile.credential.messagesOnly, + brands: response.data.data.profile.credential.brands + } + } + } + + allEdges = allEdges.concat( + response.data.data.profile.credential.certifications + ) + + return { + newProfileData: profileData, + newAllEdges: allEdges, + newPageInfo: pageInfo + } +} + +const extractSkillsData = (response, profileData, allEdges, pageInfo) => { + if (!profileData) { + profileData = { + __typename: response.data.data.profile.__typename, + id: response.data.data.profile.id + } + } + + allEdges = allEdges.concat(response.data.data.profile.earnedSkills) + + return { + newProfileData: profileData, + newAllEdges: allEdges, + newPageInfo: pageInfo + } +} + +const extractStampsData = (response, profileData, allEdges, pageInfo) => { + allEdges = allEdges.concat(response.data.data.earnedStamps.edges) + pageInfo = response.data.data.earnedStamps.pageInfo + + return { + newProfileData: profileData, + newAllEdges: allEdges, + newPageInfo: pageInfo + } +} + +// Define the factorized fetchData function +async function fetchData( + endpoint, + graphqlQuery, + extractData, + maxPage = 100, + currentPage = 1, + profileData = null, + allEdges = [], + pageInfo = null, + maxRetry = 3, + currentRetry = 0 +) { + console.log('fetchData') + // console.log('endpoint: ', endpoint) + // console.log('graphqlQuery: ', graphqlQuery) + // console.log('maxPage: ', maxPage) + // console.log('currentPage: ', currentPage) + // console.log('profileData: ', profileData) + // console.log('allEdges: ', allEdges) + // console.log('pageInfo: ', pageInfo) + + if (currentPage > maxPage) { + return { + data: { + profile: { + ...profileData, + earnedAwards: { edges: allEdges, pageInfo } + } + } } } @@ -25,20 +133,80 @@ async function fetchTrailblazerRankInfo(trailheadUsername) { } }) - return response.data + const { newProfileData, newAllEdges, newPageInfo } = extractData( + response, + profileData, + allEdges, + pageInfo + ) + + if (newPageInfo != undefined && !newPageInfo.hasNextPage) { + console.log('newPageInfo: ', newPageInfo) + return { + data: { + profile: { + ...newProfileData, + earnedAwards: { edges: newAllEdges, pageInfo: newPageInfo } + } + } + } + } + + return await fetchData( + endpoint, + graphqlQuery, + extractData, + maxPage, + currentPage + 1, + newProfileData, + newAllEdges, + newPageInfo, + maxRetry, + currentRetry + ) } catch (error) { console.error('Error fetching data: ', error) - return null + if (error.response && error.response.status === 429) { + if (currentRetry < maxRetry) { + console.log('Too many requests, waiting for 1 minute before retrying...') + await new Promise(resolve => setTimeout(resolve, 60000)) // wait for 1 minute + return await fetchData( + endpoint, + graphqlQuery, + extractData, + maxPage, + currentPage, + profileData, + allEdges, + pageInfo, + maxRetry, + currentRetry + 1 + ) + } else { + console.error('Max retry limit reached') + throw error + } + } else { + return { + data: { + profile: { + ...profileData, + earnedAwards: { edges: allEdges, pageInfo } + } + } + } + } } } -async function fetchTrailblazerBadgesInfo(trailheadUsername) { +// Use the fetchData function in each specific function +async function fetchTrailblazerRankInfo(trailheadUsername) { + console.log('fetchTrailblazerRankInfo') const endpoint = 'https://profile.api.trailhead.com/graphql' - const graphqlQuery = { - query: GET_TRAILBLAZER_BADGES, + query: GET_TRAILBLAZER_RANK, variables: { - count: 10, + count: 100, after: null, filter: null, hasSlug: true, @@ -46,23 +214,29 @@ async function fetchTrailblazerBadgesInfo(trailheadUsername) { } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) + return await fetchData(endpoint, graphqlQuery, extractRankData, 2) +} - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null +async function fetchTrailblazerBadgesInfo(trailheadUsername) { + console.log('fetchTrailblazerBadgesInfo') + const endpoint = 'https://profile.api.trailhead.com/graphql' + const graphqlQuery = { + query: GET_TRAILBLAZER_BADGES, + variables: { + count: 100, + after: null, + filter: null, + hasSlug: true, + slug: trailheadUsername + } } + + return await fetchData(endpoint, graphqlQuery, extractBadgesData) } async function fetchTrailblazerSuperBadgesInfo(trailheadUsername) { + console.log('fetchTrailblazerSuperBadgesInfo') const endpoint = 'https://profile.api.trailhead.com/graphql' - const graphqlQuery = { query: GET_TRAILBLAZER_BADGES, variables: { @@ -74,23 +248,12 @@ async function fetchTrailblazerSuperBadgesInfo(trailheadUsername) { } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) - - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null - } + return await fetchData(endpoint, graphqlQuery, extractBadgesData) } async function fetchTrailblazerEventBadgesInfo(trailheadUsername) { + console.log('fetchTrailblazerEventBadgesInfo') const endpoint = 'https://profile.api.trailhead.com/graphql' - const graphqlQuery = { query: GET_TRAILBLAZER_BADGES, variables: { @@ -102,48 +265,27 @@ async function fetchTrailblazerEventBadgesInfo(trailheadUsername) { } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) - - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null - } + return await fetchData(endpoint, graphqlQuery, extractBadgesData) } async function fetchTrailblazerCertifsInfo(trailheadUsername) { + console.log('fetchTrailblazerCertifsInfo') const endpoint = 'https://profile.api.trailhead.com/graphql' - const graphqlQuery = { query: GET_TRAILBLAZER_CERTIFS, variables: { hasSlug: true, - slug: trailheadUsername + slug: trailheadUsername, + count: 100 } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) - - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null - } + return await fetchData(endpoint, graphqlQuery, extractCertifsData, 5) } async function fetchTrailblazerSkillsInfo(trailheadUsername) { + console.log('fetchTrailblazerSkillsInfo') const endpoint = 'https://profile.api.trailhead.com/graphql' - const graphqlQuery = { query: GET_TRAILBLAZER_SKILLS, variables: { @@ -152,43 +294,24 @@ async function fetchTrailblazerSkillsInfo(trailheadUsername) { } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) - - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null - } + return await fetchData(endpoint, graphqlQuery, extractSkillsData, 5) } async function fetchTrailblazerEarnedStampsInfo(trailheadUsername) { + console.log('fetchTrailblazerEarnedStampsInfo') const endpoint = 'https://mobile.api.trailhead.com/graphql' - const graphqlQuery = { query: GET_TRAILBLAZER_EARNED_STAMPS, variables: { - slug: trailheadUsername, - first: 10 + first: 100, + count: 100, + after: null, + hasSlug: true, + slug: trailheadUsername } } - try { - const response = await axios.post(endpoint, graphqlQuery, { - headers: { - 'Content-Type': 'application/json' - } - }) - - return response.data - } catch (error) { - console.error('Error fetching data: ', error) - return null - } + return await fetchData(endpoint, graphqlQuery, extractStampsData, 5) } module.exports = { diff --git a/src/rankinfo.js b/src/rankinfo.js new file mode 100644 index 0000000..33257b2 --- /dev/null +++ b/src/rankinfo.js @@ -0,0 +1,24 @@ +async function fetchTrailblazerRankInfo(trailheadUsername) { + const endpoint = 'https://profile.api.trailhead.com/graphql' + + const graphqlQuery = { + query: GET_TRAILBLAZER_RANK, + variables: { + hasSlug: true, + slug: trailheadUsername + } + } + + try { + const response = await axios.post(endpoint, graphqlQuery, { + headers: { + 'Content-Type': 'application/json' + } + }) + + return response.data + } catch (error) { + console.error('Error fetching data: ', error) + return null + } +}