diff --git a/packages/netlify-cms-backend-gitlab/src/API.ts b/packages/netlify-cms-backend-gitlab/src/API.ts index f93ef2c2d0e8..8dc88f8dc16c 100644 --- a/packages/netlify-cms-backend-gitlab/src/API.ts +++ b/packages/netlify-cms-backend-gitlab/src/API.ts @@ -34,6 +34,7 @@ import { dirname } from 'path'; const NO_CACHE = 'no-cache'; import * as queries from './queries'; +import type { ApolloQueryResult } from 'apollo-client'; import type { NormalizedCacheObject } from 'apollo-cache-inmemory'; import type { ApiRequest, @@ -196,6 +197,13 @@ export function getMaxAccess(groups: { group_access_level: number }[]) { }, groups[0]); } +function batch(items: T[], maxPerBatch: number, action: (items: T[]) => void) { + for (let index = 0; index < items.length; index = index + maxPerBatch) { + const itemsSlice = items.slice(index, index + maxPerBatch); + action(itemsSlice); + } +} + export default class API { apiRoot: string; graphQLAPIRoot: string; @@ -460,30 +468,79 @@ export default class API { }; readFilesGraphQL = async (files: ImplementationFile[]) => { - const blobPromises = []; - - const BLOBS_TO_FETCH = 50; const paths = files.map(({ path }) => path); - for (let index = 0; index < files.length; index = index + BLOBS_TO_FETCH) { + + type BlobResult = { + project: { repository: { blobs: { nodes: { id: string; data: string }[] } } }; + }; + + const blobPromises: Promise>[] = []; + batch(paths, 90, slice => { blobPromises.push( this.graphQLClient!.query({ query: queries.blobs, variables: { repo: this.repo, branch: this.branch, - paths: paths.slice(index, index + BLOBS_TO_FETCH), + paths: slice, }, fetchPolicy: 'cache-first', }), ); - } + }); - const results = (await Promise.all(blobPromises)).map( - result => result.data.project.repository.blobs.nodes, - ); + type LastCommit = { + id: string; + authoredDate: string; + authorName: string; + author?: { + name: string; + username: string; + publicEmail: string; + }; + }; + + type CommitResult = { + project: { repository: { [tree: string]: { lastCommit: LastCommit } } }; + }; - const blobs = results.flat().map(result => result.data) as string[]; - return files.map((file, index) => ({ file, data: blobs[index] })); + const commitPromises: Promise>[] = []; + batch(paths, 8, slice => { + commitPromises.push( + this.graphQLClient!.query({ + query: queries.lastCommits(slice), + variables: { + repo: this.repo, + branch: this.branch, + }, + fetchPolicy: 'cache-first', + }), + ); + }); + + const [blobsResults, commitsResults] = await Promise.all([ + (await Promise.all(blobPromises)).map(result => result.data.project.repository.blobs.nodes), + ( + await Promise.all(commitPromises) + ).map( + result => + Object.values(result.data.project.repository) + .map(({ lastCommit }) => lastCommit) + .filter(Boolean) as LastCommit[], + ), + ]); + + const blobs = blobsResults.flat().map(result => result.data) as string[]; + const metadata = commitsResults.flat().map(({ author, authoredDate, authorName }) => ({ + author: author ? author.name || author.username || author.publicEmail : authorName, + updatedOn: authoredDate, + })); + + const filesWithData = files.map((file, index) => ({ + file: { ...file, ...metadata[index] }, + data: blobs[index], + })); + return filesWithData; }; listAllFiles = async (path: string, recursive = false, branch = this.branch) => { diff --git a/packages/netlify-cms-backend-gitlab/src/implementation.ts b/packages/netlify-cms-backend-gitlab/src/implementation.ts index 7e5f1a5b611b..bf9df133c885 100644 --- a/packages/netlify-cms-backend-gitlab/src/implementation.ts +++ b/packages/netlify-cms-backend-gitlab/src/implementation.ts @@ -220,6 +220,7 @@ export default class GitLab implements Implementation { filterFile: file => this.filterFile(folder, file, extension, depth), customFetch: this.useGraphQL ? files => this.api!.readFilesGraphQL(files) : undefined, }); + return files; } diff --git a/packages/netlify-cms-backend-gitlab/src/queries.ts b/packages/netlify-cms-backend-gitlab/src/queries.ts index d37e5383f5fe..fb6e020185e3 100644 --- a/packages/netlify-cms-backend-gitlab/src/queries.ts +++ b/packages/netlify-cms-backend-gitlab/src/queries.ts @@ -1,4 +1,5 @@ import { gql } from 'graphql-tag'; +import { oneLine } from 'common-tags'; export const files = gql` query files($repo: ID!, $branch: String!, $path: String!, $recursive: Boolean!, $cursor: String) { @@ -29,6 +30,7 @@ export const blobs = gql` repository { blobs(ref: $branch, paths: $paths) { nodes { + id data: rawBlob } } @@ -36,3 +38,36 @@ export const blobs = gql` } } `; + +export function lastCommits(paths: string[]) { + const tree = paths + .map( + (path, index) => oneLine` + tree${index}: tree(ref: $branch, path: "${path}") { + lastCommit { + authorName + authoredDate + author { + id + username + name + publicEmail + } + } + } + `, + ) + .join('\n'); + + const query = gql` + query lastCommits($repo: ID!, $branch: String!) { + project(fullPath: $repo) { + repository { + ${tree} + } + } + } +`; + + return query; +}