UNPKG

bananareporter

Version:

Easily generate a report from multiple sources

256 lines (255 loc) 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitlabIntegration = exports.GitlabConfig = void 0; const base_1 = require("./base"); // import {Axios} from 'axios' const common_1 = require("../util/common"); const zod_1 = require("zod"); const axios_1 = require("axios"); const dayjs = require("dayjs"); const logger_1 = require("../util/logger"); const objectPath = require("object-path"); exports.GitlabConfig = zod_1.z.object({ committerUsername: zod_1.z.string().min(1), userId: zod_1.z.number().optional(), token: zod_1.z.string().min(1), filters: zod_1.z.array(zod_1.z.object({ on: zod_1.z.string().min(1), regex: zod_1.z.string().min(1), })).nonempty().optional(), domain: zod_1.z.string().optional().default('gitlab.com'), apiVersion: zod_1.z.string().startsWith('v').optional().default('v4'), protocol: zod_1.z.union([zod_1.z.literal('http'), zod_1.z.literal('https')]).optional().default('https'), apiBasePath: zod_1.z.string().optional().default('api'), // TODO should be extended from a base integration from: common_1.zIsoString.optional(), to: common_1.zIsoString.optional(), delay: zod_1.z.number().min(0).optional(), }); class GitlabIntegration extends base_1.IntegrationBase { constructor(_rawConfig, bananaReporterConfig) { var _a, _b, _c; super(_rawConfig, bananaReporterConfig); this.projectIdToDetails = {}; logger_1.logger.debug('gitlab integration'); this.config = exports.GitlabConfig.parse(_rawConfig); logger_1.logger.debug('gitlab integration config', this.config); this.bananaReporterConfig = bananaReporterConfig; this.delayToUse = (_a = this.config.delay) !== null && _a !== void 0 ? _a : this.bananaReporterConfig.delay; logger_1.logger.debug(`gitlab integration delayToUse ${this.delayToUse}`); this.dateRange = { from: (_b = this.config.from) !== null && _b !== void 0 ? _b : this.bananaReporterConfig.from, to: (_c = this.config.to) !== null && _c !== void 0 ? _c : this.bananaReporterConfig.to, }; logger_1.logger.debug('gitlab integration dateRange', this.dateRange); const baseURL = `${this.config.protocol}://${this.config.domain}/${this.config.apiBasePath}/${this.config.apiVersion}`; logger_1.logger.debug(`gitlab integration baseURL ${baseURL}`); this.httpClient = axios_1.default.create({ baseURL, headers: { Authorization: `Bearer ${this.config.token}`, }, timeout: 35000, }); } setup(options) { for (const p of options.projectDetails) { if (!(p.id in this.projectIdToDetails)) { this.projectIdToDetails[p.id] = p; } } } async httpRequest(path, config) { logger_1.logger.debug(`gitlab integration http request ${path}`, config); const res = await this.httpClient(path, { ...config, }); return res.data; } /** * events API needs the user id, retrieve it * from the username provided * @returns GitLab user id */ async getUserId() { if (typeof this.config.userId !== 'undefined') { return this.config.userId; } const username = this.config.committerUsername; const data = await this.httpRequest(`/users?username=${username}`); if (data.length === 0) { throw new Error(`unable to find the GitLab username ${username}`); } return data[0].id; } async fetchData() { const userId = await this.getUserId(); logger_1.logger.debug(`gitlab integration userId ${userId}`); let page = 1; let eventList = []; const projectIds = new Set(); while (page > 0) { logger_1.logger.debug(`gitlab integration working on ${page}`); /* eslint-disable no-await-in-loop */ const events = await this.getUserEvents(userId, { from: this.dateRange.from, to: this.dateRange.to, page, }); // no more data, break loop if (events.length === 0) { logger_1.logger.debug('gitlab integration no more events to process'); break; } // eslint-disable-next-line unicorn/prefer-spread eventList = eventList.concat(events); for (const e of events) { projectIds.add(e.project_id); } logger_1.logger.debug(`gitlab integration eventList ${eventList.length} (added ${events.length} events)`); page += 1; await (0, common_1.delay)(this.delayToUse); } // get all project details const projectDetails = await this.getProjects(userId, projectIds); logger_1.logger.debug('gitlab integration projectDetails', projectDetails); for (const p of projectDetails) { this.projectIdToDetails[p.id] = p; } this.setup({ projectDetails, }); // format data to common obj let filteredData = eventList .filter(e => { var _a; return (e.action_name .startsWith('pushed') && ((_a = e === null || e === void 0 ? void 0 : e.push_data) === null || _a === void 0 ? void 0 : _a.commit_title) && !e.push_data.commit_title.startsWith('Merge branch')); }); const projectsToRemove = new Set(); for (const filter of this.config.filters || []) { const targetKey = filter.on; if (targetKey.startsWith('$project.')) { // custom logic for project matching for (const projectId of Object.keys(this.projectIdToDetails)) { const project = this.projectIdToDetails[Number(projectId)]; const targetProjectKey = targetKey.split('.').pop(); if (!(targetProjectKey in project)) { const errorMsg = `while filtering the project ${project.name} (id ${project.id}) unable to find the key ${targetProjectKey} in the project's object (available keys: ${Object.keys(project).join(',')})`; logger_1.logger.error(errorMsg); throw new Error(errorMsg); } const regex = new RegExp(filter.regex); if (!regex.test(project[targetProjectKey])) { // false, remove projectsToRemove.add(Number(projectId)); } } } filteredData = filteredData.filter(e => { const targetKey = filter.on; // custom project is filtered later on if (targetKey.startsWith('$project.')) { return true; } const regex = new RegExp(filter.regex); const value = objectPath.get(e, targetKey); if (typeof value === 'undefined') { const errorMsg = `while filtering event id ${e.id} unable to find the key ${targetKey} in the event's object (available keys: ${Object.keys(e).join(',')})`; logger_1.logger.error(errorMsg); throw new Error(errorMsg); } return regex.test(value); }); } if (projectsToRemove.size > 0) { filteredData = filteredData.filter(e => { return !projectsToRemove.has(e.project_id); }); } const formattedData = filteredData.map(e => this.toBananaReporterObj(e)); return formattedData; } /** * GitLab doesn't return project information in the events call, * need to fetch all projects to enrich the events data * @param userId identifier of gitlab user * @param projectIds set of project ids found in events call * @returns list of projects found */ async getProjects(userId, projectIds) { // get all user projects, if some projects are missing // try to get the project via a direct GET call const remainingProjectIdsToFetch = projectIds; const projects = await this.httpRequest(`/users/${userId}/projects`); for (const p of projects) { remainingProjectIdsToFetch.delete(p.id); } const projectsLoaded = projects; if (remainingProjectIdsToFetch.size > 0) { // delay before starting to fetch other data await (0, common_1.delay)(this.delayToUse); // eslint-disable-next-line unicorn/prefer-spread for (const projectId of Array.from(remainingProjectIdsToFetch)) { try { const project = await this.httpRequest(`/projects/${projectId}`); if (project) { remainingProjectIdsToFetch.delete(projectId); projectsLoaded.push(project); } } catch (error) { logger_1.logger.debug(`gitlab integration unable to fetch details for project ID ${projectId}`, error); } await (0, common_1.delay)(this.delayToUse); } } if (remainingProjectIdsToFetch.size > 0) { logger_1.logger.warn(`unable to retrieve details for the following project ids: ${[...remainingProjectIdsToFetch].join(',')} make sure you have still access to these projects, the report won't include the project names`); } return projectsLoaded; } async getUserEvents(userId, options) { var _a; const fromDate = dayjs(options.from); const toDate = dayjs(options.to); // recalculate from - to according on GitLab API behaviour const from = fromDate.subtract(1, 'day').format('YYYY-MM-DD'); const to = toDate.add(1, 'day').format('YYYY-MM-DD'); logger_1.logger.debug(`gitlab integration getUserEvents from ${from} to ${to}`); const page = (_a = options.page) !== null && _a !== void 0 ? _a : 1; const url = `/users/${userId}/events`; const events = await this.httpRequest(url, { params: { after: from, before: to, sort: 'asc', // eslint-disable-next-line camelcase per_page: 100, action: 'pushed', page, }, }); return events; } toBananaReporterObj(rawData) { const projectId = rawData.project_id; const projectDetails = this.projectIdToDetails[projectId]; const projectName = projectDetails === null || projectDetails === void 0 ? void 0 : projectDetails.path; const description = `${rawData.push_data.commit_title} branch:${rawData.push_data.ref} git:${rawData.push_data.commit_to.slice(0, 7)}`; return { id: `${String(rawData.push_data.commit_to)}`, date: rawData.created_at, username: rawData.author_username, description, projectId: String(projectId), projectName, type: GitlabIntegration.type, __raw: this.bananaReporterConfig.includeRawObject ? rawData : undefined, }; } } exports.GitlabIntegration = GitlabIntegration; GitlabIntegration.type = base_1.SourceType.Enum.gitlab;