index.js 17 KB

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