├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── README.md ├── RangeList ├── RangeList.js └── __test__ │ └── RangeList.test.js ├── __mocks__ ├── fileMock.js └── styleMock.js ├── jest.config.js ├── package.json ├── src ├── actions │ ├── build.ts │ ├── comment.ts │ ├── issue.ts │ ├── merge_request.ts │ ├── pipeline.ts │ ├── push.ts │ ├── tag_push.ts │ ├── test.ts │ └── wiki.ts ├── config │ ├── dingtalk_custom_prefix.ts │ └── user_list.ts ├── events_mock_data │ ├── build.ts │ ├── comment.ts │ ├── confidential_issue.ts │ ├── issue.ts │ ├── merge_request.ts │ ├── merge_request_closed.ts │ ├── merge_request_merged.ts │ ├── normal_comment.ts │ ├── pipeline.ts │ ├── push.ts │ ├── tag_push.ts │ └── wiki.ts ├── index.ts └── utils │ ├── functions.ts │ └── request.ts ├── tsconfig.json ├── tsconfig.production.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env" 5 | ] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb", 4 | "airbnb/hooks", 5 | "plugin:jest/recommended", 6 | "plugin:prettier/recommended" 7 | ], 8 | // "extends": ["ewt", "airbnb", "airbnb/hooks", "plugin:prettier/recommended"], 9 | "env": { 10 | "node": true, 11 | "browser": true, 12 | "es6": true, 13 | "jest/globals": true 14 | }, 15 | "plugins": ["react", "jest"], 16 | "globals": {}, 17 | "rules": { 18 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.log 3 | .idea/ 4 | .ipr 5 | .iws 6 | *~ 7 | ~* 8 | *.diff 9 | *.patch 10 | *.bak 11 | .DS_Store 12 | Thumbs.db 13 | .project 14 | .*proj 15 | .svn/ 16 | *.swp 17 | *.swo 18 | *.pyc 19 | *.pyo 20 | .build 21 | node_modules 22 | _site 23 | sea-modules 24 | spm_modules 25 | .cache 26 | .happypack 27 | /dist 28 | /build 29 | /coverage 30 | /css 31 | .husky 32 | .vscode 33 | /src/config/user_list.ts 34 | log.txt 35 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | *.cfg 3 | node_modules/ 4 | nohup.out 5 | *.iml 6 | .idea/ 7 | .ipr 8 | .iws 9 | *~ 10 | ~* 11 | *.diff 12 | *.log 13 | *.patch 14 | *.bak 15 | .DS_Store 16 | Thumbs.db 17 | .project 18 | .*proj 19 | .svn/ 20 | *.swp 21 | out/ 22 | .build 23 | .happypack 24 | node_modules 25 | _site 26 | sea-modules 27 | spm_modules 28 | .cache 29 | __mocks__ 30 | __test__ 31 | .babelrc 32 | .editorconfig 33 | .eslintignore 34 | .eslintrc 35 | .gitignore 36 | index.html 37 | .tsconfig.json 38 | src 39 | .husky 40 | .vscode 41 | docs 42 | .prettierrc.json 43 | .stylelintrc.json 44 | tsconfig.production.json 45 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于 nodeJs 的 Code-Review 项目 2 | 3 | ## 开发 4 | 5 | ### 启动 6 | 7 | ``` 8 | yarn run start 9 | ``` 10 | 11 | ### 编译 12 | 13 | ``` 14 | yarn run build 15 | ``` 16 | 17 | ### 本地调试 18 | 19 | ``` 20 | yarn run dev 21 | ``` 22 | 23 | 参考文档地址 24 | 25 | * webhooks 数据格式:https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#pipeline-events 26 | 27 | -------------------------------------------------------------------------------- /RangeList/RangeList.js: -------------------------------------------------------------------------------- 1 | class RangeList { 2 | rangeList = []; // Store range list 3 | leftBoundary = Infinity; // Range list left boundary 4 | rightBoundary = -Infinity; // Range list right boundary 5 | 6 | /** 7 | * Adds a range to the list 8 | * @param {Array} range - Array of two integers that specify beginning and end of range. 9 | */ 10 | add(range) { 11 | this.checkArgumentsValidity(range); 12 | const [rangeBegin, rangeEnd] = range; 13 | this.leftBoundary = Math.min(rangeBegin, this.leftBoundary); 14 | this.rightBoundary = Math.max(rangeEnd, this.rightBoundary); 15 | for (let i = this.leftBoundary; i < this.rightBoundary; i += 1) { 16 | if (i >= rangeBegin && i < rangeEnd) { 17 | this.rangeList[i] = true; 18 | } 19 | } 20 | } 21 | 22 | /** 23 | * Removes a range from the list 24 | * @param {Array} range - Array of two integers that specify beginning and end of range. 25 | */ 26 | remove(range) { 27 | this.checkArgumentsValidity(range); 28 | const [rangeBegin, rangeEnd] = range; 29 | for (let i = this.leftBoundary; i < this.rightBoundary; i += 1) { 30 | if (i >= rangeBegin && i < rangeEnd) { 31 | this.rangeList[i] = false; 32 | } 33 | } 34 | 35 | // Reset boundary if range list is empty now 36 | if (rangeBegin === this.leftBoundary && rangeEnd === this.rightBoundary) { 37 | this.resetBoundary(); 38 | } 39 | } 40 | 41 | /** 42 | * Prints out the list of ranges in the range list 43 | * @returns {String} Range list string e.g [1, 3) [19, 21) 44 | */ 45 | print() { 46 | let result = ''; 47 | let currentRangeStr = ''; 48 | for (let i = this.leftBoundary; i < this.rightBoundary; i += 1) { 49 | if (currentRangeStr === '' && this.rangeList[i] === true) { 50 | // Generate current range string beginning 51 | currentRangeStr = `[${i}, `; 52 | 53 | // Loop to end of current range 54 | let tmpRangeCount = 0; 55 | let nextItem = this.rangeList[i]; 56 | while (nextItem) { 57 | tmpRangeCount += 1; 58 | nextItem = this.rangeList[i + tmpRangeCount]; 59 | } 60 | 61 | // Generate current range string end 62 | i += tmpRangeCount; 63 | currentRangeStr += `${i})`; 64 | 65 | // Append current range string to result 66 | result = result === '' ? currentRangeStr : `${result} ${currentRangeStr}`; 67 | 68 | // Reset current range string to empty 69 | currentRangeStr = ''; 70 | } 71 | } 72 | 73 | // Display range list 74 | console.log(result); 75 | return result; 76 | } 77 | 78 | /** 79 | * Reset range list boundary 80 | */ 81 | resetBoundary() { 82 | this.leftBoundary = Infinity; 83 | this.rightBoundary = -Infinity; 84 | } 85 | 86 | /** 87 | * Check if arguments is valid 88 | * @param {Array} range - Array of two integers that specify beginning and end of range. 89 | */ 90 | checkArgumentsValidity(range) { 91 | const [rangeBegin, rangeEnd] = range; 92 | 93 | if (!Array.isArray(range) || (Array.isArray(range) && range.length !== 2)) { 94 | throw new Error('Arguments must be an array with two elements of integer, e.g [1, 3]'); 95 | } 96 | 97 | if (!Number.isInteger(rangeBegin) || !Number.isInteger(rangeEnd)) { 98 | throw new Error('Arguments of range[0] & range[1] both must be integer, e.g [1, 3]'); 99 | } 100 | 101 | if (rangeBegin > rangeEnd) { 102 | throw new Error('Arguments of range[0] must be less than range[1], e.g [1, 3]'); 103 | } 104 | } 105 | } 106 | 107 | module.exports = RangeList; 108 | -------------------------------------------------------------------------------- /RangeList/__test__/RangeList.test.js: -------------------------------------------------------------------------------- 1 | const RangeList = require('../RangeList'); 2 | 3 | const rl = new RangeList(); 4 | 5 | describe('Exception logic', () => { 6 | test('Arguments must be an array with two elements of integer', () => { 7 | expect(() => { rl.add('1, 5'); }).toThrowError(new Error('Arguments must be an array with two elements of integer, e.g [1, 3]')); 8 | }); 9 | 10 | test('Arguments must be an array with two elements of integer', () => { 11 | expect(() => { rl.add([1, 2, 3]); }).toThrowError(new Error('Arguments must be an array with two elements of integer, e.g [1, 3]')); 12 | }); 13 | 14 | test('Arguments of range[0] & range[1] both must be integer, e.g [1, 3]', () => { 15 | expect(() => { rl.add([1, '2']); }).toThrowError(new Error('Arguments of range[0] & range[1] both must be integer, e.g [1, 3]')); 16 | }); 17 | 18 | test('Arguments of range[0] must be less than range[1], e.g [1, 3]', () => { 19 | expect(() => { rl.add([4, 1]); }).toThrowError(new Error('Arguments of range[0] must be less than range[1], e.g [1, 3]')); 20 | }); 21 | }); 22 | 23 | describe('Function add should perform as expected', () => { 24 | test('Add [1, 5] at first, should display: [1, 5)', () => { 25 | rl.add([1, 5]); 26 | expect(rl.print()).toBe('[1, 5)'); 27 | }); 28 | 29 | test('Then add [10, 20] , should display: [1, 5) [10, 20)', () => { 30 | rl.add([10, 20]); 31 | expect(rl.print()).toBe('[1, 5) [10, 20)'); 32 | }); 33 | 34 | test('Then add [20, 20] , should display: [1, 5) [10, 20)', () => { 35 | rl.add([20, 20]); 36 | expect(rl.print()).toBe('[1, 5) [10, 20)'); 37 | }); 38 | 39 | test('Then add [20, 21] , should display: [1, 5) [10, 21)', () => { 40 | rl.add([20, 21]); 41 | expect(rl.print()).toBe('[1, 5) [10, 21)'); 42 | }); 43 | 44 | test('Then add [2, 4] , should display: [1, 5) [10, 21)', () => { 45 | rl.add([2, 4]); 46 | expect(rl.print()).toBe('[1, 5) [10, 21)'); 47 | }); 48 | 49 | test('Then add [3, 8] , should display: [1, 8) [10, 21)', () => { 50 | rl.add([3, 8]); 51 | expect(rl.print()).toBe('[1, 8) [10, 21)'); 52 | }); 53 | 54 | test('Then add [10, 10] , should display: [1, 8) [10, 21)', () => { 55 | rl.add([10, 10]); 56 | expect(rl.print()).toBe('[1, 8) [10, 21)'); 57 | }); 58 | 59 | }); 60 | 61 | describe('Function remove should perform as expected', () => { 62 | test('Then remove [10, 10] , should display: [1, 8) [10, 21)', () => { 63 | rl.remove([10, 10]); 64 | expect(rl.print()).toBe('[1, 8) [10, 21)'); 65 | }); 66 | 67 | test('Then remove [10, 11] , should display: [1, 8) [11, 21)', () => { 68 | rl.remove([10, 11]); 69 | expect(rl.print()).toBe('[1, 8) [11, 21)'); 70 | }); 71 | 72 | test('Then remove [15, 17] , should display: [1, 8) [11, 15) [17, 21)', () => { 73 | rl.remove([15, 17]); 74 | expect(rl.print()).toBe('[1, 8) [11, 15) [17, 21)'); 75 | }); 76 | 77 | test('Then remove [3, 19] , should display: [1, 3) [19, 21)', () => { 78 | rl.remove([3, 19]); 79 | expect(rl.print()).toBe('[1, 3) [19, 21)'); 80 | }); 81 | 82 | test('Then remove [-5, 1] , should display: [1, 3) [19, 21)', () => { 83 | rl.remove([-5, 1]); 84 | expect(rl.print()).toBe('[1, 3) [19, 21)'); 85 | }); 86 | 87 | test('Then remove [-7, -2] , should display: [1, 3) [19, 21)', () => { 88 | rl.remove([-7, -2]); 89 | expect(rl.print()).toBe('[1, 3) [19, 21)'); 90 | }); 91 | 92 | test('Then remove [21, 21] , should display: [1, 3) [19, 21)', () => { 93 | rl.remove([21, 21]); 94 | expect(rl.print()).toBe('[1, 3) [19, 21)'); 95 | }); 96 | 97 | test('Then remove [29, 50] , should display: [1, 3) [19, 21)', () => { 98 | rl.remove([29, 50]); 99 | expect(rl.print()).toBe('[1, 3) [19, 21)'); 100 | }); 101 | 102 | test('Then remove [2, 23] , should display: [1, 2)', () => { 103 | rl.remove([2, 23]); 104 | expect(rl.print()).toBe('[1, 2)'); 105 | }); 106 | }); 107 | 108 | describe('Then add & remove intersects should perform as expected', () => { 109 | test('Then remove [1, 10] , should display(empty string): \'\'', () => { 110 | rl.remove([1, 10]); 111 | expect(rl.print()).toBe(''); 112 | }); 113 | 114 | test('Then add [1, 5] , should display: [1, 5)', () => { 115 | rl.add([1, 5]); 116 | expect(rl.print()).toBe('[1, 5)'); 117 | }); 118 | 119 | test('Then add [10, 21] , should display: [1, 5) [10, 21)', () => { 120 | rl.add([10, 21]); 121 | expect(rl.print()).toBe('[1, 5) [10, 21)'); 122 | }); 123 | 124 | test('Then add [25, 30] , should display: [1, 5) [10, 21) [25, 30)', () => { 125 | rl.add([25, 30]); 126 | expect(rl.print()).toBe('[1, 5) [10, 21) [25, 30)'); 127 | }); 128 | 129 | test('Then add [-10, -5] , should display: [-10, -5) [1, 5) [10, 21) [25, 30)', () => { 130 | rl.add([-10, -5]); 131 | expect(rl.print()).toBe('[-10, -5) [1, 5) [10, 21) [25, 30)'); 132 | }); 133 | 134 | test('Then add [-7, 4] , should display: [-10, 5) [10, 21) [25, 30)', () => { 135 | rl.add([-7, 4]); 136 | expect(rl.print()).toBe('[-10, 5) [10, 21) [25, 30)'); 137 | }); 138 | 139 | test('Then add [-13, -10] , should display: [-13, 5) [10, 21) [25, 30)', () => { 140 | rl.add([-13, -10]); 141 | expect(rl.print()).toBe('[-13, 5) [10, 21) [25, 30)'); 142 | }); 143 | 144 | test('Then remove [-13, 5] , should display: [10, 21) [25, 30)', () => { 145 | rl.remove([-13, 5]); 146 | expect(rl.print()).toBe('[10, 21) [25, 30)'); 147 | }); 148 | 149 | test('Then add [30, 49] , should display: [10, 21) [25, 49)', () => { 150 | rl.add([30, 49]); 151 | expect(rl.print()).toBe('[10, 21) [25, 49)'); 152 | }); 153 | 154 | test('Then remove [10, 21] , should display: [25, 49)', () => { 155 | rl.remove([10, 21]); 156 | expect(rl.print()).toBe('[25, 49)'); 157 | }); 158 | }); -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.(j|t)sx?$': 'babel-jest', 4 | }, 5 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'], 6 | moduleDirectories: ['node_modules', 'bower_components', 'shared'], 7 | moduleNameMapper: { 8 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 9 | '/__mocks__/fileMock.js', 10 | '\\.(css|less|scss|sass)$': 'identity-obj-proxy', 11 | }, 12 | testEnvironment: 'jsdom', 13 | // setupFiles: ['./config/test-setup.js'], 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-review", 3 | "version": "1.0.0", 4 | "description": "nodejs + gitlab webhook code review", 5 | "main": "T/T.js", 6 | "scripts": { 7 | "test": "ts-node ./src/actions/test.ts", 8 | "dev": "nodemon ./src/index.ts", 9 | "start": "pm2 start ./dist/index.js", 10 | "build": "rm -rf dist && tsc -p tsconfig.production.json" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "http://git.mistong.com/jingweirong/code-review" 15 | }, 16 | "keywords": [ 17 | "code", 18 | "review", 19 | "koa" 20 | ], 21 | "author": "weirong.jing", 22 | "license": "MIT", 23 | "lint-staged": { 24 | "**/*": "prettier --write --ignore-unknown" 25 | }, 26 | "peerDependencies": {}, 27 | "devDependencies": { 28 | "@babel/preset-env": "^7.16.11", 29 | "@tsconfig/recommended": "^1.0.1", 30 | "@types/jest": "^27.4.0", 31 | "@types/koa": "^2.13.4", 32 | "@types/koa-bodyparser": "^4.3.5", 33 | "@types/koa-router": "^7.4.4", 34 | "babel-jest": "^27.4.5", 35 | "eslint": "^8.2.0", 36 | "eslint-config-airbnb": "19.0.2", 37 | "eslint-config-ewt": "^1.0.0", 38 | "eslint-config-prettier": "^8.3.0", 39 | "eslint-plugin-import": "^2.25.3", 40 | "eslint-plugin-jest": "^25.3.4", 41 | "eslint-plugin-jsx-a11y": "^6.5.1", 42 | "eslint-plugin-prettier": "^4.0.0", 43 | "html-webpack-plugin": "^5.5.0", 44 | "husky": "^7.0.4", 45 | "jest": "^27.4.5", 46 | "lint-staged": "^12.1.3", 47 | "nodemon": "^2.0.15", 48 | "prettier": "^2.5.1", 49 | "ts-loader": "^9.2.6", 50 | "ts-node": "^10.4.0", 51 | "typescript": "^4.5.4" 52 | }, 53 | "dependencies": { 54 | "axios": "^0.24.0", 55 | "koa": "^2.13.4", 56 | "koa-bodyparser": "^4.3.0", 57 | "koa-router": "^10.1.1", 58 | "pm2": "^5.1.2" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/actions/build.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export default function comments(gitlabDataFromWebHook: any) { 4 | const { 5 | dingTalkUrl, 6 | ref, 7 | build_name: jobName, 8 | build_stage: jobStage, 9 | build_status: jobStatus, 10 | user: { 11 | name: applyerName, 12 | }, 13 | repository: { 14 | name: projectName, 15 | homepage: projectUrl, 16 | }, 17 | } = gitlabDataFromWebHook; 18 | 19 | const text = `${applyerName} run build with branch ${ref.replace('refs/heads/', '')} at repository [${projectName}](${projectUrl}) \n` 20 | + `> ##### BuildName: ${jobName} \n` 21 | + `> ##### BuildStage: ${jobStage} \n` 22 | + `> ##### BuildStatus: ${jobStatus} \n`; 23 | request(dingTalkUrl, text); 24 | } 25 | -------------------------------------------------------------------------------- /src/actions/comment.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | import userList from '../config/user_list'; 3 | import { 4 | upperCamelCaseToLowerCase, 5 | gitlabUserToDingTalkUser, 6 | } from '../utils/functions'; 7 | 8 | export default function comments(gitlabDataFromWebHook: any) { 9 | const { 10 | dingTalkUrl, 11 | user: { 12 | name: applyerName, 13 | }, 14 | object_attributes: { 15 | url: gitActionUrl, 16 | noteable_type: noteAbleType, 17 | note, 18 | }, 19 | project: { 20 | name: projectName, 21 | }, 22 | } = gitlabDataFromWebHook; 23 | 24 | const isMergeRequestComment = noteAbleType === 'MergeRequest'; // check is normal comment or MR comment 25 | let commentSuffixDes = ''; 26 | if (isMergeRequestComment) { 27 | const { 28 | merge_request: { 29 | source_branch, 30 | target_branch, 31 | }, 32 | } = gitlabDataFromWebHook; 33 | commentSuffixDes = `from ${source_branch} to ${target_branch}`; 34 | } 35 | 36 | // if note is 1, it means this mr is ready for next process, then @ master 37 | const noteDescription = String(note) === '1' ? `1 上一轮MR已完成 @${userList['jingweirong']}` : gitlabUserToDingTalkUser(note); 38 | const text = `${applyerName} [commented](${gitActionUrl}) on ${upperCamelCaseToLowerCase(noteAbleType)} ${commentSuffixDes} \n` 39 | + `> ${noteDescription} \n` 40 | + `> [${gitActionUrl}](${gitActionUrl}) \n` 41 | + `> Repository: ${projectName} \n`; 42 | request(dingTalkUrl, text); 43 | } 44 | -------------------------------------------------------------------------------- /src/actions/issue.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | import userList from '../config/user_list'; 3 | 4 | export default function comments(gitlabDataFromWebHook: any) { 5 | const { 6 | dingTalkUrl, 7 | user: { 8 | name: applyerName, 9 | }, 10 | project: { 11 | name: projectName, 12 | }, 13 | object_attributes: { 14 | title, 15 | url: issueUrl, 16 | state: actionState, 17 | }, 18 | assignee: { 19 | name: assigneeName, 20 | username: assignUsername, 21 | } 22 | } = gitlabDataFromWebHook; 23 | let assigneeStr = ''; 24 | if (actionState === 'opened') { 25 | assigneeStr = `@${userList[assigneeName] || userList[assignUsername]}`; 26 | // if no assignee then @ the applyer him/her self 27 | if (!userList[assigneeName] && !userList[assignUsername]) { 28 | assigneeStr = ''; 29 | } 30 | } 31 | // ##### is needed to break line into multiple 32 | const text = `${applyerName} open issue at repository ${projectName} \n` 33 | + `> ##### [${title}](${issueUrl}) \n` 34 | + `> ##### State: ${actionState} \n` 35 | + `> ##### Assignee: ${assigneeName} ${assigneeStr}`; 36 | request(dingTalkUrl, text); 37 | } 38 | -------------------------------------------------------------------------------- /src/actions/merge_request.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | import userList from '../config/user_list'; 3 | 4 | /** 5 | * 需求 6 | * 1. merge request 直接@ 对应的 assignee user 7 | * 2. 如果没有设置默认的 assignee user, 则不@ 8 | */ 9 | 10 | export default function mergeRequest(gitlabDataFromWebHook: any) { 11 | const { 12 | dingTalkUrl, 13 | object_kind, 14 | user: { 15 | name: applyerName, 16 | username: applyerUsername, 17 | }, 18 | assignee: { 19 | name: assigneeName, 20 | username: assignUsername, 21 | }, 22 | object_attributes: { 23 | action, 24 | url: gitActionUrl, 25 | title, 26 | description, 27 | source_branch, 28 | target_branch, 29 | state: actionState, 30 | }, 31 | project: { 32 | name: projectName, 33 | }, 34 | } = gitlabDataFromWebHook; 35 | 36 | // if merge request no assignee, then @ applyer and notice him/her 37 | // only @assignee if mr is opened 38 | let assigneeStr = ''; 39 | if (actionState === 'opened') { 40 | assigneeStr = `@${userList[assigneeName] || userList[assignUsername]}`; 41 | // if no assignee then @ the applyer him/her self 42 | if (!userList[assigneeName] && !userList[assignUsername]) { 43 | assigneeStr = `@${userList[applyerName] || userList[applyerUsername]} 要找谁帮你merge代码呢?记得选择Assignee哦` 44 | } 45 | } 46 | if (actionState === 'merged') { 47 | const { 48 | object_attributes: { 49 | // last_commit is exist this case 50 | last_commit: { 51 | author: { 52 | email: applyerEmail, 53 | }, 54 | }, 55 | }, 56 | } = gitlabDataFromWebHook 57 | const lastCommitUsername = applyerEmail.replace('@mistong.com', ''); 58 | if (lastCommitUsername !== applyerName) { 59 | // only @ user if this actions is not dispatched by him/her self 60 | assigneeStr = `@${userList[lastCommitUsername] || '佚名'}, you can go on now`; 61 | } 62 | } 63 | 64 | const text = `${applyerName} ${action} the ${object_kind} from ${source_branch} to ${target_branch} ${assigneeStr} \n ` 65 | + `> [${title}](${gitActionUrl}) \n ` 66 | + `> ###### [${description}](${gitActionUrl}) \n ` 67 | + ` > ###### Status: ${actionState} \n ` 68 | + `> Repository: ${projectName} \n ` 69 | + `> ###### [${gitActionUrl}](${gitActionUrl})`; 70 | 71 | request(dingTalkUrl, text); 72 | } 73 | -------------------------------------------------------------------------------- /src/actions/pipeline.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export default function comments(gitlabDataFromWebHook: any) { 4 | const { 5 | dingTalkUrl, 6 | object_attributes: { 7 | ref, 8 | }, 9 | user: { 10 | name: applyerName, 11 | }, 12 | project: { 13 | name: projectName, 14 | web_url: projectUrl, 15 | }, 16 | } = gitlabDataFromWebHook; 17 | 18 | const text = `${applyerName} run pipeline with branch ${ref.replace('refs/heads/', '')} at repository [${projectName}](${projectUrl}) \n`; 19 | request(dingTalkUrl, text); 20 | } 21 | -------------------------------------------------------------------------------- /src/actions/push.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export default function comments(gitlabDataFromWebHook: any) { 4 | const { 5 | dingTalkUrl, 6 | ref, 7 | user_name: applyerName, 8 | commits, 9 | project: { 10 | name: projectName, 11 | }, 12 | } = gitlabDataFromWebHook; 13 | // ##### is needed to break line into multiple 14 | const commitsStr = commits.map((item: any) => `> ##### [${String(item.id).substring(0, 8)}](${item.url})`).join('\n'); 15 | const text = `${applyerName} pushed to branch ${ref.replace('refs/heads/', '')} at repository ${projectName} \n ${commitsStr}`; 16 | request(dingTalkUrl, text); 17 | } 18 | -------------------------------------------------------------------------------- /src/actions/tag_push.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export default function comments(gitlabDataFromWebHook: any) { 4 | const { 5 | dingTalkUrl, 6 | ref, 7 | user_name: applyerName, 8 | project: { 9 | name: projectName, 10 | web_url: projectUrl, 11 | }, 12 | } = gitlabDataFromWebHook; 13 | 14 | const text = `${applyerName} pushed tag ${ref.replace('refs/tags/', '')} at repository [${projectName}](${projectUrl}) \n`; 15 | request(dingTalkUrl, text); 16 | } 17 | -------------------------------------------------------------------------------- /src/actions/test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | // import gitlabDataFromWebHook from '../events_mock_data/merge_request'; 3 | // import gitlabDataFromWebHook from '../events_mock_data/comment'; 4 | // import gitlabDataFromWebHook from '../events_mock_data/normal_comment'; 5 | // import gitlabDataFromWebHook from '../events_mock_data/merge_request_closed'; 6 | // import gitlabDataFromWebHook from '../events_mock_data/merge_request_merged'; 7 | // import gitlabDataFromWebHook from '../events_mock_data/push'; 8 | // import gitlabDataFromWebHook from '../events_mock_data/tag_push'; 9 | // import gitlabDataFromWebHook from '../events_mock_data/issue'; 10 | // import gitlabDataFromWebHook from '../events_mock_data/build'; 11 | // import gitlabDataFromWebHook from '../events_mock_data/pipeline'; 12 | import gitlabDataFromWebHook from '../events_mock_data/wiki'; 13 | 14 | const crTestDingTalkGroupUrl = 'https://oapi.dingtalk.com/robot/send?access_token=ce934d72606e0fe43cfa015f628cbc1b95438f47f0d575c6336a95eea1490b96'; 15 | const localHook = `http://127.0.0.1:50001/code-review?dingTalkUrl=${encodeURIComponent(crTestDingTalkGroupUrl)}`; 16 | // const localHook = `http://121.196.38.64:50001/code-review?dingTalkUrl=${encodeURIComponent(crTestDingTalkGroupUrl)}`; 17 | 18 | axios.post( 19 | localHook, 20 | gitlabDataFromWebHook, 21 | ).then((res) => { 22 | console.log('Action successfully'); 23 | }).catch((e) => { 24 | console.error('error', e); 25 | }); 26 | -------------------------------------------------------------------------------- /src/actions/wiki.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export default function comments(gitlabDataFromWebHook: any) { 4 | const { 5 | dingTalkUrl, 6 | object_attributes: { 7 | title: wikiPageTitle, 8 | url: wikiPageUrl, 9 | }, 10 | user: { 11 | name: applyerName, 12 | }, 13 | project: { 14 | name: projectName, 15 | }, 16 | } = gitlabDataFromWebHook; 17 | 18 | const text = `${applyerName} create a wiki page at repository ${projectName} \n` 19 | + `> [${wikiPageTitle}](${wikiPageUrl})`; 20 | request(dingTalkUrl, text); 21 | } 22 | -------------------------------------------------------------------------------- /src/config/dingtalk_custom_prefix.ts: -------------------------------------------------------------------------------- 1 | export default 'CodeReview'; -------------------------------------------------------------------------------- /src/config/user_list.ts: -------------------------------------------------------------------------------- 1 | const userList: any = { 2 | jing: '135xxxxxxxx', 3 | }; 4 | 5 | export default userList; 6 | -------------------------------------------------------------------------------- /src/events_mock_data/build.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | "object_kind": "build", 3 | "ref": "gitlab-script-trigger", 4 | "tag": false, 5 | "before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", 6 | "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", 7 | "build_id": 1977, 8 | "build_name": "test", 9 | "build_stage": "test", 10 | "build_status": "created", 11 | "build_created_at": "2021-02-23T02:41:37.886Z", 12 | "build_started_at": null, 13 | "build_finished_at": null, 14 | "build_duration": null, 15 | "build_allow_failure": false, 16 | "build_failure_reason": "script_failure", 17 | "pipeline_id": 2366, 18 | "project_id": 380, 19 | "project_name": "gitlab-org/gitlab-test", 20 | "user": { 21 | "id": 3, 22 | "name": "tang", 23 | "email": "tang@gitlab.com", 24 | "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon", 25 | }, 26 | "commit": { 27 | "id": 2366, 28 | "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", 29 | "message": "test\n", 30 | "author_name": "User", 31 | "author_email": "user@gitlab.com", 32 | "status": "created", 33 | "duration": null, 34 | "started_at": null, 35 | "finished_at": null 36 | }, 37 | "repository": { 38 | "name": "gitlab_test", 39 | "description": "Atque in sunt eos similique dolores voluptatem.", 40 | "homepage": "http://192.168.64.1:3005/gitlab-org/gitlab-test", 41 | "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git", 42 | "git_http_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test.git", 43 | "visibility_level": 20 44 | }, 45 | "runner": { 46 | "active": true, 47 | "runner_type": "project_type", 48 | "is_shared": false, 49 | "id": 380987, 50 | "description": "shared-runners-manager-6.gitlab.com", 51 | "tags": [ 52 | "linux", 53 | "docker" 54 | ] 55 | }, 56 | "environment": null 57 | } -------------------------------------------------------------------------------- /src/events_mock_data/comment.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | object_kind: 'note', 3 | user: { 4 | name: 'tang', 5 | username: 'tang', 6 | avatar_url: 7 | 'http://git.mistong.com/uploads/user/avatar/4/4e97a4977e71a729cbe3a9aa082b3ee.jpg', 8 | }, 9 | project_id: 2810, 10 | project: { 11 | name: 'code-review', 12 | description: '', 13 | web_url: 'http://git.mistong.com/jingweirong/code-review', 14 | avatar_url: null, 15 | git_ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 16 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 17 | namespace: 'jingweirong', 18 | visibility_level: 20, 19 | path_with_namespace: 'jingweirong/code-review', 20 | default_branch: 'master', 21 | homepage: 'http://git.mistong.com/jingweirong/code-review', 22 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 23 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 24 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 25 | }, 26 | object_attributes: { 27 | id: 84073, 28 | note: '评论 @tang @jingweirong', 29 | noteable_type: 'MergeRequest', 30 | author_id: 4, 31 | created_at: '2022-01-19 03:06:13 UTC', 32 | updated_at: '2022-01-19 03:06:13 UTC', 33 | project_id: 2810, 34 | attachment: null, 35 | line_code: '8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d_4_4', 36 | commit_id: '', 37 | noteable_id: 33701, 38 | system: false, 39 | st_diff: null, 40 | updated_by_id: null, 41 | type: 'DiffNote', 42 | position: { 43 | old_path: 'README.md', 44 | new_path: 'README.md', 45 | old_line: null, 46 | new_line: 4, 47 | base_sha: 'adf4ea71d6996cf1e51b88bced9f501aa25358df', 48 | start_sha: 'adf4ea71d6996cf1e51b88bced9f501aa25358df', 49 | head_sha: '840a5c1f2a58e83f29a168d29ab59b0f0ff45e05', 50 | }, 51 | original_position: { 52 | old_path: 'README.md', 53 | new_path: 'README.md', 54 | old_line: null, 55 | new_line: 4, 56 | base_sha: 'adf4ea71d6996cf1e51b88bced9f501aa25358df', 57 | start_sha: 'adf4ea71d6996cf1e51b88bced9f501aa25358df', 58 | head_sha: '840a5c1f2a58e83f29a168d29ab59b0f0ff45e05', 59 | }, 60 | resolved_at: null, 61 | resolved_by_id: null, 62 | discussion_id: '4ea4a9bb6b7b1dce628e70623c99be817ac88e8e', 63 | original_discussion_id: '4ea4a9bb6b7b1dce628e70623c99be817ac88e8e', 64 | url: 'http://git.mistong.com/jingweirong/code-review/merge_requests/2#note_84073', 65 | }, 66 | repository: { 67 | name: 'code-review', 68 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 69 | description: '', 70 | homepage: 'http://git.mistong.com/jingweirong/code-review', 71 | }, 72 | merge_request: { 73 | id: 33701, 74 | target_branch: 'trunk', 75 | source_branch: 'master', 76 | source_project_id: 2810, 77 | author_id: 4, 78 | assignee_id: 251, 79 | title: 'title', 80 | created_at: '2022-01-18 02:21:35 UTC', 81 | updated_at: '2022-01-19 03:06:13 UTC', 82 | milestone_id: null, 83 | state: 'opened', 84 | merge_status: 'can_be_merged', 85 | target_project_id: 2810, 86 | iid: 2, 87 | description: 'Description', 88 | position: 0, 89 | locked_at: null, 90 | updated_by_id: null, 91 | merge_error: null, 92 | merge_params: { force_remove_source_branch: null }, 93 | merge_when_build_succeeds: false, 94 | merge_user_id: null, 95 | merge_commit_sha: null, 96 | deleted_at: null, 97 | in_progress_merge_commit_sha: null, 98 | lock_version: 0, 99 | source: { 100 | name: 'code-review', 101 | description: '', 102 | web_url: 'http://git.mistong.com/jingweirong/code-review', 103 | avatar_url: null, 104 | git_ssh_url: 105 | 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 106 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 107 | namespace: 'jingweirong', 108 | visibility_level: 20, 109 | path_with_namespace: 'jingweirong/code-review', 110 | default_branch: 'master', 111 | homepage: 'http://git.mistong.com/jingweirong/code-review', 112 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 113 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 114 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 115 | }, 116 | target: { 117 | name: 'code-review', 118 | description: '', 119 | web_url: 'http://git.mistong.com/jingweirong/code-review', 120 | avatar_url: null, 121 | git_ssh_url: 122 | 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 123 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 124 | namespace: 'jingweirong', 125 | visibility_level: 20, 126 | path_with_namespace: 'jingweirong/code-review', 127 | default_branch: 'master', 128 | homepage: 'http://git.mistong.com/jingweirong/code-review', 129 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 130 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 131 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 132 | }, 133 | last_commit: { 134 | id: '840a5c1f2a58e83f29a168d29ab59b0f0ff45e05', 135 | message: 'Update README.md', 136 | timestamp: '2022-01-18T10:14:27+08:00', 137 | url: 'http://git.mistong.com/jingweirong/code-review/commit/840a5c1f2a58e83f29a168d29ab59b0f0ff45e05', 138 | author: { name: 'jingweirong', email: 'jingweirong@mistong.com' }, 139 | }, 140 | work_in_progress: false, 141 | }, 142 | }; 143 | -------------------------------------------------------------------------------- /src/events_mock_data/confidential_issue.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | object_kind: 'issue', 3 | user: { 4 | name: 'jingweirong', 5 | username: 'jingweirong', 6 | avatar_url: 'http://git.mistong.com/uploads/user/avatar/251/avatar.png', 7 | }, 8 | project: { 9 | name: 'code-review', 10 | description: '', 11 | web_url: 'http://git.mistong.com/jingweirong/code-review', 12 | avatar_url: null, 13 | git_ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 14 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 15 | namespace: 'jingweirong', 16 | visibility_level: 20, 17 | path_with_namespace: 'jingweirong/code-review', 18 | default_branch: 'master', 19 | homepage: 'http://git.mistong.com/jingweirong/code-review', 20 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 21 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 22 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 23 | }, 24 | object_attributes: { 25 | id: 166, 26 | title: '条件issue标题', 27 | assignee_id: 402, 28 | author_id: 251, 29 | project_id: 2810, 30 | created_at: '2022-02-08 10:43:30 +0800', 31 | updated_at: '2022-02-08 10:43:30 +0800', 32 | position: 0, 33 | branch_name: null, 34 | description: '条件issue描述', 35 | milestone_id: null, 36 | state: 'opened', 37 | iid: 2, 38 | updated_by_id: null, 39 | confidential: true, 40 | deleted_at: null, 41 | due_date: null, 42 | moved_to_id: null, 43 | lock_version: 0, 44 | url: 'http://git.mistong.com/jingweirong/code-review/issues/2', 45 | action: 'open', 46 | }, 47 | repository: { 48 | name: 'code-review', 49 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 50 | description: '', 51 | homepage: 'http://git.mistong.com/jingweirong/code-review', 52 | }, 53 | assignee: { 54 | name: '吴>达建', 55 | username: 'wudajian', 56 | avatar_url: 57 | 'http://www.gravatar.com/avatar/75730c2b8be0cb5642832bc4807acd11?s=80&d=identicon', 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/events_mock_data/issue.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | object_kind: 'issue', 3 | user: { 4 | name: 'jingweirong', 5 | username: 'jingweirong', 6 | avatar_url: 'http://git.mistong.com/uploads/user/avatar/251/avatar.png', 7 | }, 8 | project: { 9 | name: 'code-review', 10 | description: '', 11 | web_url: 'http://git.mistong.com/jingweirong/code-review', 12 | avatar_url: null, 13 | git_ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 14 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 15 | namespace: 'jingweirong', 16 | visibility_level: 20, 17 | path_with_namespace: 'jingweirong/code-review', 18 | default_branch: 'master', 19 | homepage: 'http://git.mistong.com/jingweirong/code-review', 20 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 21 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 22 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 23 | }, 24 | object_attributes: { 25 | id: 165, 26 | title: '第一个issue', 27 | assignee_id: 4, 28 | author_id: 251, 29 | project_id: 2810, 30 | created_at: '2022-02-08 10:34:27 +0800', 31 | updated_at: '2022-02-08 10:34:27 +0800', 32 | position: 0, 33 | branch_name: null, 34 | description: 'issue的描述', 35 | milestone_id: null, 36 | state: 'opened', 37 | iid: 1, 38 | updated_by_id: null, 39 | confidential: false, 40 | deleted_at: null, 41 | due_date: null, 42 | moved_to_id: null, 43 | lock_version: 0, 44 | url: 'http://git.mistong.com/jingweirong/code-review/issues/1', 45 | action: 'open', 46 | }, 47 | repository: { 48 | name: 'code-review', 49 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 50 | description: '', 51 | homepage: 'http://git.mistong.com/jingweirong/code-review', 52 | }, 53 | assignee: { 54 | name: 'tang', 55 | username: 'tang', 56 | avatar_url: 57 | 'http://git.mistong.com/uploads/user/avatar/4/4e97a4977e71a729cbe3a9aa082b3ee.jpg', 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/events_mock_data/merge_request.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | object_kind: 'merge_request', 3 | user: { 4 | name: 'tang', 5 | username: 'tang', 6 | avatar_url: 7 | 'http://git.mistong.com/uploads/user/avatar/4/4e97a4977e71a729cbe3a9aa082b3ee.jpg', 8 | }, 9 | project: { 10 | name: 'code-review', 11 | description: '', 12 | web_url: 'http://git.mistong.com/jingweirong/code-review', 13 | avatar_url: null, 14 | git_ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 15 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 16 | namespace: 'jingweirong', 17 | visibility_level: 20, 18 | path_with_namespace: 'jingweirong/code-review', 19 | default_branch: 'master', 20 | homepage: 'http://git.mistong.com/jingweirong/code-review', 21 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 22 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 23 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 24 | }, 25 | object_attributes: { 26 | id: 33701, 27 | target_branch: 'trunk', 28 | source_branch: 'master', 29 | source_project_id: 2810, 30 | author_id: 4, 31 | assignee_id: 251, 32 | title: 'title', 33 | created_at: '2022-01-18 10:21:35 +0800', 34 | updated_at: '2022-01-18 10:21:35 +0800', 35 | milestone_id: null, 36 | state: 'opened', 37 | merge_status: 'unchecked', 38 | target_project_id: 2810, 39 | iid: 2, 40 | description: 'Description', 41 | position: 0, 42 | locked_at: null, 43 | updated_by_id: null, 44 | merge_error: null, 45 | merge_params: { force_remove_source_branch: null }, 46 | merge_when_build_succeeds: false, 47 | merge_user_id: null, 48 | merge_commit_sha: null, 49 | deleted_at: null, 50 | in_progress_merge_commit_sha: null, 51 | lock_version: 0, 52 | source: { 53 | name: 'code-review', 54 | description: '', 55 | web_url: 'http://git.mistong.com/jingweirong/code-review', 56 | avatar_url: null, 57 | git_ssh_url: 58 | 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 59 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 60 | namespace: 'jingweirong', 61 | visibility_level: 20, 62 | path_with_namespace: 'jingweirong/code-review', 63 | default_branch: 'master', 64 | homepage: 'http://git.mistong.com/jingweirong/code-review', 65 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 66 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 67 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 68 | }, 69 | target: { 70 | name: 'code-review', 71 | description: '', 72 | web_url: 'http://git.mistong.com/jingweirong/code-review', 73 | avatar_url: null, 74 | git_ssh_url: 75 | 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 76 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 77 | namespace: 'jingweirong', 78 | visibility_level: 20, 79 | path_with_namespace: 'jingweirong/code-review', 80 | default_branch: 'master', 81 | homepage: 'http://git.mistong.com/jingweirong/code-review', 82 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 83 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 84 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 85 | }, 86 | last_commit: { 87 | id: '840a5c1f2a58e83f29a168d29ab59b0f0ff45e05', 88 | message: 'Update README.md', 89 | timestamp: '2022-01-18T10:14:27+08:00', 90 | url: 'http://git.mistong.com/jingweirong/code-review/commit/840a5c1f2a58e83f29a168d29ab59b0f0ff45e05', 91 | author: { name: 'jingweirong', email: 'jingweirong@mistong.com' }, 92 | }, 93 | work_in_progress: false, 94 | url: 'http://git.mistong.com/jingweirong/code-review/merge_requests/2', 95 | action: 'open', 96 | }, 97 | repository: { 98 | name: 'code-review', 99 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 100 | description: '', 101 | homepage: 'http://git.mistong.com/jingweirong/code-review', 102 | }, 103 | assignee: { 104 | name: 'jingweirong', 105 | username: 'jingweirong', 106 | avatar_url: 'http://git.mistong.com/uploads/user/avatar/251/avatar.png', 107 | }, 108 | }; 109 | -------------------------------------------------------------------------------- /src/events_mock_data/merge_request_closed.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | object_kind: 'merge_request', 3 | user: { 4 | name: 'jingweirong', 5 | username: 'jingweirong', 6 | avatar_url: 'http://git.mistong.com/uploads/user/avatar/251/avatar.png', 7 | }, 8 | project: { 9 | name: 'bend-homework', 10 | description: 'B 端作业体系(PC—新),owner 达建,长老', 11 | web_url: 'http://git.mistong.com/frontend-to-b/bend-homework', 12 | avatar_url: null, 13 | git_ssh_url: 14 | 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 15 | git_http_url: 'http://git.mistong.com/frontend-to-b/bend-homework.git', 16 | namespace: 'frontend-to-b', 17 | visibility_level: 20, 18 | path_with_namespace: 'frontend-to-b/bend-homework', 19 | default_branch: 'master', 20 | homepage: 'http://git.mistong.com/frontend-to-b/bend-homework', 21 | url: 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 22 | ssh_url: 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 23 | http_url: 'http://git.mistong.com/frontend-to-b/bend-homework.git', 24 | }, 25 | object_attributes: { 26 | id: 33946, 27 | target_branch: 'trunk', 28 | source_branch: 'codereview_test', 29 | source_project_id: 2378, 30 | author_id: 251, 31 | assignee_id: 380, 32 | title: '测试代码,无需合并', 33 | created_at: '2022-01-24 15:28:16 +0800', 34 | updated_at: '2022-01-24 15:30:42 +0800', 35 | milestone_id: null, 36 | state: 'closed', 37 | merge_status: 'can_be_merged', 38 | target_project_id: 2378, 39 | iid: 303, 40 | description: 'MR的描述', 41 | position: 0, 42 | locked_at: null, 43 | updated_by_id: 251, 44 | merge_error: null, 45 | merge_params: { force_remove_source_branch: '0' }, 46 | merge_when_build_succeeds: false, 47 | merge_user_id: null, 48 | merge_commit_sha: null, 49 | deleted_at: null, 50 | in_progress_merge_commit_sha: null, 51 | lock_version: 0, 52 | source: { 53 | name: 'bend-homework', 54 | description: 'B 端作业体系(PC—新),owner 达建>,长老', 55 | web_url: 'http://git.mistong.com/frontend-to-b/bend-homework', 56 | avatar_url: null, 57 | git_ssh_url: 58 | 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 59 | git_http_url: 'http://git.mistong.com/frontend-to-b/bend-homework.git', 60 | namespace: 'frontend-to-b', 61 | visibility_level: 20, 62 | path_with_namespace: 'frontend-to-b/bend-homework', 63 | default_branch: 'master', 64 | homepage: 'http://git.mistong.com/frontend-to-b/bend-homework', 65 | url: 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 66 | ssh_url: 67 | 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 68 | http_url: 'http://git.mistong.com/frontend-to-b/bend-homework.git', 69 | }, 70 | target: { 71 | name: 'bend-homework', 72 | description: 'B 端作业体系(PC—新),owner 达建,长老', 73 | web_url: 'http://git.mistong.com/frontend-to-b/bend-homework', 74 | avatar_url: null, 75 | git_ssh_url: 76 | 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 77 | git_http_url: 'http://git.mistong.com/frontend-to-b/bend-homework.git', 78 | namespace: 'frontend-to-b', 79 | visibility_level: 20, 80 | path_with_namespace: 'frontend-to-b/bend-homework', 81 | default_branch: 'master', 82 | homepage: 'http://git.mistong.com/frontend-to-b/bend-homework', 83 | url: 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 84 | ssh_url: 85 | 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 86 | http_url: 'http://git.mistong.com/frontend-to-b/bend-homework.git', 87 | }, 88 | last_commit: { 89 | id: '6f10c1aeb9d7634a30ee353d54790ccca67488ab', 90 | message: 'Update README.md', 91 | timestamp: '2022-01-24T15:04:17+08:00', 92 | url: 'http://git.mistong.com/frontend-to-b/bend-homework/commit/6f10c1aeb9d7634a30ee353d54790ccca67488ab', 93 | author: { name: 'jingweirong', email: 'jingweirong@mistong.com' }, 94 | }, 95 | work_in_progress: false, 96 | url: 'http://git.mistong.com/frontend-to-b/bend-homework/merge_requests/303', 97 | action: 'update', 98 | }, 99 | repository: { 100 | name: 'bend-homework', 101 | url: 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 102 | description: 'B 端作业体系(PC—新),owner 达建,长老', 103 | homepage: 'http://git.mistong.com/frontend-to-b/bend-homework', 104 | }, 105 | assignee: { 106 | name: '娄金亮', 107 | username: 'loujinliang', 108 | avatar_url: 'http://git.mistong.com/uploads/user/avatar/380/avatar.png', 109 | }, 110 | }; 111 | -------------------------------------------------------------------------------- /src/events_mock_data/merge_request_merged.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | object_kind: 'merge_request', 3 | user: { 4 | name: 'duting', 5 | username: 'duting', 6 | avatar_url: 7 | 'http://www.gravatar.com/avatar/be0433ba7a4c6508dc659306cb545bf4?s=80&d=identicon', 8 | }, 9 | project: { 10 | name: 'live2020', 11 | description: '', 12 | web_url: 'http://git.mistong.com/web/live2020', 13 | avatar_url: null, 14 | git_ssh_url: 'ssh://git@git.mistong.com:10022/web/live2020.git', 15 | git_http_url: 'http://git.mistong.com/web/live2020.git', 16 | namespace: 'web', 17 | visibility_level: 0, 18 | path_with_namespace: 'web/live2020', 19 | default_branch: 'master', 20 | homepage: 'http://git.mistong.com/web/live2020', 21 | url: 'ssh://git@git.mistong.com:10022/web/live2020.git', 22 | ssh_url: 'ssh://git@git.mistong.com:10022/web/live2020.git', 23 | http_url: 'http://git.mistong.com/web/live2020.git', 24 | }, 25 | object_attributes: { 26 | id: 33956, 27 | target_branch: 'release_pre', 28 | source_branch: 'feat_ewtLogoReplace', 29 | source_project_id: 2208, 30 | author_id: 251, 31 | assignee_id: 441, 32 | title: 'feat: change ewtlogo to Mst happy new year', 33 | created_at: '2022-01-24 09:12:37 UTC', 34 | updated_at: '2022-01-24 09:16:47 UTC', 35 | milestone_id: null, 36 | state: 'merged', 37 | merge_status: 'can_be_merged', 38 | target_project_id: 2208, 39 | iid: 3, 40 | description: '大年夜直播,logo临时替换一下', 41 | position: 0, 42 | locked_at: null, 43 | updated_by_id: null, 44 | merge_error: null, 45 | merge_params: { force_remove_source_branch: '0' }, 46 | merge_when_build_succeeds: false, 47 | merge_user_id: null, 48 | merge_commit_sha: '37a1c98fd7193cdbbdd104dcd9f365954fcbc403', 49 | deleted_at: null, 50 | in_progress_merge_commit_sha: null, 51 | lock_version: 0, 52 | source: { 53 | name: 'live2020', 54 | description: '', 55 | web_url: 'http://git.mistong.com/web/live2020', 56 | avatar_url: null, 57 | git_ssh_url: 'ssh://git@git.mistong.com:10022/web/live2020.git', 58 | git_http_url: 'http://git.mistong.com/web/live2020.git', 59 | namespace: 'web', 60 | visibility_level: 0, 61 | path_with_namespace: 'web/live2020', 62 | default_branch: 'master', 63 | homepage: 'http://git.mistong.com/web/live2020', 64 | url: 'ssh://git@git.mistong.com:10022/web/live2020.git', 65 | ssh_url: 'ssh://git@git.mistong.com:10022/web/live2020.git', 66 | http_url: 'http://git.mistong.com/web/live2020.git', 67 | }, 68 | target: { 69 | name: 'live2020', 70 | description: '', 71 | web_url: 'http://git.mistong.com/web/live2020', 72 | avatar_url: null, 73 | git_ssh_url: 'ssh://git@git.mistong.com:10022/web/live2020.git', 74 | git_http_url: 'http://git.mistong.com/web/live2020.git', 75 | namespace: 'web', 76 | visibility_level: 0, 77 | path_with_namespace: 'web/live2020', 78 | default_branch: 'master', 79 | homepage: 'http://git.mistong.com/web/live2020', 80 | url: 'ssh://git@git.mistong.com:10022/web/live2020.git', 81 | ssh_url: 'ssh://git@git.mistong.com:10022/web/live2020.git', 82 | http_url: 'http://git.mistong.com/web/live2020.git', 83 | }, 84 | last_commit: { 85 | id: '658e8c83dde509d10bc2ddf81ba0f9078f33b16c', 86 | message: 'feat: change ewtlogo to Mst happy new year\n', 87 | timestamp: '2022-01-24T17:04:22+08:00', 88 | url: 'http://git.mistong.com/web/live2020/commit/658e8c83dde509d10bc2ddf81ba0f9078f33b16c', 89 | author: { name: 'yukap', email: 'jingweirong@mistong.com' }, 90 | }, 91 | work_in_progress: false, 92 | url: 'http://git.mistong.com/web/live2020/merge_requests/3', 93 | action: 'merge', 94 | }, 95 | repository: { 96 | name: 'live2020', 97 | url: 'ssh://git@git.mistong.com:10022/web/live2020.git', 98 | description: '', 99 | homepage: 'http://git.mistong.com/web/live2020', 100 | }, 101 | assignee: { 102 | name: 'duting', 103 | username: 'duting', 104 | avatar_url: 105 | 'http://www.gravatar.com/avatar/be0433ba7a4c6508dc659306cb545bf4?s=80&d=identicon', 106 | }, 107 | }; 108 | -------------------------------------------------------------------------------- /src/events_mock_data/normal_comment.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | object_kind: 'note', 3 | user: { 4 | name: 'jingweirong', 5 | username: 'jingweirong', 6 | avatar_url: 'http://git.mistong.com/uploads/user/avatar/251/avatar.png', 7 | }, 8 | project_id: 2378, 9 | project: { 10 | name: 'bend-homework', 11 | description: 'B 端�~�~Z�~S系�~HPC�~@~T�~V��~I�~Lowner 达建�~L�~U��~@~A', 12 | web_url: 'http://git.mistong.com/fronttend-to-b/bend-homework', 13 | avatar_url: null, 14 | git_ssh_url: 15 | 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 16 | git_http_url: 'http://git.mistong.com/frontend-to-b/bend-homework.git', 17 | namespace: 'frontend-to-b', 18 | visibility_level: 20, 19 | path_with_namespace: 'frontend-to-b/bend-homework', 20 | default_branch: 'master', 21 | homepage: 'http://git.mistong.com/frontend-to-b/bend-homework', 22 | url: 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 23 | ssh_url: 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 24 | http_url: 'http://git.mistong.com/frontend-to-b/bend-homework.git', 25 | }, 26 | object_attributes: { 27 | id: 84385, 28 | note: '@wudajian quanyixia 所有人 @loujinliang @duting @ouliancheng @tang ', 29 | noteable_type: 'Commit', 30 | author_id: 251, 31 | created_at: '2022-01-24 06:57:48 UTC', 32 | updated_at: '20022-01-24 06:57:48 UTC', 33 | project_id: 2378, 34 | attachment: null, 35 | line_code: '6c9db7f46541b000964a8fe97f3a4741e2ca19d3_10_11', 36 | commit_id: '2835f4ef7b436845371756af6484bd725e5c9916', 37 | noteable_id: null, 38 | system: false, 39 | st_diff: null, 40 | updated_by_id: null, 41 | type: 'DiffNote', 42 | position: { 43 | old_path: 'src/routes/baskets/containers/HomeworkBasket/index.jsx', 44 | new_path: 'src/routes/baskets/containers/HomeworkBasket/index.jsx', 45 | old_line: 10, 46 | new_line: 11, 47 | base_sha: 'ffa81dc5ea47738bcbc85bbcc0a51c2268d247d4', 48 | start_sha: 'ffa81dc5ea47738bcbc85bbcc0a51c2268d247d4', 49 | head_sha: '2835f4ef7b436845371756af6484bd725e5c9916', 50 | }, 51 | original_position: { 52 | old_path: 'src/routes/baskets/containers/HomeworkBasket/index.jsx', 53 | new_path: 'src/routes/baskets/containers/HomeworkBasket/index.jsx', 54 | old_line: 10, 55 | new_line: 11, 56 | base_sha: 'ffa81dc5ea47738bcbc85bbcc0a51c2268d247d4', 57 | start_sha: 'ffa81dc5ea47738bcbc85bbcc0a51c2268d247d4', 58 | head_sha: '2835f4ef7b436845371756af6484bd725e5c9916', 59 | }, 60 | resolved_at: null, 61 | resolved_by_id: null, 62 | discussion_id: 'aa2e39c1a820a534c4f7066d2cf44aedce8549c6', 63 | original_discussion_id: 'aa2e39c1a820a534c4f7066d2cf44aedce8549c6', 64 | url: 'http://git.mistong.com/frontend-to-b/bend-homework/commit/2835f4ef7b436845371756af6484bd725e5c9916#note_84385', 65 | }, 66 | repository: { 67 | name: 'bend-homework', 68 | url: 'ssh://git@git.mistong.com:10022/frontend-to-b/bend-homework.git', 69 | description: 'B 端�~�~Z�~S系�~HPC�~@~T�~V��~I�~Lowner 达建�~L�~U��~@~A', 70 | homepage: 'http://git.mistong.comm/frontend-to-b/bend-homework', 71 | }, 72 | commit: { 73 | id: '2835f4ef7b436845371756af6484bd725e5c9916', 74 | message: 75 | "Merge branch 'release_prod' into 'master'\r\n\r\nRelease prod\r\n\r\nSee merge request !298", 76 | timestamp: '2022-01-21T14:23:28+08:00', 77 | url: 'http://git.mistong.com/frontend-to-b/bend-homework/commit/2835f4ef7b436845371756af6484bd725e5c9916', 78 | author: { name: 'jingweirong', email: 'jingweirong@mistong.com' }, 79 | }, 80 | }; 81 | -------------------------------------------------------------------------------- /src/events_mock_data/pipeline.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | "object_kind": "pipeline", 3 | "object_attributes": { 4 | "id": 31, 5 | "ref": "master", 6 | "tag": false, 7 | "sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2", 8 | "before_sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2", 9 | "source": "merge_request_event", 10 | "status": "success", 11 | "stages": [ 12 | "build", 13 | "test", 14 | "deploy" 15 | ], 16 | "created_at": "2016-08-12 15:23:28 UTC", 17 | "finished_at": "2016-08-12 15:26:29 UTC", 18 | "duration": 63, 19 | "variables": [ 20 | { 21 | "key": "NESTOR_PROD_ENVIRONMENT", 22 | "value": "us-west-1" 23 | } 24 | ] 25 | }, 26 | "merge_request": { 27 | "id": 1, 28 | "iid": 1, 29 | "title": "Test", 30 | "source_branch": "test", 31 | "source_project_id": 1, 32 | "target_branch": "master", 33 | "target_project_id": 1, 34 | "state": "opened", 35 | "merge_status": "can_be_merged", 36 | "url": "http://192.168.64.1:3005/gitlab-org/gitlab-test/merge_requests/1" 37 | }, 38 | "user": { 39 | "id": 1, 40 | "name": "Administrator", 41 | "username": "root", 42 | "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon", 43 | "email": "user_email@gitlab.com" 44 | }, 45 | "project": { 46 | "id": 1, 47 | "name": "Gitlab Test", 48 | "description": "Atque in sunt eos similique dolores voluptatem.", 49 | "web_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test", 50 | "avatar_url": null, 51 | "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git", 52 | "git_http_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test.git", 53 | "namespace": "Gitlab Org", 54 | "visibility_level": 20, 55 | "path_with_namespace": "gitlab-org/gitlab-test", 56 | "default_branch": "master" 57 | }, 58 | "commit": { 59 | "id": "bcbb5ec396a2c0f828686f14fac9b80b780504f2", 60 | "message": "test\n", 61 | "timestamp": "2016-08-12T17:23:21+02:00", 62 | "url": "http://example.com/gitlab-org/gitlab-test/commit/bcbb5ec396a2c0f828686f14fac9b80b780504f2", 63 | "author": { 64 | "name": "User", 65 | "email": "user@gitlab.com" 66 | } 67 | }, 68 | "builds": [ 69 | { 70 | "id": 380, 71 | "stage": "deploy", 72 | "name": "production", 73 | "status": "skipped", 74 | "created_at": "2016-08-12 15:23:28 UTC", 75 | "started_at": null, 76 | "finished_at": null, 77 | "when": "manual", 78 | "manual": true, 79 | "allow_failure": false, 80 | "user": { 81 | "id": 1, 82 | "name": "Administrator", 83 | "username": "root", 84 | "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon", 85 | "email": "admin@example.com" 86 | }, 87 | "runner": null, 88 | "artifacts_file": { 89 | "filename": null, 90 | "size": null 91 | }, 92 | "environment": { 93 | "name": "production", 94 | "action": "start", 95 | "deployment_tier": "production" 96 | } 97 | }, 98 | { 99 | "id": 377, 100 | "stage": "test", 101 | "name": "test-image", 102 | "status": "success", 103 | "created_at": "2016-08-12 15:23:28 UTC", 104 | "started_at": "2016-08-12 15:26:12 UTC", 105 | "finished_at": null, 106 | "when": "on_success", 107 | "manual": false, 108 | "allow_failure": false, 109 | "user": { 110 | "id": 1, 111 | "name": "Administrator", 112 | "username": "root", 113 | "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon", 114 | "email": "admin@example.com" 115 | }, 116 | "runner": { 117 | "id": 380987, 118 | "description": "shared-runners-manager-6.gitlab.com", 119 | "active": true, 120 | "runner_type": "instance_type", 121 | "is_shared": true, 122 | "tags": [ 123 | "linux", 124 | "docker", 125 | "shared-runner" 126 | ] 127 | }, 128 | "artifacts_file": { 129 | "filename": null, 130 | "size": null 131 | }, 132 | "environment": null 133 | }, 134 | { 135 | "id": 378, 136 | "stage": "test", 137 | "name": "test-build", 138 | "status": "success", 139 | "created_at": "2016-08-12 15:23:28 UTC", 140 | "started_at": "2016-08-12 15:26:12 UTC", 141 | "finished_at": "2016-08-12 15:26:29 UTC", 142 | "when": "on_success", 143 | "manual": false, 144 | "allow_failure": false, 145 | "user": { 146 | "id": 1, 147 | "name": "Administrator", 148 | "username": "root", 149 | "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon", 150 | "email": "admin@example.com" 151 | }, 152 | "runner": { 153 | "id": 380987, 154 | "description": "shared-runners-manager-6.gitlab.com", 155 | "active": true, 156 | "runner_type": "instance_type", 157 | "is_shared": true, 158 | "tags": [ 159 | "linux", 160 | "docker" 161 | ] 162 | }, 163 | "artifacts_file": { 164 | "filename": null, 165 | "size": null 166 | }, 167 | "environment": null 168 | }, 169 | { 170 | "id": 376, 171 | "stage": "build", 172 | "name": "build-image", 173 | "status": "success", 174 | "created_at": "2016-08-12 15:23:28 UTC", 175 | "started_at": "2016-08-12 15:24:56 UTC", 176 | "finished_at": "2016-08-12 15:25:26 UTC", 177 | "when": "on_success", 178 | "manual": false, 179 | "allow_failure": false, 180 | "user": { 181 | "id": 1, 182 | "name": "Administrator", 183 | "username": "root", 184 | "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon", 185 | "email": "admin@example.com" 186 | }, 187 | "runner": { 188 | "id": 380987, 189 | "description": "shared-runners-manager-6.gitlab.com", 190 | "active": true, 191 | "runner_type": "instance_type", 192 | "is_shared": true, 193 | "tags": [ 194 | "linux", 195 | "docker" 196 | ] 197 | }, 198 | "artifacts_file": { 199 | "filename": null, 200 | "size": null 201 | }, 202 | "environment": null 203 | }, 204 | { 205 | "id": 379, 206 | "stage": "deploy", 207 | "name": "staging", 208 | "status": "created", 209 | "created_at": "2016-08-12 15:23:28 UTC", 210 | "started_at": null, 211 | "finished_at": null, 212 | "when": "on_success", 213 | "manual": false, 214 | "allow_failure": false, 215 | "user": { 216 | "id": 1, 217 | "name": "Administrator", 218 | "username": "root", 219 | "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon", 220 | "email": "admin@example.com" 221 | }, 222 | "runner": null, 223 | "artifacts_file": { 224 | "filename": null, 225 | "size": null 226 | }, 227 | "environment": { 228 | "name": "staging", 229 | "action": "start", 230 | "deployment_tier": "staging" 231 | } 232 | } 233 | ] 234 | }; -------------------------------------------------------------------------------- /src/events_mock_data/push.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | object_kind: 'push', 3 | event_name: 'push', 4 | before: '48bfed1c48bff07bcf4a3b05a384377c97b8f391', 5 | after: 'e3ff0d02a0447ce0d0c89d1a03432bc3dc1a3536', 6 | ref: 'refs/heads/master', 7 | checkout_sha: 'e3ff0d02a0447ce0d0c89d1a03432bc3dc1a3536', 8 | message: null, 9 | user_id: 251, 10 | user_name: 'jingweirong', 11 | user_email: 'jingweirong@mistong.com', 12 | user_avatar: 'http://git.mistong.com/uploads/user/avatar/251/avatar.png', 13 | project_id: 2810, 14 | project: { 15 | name: 'code-review', 16 | description: '', 17 | web_url: 'http://git.mistong.com/jingweirong/code-review', 18 | avatar_url: null, 19 | git_ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 20 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 21 | namespace: 'jingweirong', 22 | visibility_level: 20, 23 | path_with_namespace: 'jingweirong/code-review', 24 | default_branch: 'master', 25 | homepage: 'http://git.mistong.com/jingweirong/code-review', 26 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 27 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 28 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 29 | }, 30 | commits: [ 31 | { 32 | id: 'e3ff0d02a0447ce0d0c89d1a03432bc3dc1a3536', 33 | message: 'Update README.md', 34 | timestamp: '2022-02-08T10:27:12+08:00', 35 | url: 'http://git.mistong.com/jingweirong/code-review/commit/e3ff0d02a0447ce0d0c89d1a03432bc3dc1a3536', 36 | author: { name: 'jingweirong', email: 'jingweirong@mistong.com' }, 37 | added: [], 38 | modified: ['README.md'], 39 | removed: [], 40 | }, 41 | { 42 | id: 'a233ggsda0447ce0d0c89d1a03432bc3dc1a3536', 43 | message: 'Update README.md', 44 | timestamp: '2022-02-08T10:27:12+08:00', 45 | url: 'http://git.mistong.com/jingweirong/code-review/commit/e3ff0d02a0447ce0d0c89d1a03432bc3dc1a3536', 46 | author: { name: 'jingweirong', email: 'jingweirong@mistong.com' }, 47 | added: [], 48 | modified: ['README.md'], 49 | removed: [], 50 | }, 51 | ], 52 | total_commits_count: 1, 53 | repository: { 54 | name: 'code-review', 55 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 56 | description: '', 57 | homepage: 'http://git.mistong.com/jingweirong/code-review', 58 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 59 | git_ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 60 | visibility_level: 20, 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /src/events_mock_data/tag_push.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | object_kind: 'tag_push', 3 | event_name: 'tag_push', 4 | before: '0000000000000000000000000000000000000000', 5 | after: '74584639928afed5f5f6d54a5d3002397897e2d9', 6 | ref: 'refs/tags/20220208', 7 | checkout_sha: 'e3ff0d02a0447ce0d0c89d1a03432bc3dc1a3536', 8 | message: 'tag的描述', 9 | user_id: 251, 10 | user_name: 'jingweirong', 11 | user_email: 'jingweirong@mistong.com', 12 | user_avatar: 'http://git.mistong.com/uploads/user/avatar/251/avatar.png', 13 | project_id: 2810, 14 | project: { 15 | name: 'code-review', 16 | description: '', 17 | web_url: 'http://git.mistong.com/jingweirong/code-review', 18 | avatar_url: null, 19 | git_ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 20 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 21 | namespace: 'jingweirong', 22 | visibility_level: 20, 23 | path_with_namespace: 'jingweirong/code-review', 24 | default_branch: 'master', 25 | homepage: 'http://git.mistong.com/jingweirong/code-review', 26 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 27 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 28 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 29 | }, 30 | commits: [ 31 | { 32 | id: 'e3ff0d02a0447ce0d0c89d1a03432bc3dc1a3536', 33 | message: 'Update README.md', 34 | timestamp: '2022-02-08T10:27:12+08:00', 35 | url: 'http://git.mistong.com/jingweirong/code-review/commit/e3ff0d02a0447ce0d0c89d1a03432bc3dc1a3536', 36 | author: { name: 'jingweirong', email: 'jingweirong@mistong.com' }, 37 | added: [], 38 | modified: ['README.md'], 39 | removed: [], 40 | }, 41 | ], 42 | total_commits_count: 1, 43 | repository: { 44 | name: 'code-review', 45 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 46 | description: '', 47 | homepage: 'http://git.mistong.com/jingweirong/code-review', 48 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 49 | git_ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 50 | visibility_level: 20, 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /src/events_mock_data/wiki.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | dingTalkUrl: 3 | 'https://oapi.dingtalk.com/robot/send?access_token=ce934d72606e0fe43cfa015f628cbc1b95438f47f0d575c6336a95eea1490b96', 4 | object_kind: 'wiki_page', 5 | user: { 6 | name: 'jingweirong', 7 | username: 'jingweirong', 8 | avatar_url: 'http://git.mistong.com/uploads/user/avatar/251/avatar.png', 9 | }, 10 | project: { 11 | name: 'code-review', 12 | description: '', 13 | web_url: 'http://git.mistong.com/jingweirong/code-review', 14 | avatar_url: null, 15 | git_ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 16 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.git', 17 | namespace: 'jingweirong', 18 | visibility_level: 20, 19 | path_with_namespace: 'jingweirong/code-review', 20 | default_branch: 'master', 21 | homepage: 'http://git.mistong.com/jingweirong/code-review', 22 | url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 23 | ssh_url: 'ssh://git@git.mistong.com:10022/jingweirong/code-review.git', 24 | http_url: 'http://git.mistong.com/jingweirong/code-review.git', 25 | }, 26 | wiki: { 27 | web_url: 'http://git.mistong.com/jingweirong/code-review/wikis/home', 28 | git_ssh_url: 29 | 'ssh://git@git.mistong.com:10022/jingweirong/code-review.wiki.git', 30 | git_http_url: 'http://git.mistong.com/jingweirong/code-review.wiki.git', 31 | path_with_namespace: 'jingweirong/code-review.wiki', 32 | default_branch: 'master', 33 | }, 34 | object_attributes: { 35 | title: 'home', 36 | content: '## 你好呀,wiki', 37 | format: 'markdown', 38 | message: 'wiki 的 commit message', 39 | slug: 'home', 40 | url: 'http://git.mistong.com/jingweirong/code-review/wikis/home', 41 | action: 'create', 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Koa from "koa"; 2 | import bodyParser from 'koa-bodyparser'; 3 | import Router from 'koa-router'; 4 | import fs from 'fs'; 5 | import actionComment from './actions/comment'; 6 | import actionMergeRequest from './actions/merge_request'; 7 | import actionPush from './actions/push'; 8 | import actionTagPush from './actions/tag_push'; 9 | import actionIssue from './actions/issue'; 10 | import actionBuild from './actions/build'; 11 | import actionPipeline from './actions/pipeline'; 12 | import actionWiki from './actions/wiki'; 13 | 14 | interface ICtx { 15 | request: any; 16 | body?: any; 17 | } 18 | 19 | const app = new Koa(); 20 | const router = new Router(); 21 | // DingTalk group code review url 22 | const defaultDingtalkUrl = 'https://oapi.dingtalk.com/robot/send?access_token=ae02abd824a4c628b97dd95a2ce3f2a67303cccfe38b6c3dd2aee2c6efb8c169'; 23 | 24 | router.post('/code-review', async (ctx: ICtx) => { 25 | const { dingTalkUrl } = ctx.request.query || {}; 26 | const gitlabDataFromWebHook = { dingTalkUrl: dingTalkUrl || defaultDingtalkUrl, ...ctx.request.body, }; 27 | fs.writeFile('log.txt', JSON.stringify(gitlabDataFromWebHook), () => { 28 | 29 | }); 30 | const { 31 | object_kind, 32 | } = gitlabDataFromWebHook || {}; 33 | // send message to dingTalk group asynchronous 34 | switch (object_kind) { 35 | case 'merge_request': 36 | actionMergeRequest(gitlabDataFromWebHook); 37 | break; 38 | case 'note': 39 | actionComment(gitlabDataFromWebHook); 40 | break; 41 | case 'push': 42 | actionPush(gitlabDataFromWebHook); 43 | break; 44 | case 'tag_push': 45 | actionTagPush(gitlabDataFromWebHook); 46 | break; 47 | case 'issue': 48 | actionIssue(gitlabDataFromWebHook); 49 | break; 50 | case 'build': 51 | actionBuild(gitlabDataFromWebHook); 52 | break; 53 | case 'pipeline': 54 | actionPipeline(gitlabDataFromWebHook); 55 | break; 56 | case 'wiki_page': 57 | actionWiki(gitlabDataFromWebHook); 58 | break; 59 | default: 60 | // nothing 61 | } 62 | ctx.body = "hello code review"; 63 | }); 64 | 65 | app 66 | .use(bodyParser()) 67 | .use(router.routes()) 68 | .use(router.allowedMethods()); 69 | 70 | app.listen(50001); -------------------------------------------------------------------------------- /src/utils/functions.ts: -------------------------------------------------------------------------------- 1 | import userList from '../config/user_list'; 2 | 3 | export function upperCamelCaseToLowerCase(str = '') { 4 | const reg = /([A-Z])/g; 5 | return str.replace(reg, (match: string) => { 6 | return ` ${match.toLocaleLowerCase()}`; 7 | }).replace(/^ /, ''); // 去掉开头的空格 8 | } 9 | 10 | export function gitlabUserToDingTalkUser(str = '') { 11 | const reg = /\@[^\s]*/g; 12 | return str.replace(reg, (match: string) => { 13 | return `@${userList[match.replace('@', '')] || match}`; 14 | }); 15 | } -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import userList from '../config/user_list'; 3 | import dingTalkCustomPrefix from '../config/dingtalk_custom_prefix'; 4 | 5 | export default function request(dingTalkUrl: string, text: string) { 6 | axios.post( 7 | dingTalkUrl, 8 | { 9 | "msgtype": "markdown", 10 | "at": { 11 | // TODO: this could be optimized with real @ user 12 | "atMobiles": Object.values(userList), // @ need to config 2 place, this is first place 13 | "atUserIds": [], 14 | "isAtAll": false 15 | }, 16 | "markdown": { 17 | "title": dingTalkCustomPrefix, 18 | // // @ need to config 2 place, this is second place 19 | "text": `#### ${dingTalkCustomPrefix}: ${text}`, 20 | }, 21 | } 22 | ).then((res: any) => { 23 | console.log(res.data); 24 | }); 25 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/recommended/tsconfig.json", 3 | "compilerOptions": { 4 | // Ensure that .d.ts files are created by tsc, but not .js files 5 | // "declaration": true, 6 | // "emitDeclarationOnly": true, 7 | // Ensure that Babel can safely transpile files in the TypeScript project 8 | // "isolatedModules": true, 9 | // Ensure don't emit files if there are errors 10 | "noEmitOnError": true, 11 | "moduleResolution": "node", 12 | "sourceMap": true, 13 | // Compiler output directory 14 | "outDir": "dist", 15 | "target": "es5", 16 | 17 | "lib": ["es6"], 18 | "types": ["node", "jest"], 19 | "module": "commonjs", 20 | "noImplicitAny": true, 21 | "noUnusedLocals": true, 22 | "allowSyntheticDefaultImports": true, 23 | "declaration": true, 24 | "esModuleInterop": true 25 | }, 26 | "include": ["src/*.ts"], 27 | "exclude": ["node_modules", "**/*.spec.ts"] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "target": "es5" 6 | } 7 | } 8 | --------------------------------------------------------------------------------