diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts
index d587a88402..afc96a68db 100644
--- a/packages/app/src/apis.ts
+++ b/packages/app/src/apis.ts
@@ -32,8 +32,10 @@ import {
configApiRef,
createApiFactory,
errorApiRef,
- githubAuthApiRef,
+ githubAuthApiRef, gitlabAuthApiRef,
} from '@backstage/core-plugin-api';
+import { cicdStatisticsApiRef } from '@backstage/plugin-cicd-statistics';
+import {CicdStatisticsApiGitlab} from "@backstage/plugin-cicd-statistics-module-gitlab";
export const apis: AnyApiFactory[] = [
createApiFactory({
@@ -42,6 +44,7 @@ export const apis: AnyApiFactory[] = [
factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
}),
+
ScmAuth.createDefaultApiFactory(),
createApiFactory({
@@ -64,4 +67,12 @@ export const apis: AnyApiFactory[] = [
}),
createApiFactory(costInsightsApiRef, new ExampleCostInsightsClient()),
+ createApiFactory({
+ api: cicdStatisticsApiRef,
+ deps: {gitlabAuthApi: gitlabAuthApiRef},
+ factory({ gitlabAuthApi }) {
+ return new CicdStatisticsApiGitlab(gitlabAuthApi);
+ },
+ }),
+// createApiFactory(cicdStatisticsApiRef, new CicdStatisticsApiGitlab()),
];
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 26068c6c8f..89897905b8 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -136,6 +136,7 @@ import {
EntityNewRelicDashboardCard,
} from '@backstage/plugin-newrelic-dashboard';
import { EntityGoCdContent, isGoCdAvailable } from '@backstage/plugin-gocd';
+import {EntityCicdStatisticsContent} from "@backstage/plugin-cicd-statistics";
import React, { ReactNode, useMemo, useState } from 'react';
@@ -444,6 +445,10 @@ const websiteEntityPage = (
{cicdContent}
+
+
+
+
diff --git a/plugins/cicd-statistics-module-gitlab/.eslintrc.js b/plugins/cicd-statistics-module-gitlab/.eslintrc.js
new file mode 100644
index 0000000000..13573efa9c
--- /dev/null
+++ b/plugins/cicd-statistics-module-gitlab/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: [require.resolve('@backstage/cli/config/eslint')],
+};
diff --git a/plugins/cicd-statistics-module-gitlab/README.md b/plugins/cicd-statistics-module-gitlab/README.md
new file mode 100644
index 0000000000..85c297932e
--- /dev/null
+++ b/plugins/cicd-statistics-module-gitlab/README.md
@@ -0,0 +1,9 @@
+# cicd-statistics-module-gitlab
+
+## Getting started
+
+Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/cicd-statistics-module-gitlab](http://localhost:3000/cicd-statistics-module-gitlab).
+
+You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
+This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
+It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
diff --git a/plugins/cicd-statistics-module-gitlab/package.json b/plugins/cicd-statistics-module-gitlab/package.json
new file mode 100644
index 0000000000..df9ccef6b8
--- /dev/null
+++ b/plugins/cicd-statistics-module-gitlab/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "@backstage/plugin-cicd-statistics-module-gitlab",
+ "description": "CI/CD Statistics plugin module; Gitlab CICD",
+ "version": "0.0.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "private": false,
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.esm.js",
+ "types": "dist/index.d.ts"
+ },
+ "backstage": {
+ "role": "frontend-plugin"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/cicd-statistics-module-gitlab"
+ },
+ "keywords": [
+ "backstage",
+ "cicd statestics",
+ "gitlab"
+ ],
+ "scripts": {
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test"
+ },
+ "dependencies": {
+ "@backstage/catalog-model": "^0.9.10",
+ "@backstage/core-plugin-api": "^0.8.0",
+ "@backstage/plugin-cicd-statistics": "^0.1.0",
+ "@gitbeaker/browser": "34.2.0",
+ "@gitbeaker/core": "^35.4.0",
+ "lodash": "^4.17.21",
+ "luxon": "^2.0.2",
+ "p-limit": "^4.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.13.1 || ^17.0.0"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/cicd-statistics-module-gitlab/src/api/gitlab.ts b/plugins/cicd-statistics-module-gitlab/src/api/gitlab.ts
new file mode 100644
index 0000000000..a28ce612d5
--- /dev/null
+++ b/plugins/cicd-statistics-module-gitlab/src/api/gitlab.ts
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ CicdStatisticsApi,
+ CicdState,
+ CicdConfiguration,
+ Build,
+ FilterStatusType,
+ FetchBuildsOptions,
+ Stage,
+} from '@backstage/plugin-cicd-statistics';
+import { Gitlab } from '@gitbeaker/browser';
+import { OAuthApi } from '@backstage/core-plugin-api';
+import limiterFactory from 'p-limit';
+
+import { Types } from '@gitbeaker/core';
+import { Entity, getEntitySourceLocation } from '@backstage/catalog-model';
+
+const statusMap: Record = {
+ manual: 'unknown',
+ created: 'enqueued',
+ waiting_for_resource: 'stalled',
+ preparing: 'unknown',
+ pending: 'scheduled',
+ running: 'running',
+ success: 'succeeded',
+ failed: 'failed',
+ canceled: 'aborted',
+ skipped: 'aborted',
+ scheduled: 'scheduled',
+};
+
+function jobtToBuild(jobs: Array): Build[] {
+ return jobs.map(j => {
+ return {
+ id: j.id.toString(),
+ status: statusMap[j.status],
+ branchType: 'master',
+ duration: 0, // will get filled in later in a seperate API call
+ requestedAt: new Date(j.created_at),
+ stages: [],
+ };
+ });
+}
+
+function jobsToStages(jobs: Array): Stage[] {
+ return jobs.map(j => {
+ const status = statusMap[j.status] ? statusMap[j.status] : 'unknown';
+ return {
+ name: j.name,
+ status: status,
+ duration: j.duration ? ((j.duration * 1000) as number) : 0,
+ };
+ });
+}
+
+type GitlabClient = {
+ api: InstanceType;
+ owner: string;
+};
+
+export class CicdStatisticsApiGitlab implements CicdStatisticsApi {
+ readonly #gitLabAuthApi: OAuthApi;
+
+ constructor(gitLabAuthApi: OAuthApi) {
+ this.#gitLabAuthApi = gitLabAuthApi;
+ }
+
+ public async createGitlabApi(
+ entity: Entity,
+ scopes: string[],
+ ): Promise {
+ const entityInfo = getEntitySourceLocation(entity);
+ const url = new URL(entityInfo.target);
+ const owner = url.pathname.split('/-/blob/')[0];
+ const oauthToken = await this.#gitLabAuthApi.getAccessToken(scopes);
+ return {
+ api: new Gitlab({
+ host: `https://${url.host}`,
+ oauthToken,
+ }),
+ owner: owner.substring(1),
+ };
+ }
+
+ private static async updateBuildWithStages(
+ gitbeaker: InstanceType,
+ owner: string,
+ build: Build,
+ ): Promise {
+ const jobs = await gitbeaker.Jobs.showPipelineJobs(
+ owner,
+ parseInt(build.id, 10),
+ );
+ const stages = jobsToStages(jobs);
+ return stages;
+ }
+
+ private static async getDurationOfBuild(
+ gitbeaker: InstanceType,
+ owner: string,
+ build: Build,
+ ): Promise {
+ const pipeline = (await gitbeaker.Pipelines.show(
+ owner,
+ parseInt(build.id, 10),
+ )) as Types.PipelineExtendedSchema;
+ return parseInt(pipeline.duration as string, 10) * 1000;
+ }
+
+ private static async getDefaultBranch(
+ gitbeaker: InstanceType,
+ owner: string,
+ ): Promise {
+ const branches = await gitbeaker.Branches.all(owner);
+ return branches.find(b => b.default)?.name;
+ }
+
+ public async fetchBuilds(options: FetchBuildsOptions): Promise {
+ const {
+ entity,
+ updateProgress,
+ timeFrom,
+ timeTo,
+ filterStatus = ['all'],
+ filterType = 'all',
+ } = options;
+ const { api, owner } = await this.createGitlabApi(entity as Entity, [
+ 'read_api',
+ ]);
+ updateProgress(0, 0, 0);
+
+ const branch =
+ filterType === 'master'
+ ? await CicdStatisticsApiGitlab.getDefaultBranch(api, owner)
+ : undefined;
+ const pipelines = await api.Pipelines.all(owner, {
+ perPage: 25,
+ updated_after: timeFrom.toISOString(),
+ updated_before: timeTo.toISOString(),
+ ref: branch,
+ });
+
+ const limiter = limiterFactory(10);
+ const builds = jobtToBuild(pipelines).map(async build => ({
+ ...build,
+ duration: await limiter(() =>
+ CicdStatisticsApiGitlab.getDurationOfBuild(api, owner, build),
+ ),
+ stages: await limiter(() =>
+ CicdStatisticsApiGitlab.updateBuildWithStages(api, owner, build),
+ ),
+ }));
+ const promisedBuilds = (await Promise.all(builds)).filter(b =>
+ filterStatus.includes(b.status),
+ ) as unknown as Build[];
+
+ return { builds: promisedBuilds };
+ }
+
+ public async getConfiguration(): Promise> {
+ return {
+ availableStatuses: [
+ 'succeeded',
+ 'failed',
+ 'enqueued',
+ 'running',
+ 'aborted',
+ 'stalled',
+ 'expired',
+ 'unknown',
+ ] as const,
+ };
+ }
+}
diff --git a/plugins/cicd-statistics-module-gitlab/src/api/index.ts b/plugins/cicd-statistics-module-gitlab/src/api/index.ts
new file mode 100644
index 0000000000..686c8b5f63
--- /dev/null
+++ b/plugins/cicd-statistics-module-gitlab/src/api/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from "./gitlab";
diff --git a/plugins/cicd-statistics-module-gitlab/src/index.ts b/plugins/cicd-statistics-module-gitlab/src/index.ts
new file mode 100644
index 0000000000..409c45eecd
--- /dev/null
+++ b/plugins/cicd-statistics-module-gitlab/src/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { CicdStatisticsApiGitlab } from './api';
diff --git a/plugins/cicd-statistics-module-gitlab/src/setupTests.ts b/plugins/cicd-statistics-module-gitlab/src/setupTests.ts
new file mode 100644
index 0000000000..9bb3e72355
--- /dev/null
+++ b/plugins/cicd-statistics-module-gitlab/src/setupTests.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '@testing-library/jest-dom';
+import 'cross-fetch/polyfill';
diff --git a/plugins/cicd-statistics/src/apis/types.ts b/plugins/cicd-statistics/src/apis/types.ts
index 32d9d3f2df..975f69525c 100644
--- a/plugins/cicd-statistics/src/apis/types.ts
+++ b/plugins/cicd-statistics/src/apis/types.ts
@@ -117,7 +117,7 @@ export interface Build {
duration: number;
/** Top-level build stages */
- stages: Array;
+ stages?: Array;
}
/**
diff --git a/plugins/cicd-statistics/src/components/button-switch.tsx b/plugins/cicd-statistics/src/components/button-switch.tsx
index 65f55eee41..89a5f7735a 100644
--- a/plugins/cicd-statistics/src/components/button-switch.tsx
+++ b/plugins/cicd-statistics/src/components/button-switch.tsx
@@ -78,6 +78,9 @@ export function ButtonSwitch(props: ButtonSwitchProps) {
);
const value = switchValue(values[index]);
+ if(!value){
+ console.log(values[index]);
+ }
if (props.multi) {
props.onChange(
props.selection.includes(value as T)
diff --git a/plugins/cicd-statistics/src/entity-page.tsx b/plugins/cicd-statistics/src/entity-page.tsx
index 3c51dabf2a..8c3209c854 100644
--- a/plugins/cicd-statistics/src/entity-page.tsx
+++ b/plugins/cicd-statistics/src/entity-page.tsx
@@ -166,6 +166,8 @@ function CicdCharts(props: CicdChartsProps) {
errorApi.post(chartableStagesState.error);
}, [errorApi, chartableStagesState.error]);
+ console.log(chartableStagesState);
+
return (
diff --git a/plugins/cicd-statistics/src/utils/stage-names.ts b/plugins/cicd-statistics/src/utils/stage-names.ts
index 69c8cdcf25..8853336fef 100644
--- a/plugins/cicd-statistics/src/utils/stage-names.ts
+++ b/plugins/cicd-statistics/src/utils/stage-names.ts
@@ -68,6 +68,8 @@ export async function cleanupBuildTree(
return map(builds, { chunk: 'idle' }, build => ({
...build,
- stages: build.stages.map(stage => recurseStage(stage, [])),
+ stages: build.stages
+ ? build.stages.map(stage => recurseStage(stage, []))
+ : [],
}));
}