Skip to content

Conversation

@tommaso1
Copy link
Collaborator

@tommaso1 tommaso1 commented Sep 30, 2025

List of Changes

Optimize GitBook sync workflow to reduce API calls and improve performance

  • Add unified metadata generation script that reduces Strapi API calls from 8 to 5
  • Implement S3 operations caching to avoid duplicate file downloads
  • Parallelize all Strapi fetches using Promise.all()
  • Create optimized workflow that uses the unified script
  • Maintain backward compatibility with existing workflow

Motivation and Context

How Has This Been Tested?

  • Frist go to the gitbook docs folder
    cd packages/gitbook-docs
  • compile
    npm run compile
  • set env variables
export GENERATE_URL_METADATA=false
export METADATA_TYPE=guides
export S3_BUCKET_NAME=devportal-d-website-static-content
export ENVIRONMENT=dev

or 

export GENERATE_URL_METADATA=false
export METADATA_TYPE=all
export S3_BUCKET_NAME=devportal-d-website-static-content
export ENVIRONMENT=dev
  • run the script
    npm run sync-all-metadata

Screenshots (if appropriate):

Types of changes

  • Chore (nothing changes by a user perspective)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.

@changeset-bot
Copy link

changeset-bot bot commented Sep 30, 2025

🦋 Changeset detected

Latest commit: 332a95a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
gitbook-docs Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

guides: StrapiGuide[];
solutions: StrapiSolution[];
releaseNotes: StrapiReleaseNote[];
guidesResponse?: any;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you should find a more explicit name for *Response attributes

}[];
}

let s3Client: ReturnType<typeof makeS3Client> | undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you use S3Client?

Suggested change
let s3Client: ReturnType<typeof makeS3Client> | undefined;
let s3Client: S3Client | undefined;


let s3Client: ReturnType<typeof makeS3Client> | undefined;

function getS3Client(): ReturnType<typeof makeS3Client> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

