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 @@
-
\ No newline at end of file
+
\ 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
+ }
+}