diff --git a/.vscode/settings.json b/.vscode/settings.json index b593b68..eef5140 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,7 @@ "Gruntfuggly", "micr", "mioffice", + "oxlint", "tseslint", "wlpbbgiky", "wujiali", diff --git a/bun.lockb b/bun.lockb index c4d56c8..4c6db08 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/controllers/managePipelineEvent/index.ts b/controllers/managePipelineEvent/index.ts index 6fc0b91..ea6fc61 100644 --- a/controllers/managePipelineEvent/index.ts +++ b/controllers/managePipelineEvent/index.ts @@ -1,7 +1,8 @@ +import db from "../../db" import service from "../../service" import netTool from "../../service/netTool" +import { EggMessage } from "../../types/eggMessage" import { Gitlab } from "../../types/gitlab" -import { Notify } from "../../types/notify" import { sec2minStr } from "../../utils/timeTools" /** @@ -19,8 +20,44 @@ const checkIsMergeCommit = (pipeline: Gitlab.PipelineEvent) => { * @param pipeline * @returns */ -const checkIsSuccess = (pipeline: Gitlab.PipelineEvent) => { - return pipeline.object_attributes.status === "success" +const checkIsSuccess = async ( + pipeline: Gitlab.PipelineEvent, + stage?: string | null +) => { + /** + * 创建结果对象 + * @param buildId 构建ID + * @param continueFlag 是否继续 + * @returns 结果对象 + */ + const makeResult = (buildId: string, continueFlag: boolean) => ({ + buildId: continueFlag ? buildId : "", + continueFlag, + }) + + // 没有指定Stage则整个流水线成功即为成功 + if (!stage) + return makeResult( + pipeline.builds + .sort((a, b) => a.finished_at.localeCompare(b.finished_at))[0] + .id.toString(), + pipeline.object_attributes.status === "success" + ) + // 指定了Stage,该Stage是否全部成功 + const builds = pipeline.builds.filter((build) => build.stage === stage) + // 没有该Stage的构建 + if (builds.length === 0) return makeResult("", false) + // 有该Stage的构建,但不全成功 + if (!builds.every((build) => build.status === "success")) + return makeResult("", false) + // 按finished_at排序,获取最后一个运行的id + const lastId = builds.sort((a, b) => + a.finished_at.localeCompare(b.finished_at) + )[0].id + // 该ID的通知是否已经发送过 + const notify = await db.notify.getOne(lastId.toString()) + if (notify) return makeResult("", false) + return makeResult(lastId.toString(), true) } /** @@ -63,7 +100,7 @@ const getUserInfo = ( * @param variable 模板变量 * @returns */ -const getRobotMsg = async (variable: Notify.CardVariable) => +const getRobotMsg = async (variable: EggMessage.CardVariable) => JSON.stringify({ type: "template", data: { @@ -83,16 +120,26 @@ const getRobotMsg = async (variable: Notify.CardVariable) => */ const sendNotifyMsg = async ( pipeline: Gitlab.PipelineEvent, - apiKey: string + apiKey: string, + params: URLSearchParams ) => { + const { continueFlag, buildId } = await checkIsSuccess( + pipeline, + params.get("stage") + ) // 只处理成功的CICD - if (!checkIsSuccess(pipeline)) return netTool.ok() + if (!continueFlag) return netTool.ok() // 获取对应的合并请求 const mergeRequest = await getMergeRequest(pipeline) // 获取用户信息 const { participant, receiver } = getUserInfo(pipeline, mergeRequest) + // sonar的ID + const sonarParams = new URLSearchParams({ + id: pipeline.project.path_with_namespace.replaceAll("/", ":"), + branch: pipeline.object_attributes.ref, + }) // 获取模板变量 - const variable: Notify.CardVariable = { + const variable: EggMessage.CardVariable = { project: pipeline.project.path_with_namespace, project_link: pipeline.project.web_url, pipeline: pipeline.object_attributes.id.toString(), @@ -105,11 +152,14 @@ const sendNotifyMsg = async ( commit_message: pipeline.commit.title, mr: mergeRequest ? mergeRequest.references.full : "无关联的MR", mr_link: mergeRequest ? mergeRequest.web_url : "", + sonar_link: `https://sonarqube.mioffice.cn/dashboard?${sonarParams}`, } // 获取机器人消息 const robotMsg = await getRobotMsg(variable) // 发送消息 - await service.message.byUserIdList(receiver, robotMsg, apiKey) + service.message.byUserIdList(receiver, robotMsg, apiKey) + // 记录日志 + await db.notify.create({ ...variable, build_id: buildId }) // 返回成功 return netTool.ok() } diff --git a/db/index.ts b/db/index.ts index 7f1d34e..7c59dab 100644 --- a/db/index.ts +++ b/db/index.ts @@ -1,3 +1,4 @@ +import notify from "./notify" import pipeline from "./pipeline" import project from "./project" import user from "./user" @@ -8,6 +9,7 @@ const db = { pipeline, user, view, + notify, } export default db diff --git a/db/notify/index.ts b/db/notify/index.ts new file mode 100644 index 0000000..3e7d93b --- /dev/null +++ b/db/notify/index.ts @@ -0,0 +1,29 @@ +import { DB } from "../../types/db" +import { managePb404 } from "../../utils/pbTools" +import pbClient from "../pbClient" + +/** + * 从数据库检索一个通知。 + * @param id 要检索的通知的ID。 + * @returns 如果找到,返回解析为通知对象的promise;如果未找到,抛出404错误。 + */ +const getOne = (id: string) => + managePb404( + async () => + await pbClient.collection("notify").getFirstListItem(`build_id="${id}"`) + ) + +/** + * 创建一个新的通知。 + * @param data 新通知的数据。 + * @returns 返回解析为已创建通知对象的promise。 + */ +const create = async (data: Partial) => + await pbClient.collection("notify").create(data) + +const notify = { + create, + getOne, +} + +export default notify diff --git a/package.json b/package.json index 324c34a..09f5ec3 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,9 @@ }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ + "oxlint --fix", "eslint --fix", - "prettier --write", - "git add" + "prettier --write" ] }, "devDependencies": { @@ -27,6 +27,7 @@ "globals": "^15.8.0", "husky": "^9.1.1", "lint-staged": "^15.2.7", + "oxlint": "^0.6.1", "prettier": "^3.3.3", "typescript-eslint": "^7.17.0" }, diff --git a/readme.md b/readme.md index fe16523..4a8f3f3 100644 --- a/readme.md +++ b/readme.md @@ -18,6 +18,9 @@ 组织卡片信息,给 Commit的用户以及 可能的 MR发起者发送通知 +## 处理在中间Stage需要提醒的情况 +在stage全部成功的情况下,按finish_at时间排序,找到最后一个stage,如果stage的状态是成功,且该build的id没有发送过通知,则发送通知 + # 数据库表信息 [数据库地址](https://gitlab-pb.xiaomiwh.cn/_/) diff --git a/routes/event/index.ts b/routes/event/index.ts index 23b9d1a..4b0aa0e 100644 --- a/routes/event/index.ts +++ b/routes/event/index.ts @@ -14,7 +14,8 @@ export const manageGitlabEventReq = async (req: Request) => { // 只处理流水线钩子 if (eventType === "Pipeline Hook") { const body = (await req.json()) as Gitlab.PipelineEvent - return managePipelineEvent.sendNotifyMsg(body, apiKey) + const params = new URLSearchParams(req.url) + return managePipelineEvent.sendNotifyMsg(body, apiKey, params) } return netTool.ok() } diff --git a/types/db.ts b/types/db.ts index 33aaa6f..2a215a2 100644 --- a/types/db.ts +++ b/types/db.ts @@ -1,5 +1,7 @@ import { RecordModel } from "pocketbase" +import { EggMessage } from "./eggMessage" + export namespace DB { export interface Pipeline extends RecordModel { project_id: string @@ -49,4 +51,8 @@ export namespace DB { duration: number ref: string } + + export interface Notify extends RecordModel, EggMessage.CardVariable { + build_id: string + } } diff --git a/types/notify.ts b/types/eggMessage.ts similarity index 84% rename from types/notify.ts rename to types/eggMessage.ts index 9ff955d..c9bed11 100644 --- a/types/notify.ts +++ b/types/eggMessage.ts @@ -1,4 +1,4 @@ -export namespace Notify { +export namespace EggMessage { export interface CardVariable { project: string project_link: string @@ -12,5 +12,6 @@ export namespace Notify { commit_message: string mr: string mr_link: string + sonar_link: string } } diff --git a/types/gitlab.ts b/types/gitlab.ts index 6e16e87..bceef33 100644 --- a/types/gitlab.ts +++ b/types/gitlab.ts @@ -268,6 +268,28 @@ export namespace Gitlab { */ url: string } + /** + * 构建信息 + */ + builds: { + /** + * 构建的ID + */ + id: number + /** + * 构建的步骤 + */ + stage: string + /** + * 构建的状态 + */ + status: string + /** + * 构建的结束时间 + * @example 2024-07-29 15:49:21 +0800 + */ + finished_at: string + }[] } /* 合并请求 */