From 60a5c7b56d6ed8796f0fce329f41c5a5d8ac2854 Mon Sep 17 00:00:00 2001 From: maple <75726489+JWWilks@users.noreply.github.com.> Date: Thu, 11 Jul 2024 19:52:45 +0100 Subject: [PATCH 1/4] feat(pipelines): :sparkles: adds the ability to filter which pipelines will show at the config level --- README.md | 27 +++++----- packages/gitlab/dev/index.tsx | 2 + packages/gitlab/src/api/GitlabCIClient.ts | 13 ++++- .../gitlab/src/components/gitlabAppData.tsx | 13 +++++ packages/gitlab/src/components/util.test.tsx | 53 +++++++++++++++++++ packages/gitlab/src/components/utils.tsx | 11 ++++ .../widgets/PipelinesTable/PipelinesTable.tsx | 52 ++++++++++++------ 7 files changed, 140 insertions(+), 31 deletions(-) create mode 100644 packages/gitlab/src/components/util.test.tsx diff --git a/README.md b/README.md index 44193963..0ab27737 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,19 @@ -- [Features](#features) -- [Screenshots](#screenshots) -- [Setup](#setup) - - [Setup Frontend Plugin](#setup-frontend-plugin) - - [Setup Backend Plugin](#setup-backend-plugin) - - [Extra OIDC/OAuth](#extra-oidcoauth) - - [Register To The New Backend System](#register-to-the-new-backend-system) -- [Annotations](#annotations) - - [Code owners file](#code-owners-file) -- [Old/New GitLab Versions](#oldnew-gitlab-versions) -- [Migration guides](#migration-guides) -- [Support & Contribute](#support--contribute) -- [License](#license) +- [Features](#features) +- [Screenshots](#screenshots) +- [Setup](#setup) + * [Setup Frontend Plugin](#setup-frontend-plugin) + * [Setup Backend Plugin](#setup-backend-plugin) + * [Extra OIDC/OAuth](#extra-oidcoauth) + * [Register To The New Backend System](#register-to-the-new-backend-system) +- [Annotations](#annotations) + * [Code owners file](#code-owners-file) +- [Old/New GitLab Versions](#oldnew-gitlab-versions) +- [Migration guides](#migration-guides) +- [Support & Contribute](#support--contribute) +- [License](#license) @@ -301,6 +301,7 @@ metadata: gitlab.com/project-slug: 'project-slug' # group_name/project_name # or gitlab.com/instance: gitlab.internal.abcd # abcd, represents local instance used + gitlab.com/pipeline-refs: 'main,develop,feature/*,refs/merge-requests/1234/merge' # Optional comma seperated lists of branches/refs to show in pipeline table, default is all, accepts wildcard "*". spec: type: service # ... diff --git a/packages/gitlab/dev/index.tsx b/packages/gitlab/dev/index.tsx index 8c7b71fb..b9cd0983 100644 --- a/packages/gitlab/dev/index.tsx +++ b/packages/gitlab/dev/index.tsx @@ -67,6 +67,8 @@ createDevApp() 'gitlab.com/project-id': `${projectId}`, 'gitlab.com/codeowners-path': `CODEOWNERS`, 'gitlab.com/readme-path': `README.md`, + 'gitlab.com/pipeline-refs': + 'master,refs/merge-requests/3678/*', }, name: 'backstage', }, diff --git a/packages/gitlab/src/api/GitlabCIClient.ts b/packages/gitlab/src/api/GitlabCIClient.ts index 29d33c9b..502a39a5 100644 --- a/packages/gitlab/src/api/GitlabCIClient.ts +++ b/packages/gitlab/src/api/GitlabCIClient.ts @@ -146,7 +146,8 @@ export class GitlabCIClient implements GitlabCIApi { } async getPipelineSummary( - projectID?: string | number + projectID?: string | number, + refList?: string[] ): Promise { const [pipelineObjects, projectObj] = await Promise.all([ this.callApi( @@ -155,12 +156,20 @@ export class GitlabCIClient implements GitlabCIApi { ), this.callApi>('projects/' + projectID, {}), ]); + if (pipelineObjects && projectObj) { pipelineObjects.forEach((element) => { element.project_name = projectObj.name; }); } - return pipelineObjects || undefined; + + const relevantPipelineObjects = refList + ? pipelineObjects?.filter((pipeline) => + refList.includes(pipeline.ref) + ) + : pipelineObjects; + + return relevantPipelineObjects || undefined; } async getIssuesSummary( diff --git a/packages/gitlab/src/components/gitlabAppData.tsx b/packages/gitlab/src/components/gitlabAppData.tsx index 178650ff..b8783c46 100644 --- a/packages/gitlab/src/components/gitlabAppData.tsx +++ b/packages/gitlab/src/components/gitlabAppData.tsx @@ -28,6 +28,7 @@ export const GITLAB_ANNOTATION_PROJECT_SLUG = 'gitlab.com/project-slug'; export const GITLAB_ANNOTATION_INSTANCE = 'gitlab.com/instance'; export const GITLAB_ANNOTATION_CODEOWNERS_PATH = 'gitlab.com/codeowners-path'; export const GITLAB_ANNOTATION_README_PATH = 'gitlab.com/readme-path'; +export const GITLAB_ANNOTATION_PIPELINE_REFS = 'gitlab.com/pipeline-refs'; const defaultGitlabIntegration = { hostname: 'gitlab.com', baseUrl: 'https://gitlab.com/api/v4', @@ -102,3 +103,15 @@ export const gitlabReadmePath = () => { return readme_path; }; + +export const gitlabPipelineRelevantRefs = () => { + const { entity } = useEntity(); + + const relevant_refs = + entity.metadata.annotations?.[GITLAB_ANNOTATION_PIPELINE_REFS]; + + // I'd prefer to return an array here, but annotations being strings is a requrement of the backstage entity model + return relevant_refs + ? relevant_refs.split(',').map((ref) => ref.trim()) + : undefined; +}; diff --git a/packages/gitlab/src/components/util.test.tsx b/packages/gitlab/src/components/util.test.tsx new file mode 100644 index 00000000..aa5ea7d8 --- /dev/null +++ b/packages/gitlab/src/components/util.test.tsx @@ -0,0 +1,53 @@ +import { convertWildcardFilterArrayToFilterFunction } from './utils'; + +describe('convertWildcardFilterArrayToFilterFunction', () => { + it('should return true if input matches any of the validInputs', () => { + const input = 'foobar'; + const validInputs = ['foo*', 'bar', 'baz']; + const result = convertWildcardFilterArrayToFilterFunction( + input, + validInputs + ); + expect(result).toBeTruthy(); + }); + + it('should return false if input does not match any of the validInputs', () => { + const input = 'foobar'; + const validInputs = ['baz', 'qux']; + const result = convertWildcardFilterArrayToFilterFunction( + input, + validInputs + ); + expect(result).toBeFalsy(); + }); + + it('should account for multiple wildcards in the validInputs', () => { + const input = 'foobar'; + const validInputs = ['foo*', '*bar', 'baz']; + const result = convertWildcardFilterArrayToFilterFunction( + input, + validInputs + ); + expect(result).toBeTruthy(); + }); + + it('should always return true if any of the valid inputs is *', () => { + const input = 'foobar'; + const validInputs = ['*']; + const result = convertWildcardFilterArrayToFilterFunction( + input, + validInputs + ); + expect(result).toBeTruthy(); + }); + + it('should account for ** in the validInputs', () => { + const input = 'foobar'; + const validInputs = ['foo**', 'bar', 'baz']; + const result = convertWildcardFilterArrayToFilterFunction( + input, + validInputs + ); + expect(result).toBeTruthy(); + }); +}); diff --git a/packages/gitlab/src/components/utils.tsx b/packages/gitlab/src/components/utils.tsx index 49508ca0..3ee07d5f 100644 --- a/packages/gitlab/src/components/utils.tsx +++ b/packages/gitlab/src/components/utils.tsx @@ -114,3 +114,14 @@ const parseCodeOwnerLine = (rule: string): FileOwnership => { // ensures that only the following patterns are allowed @octocat @octocat/kitty docs@example.com const codeOwnerRegex = /(^@[a-zA-Z0-9_\-/]*$)|(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; + +export const convertWildcardFilterArrayToFilterFunction = ( + input: string, // eg: 'foobar' + validInputs: string[] // eg: ['foo*', 'bar', 'baz'] +) => { + const regex = new RegExp( + `^${validInputs.join('|').split('*').join('(.+)')}$` + ); + + return regex.test(input); +}; diff --git a/packages/gitlab/src/components/widgets/PipelinesTable/PipelinesTable.tsx b/packages/gitlab/src/components/widgets/PipelinesTable/PipelinesTable.tsx index 9a25b65e..d6cab37e 100644 --- a/packages/gitlab/src/components/widgets/PipelinesTable/PipelinesTable.tsx +++ b/packages/gitlab/src/components/widgets/PipelinesTable/PipelinesTable.tsx @@ -1,16 +1,21 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Table, TableColumn, Progress } from '@backstage/core-components'; import Alert from '@material-ui/lab/Alert'; import { useAsync } from 'react-use'; import { gitlabInstance, + gitlabPipelineRelevantRefs, gitlabProjectId, gitlabProjectSlug, } from '../../gitlabAppData'; import { GitlabCIApiRef } from '../../../api'; import { useApi } from '@backstage/core-plugin-api'; import { createStatusColumn, createWebURLColumn } from './columns'; -import { getDuration, getElapsedTime } from '../../utils'; +import { + convertWildcardFilterArrayToFilterFunction, + getDuration, + getElapsedTime, +} from '../../utils'; import type { PipelineSchema } from '@gitbeaker/rest'; export type PipelineDenseTableProps = { @@ -32,19 +37,23 @@ export const PipelineDenseTable = ({ ]; const title = 'Gitlab Pipelines: ' + projectName; - const data = summary.map((pipelineObject) => { - return { - id: pipelineObject.id, - status: pipelineObject.status, - ref: pipelineObject.ref, - web_url: pipelineObject.web_url, - created_date: getElapsedTime(pipelineObject.created_at), - duration: getDuration( - pipelineObject.created_at, - pipelineObject.updated_at - ), - }; - }); + const data = useMemo( + () => + summary.map((pipelineObject) => { + return { + id: pipelineObject.id, + status: pipelineObject.status, + ref: pipelineObject.ref, + web_url: pipelineObject.web_url, + created_date: getElapsedTime(pipelineObject.created_at), + duration: getDuration( + pipelineObject.created_at, + pipelineObject.updated_at + ), + }; + }), + [summary] + ); return ( { const project_id = gitlabProjectId(); const project_slug = gitlabProjectSlug(); const gitlab_instance = gitlabInstance(); + const gitlab_relevant_refs = gitlabPipelineRelevantRefs(); const GitlabCIAPI = useApi(GitlabCIApiRef).build( gitlab_instance || 'gitlab.com' @@ -75,7 +85,17 @@ export const PipelinesTable = ({}) => { const summary = await GitlabCIAPI.getPipelineSummary(projectDetails.id); if (!summary) throw new Error('Merge request summary is undefined!'); - return { summary, projectName: projectDetails.name }; + + const relevantPipelines = gitlab_relevant_refs + ? summary.filter(({ ref }) => + convertWildcardFilterArrayToFilterFunction( + ref, + gitlab_relevant_refs + ) + ) + : summary; + + return { summary: relevantPipelines, projectName: projectDetails.name }; }, []); if (loading) { From d36fcccfdc83b49554760fed8e89d6bd5936b711 Mon Sep 17 00:00:00 2001 From: maple <75726489+JWWilks@users.noreply.github.com.> Date: Thu, 11 Jul 2024 20:54:25 +0100 Subject: [PATCH 2/4] refactor: :recycle: remove duplication --- README.md | 26 +++++++++---------- packages/gitlab/src/api/GitlabCIApi.ts | 3 ++- packages/gitlab/src/api/GitlabCIClient.ts | 12 ++++++--- .../widgets/PipelinesTable/PipelinesTable.tsx | 22 +++++----------- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 0ab27737..4f509d29 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,19 @@ -- [Features](#features) -- [Screenshots](#screenshots) -- [Setup](#setup) - * [Setup Frontend Plugin](#setup-frontend-plugin) - * [Setup Backend Plugin](#setup-backend-plugin) - * [Extra OIDC/OAuth](#extra-oidcoauth) - * [Register To The New Backend System](#register-to-the-new-backend-system) -- [Annotations](#annotations) - * [Code owners file](#code-owners-file) -- [Old/New GitLab Versions](#oldnew-gitlab-versions) -- [Migration guides](#migration-guides) -- [Support & Contribute](#support--contribute) -- [License](#license) +- [Features](#features) +- [Screenshots](#screenshots) +- [Setup](#setup) + - [Setup Frontend Plugin](#setup-frontend-plugin) + - [Setup Backend Plugin](#setup-backend-plugin) + - [Extra OIDC/OAuth](#extra-oidcoauth) + - [Register To The New Backend System](#register-to-the-new-backend-system) +- [Annotations](#annotations) + - [Code owners file](#code-owners-file) +- [Old/New GitLab Versions](#oldnew-gitlab-versions) +- [Migration guides](#migration-guides) +- [Support & Contribute](#support--contribute) +- [License](#license) diff --git a/packages/gitlab/src/api/GitlabCIApi.ts b/packages/gitlab/src/api/GitlabCIApi.ts index b91db87a..1e076a16 100644 --- a/packages/gitlab/src/api/GitlabCIApi.ts +++ b/packages/gitlab/src/api/GitlabCIApi.ts @@ -43,7 +43,8 @@ export type GitlabProjectCoverageResponse = { export type GitlabCIApi = { getPipelineSummary( - projectID: string | number + projectID: string | number, + refList?: string[] ): Promise; getContributorsSummary( projectID: string | number diff --git a/packages/gitlab/src/api/GitlabCIClient.ts b/packages/gitlab/src/api/GitlabCIClient.ts index 502a39a5..9e4a6633 100644 --- a/packages/gitlab/src/api/GitlabCIClient.ts +++ b/packages/gitlab/src/api/GitlabCIClient.ts @@ -4,7 +4,10 @@ import { OAuthApi, } from '@backstage/core-plugin-api'; import { PeopleCardEntityData } from '../components/types'; -import { parseCodeOwners } from '../components/utils'; +import { + convertWildcardFilterArrayToFilterFunction, + parseCodeOwners, +} from '../components/utils'; import { ContributorsSummary, GitlabCIApi, @@ -165,11 +168,14 @@ export class GitlabCIClient implements GitlabCIApi { const relevantPipelineObjects = refList ? pipelineObjects?.filter((pipeline) => - refList.includes(pipeline.ref) + convertWildcardFilterArrayToFilterFunction( + pipeline.ref, + refList + ) ) : pipelineObjects; - return relevantPipelineObjects || undefined; + return relevantPipelineObjects ?? undefined; } async getIssuesSummary( diff --git a/packages/gitlab/src/components/widgets/PipelinesTable/PipelinesTable.tsx b/packages/gitlab/src/components/widgets/PipelinesTable/PipelinesTable.tsx index d6cab37e..a9941559 100644 --- a/packages/gitlab/src/components/widgets/PipelinesTable/PipelinesTable.tsx +++ b/packages/gitlab/src/components/widgets/PipelinesTable/PipelinesTable.tsx @@ -11,11 +11,7 @@ import { import { GitlabCIApiRef } from '../../../api'; import { useApi } from '@backstage/core-plugin-api'; import { createStatusColumn, createWebURLColumn } from './columns'; -import { - convertWildcardFilterArrayToFilterFunction, - getDuration, - getElapsedTime, -} from '../../utils'; +import { getDuration, getElapsedTime } from '../../utils'; import type { PipelineSchema } from '@gitbeaker/rest'; export type PipelineDenseTableProps = { @@ -82,20 +78,14 @@ export const PipelinesTable = ({}) => { if (!projectDetails) throw new Error('wrong project_slug or project_id'); - const summary = await GitlabCIAPI.getPipelineSummary(projectDetails.id); + const summary = await GitlabCIAPI.getPipelineSummary( + projectDetails.id, + gitlab_relevant_refs + ); if (!summary) throw new Error('Merge request summary is undefined!'); - const relevantPipelines = gitlab_relevant_refs - ? summary.filter(({ ref }) => - convertWildcardFilterArrayToFilterFunction( - ref, - gitlab_relevant_refs - ) - ) - : summary; - - return { summary: relevantPipelines, projectName: projectDetails.name }; + return { summary, projectName: projectDetails.name }; }, []); if (loading) { From 15965bb87196417cfa9d7f93ee00eb4299b5d064 Mon Sep 17 00:00:00 2001 From: antoniomuso Date: Tue, 23 Jul 2024 13:22:14 +0000 Subject: [PATCH 3/4] chore(release): publish --- CHANGELOG.md | 4 ++++ lerna.json | 2 +- packages/gitlab-backend/CHANGELOG.md | 4 ++++ packages/gitlab-backend/package.json | 2 +- packages/gitlab/CHANGELOG.md | 4 ++++ packages/gitlab/package.json | 2 +- 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0fdc0f9..1d53cbf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [6.6.0](https://github.com/immobiliare/backstage-plugin-gitlab/compare/v6.6.0-alpha.0...v6.6.0) (2024-07-23) + +**Note:** Version bump only for package root + # [6.6.0-alpha.0](https://github.com/immobiliare/backstage-plugin-gitlab/compare/v6.5.1...v6.6.0-alpha.0) (2024-07-23) ### Features diff --git a/lerna.json b/lerna.json index 9a3391c9..02031e90 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "npmClient": "yarn", - "version": "6.6.0-alpha.0" + "version": "6.6.0" } diff --git a/packages/gitlab-backend/CHANGELOG.md b/packages/gitlab-backend/CHANGELOG.md index 728d716b..c8e45fba 100644 --- a/packages/gitlab-backend/CHANGELOG.md +++ b/packages/gitlab-backend/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [6.6.0](https://github.com/immobiliare/backstage-plugin-gitlab/compare/v6.6.0-alpha.0...v6.6.0) (2024-07-23) + +**Note:** Version bump only for package @immobiliarelabs/backstage-plugin-gitlab-backend + # [6.6.0-alpha.0](https://github.com/immobiliare/backstage-plugin-gitlab/compare/v6.5.1...v6.6.0-alpha.0) (2024-07-23) ### Features diff --git a/packages/gitlab-backend/package.json b/packages/gitlab-backend/package.json index db82b240..d3ccc176 100644 --- a/packages/gitlab-backend/package.json +++ b/packages/gitlab-backend/package.json @@ -1,6 +1,6 @@ { "name": "@immobiliarelabs/backstage-plugin-gitlab-backend", - "version": "6.6.0-alpha.0", + "version": "6.6.0", "main": "dist/index.cjs.js", "types": "dist/index.d.ts", "license": "Apache-2.0", diff --git a/packages/gitlab/CHANGELOG.md b/packages/gitlab/CHANGELOG.md index 13e16399..413b0f8b 100644 --- a/packages/gitlab/CHANGELOG.md +++ b/packages/gitlab/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [6.6.0](https://github.com/immobiliare/backstage-plugin-gitlab/compare/v6.6.0-alpha.0...v6.6.0) (2024-07-23) + +**Note:** Version bump only for package @immobiliarelabs/backstage-plugin-gitlab + # [6.6.0-alpha.0](https://github.com/immobiliare/backstage-plugin-gitlab/compare/v6.5.1...v6.6.0-alpha.0) (2024-07-23) ### Features diff --git a/packages/gitlab/package.json b/packages/gitlab/package.json index 40f55245..667518d1 100644 --- a/packages/gitlab/package.json +++ b/packages/gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@immobiliarelabs/backstage-plugin-gitlab", - "version": "6.6.0-alpha.0", + "version": "6.6.0", "main": "dist/index.esm.js", "types": "dist/index.d.ts", "license": "Apache-2.0", From 43109ff0fa625b595ab36f5b9f171a61fa33291b Mon Sep 17 00:00:00 2001 From: maple <75726489+JWWilks@users.noreply.github.com.> Date: Wed, 7 Aug 2024 13:51:27 +0100 Subject: [PATCH 4/4] refactor: fetch all pipelines despite filtering --- packages/gitlab/dev/index.tsx | 1 + .../gitlab/dev/mock-gitlab/api-v4-v15.7.0.ts | 2 +- packages/gitlab/src/api/GitlabCIClient.ts | 31 ++++++++++++++++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/gitlab/dev/index.tsx b/packages/gitlab/dev/index.tsx index e2f7c57f..3e6b6008 100644 --- a/packages/gitlab/dev/index.tsx +++ b/packages/gitlab/dev/index.tsx @@ -17,6 +17,7 @@ const devEntity = { 'gitlab.com/project-id': `${projectId}`, 'gitlab.com/codeowners-path': `CODEOWNERS`, 'gitlab.com/readme-path': `README.md`, + 'gitlab.com/pipeline-refs': 'master,refs/merge-requests/3678/*', }, name: 'backstage', }, diff --git a/packages/gitlab/dev/mock-gitlab/api-v4-v15.7.0.ts b/packages/gitlab/dev/mock-gitlab/api-v4-v15.7.0.ts index f3d312f9..248da2f1 100644 --- a/packages/gitlab/dev/mock-gitlab/api-v4-v15.7.0.ts +++ b/packages/gitlab/dev/mock-gitlab/api-v4-v15.7.0.ts @@ -1756,7 +1756,7 @@ export const mockedGitlabReqToRes: Record = { approvals_before_merge: null, }, ], - 'projects/10174980/pipelines?': [ + 'projects/10174980/pipelines?page=1&per_page=100': [ { id: 721712493, iid: 21249, diff --git a/packages/gitlab/src/api/GitlabCIClient.ts b/packages/gitlab/src/api/GitlabCIClient.ts index 9e4a6633..6aed5d37 100644 --- a/packages/gitlab/src/api/GitlabCIClient.ts +++ b/packages/gitlab/src/api/GitlabCIClient.ts @@ -152,13 +152,34 @@ export class GitlabCIClient implements GitlabCIApi { projectID?: string | number, refList?: string[] ): Promise { - const [pipelineObjects, projectObj] = await Promise.all([ - this.callApi( + if (!refList || refList.length === 0) { + return this.callApi( 'projects/' + projectID + '/pipelines', {} - ), - this.callApi>('projects/' + projectID, {}), - ]); + ); + } + + const projectObj = await this.callApi>( + 'projects/' + projectID, + {} + ); + + const pipelineObjects = []; + let page = 1; + let response; + do { + response = await this.callApi( + 'projects/' + projectID + '/pipelines', + { page: page.toString(), per_page: '100' } + ); + + if (!response) { + break; + } + + pipelineObjects.push(...response); + page++; + } while (response.length > 0); if (pipelineObjects && projectObj) { pipelineObjects.forEach((element) => {