'api/release-notes/?populate[bannerLinks][populate][0]=icon&populate[product][populate][0]=logo&populate[product][populate][1]=bannerLinks.icon&populate[product][populate][2]=overview&populate[product][populate][3]=quickstart_guide&populate[product][populate][4]=release_note&populate[product][populate][5]=api_data_list_page&populate[product][populate][6]=api_data_list_page.apiData.*&populate[product][populate][7]=api_data_list_page.apiData.apiRestDetail.slug&populate[product][populate][8]=api_data_list_page.apiData.apiRestDetail.specUrls&populate[product][populate][9]=api_data_list_page.apiData.apiSoapDetail.*&populate[product][populate][10]=guide_list_page&populate[product][populate][11]=tutorial_list_page&populate[seo][populate]=*,metaImage,metaSocial.image&pagination[pageSize]=1000&pagination[page]=1'
),
// Guide list pages
getResponseFromStrapi(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use the typed function fetchFromStrapi

Comment on lines 161 to 168
guides: guidesResult.data,
solutions: solutionsResult.data,
releaseNotes: releaseNotesResult.data,
guidesResponse: guidesResult.responseJson,
solutionsResponse: solutionsResult.responseJson,
releaseNotesResponse: releaseNotesResult.responseJson,
guideListPagesResponse,
solutionListPageResponse,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to distinguish between data, responseJson and full Response?

Comment on lines 101 to 121
async function cachedListS3Files(prefix: string): Promise<string[]> {
if (s3ListCache.has(prefix)) {
return s3ListCache.get(prefix)!;
}
const files = await listS3Files(prefix, S3_BUCKET_NAME!, getS3Client());
s3ListCache.set(prefix, files);
return files;
}

async function cachedDownloadS3File(filePath: string): Promise<string> {
if (s3FileCache.has(filePath)) {
return s3FileCache.get(filePath)!;
}
const content = await downloadS3File(
filePath,
S3_BUCKET_NAME!,
getS3Client()
);
s3FileCache.set(filePath, content);
return content;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should read files from file system

] = await Promise.all([
// Guides with full populate
fetchFromStrapi<StrapiGuide>(
'api/guides?populate[0]=product&populate[1]=versions&populate[2]=image&populate[3]=mobileImage&populate[4]=bannerLinks&populate[5]=bannerLinks.icon&populate[6]=listItems&populate[7]=seo&populate[8]=seo.metaImage&populate[9]=seo.metaSocial.image&pagination[pageSize]=1000&pagination[page]=1'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we should use Query String's stringify function like in apps/nextjs-website/src/lib/strapi/fetches/fetchGuides.ts.
By doing so, we could manage this query params by declaring a const like in this example:

  populate: {
    image: { populate: '*' },
    mobileImage: { populate: '*' },
    listItems: { populate: '*' },
    versions: { populate: '*' },
    bannerLinks: { populate: ['icon'] },
    seo: { populate: 'metaSocial.image' },
    product: {
      ...productRelationsPopulate,
    },
  },
};```

and then converting it to a string:
`qs.stringify({
    ...guidesPopulate,
  });`

] = await Promise.all([
// Guides with full populate
fetchFromStrapi<StrapiGuide>(
'api/guides?populate[0]=product&populate[1]=versions&populate[2]=image&populate[3]=mobileImage&populate[4]=bannerLinks&populate[5]=bannerLinks.icon&populate[6]=listItems&populate[7]=seo&populate[8]=seo.metaImage&populate[9]=seo.metaSocial.image&pagination[pageSize]=1000&pagination[page]=1'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add &populate[product][populate][10]=use_case_list_page

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the current and updated query:

api/guides/?populate[image][populate]=*&populate[mobileImage][populate]=*&populate[listItems][populate]=*&populate[versions][populate]=*&populate[bannerLinks][populate][0]=icon&populate[seo][populate]=metaSocial.image&populate[product][populate][0]=logo&populate[product][populate][1]=bannerLinks.icon&populate[product][populate][2]=overview&populate[product][populate][3]=quickstart_guide&populate[product][populate][4]=release_note&populate[product][populate][5]=api_data_list_page&populate[product][populate][6]=api_data_list_page.apiData.*&populate[product][populate][7]=api_data_list_page.apiData.apiRestDetail.*&populate[product][populate][8]=guide_list_page&populate[product][populate][9]=tutorial_list_page&populate[product][populate][10]=use_case_list_page

),
// Guide list pages
fetchFromStrapi<StrapiGuideListPageResponse>(
'api/guide-list-pages/?populate%5Bproduct%5D%5Bpopulate%5D%5B0%5D=logo&populate%5Bproduct%5D%5Bpopulate%5D%5B1%5D=bannerLinks.icon&populate%5Bproduct%5D%5Bpopulate%5D%5B2%5D=overview&populate%5Bproduct%5D%5Bpopulate%5D%5B3%5D=quickstart_guide&populate%5Bproduct%5D%5Bpopulate%5D%5B4%5D=release_note&populate%5Bproduct%5D%5Bpopulate%5D%5B5%5D=api_data_list_page&populate%5Bproduct%5D%5Bpopulate%5D%5B6%5D=api_data_list_page.apiData.%2A&populate%5Bproduct%5D%5Bpopulate%5D%5B7%5D=api_data_list_page.apiData.apiRestDetail.%2A&populate%5Bproduct%5D%5Bpopulate%5D%5B8%5D=guide_list_page&populate%5Bproduct%5D%5Bpopulate%5D%5B9%5D=tutorial_list_page&populate%5BguidesByCategory%5D%5Bpopulate%5D%5B0%5D=guides.mobileImage&populate%5BguidesByCategory%5D%5Bpopulate%5D%5B1%5D=guides.image&populate%5BguidesByCategory%5D%5Bpopulate%5D%5B2%5D=guides.listItems&populate%5BbannerLinks%5D%5Bpopulate%5D%5B0%5D=icon&populate%5Bseo%5D%5Bpopulate%5D=%2A%2CmetaImage%2CmetaSocial.image'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for StrapiGuideListPage:

api/guide-list-pages/?populate[product][populate][0]=logo&populate[product][populate][1]=bannerLinks.icon&populate[product][populate][2]=overview&populate[product][populate][3]=quickstart_guide&populate[product][populate][4]=release_note&populate[product][populate][5]=api_data_list_page&populate[product][populate][6]=api_data_list_page.apiData.*&populate[product][populate][7]=api_data_list_page.apiData.apiRestDetail.*&populate[product][populate][8]=guide_list_page&populate[product][populate][9]=tutorial_list_page&populate[product][populate][10]=use_case_list_page&populate[guidesByCategory][populate][0]=guides.mobileImage&populate[guidesByCategory][populate][1]=guides.image&populate[guidesByCategory][populate][2]=guides.listItems&populate[bannerLinks][populate][0]=icon&populate[seo][populate]=*,metaImage,metaSocial.image

),
// Release notes with full populate
fetchFromStrapi<StrapiReleaseNote>(
'api/release-notes/?populate[bannerLinks][populate][0]=icon&populate[product][populate][0]=logo&populate[product][populate][1]=bannerLinks.icon&populate[product][populate][2]=overview&populate[product][populate][3]=quickstart_guide&populate[product][populate][4]=release_note&populate[product][populate][5]=api_data_list_page&populate[product][populate][6]=api_data_list_page.apiData.*&populate[product][populate][7]=api_data_list_page.apiData.apiRestDetail.slug&populate[product][populate][8]=api_data_list_page.apiData.apiRestDetail.specUrls&populate[product][populate][9]=api_data_list_page.apiData.apiSoapDetail.*&populate[product][populate][10]=guide_list_page&populate[product][populate][11]=tutorial_list_page&populate[seo][populate]=*,metaImage,metaSocial.image&pagination[pageSize]=1000&pagination[page]=1'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the updated query with use_case_list_page:

api/release-notes/?populate[bannerLinks][populate][0]=icon&populate[product][populate][0]=logo&populate[product][populate][1]=bannerLinks.icon&populate[product][populate][2]=overview&populate[product][populate][3]=quickstart_guide&populate[product][populate][4]=release_note&populate[product][populate][5]=api_data_list_page&populate[product][populate][6]=api_data_list_page.apiData.*&populate[product][populate][7]=api_data_list_page.apiData.apiRestDetail.slug&populate[product][populate][8]=api_data_list_page.apiData.apiRestDetail.specUrls&populate[product][populate][9]=api_data_list_page.apiData.apiSoapDetail.*&populate[product][populate][10]=guide_list_page&populate[product][populate][11]=tutorial_list_page&populate[product][populate][12]=use_case_list_page&populate[seo][populate]=*,metaImage,metaSocial.image&pagination[pageSize]=1000&pagination[page]=1

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tommaso1 i added a step in [CAI-612] which generates and saves on the s3 bucket two files (response from fetchFromStrapi for products and api-datas) and the sitemap.xml. Please don't forget to add the same step here (or in sync all metadata, where is more fitting)

tommaso1 and others added 3 commits October 22, 2025 23:16
Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com>
…a new position to sync metadata before markdown docs
GENERATE_URL_METADATA: 'false'
GENERATE_SITEMAP_METADATA: 'true'
SAVE_STRAPI_RESPONSES: 'true'
run: npm run sync-all-metadata -w gitbook-docs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sync-all-metadata is called two times in this action. Can we optimize it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to generate all metadatas before the url parsing step! Move the AWS cretential step up in order to sync the metadatas on the S3 bucket

- Replace SitemapItem with MetadataItem (sitemapItem.ts was removed)
- Replace writeSitemapJson with writeMetadataJson (function was renamed)
- Optimize sync_gitbook_docs_optimized.yaml workflow:
  * Move AWS credentials before metadata generation
  * Combine two metadata generation steps into one
  * Eliminate duplicate sync-all-metadata calls
@github-actions
Copy link
Contributor

Branch is not up to date with base branch

@tommaso1 it seems this Pull Request is not updated with base branch.
Please proceed with a merge or rebase to solve this.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 28, 2025

Jira Pull Request Link

This Pull Request refers to the following Jira issue DEV-2583

@github-actions
Copy link
Contributor

This PR exceeds the recommended size of 800 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants