index.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. const Student = require("../models/student");
  2. const Database = require('../config/database');
  3. const Mysql = require('../config/mysql');
  4. const CsvReader = require("../config/csvReader");
  5. const StudentService = require("../services/studentService");
  6. const GitlabApi = require("../apis/gitlabClient");
  7. const JsonHandler = require("../config/jsonHandler");
  8. const StudentConstructor = require("../models/constructors/StudentConstructor");
  9. const GroupConstructor = require("../models/constructors/GroupConstructor");
  10. const GroupService = require("../services/groupService");
  11. class Analysis {
  12. async run() {
  13. try {
  14. /* // mysql 连接
  15. const mysql = await Mysql.connect();
  16. let [rows, fields] = await mysql.execute("select * from user limit 10");
  17. console.log(rows);
  18. mysql.end();
  19. */
  20. /* // mongo 连接
  21. await Database.asyncConnect();
  22. let data = await StudentService.getAll();
  23. console.log(data);
  24. Database.close();
  25. */
  26. await Database.asyncConnect();
  27. let filter = {
  28. group: 5
  29. };
  30. let data = await GroupService.findOne(filter);
  31. console.log(data);
  32. Database.close();
  33. } catch (errors) {
  34. console.error(errors);
  35. }
  36. }
  37. async getUserInfo() { // 集成所有用户信息
  38. let portal_user = await CsvReader.readFile(__dirname + '/portal_user.csv', data => {
  39. let { id, email, username, name, phone } = data;
  40. let emailReg = /^[0-9]+@s.ail\.nju\.edu\.cn$/;
  41. if (emailReg.test(email)) {
  42. username = email.split("@")[0];
  43. }
  44. return {
  45. userId: id, email, studentId: username, name, phone
  46. }
  47. });
  48. let devcloud_user = await CsvReader.readFile(__dirname + '/软工II小组名单.csv', data => {
  49. let { 组号, 姓名, 学号 } = data;
  50. return {
  51. groupId: 组号,
  52. name: 姓名,
  53. studentId: 学号
  54. }
  55. });
  56. portal_user.sort((a, b) => {
  57. if (a.studentId < b.studentId) return -1;
  58. else if (a.studentId == b.studentId) return 0;
  59. else return 1;
  60. });
  61. if (devcloud_user instanceof Array)
  62. devcloud_user.forEach((val, idx, arr) => {
  63. let info = this.findInfo(val, portal_user);
  64. if (info) {
  65. arr[idx].userId = info.userId;
  66. arr[idx].email = info.email;
  67. arr[idx].phone = info.phone;
  68. } else {
  69. console.log('cannot find', val);
  70. }
  71. });
  72. // await JsonHandler.writeSync(devcloud_user, __dirname + '/user_info.json');
  73. }
  74. async transformUserInfo() { // 改为小组形式展示用户
  75. let user_info = await JsonHandler.readSync(__dirname+'/user_info.json');
  76. let group_user_map = {};
  77. for(let user of user_info) {
  78. if(!group_user_map[user.groupId]) group_user_map[user.groupId] = [];
  79. group_user_map[user.groupId].push(user);
  80. }
  81. await JsonHandler.writeSync(group_user_map, __dirname+'/group_user_map.json');
  82. }
  83. async getProjectsByGroup() { // 按小组拿到项目
  84. let devcloud_user = await JsonHandler.readSync(__dirname + '/user_info.json');
  85. let group_project_info_map = {};
  86. for(let i=0;i<devcloud_user.length;i++) {
  87. // for (let i = 0; i < 5; i++) {
  88. if (!group_project_info_map[devcloud_user[i].groupId]) {
  89. group_project_info_map[devcloud_user[i].groupId] = [];
  90. }
  91. let proj_list = group_project_info_map[devcloud_user[i].groupId];
  92. let user_projects = await GitlabApi.getAllProjetsByUserId(devcloud_user[i].userId);
  93. if (proj_list.length === 0) proj_list = user_projects.res;
  94. else proj_list = this.getIntersection(proj_list, user_projects.res, "projectId");
  95. group_project_info_map[devcloud_user[i].groupId]=proj_list;
  96. console.log(i, " -- finish")
  97. }
  98. // console.log(group_project_info_map);
  99. // await JsonHandler.writeSync(group_project_info_map, __dirname + '/group_project_info_map.json');
  100. }
  101. async getCommitsByGroup() { // 按照小组拿到commit 记录
  102. let group_project_info_map = await JsonHandler.readSync(__dirname + '/group_project_info_map.json');
  103. let group_commits_map = {};
  104. for(let k of Object.keys(group_project_info_map)) {
  105. console.log("group -- ",k);
  106. if(!group_commits_map[k]) group_commits_map[k] = [];
  107. for(let project of group_project_info_map[k]) {
  108. console.log("project -- ",project.projectId);
  109. let commits = await GitlabApi.getCommitsByGitlabProjectId(project.projectId);
  110. group_commits_map[k].push(...commits.map((commit) => {
  111. let {id, message, committer_name, committer_email} = commit;
  112. return {id, message, committer_name, committer_email, projectId: project.projectId};
  113. }));
  114. }
  115. }
  116. // JsonHandler.writeSync(group_commits_map, __dirname + '/group_commits_map.json');
  117. // console.log(group_commits_map)
  118. }
  119. async getCommitsStatistics() { // 按小组生成最终的commit报告
  120. let group_commits_map = await JsonHandler.readSync(__dirname+'/group_commits_map.json');
  121. let group_user_map = await JsonHandler.readSync(__dirname+'/group_user_map.json')
  122. let group_user_commits_map = {};
  123. for(let k of Object.keys(group_commits_map)) {
  124. if(!group_user_commits_map[k]) group_user_commits_map[k] = {};
  125. let tmpMap = {};
  126. for(let commit of group_commits_map[k]) {
  127. if(!tmpMap[commit.committer_name]) tmpMap[commit.committer_name] = {
  128. count: 0,
  129. committer_email: commit.committer_email,
  130. commit_messages: []
  131. };
  132. tmpMap[commit.committer_name].count += 1;
  133. tmpMap[commit.committer_name].commit_messages.push(commit.message);
  134. }
  135. for(let committer_name of Object.keys(tmpMap)) {
  136. let user = this.findUserByCommitterName(committer_name, group_user_map[k]);
  137. if(user == null) user = this.findUserByCommitterEmail(tmpMap[committer_name].committer_email, group_user_map[k]);
  138. let user_identity = committer_name;
  139. if(user) {
  140. user_identity = user.studentId
  141. }
  142. // 根据 committer_name 从数据库拿到 "valid-commit-times","buglist-commit","feature-commit"
  143. const mysql = await Mysql.connect();
  144. let bug = await mysql.execute("select count(*) as count from `commit` where `gitlab_username` = ? and `related_type` = 'BUG_ID'", [committer_name]);
  145. let tree = await mysql.execute("select count(*) as count from `commit` where `gitlab_username` = ? and `related_type` = 'TREE_ID'", [committer_name]);
  146. mysql.end();
  147. group_user_commits_map[k][user_identity] = {
  148. 'commit-times': tmpMap[committer_name].count,
  149. 'commit-msg-overview': tmpMap[committer_name].commit_messages,
  150. 'buglist-commit': bug[0][0]['count'],
  151. 'feature-commit': tree[0][0]['count'],
  152. 'valid-commit-times': bug[0][0]['count'] + tree[0][0]['count']
  153. }
  154. }
  155. console.log(group_user_commits_map)
  156. await JsonHandler.writeSync(group_user_commits_map, __dirname+'/group_user_commits_map.json');
  157. }
  158. }
  159. async getPipelineStatistics() { // 按小组生成Pipeline报告
  160. // 警告: 理论上应该从项目出发统计Pipeline触发数,但是数据库中存在很多项目Pipeline被删除的情况,
  161. // 因此我直接统计一段时间内所有用户的Pipeline触发次数, 非本次项目的也可能会统计在内
  162. // (宁愿多取一点数据)
  163. // "pipeline": {
  164. // "pipeline-deploy":0,
  165. // "pipeline-pass":0,
  166. // "pipeline-failure":0
  167. // },
  168. let group_user_map = await JsonHandler.readSync(__dirname+'/group_user_map.json');
  169. let group_user_pipeline_map = {};
  170. let startTime = '2022-02-13 20:13:10';
  171. const mysql = await Mysql.connect();
  172. for(let k of Object.keys(group_user_map)) {
  173. if(!group_user_pipeline_map[k]) group_user_pipeline_map[k] = {};
  174. let user_list = group_user_map[k];
  175. for(let user of user_list) {
  176. group_user_pipeline_map[k][user.studentId] = {
  177. "pipeline-deploy":0,
  178. "pipeline-pass":0,
  179. "pipeline-failure":0
  180. };
  181. let [row] = await mysql.execute("select result,count(*) as count from pipeline_record where start_time > ? and user_id = ? group by result;", [startTime, user.userId]);
  182. for(let res of row) {
  183. if(res.result === '部署失败') {
  184. group_user_pipeline_map[k][user.studentId]["pipeline-failure"] += res.count;
  185. } else if(res.result === '部署成功') {
  186. group_user_pipeline_map[k][user.studentId]["pipeline-pass"] += res.count;
  187. }
  188. }
  189. group_user_pipeline_map[k][user.studentId]["pipeline-deploy"] =
  190. group_user_pipeline_map[k][user.studentId]["pipeline-pass"] +
  191. group_user_pipeline_map[k][user.studentId]["pipeline-failure"];
  192. }
  193. }
  194. mysql.end();
  195. // await JsonHandler.writeSync(group_user_pipeline_map, __dirname+'/group_user_pipeline_map.json');
  196. }
  197. async getInterfaceTestStatistics() { // 按小组生成接口测试报告
  198. // 由于依赖Pipeline, 所以也有上述问题
  199. let group_user_map = await JsonHandler.readSync(__dirname+'/group_user_map.json');
  200. let group_user_interface_test_map = {};
  201. let startTime = '2022-02-13 20:13:10';
  202. const mysql = await Mysql.connect();
  203. for(let k of Object.keys(group_user_map)) {
  204. if(!group_user_interface_test_map[k]) group_user_interface_test_map[k] = {};
  205. let user_list = group_user_map[k];
  206. for(let user of user_list) {
  207. let [row] = await mysql.execute("select count(*) as times, COALESCE(sum(success),0) as success, COALESCE(sum(failure),0) as failure from test_result where test_result.pipeline_record_id in (select id as pipe_record_id from pipeline_record where start_time > ? and user_id = ? ) ;", [startTime, user.userId]);
  208. // console.log(row[0]); // { times: 0, success: null, failure: null }
  209. row[0]['times'] = parseInt(row[0]['times']);
  210. row[0]['failure'] = parseInt(row[0]['failure']);
  211. row[0]['success'] = parseInt(row[0]['success']);
  212. group_user_interface_test_map[k][user.studentId] = {
  213. "interface-test-times": row[0]['times'],
  214. "interface-test-concurrency-count": row[0]['failure'] + row[0]['success'],
  215. "interface-test-concurrency-failure": row[0]['failure'],
  216. "interface-test-concurrency-pass": row[0]['success']
  217. };
  218. }
  219. }
  220. mysql.end();
  221. await JsonHandler.writeSync(group_user_interface_test_map, __dirname+'/group_user_interface_test_map.json');
  222. }
  223. async fromJsonToDatabase() { // 将上面生成的一系列json文件组合导入数据库
  224. // TODO
  225. const group_user_map = await JsonHandler.readSync(__dirname+'/group_user_map.json');
  226. const group_project_info_map = await JsonHandler.readSync(__dirname+'/group_project_info_map.json');
  227. const group_user_commits_map = await JsonHandler.readSync(__dirname+'/group_user_commits_map.json');
  228. const group_user_pipeline_map = await JsonHandler.readSync(__dirname+'/group_user_pipeline_map.json');
  229. const group_user_interface_test_map = await JsonHandler.readSync(__dirname+'/group_user_interface_test_map.json');
  230. // mongo 连接
  231. await Database.asyncConnect();
  232. const generateGroupMembers = function(g) {
  233. return group_user_map[g].map(item => {
  234. return {
  235. "userId": item.userId,
  236. "studentId":item.studentId,
  237. "name":item.name
  238. };
  239. });
  240. }
  241. const generateGroupCommit = function(g) {
  242. let raw = Object.entries(group_user_commits_map[g])
  243. .map(v => {
  244. v[1].identity = v[0];
  245. return v[1];
  246. });
  247. let res = raw.reduce((prev, cur) => {
  248. prev["commit-times"] += cur["commit-times"];
  249. prev["valid-commit-times"] += cur["valid-commit-times"];
  250. prev["buglist-commit"] += cur["buglist-commit"];
  251. prev["feature-commit"] += cur["feature-commit"];
  252. prev["commit-msg-overview"].push(...cur[["commit-msg-overview"]]);
  253. return prev;
  254. },{
  255. "commit-times": 0,
  256. "valid-commit-times": 0,
  257. "buglist-commit": 0,
  258. "feature-commit": 0,
  259. "commit-msg-overview": []
  260. });
  261. res["commit-contribution"] = raw.map(item => {
  262. return {
  263. "name": item.identity,
  264. "times": item["commit-times"],
  265. "proportion": res["commit-times"] === 0 ? 0 : (item["commit-times"] / res["commit-times"] * 100).toFixed(2)
  266. }
  267. });
  268. return res;
  269. }
  270. const generateGroupInterfaceTest = function(g) {
  271. let raw = Object.entries(group_user_interface_test_map[g])
  272. .map(v => {
  273. v[1].identity = v[0];
  274. return v[1];
  275. });
  276. let res = raw.reduce((prev, cur) => {
  277. prev["interface-test-times"] += cur["interface-test-times"];
  278. prev["interface-test-concurrency-count"] += cur["interface-test-concurrency-count"];
  279. prev["interface-test-concurrency-failure"] += cur["interface-test-concurrency-failure"];
  280. prev["interface-test-concurrency-pass"] += cur["interface-test-concurrency-pass"];
  281. return prev;
  282. },{
  283. "interface-test-times": 0,
  284. "interface-test-concurrency-count": 0,
  285. "interface-test-concurrency-failure": 0,
  286. "interface-test-concurrency-pass": 0
  287. });
  288. res["interface-test-contribution"] = raw.map(item => {
  289. return {
  290. "name": item.identity,
  291. "times": item["interface-test-times"],
  292. "proportion": res["interface-test-times"] === 0 ? 0 : (item["interface-test-times"] / res["interface-test-times"] * 100).toFixed(2)
  293. }
  294. });
  295. return res;
  296. }
  297. const generateGroupPipeline = function(g) {
  298. let raw = Object.entries(group_user_pipeline_map[g])
  299. .map(v => {
  300. v[1].identity = v[0];
  301. return v[1];
  302. });
  303. let res = raw.reduce((prev, cur) => {
  304. prev["pipeline-deploy"] += cur["pipeline-deploy"];
  305. prev["pipeline-pass"] += cur["pipeline-pass"];
  306. prev["pipeline-failure"] += cur["pipeline-failure"];
  307. return prev;
  308. },{
  309. "pipeline-deploy": 0,
  310. "pipeline-pass": 0,
  311. "pipeline-failure": 0,
  312. });
  313. res["pipeline-contribution"] = raw.map(item => {
  314. return {
  315. "name": item.identity,
  316. "times": item["pipeline-deploy"],
  317. "proportion": res["pipeline-deploy"] === 0 ? 0 : (item["pipeline-deploy"] / res["pipeline-deploy"] * 100).toFixed(2)
  318. }
  319. });
  320. return res;
  321. }
  322. for(let group of Object.keys(group_user_map)) {
  323. // 生成小组报告, 入库
  324. let projects = group_project_info_map[group];
  325. let members = generateGroupMembers(group);
  326. let group_commit = generateGroupCommit(group);
  327. let group_interface_test = generateGroupInterfaceTest(group);
  328. let group_pipeline = generateGroupPipeline(group);
  329. let groupInfo = new GroupConstructor(
  330. group,
  331. projects,
  332. members,
  333. group_commit,
  334. group_interface_test,
  335. group_pipeline
  336. );
  337. // console.log(groupInfo);
  338. GroupService.create(groupInfo);
  339. // for(let user of group_user_map[group]) { // 生成个人报告, 入库
  340. // let commit = group_user_commits_map[group][user.studentId];
  341. // if(commit == null) commit = {
  342. // "commit-times": 0,
  343. // "valid-commit-times": 0,
  344. // "buglist-commit": 0,
  345. // "feature-commit": 0,
  346. // "commit-msg-overview": []
  347. // };
  348. // let interface_test = group_user_interface_test_map[group][user.studentId];
  349. // let pipeline = group_user_pipeline_map_map[group][user.studentId];
  350. // let studentInfo = new StudentConstructor(
  351. // user.name,
  352. // user.userId,
  353. // user.studentId,
  354. // group,
  355. // user.email,
  356. // commit,
  357. // projects,
  358. // interface_test,
  359. // pipeline
  360. // );
  361. // // console.log(studentInfo)
  362. // StudentService.create(studentInfo);
  363. // }
  364. }
  365. }
  366. findUserByCommitterEmail(committer_email, user_list) { // 通过committer email找用户
  367. for(let user of user_list) {
  368. if(committer_email === user.email) return user;
  369. }
  370. return null;
  371. }
  372. findUserByCommitterName(committer_name, user_list) { // 通过committer name找用户
  373. for(let user of user_list) {
  374. if(committer_name === user.name || committer_name === user.studentId || committer_name === user.phone) return user;
  375. }
  376. return null;
  377. }
  378. findInfo(devUser, portalUsers) { // 在portalUser中找到devUser所对应的信息, 二分查找
  379. let l = 0, r = portalUsers.length - 1;
  380. while (l <= r) {
  381. let m = Math.floor((r - l) / 2) + l;
  382. if (portalUsers[m].studentId === devUser.studentId) {
  383. return portalUsers[m];
  384. } else if (portalUsers[m].studentId < devUser.studentId) {
  385. l = m + 1;
  386. } else {
  387. r = m - 1;
  388. }
  389. }
  390. return null;
  391. }
  392. getIntersection(arr_a, arr_b, key) { // 求两个数组的交集
  393. let res = [];
  394. arr_a.forEach(a => {
  395. arr_b.forEach(b => {
  396. if (a[key] === b[key]) {
  397. res.push(a);
  398. }
  399. })
  400. })
  401. return res;
  402. }
  403. }
  404. (new Analysis()).run().then(_ => {
  405. // process.exit(0);
  406. }).catch(err => {
  407. console.error(err);
  408. })