nitor 1.2.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +29 -0
- package/package.json +1 -1
- package/services/enums/actions.enum.js +2 -0
- package/services/process-commands.js +109 -2
- package/services/review.js +1 -0
- package/services/task-stats.js +196 -0
- package/services/time-entry/get-gitlab-activities.js +74 -44
- package/services/time-entry/get-zoho-tasks.js +1 -1
- package/services/utils.js +9 -16
- package/services/time-entry/enums/actions.enum.js +0 -16
package/CHANGELOG.md
CHANGED
|
@@ -30,3 +30,11 @@ For a complete list of changes and discussion, see [Issue #1](https://github.com
|
|
|
30
30
|
- Added time entry commands.
|
|
31
31
|
|
|
32
32
|
- [Issue #5](https://github.com/codebynithin/nitor/issues/5)
|
|
33
|
+
|
|
34
|
+
## [1.3.0] - 2025-11-29
|
|
35
|
+
|
|
36
|
+
### New Features
|
|
37
|
+
|
|
38
|
+
- Added support for task stats and get task commands.
|
|
39
|
+
|
|
40
|
+
- [Issue #7](https://github.com/codebynithin/nitor/issues/7)
|
package/README.md
CHANGED
|
@@ -26,6 +26,9 @@ A CLI utility toolkit for automating and managing build, deploy, and status oper
|
|
|
26
26
|
- Cleanup local git branches
|
|
27
27
|
- Time entry management with Zoho integration
|
|
28
28
|
- GitLab activity tracking
|
|
29
|
+
- Task statistics with merge request details
|
|
30
|
+
- Extract Zoho task IDs from GitLab issue descriptions
|
|
31
|
+
- Merge request status tracking for active tasks
|
|
29
32
|
|
|
30
33
|
## Requirements
|
|
31
34
|
|
|
@@ -36,6 +39,7 @@ A CLI utility toolkit for automating and managing build, deploy, and status oper
|
|
|
36
39
|
- `COOKIE` - Cookie for Gitlab (Copy from browser)
|
|
37
40
|
- `GITLAB_URI` - GitLab API URL, eg: `https://gitlab.com/`
|
|
38
41
|
- `GITLAB_TOKEN` - Gitlab token
|
|
42
|
+
- `GITLAB_DEFAULT_PROJECT_ID` - Default GitLab project ID for issue lookups
|
|
39
43
|
- `MR_PROMPT` - Merge request prompt
|
|
40
44
|
- `MR_LANG` - Merge request language
|
|
41
45
|
- `AI_API_KEY` - AI API key
|
|
@@ -155,6 +159,26 @@ Once enabled, you can use Tab to autocomplete:
|
|
|
155
159
|
```bash
|
|
156
160
|
nitor time-zoho
|
|
157
161
|
```
|
|
162
|
+
- **Time Entry - GitLab Activities:**
|
|
163
|
+
```bash
|
|
164
|
+
nitor time-gitlab -from <YYYY-MM-DD> -to <YYYY-MM-DD>
|
|
165
|
+
```
|
|
166
|
+
- **Time Entry - Merge Request Status:**
|
|
167
|
+
```bash
|
|
168
|
+
nitor time-merge
|
|
169
|
+
```
|
|
170
|
+
- **Time Entry - Switch Project:**
|
|
171
|
+
```bash
|
|
172
|
+
nitor time-switch
|
|
173
|
+
```
|
|
174
|
+
- **Task Stats:**
|
|
175
|
+
```bash
|
|
176
|
+
nitor task-stats -task <task numbers with space>
|
|
177
|
+
```
|
|
178
|
+
- **Get Task (Extract Zoho Task IDs):**
|
|
179
|
+
```bash
|
|
180
|
+
nitor get-task -task <task numbers with space>
|
|
181
|
+
```
|
|
158
182
|
|
|
159
183
|
### Command Reference
|
|
160
184
|
|
|
@@ -178,6 +202,11 @@ Once enabled, you can use Tab to autocomplete:
|
|
|
178
202
|
- `time-entries` : View time entries by date range
|
|
179
203
|
- `time-stats` : View daily statistics of time entries
|
|
180
204
|
- `time-zoho` : Sync time entries to Zoho
|
|
205
|
+
- `time-gitlab` : Get GitLab activities for a date range
|
|
206
|
+
- `time-merge` : View merge request status for active tasks
|
|
207
|
+
- `time-switch` : Switch between default projects
|
|
208
|
+
- `task-stats` : View task statistics with GitLab merge request details
|
|
209
|
+
- `get-task` : Extract Zoho task IDs from GitLab issue descriptions
|
|
181
210
|
|
|
182
211
|
### Options
|
|
183
212
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "A comprehensive CLI toolkit for automating GitLab operations, AI-powered code review, build/deploy automation, MongoDB backup/restore, and developer productivity tools",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Nithin V <mails2nithin@gmail.com>",
|
|
@@ -11,13 +11,19 @@ const { merge } = require('./merge');
|
|
|
11
11
|
const { cleanup } = require('./cleanup');
|
|
12
12
|
|
|
13
13
|
// Time entry imports
|
|
14
|
-
const { removeEmpty } = require('./time-entry/utils');
|
|
14
|
+
const { removeEmpty, getSprints } = require('./time-entry/utils');
|
|
15
15
|
const { getStatus, getTasksByDate } = require('./time-entry/get-report');
|
|
16
16
|
const { logTaskHoursAndSync } = require('./time-entry/log-task-hours-and-sync');
|
|
17
17
|
const { addNewTask } = require('./time-entry/add-task');
|
|
18
18
|
const { updateTask, deleteTask } = require('./time-entry/update-delete-task');
|
|
19
19
|
const { getGitlabActivities } = require('./time-entry/get-gitlab-activities');
|
|
20
20
|
const { getZohoTasks } = require('./time-entry/get-zoho-tasks');
|
|
21
|
+
const {
|
|
22
|
+
getTaskStats,
|
|
23
|
+
getGitIdStats,
|
|
24
|
+
getGitlabIssueMergeRequests,
|
|
25
|
+
getGitMergeRequestDetails,
|
|
26
|
+
} = require('./task-stats');
|
|
21
27
|
|
|
22
28
|
const processArgs = async (type, value) => {
|
|
23
29
|
try {
|
|
@@ -280,6 +286,105 @@ ${cyan}${bold}╔═════════════════════
|
|
|
280
286
|
break;
|
|
281
287
|
}
|
|
282
288
|
|
|
289
|
+
case ACTIONS.TASK_STATS: {
|
|
290
|
+
if (value === '-help' || value === '--h') {
|
|
291
|
+
console.log(`usage: \tnu task-stats [-task <task number>]
|
|
292
|
+
\tnu task-stats [-t <task number>]
|
|
293
|
+
|
|
294
|
+
View task stats
|
|
295
|
+
|
|
296
|
+
Options:
|
|
297
|
+
-t, --task <number> task number`);
|
|
298
|
+
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (values.sprint) {
|
|
303
|
+
const sprints = await getSprints({ params: {} });
|
|
304
|
+
const taskDetails = await getZohoTasks({
|
|
305
|
+
params: { sprint: sprints.find(({ label }) => label === values.sprint)?.value },
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
values.task = taskDetails.map(({ taskId }) => taskId);
|
|
309
|
+
} else {
|
|
310
|
+
values.task = values.task.split(' ');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Process all tasks in parallel
|
|
314
|
+
const taskResults = await Promise.all(
|
|
315
|
+
values.task.map(async (task) => {
|
|
316
|
+
const taskDetail = await getTaskStats(task);
|
|
317
|
+
|
|
318
|
+
if (!taskDetail?.itemIds) {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const gitIdDetails = await getGitIdStats(taskDetail.itemIds);
|
|
323
|
+
|
|
324
|
+
if (!gitIdDetails?.length) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const mrDetails = await getGitlabIssueMergeRequests(gitIdDetails);
|
|
329
|
+
|
|
330
|
+
return mrDetails.map((mrDetail) => ({
|
|
331
|
+
Task: task,
|
|
332
|
+
Owner: taskDetail.owner,
|
|
333
|
+
...mrDetail,
|
|
334
|
+
}));
|
|
335
|
+
}),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// Flatten the results
|
|
339
|
+
const list = taskResults.flat();
|
|
340
|
+
|
|
341
|
+
console.table(list);
|
|
342
|
+
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
case ACTIONS.GET_TASK: {
|
|
347
|
+
if (value === '-help' || value === '--h') {
|
|
348
|
+
console.log(`usage: \tnu get-task [-task <task numbers with space>]
|
|
349
|
+
\tnu get-task [-t <task numbers with space>]
|
|
350
|
+
|
|
351
|
+
Get task details
|
|
352
|
+
|
|
353
|
+
Options:
|
|
354
|
+
-t, --task <numbers with space> task numbers with space`);
|
|
355
|
+
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!values.task) {
|
|
360
|
+
console.log('Task number is required');
|
|
361
|
+
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const tasks = {};
|
|
366
|
+
|
|
367
|
+
for (const task of values.task.split(' ')) {
|
|
368
|
+
const taskDetails = await getGitMergeRequestDetails(task);
|
|
369
|
+
|
|
370
|
+
if (taskDetails.error) {
|
|
371
|
+
console.log(`No merge requests found for task ${task}`);
|
|
372
|
+
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
for (const taskDetail of taskDetails) {
|
|
377
|
+
const zohoTaskMatch = taskDetail.description?.match(/itemdetails\/(I\d+)\)/);
|
|
378
|
+
|
|
379
|
+
tasks[task] = zohoTaskMatch ? zohoTaskMatch[1] : 'Not found';
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
console.log(tasks);
|
|
384
|
+
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
|
|
283
388
|
// Time entry commands
|
|
284
389
|
case ACTIONS.TIME_ADD: {
|
|
285
390
|
const timeValues = value ? removeEmpty(value?.split(' -')) : value;
|
|
@@ -409,9 +514,11 @@ Available commands:\n
|
|
|
409
514
|
completion : Setup shell autocomplete
|
|
410
515
|
create-branch : Create git branch
|
|
411
516
|
deploy : Deploy specified components
|
|
517
|
+
get-task : Get task details
|
|
412
518
|
merge : Merge source branch into target branch
|
|
413
519
|
refactor : Refactor the provided text for improved clarity, conciseness, and professional quality
|
|
414
520
|
review : AI Review specified merge request
|
|
521
|
+
task-stats : View task statistics
|
|
415
522
|
version : Show version info
|
|
416
523
|
help : Show help
|
|
417
524
|
|
|
@@ -422,7 +529,7 @@ Time Entry commands:
|
|
|
422
529
|
time-entries : View time entries by date
|
|
423
530
|
time-gitlab : Get GitLab activities
|
|
424
531
|
time-merge : View merge request status for tasks
|
|
425
|
-
time-stats
|
|
532
|
+
time-stats : View daily status of time entries
|
|
426
533
|
time-switch : Switch between default projects
|
|
427
534
|
time-update : Update existing time entry
|
|
428
535
|
time-zoho : Sync time entries to Zoho
|
package/services/review.js
CHANGED
|
@@ -7,6 +7,7 @@ const executeMergeRequestReview = async (values) => {
|
|
|
7
7
|
maxBodyLength: Infinity,
|
|
8
8
|
url: `${mrApiUri}/${projectIdMap[values.project]}/mergeid/${values.mergeId}`,
|
|
9
9
|
headers: { 'Content-Type': 'application/json' },
|
|
10
|
+
timeout: 10 * 60 * 1000, // 10 minutes
|
|
10
11
|
data: JSON.stringify({
|
|
11
12
|
language: mrLang,
|
|
12
13
|
gitlabToken: gitlabConfig.token,
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const { zohoConfig, gitlabConfig } = require('./utils');
|
|
3
|
+
|
|
4
|
+
const getZohoUrl = (type, taskNumber) => {
|
|
5
|
+
switch (type) {
|
|
6
|
+
case 'task':
|
|
7
|
+
return `${zohoConfig.url}/zsapi/team/${zohoConfig.team}/projects/${zohoConfig.defaultProjectId}/item/no-${taskNumber}/?action=details`;
|
|
8
|
+
|
|
9
|
+
case 'gitId':
|
|
10
|
+
return `${zohoConfig.url}/zsapi/team/${zohoConfig.team}/projects/${zohoConfig.defaultProjectId}/item/${taskNumber}/scm/3/issuedetails/?action=scmissuedetails`;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const getZohoConfig = (type, taskNumber) => {
|
|
15
|
+
return {
|
|
16
|
+
method: 'get',
|
|
17
|
+
maxBodyLength: Infinity,
|
|
18
|
+
url: getZohoUrl(type, taskNumber),
|
|
19
|
+
headers: {
|
|
20
|
+
'User-Agent':
|
|
21
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0',
|
|
22
|
+
Accept: '*/*',
|
|
23
|
+
'Accept-Language': 'en-GB,en;q=0.5',
|
|
24
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
25
|
+
Referer: `${zohoConfig.url}/workspace/4medica/client/wmoku`,
|
|
26
|
+
'X-ZA-REQSIZE': 'large',
|
|
27
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
28
|
+
'X-ZA-SOURCE': zohoConfig.source,
|
|
29
|
+
'X-ZA-UI-VERSION': 'v2',
|
|
30
|
+
'X-ZCSRF-TOKEN': zohoConfig.token,
|
|
31
|
+
'X-ZA-CLIENTPORTALID': zohoConfig.portalId,
|
|
32
|
+
'X-ZA-SESSIONID': zohoConfig.sessionId,
|
|
33
|
+
Connection: 'keep-alive',
|
|
34
|
+
Cookie: zohoConfig.cookie,
|
|
35
|
+
'Sec-Fetch-Dest': 'empty',
|
|
36
|
+
'Sec-Fetch-Mode': 'cors',
|
|
37
|
+
'Sec-Fetch-Site': 'same-origin',
|
|
38
|
+
Priority: 'u=0',
|
|
39
|
+
Pragma: 'no-cache',
|
|
40
|
+
'Cache-Control': 'no-cache',
|
|
41
|
+
TE: 'trailers',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getGitlabConfig = (url) => {
|
|
47
|
+
return {
|
|
48
|
+
method: 'get',
|
|
49
|
+
maxBodyLength: Infinity,
|
|
50
|
+
url,
|
|
51
|
+
headers: {
|
|
52
|
+
'User-Agent':
|
|
53
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0',
|
|
54
|
+
Accept: 'application/json',
|
|
55
|
+
'Accept-Language': 'en-GB,en;q=0.5',
|
|
56
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
57
|
+
'PRIVATE-TOKEN': gitlabConfig.token,
|
|
58
|
+
Connection: 'keep-alive',
|
|
59
|
+
Cookie: gitlabConfig.cookie,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const getTaskStats = async (task) => {
|
|
65
|
+
try {
|
|
66
|
+
const config = getZohoConfig('task', task);
|
|
67
|
+
const response = await axios.request(config);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
itemIds: response.data.itemIds,
|
|
71
|
+
owner: Object.values(response.data.userDisplayName).join(', '),
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.log(error);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getGitIdStats = async (gitIds) => {
|
|
79
|
+
const gitIdStats = [];
|
|
80
|
+
|
|
81
|
+
for (const gitId of gitIds) {
|
|
82
|
+
try {
|
|
83
|
+
const config = getZohoConfig('gitId', gitId);
|
|
84
|
+
const response = await axios.request(config);
|
|
85
|
+
|
|
86
|
+
gitIdStats.push(response.data);
|
|
87
|
+
} catch (error) {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return gitIdStats;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getGitlabIssueDetails = async (projectId, issueIid) => {
|
|
94
|
+
try {
|
|
95
|
+
const config = getGitlabConfig(`${projectId}/issues/${issueIid}`);
|
|
96
|
+
const response = await axios.request(config);
|
|
97
|
+
|
|
98
|
+
return response.data;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('Error fetching GitLab issue details:', error.message);
|
|
101
|
+
if (error.response) {
|
|
102
|
+
console.error('Response status:', error.response.status);
|
|
103
|
+
console.error('Response data:', error.response.data);
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const getGitMergeRequestDetails = async (issueIid, projectId) => {
|
|
110
|
+
try {
|
|
111
|
+
if (!projectId) {
|
|
112
|
+
projectId = `${gitlabConfig.url}/api/v4/projects/${gitlabConfig.defaultProjectId}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const config = getGitlabConfig(`${projectId}/issues/${issueIid}/related_merge_requests`);
|
|
116
|
+
const response = await axios.request(config);
|
|
117
|
+
|
|
118
|
+
return response.data;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return { error };
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const getGitlabIssueMergeRequests = async (gitDetails) => {
|
|
125
|
+
for (const gitDetail of gitDetails) {
|
|
126
|
+
try {
|
|
127
|
+
const mergeRequests = await getGitMergeRequestDetails(
|
|
128
|
+
gitDetail.iid,
|
|
129
|
+
gitDetail._links.project,
|
|
130
|
+
);
|
|
131
|
+
const detailedMRs = await Promise.all(
|
|
132
|
+
mergeRequests.map(async (mr) => {
|
|
133
|
+
try {
|
|
134
|
+
const detailConfig = getGitlabConfig(
|
|
135
|
+
`${gitlabConfig.url}/api/v4/projects/${mr.project_id}/merge_requests/${mr.iid}`,
|
|
136
|
+
);
|
|
137
|
+
const detailResponse = await axios.request(detailConfig);
|
|
138
|
+
const mrData = detailResponse.data;
|
|
139
|
+
|
|
140
|
+
// Fetch approval details separately
|
|
141
|
+
try {
|
|
142
|
+
const approvalConfig = getGitlabConfig(
|
|
143
|
+
`${gitlabConfig.url}/api/v4/projects/${mr.project_id}/merge_requests/${mr.iid}/approvals`,
|
|
144
|
+
);
|
|
145
|
+
const approvalResponse = await axios.request(approvalConfig);
|
|
146
|
+
const approvalData = approvalResponse.data;
|
|
147
|
+
|
|
148
|
+
// Merge approval data with MR data
|
|
149
|
+
return {
|
|
150
|
+
IssueID: gitDetail.iid,
|
|
151
|
+
MRID: mrData.iid,
|
|
152
|
+
MRAssignedTo: mrData.assignee?.name,
|
|
153
|
+
MRPointedTo: mrData.target_branch,
|
|
154
|
+
Repo: mr.reference?.split('!')?.[0],
|
|
155
|
+
ApprovedBy:
|
|
156
|
+
(approvalData.approved_by || [])
|
|
157
|
+
.map((approvedBy) => approvedBy?.user?.name || '')
|
|
158
|
+
?.join(', ') || 'Not Approved',
|
|
159
|
+
ApprovedCount: approvalData.approved_by?.length || 0,
|
|
160
|
+
MergedBy: mrData.merged_by?.name || 'Not Merged',
|
|
161
|
+
MergedOn: mrData.merged_at?.split('T')?.[0] || 'Not Merged',
|
|
162
|
+
};
|
|
163
|
+
} catch (approvalError) {
|
|
164
|
+
console.error(`Error fetching approvals for MR ${mr.iid}:`, approvalError.message);
|
|
165
|
+
// Return MR data without approval details if approval fetch fails
|
|
166
|
+
return mrData;
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(`Error fetching details for MR ${mr.iid}:`, error.message);
|
|
170
|
+
// Return the basic MR data if detailed fetch fails
|
|
171
|
+
return mr;
|
|
172
|
+
}
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return detailedMRs;
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error('Error fetching GitLab issue merge requests:', error.message);
|
|
179
|
+
|
|
180
|
+
if (error.response) {
|
|
181
|
+
console.error('Response status:', error.response.status);
|
|
182
|
+
console.error('Response data:', error.response.data);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
getTaskStats,
|
|
192
|
+
getGitIdStats,
|
|
193
|
+
getGitlabIssueDetails,
|
|
194
|
+
getGitlabIssueMergeRequests,
|
|
195
|
+
getGitMergeRequestDetails,
|
|
196
|
+
};
|
|
@@ -2,25 +2,61 @@ const axios = require('axios');
|
|
|
2
2
|
const cheerio = require('cheerio');
|
|
3
3
|
const { gitlabConfig } = require('../utils');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Fetches GitLab activities for a date range
|
|
7
|
+
* Defaults to last one week if no values are provided
|
|
8
|
+
* @param {Array} values - Optional array of filter values (e.g., ['-from 2024-11-21', '-to 2024-11-28'])
|
|
9
|
+
* @returns {Object} Object with dates as keys and activities grouped by action
|
|
10
|
+
*/
|
|
5
11
|
const getGitlabActivities = async (values) => {
|
|
6
12
|
const resp = {};
|
|
13
|
+
const res = [];
|
|
7
14
|
const filters = getFilters(values);
|
|
8
15
|
const startDate = new Date(filters.from);
|
|
9
16
|
|
|
17
|
+
// Iterate through each date in the range
|
|
10
18
|
while (startDate <= new Date(filters.to)) {
|
|
11
19
|
const date = startDate.toISOString().split('T')[0];
|
|
12
20
|
const activities = await getGitlabActivitiesByDate(date);
|
|
13
21
|
|
|
14
|
-
if (activities?.length) {
|
|
22
|
+
/* if (activities?.length) {
|
|
15
23
|
resp[date] = arrayToObjectByKey(activities, 'action');
|
|
16
|
-
}
|
|
24
|
+
} */
|
|
25
|
+
res.push(...activities);
|
|
17
26
|
|
|
18
27
|
startDate.setDate(startDate.getDate() + 1);
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
|
|
30
|
+
// Format and display each entry in a single line with ellipses
|
|
31
|
+
const truncate = (str, maxLen = 50) => {
|
|
32
|
+
if (!str) return '';
|
|
33
|
+
return str.length > maxLen ? str.substring(0, maxLen - 3) + '...' : str;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
console.log('\n📊 GitLab Activities (Last Week)\n');
|
|
37
|
+
console.log(
|
|
38
|
+
`\n${'index'.toString().padStart(8)} | ${'date'.toString().padStart(10)} ${'time'.toString().padEnd(8) || ''.padEnd(8)} | ${'action'.toString().padEnd(18)} | ${'text'.toString().padEnd(100)} | ${'project'.toString().padEnd(25)}`,
|
|
39
|
+
);
|
|
40
|
+
res.forEach((activity, index) => {
|
|
41
|
+
const { date, time, action, text, project } = activity;
|
|
42
|
+
const formattedText = truncate(text, 100);
|
|
43
|
+
const formattedAction = action?.padEnd(18) || ''.padEnd(18);
|
|
44
|
+
const formattedProject = truncate(project, 25);
|
|
45
|
+
|
|
46
|
+
console.log(
|
|
47
|
+
`${(index + 1).toString().padStart(8)} | ${date} ${time?.padEnd(8) || ''.padEnd(8)} | ${formattedAction} | ${formattedText.padEnd(100)} | ${formattedProject}`,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
console.log(`\n✅ Total: ${res.length} activities\n`);
|
|
51
|
+
|
|
22
52
|
return resp;
|
|
23
53
|
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parses filter values or sets default to last one week
|
|
57
|
+
* @param {Array} values - Array of filter strings
|
|
58
|
+
* @returns {Object} Object with 'from' and 'to' date strings
|
|
59
|
+
*/
|
|
24
60
|
const getFilters = (values) => {
|
|
25
61
|
const filters = {};
|
|
26
62
|
const keyMap = {
|
|
@@ -31,10 +67,12 @@ const getFilters = (values) => {
|
|
|
31
67
|
};
|
|
32
68
|
|
|
33
69
|
if (values?.length) {
|
|
70
|
+
// Parse provided filter values
|
|
34
71
|
for (const item of values) {
|
|
35
72
|
let [key, ...itemValues] = item.split(' ');
|
|
36
73
|
const itemValue = itemValues.join(' ');
|
|
37
74
|
|
|
75
|
+
// Remove leading dash if present
|
|
38
76
|
if (key.charAt(0) === '-') {
|
|
39
77
|
key = key.substring(1);
|
|
40
78
|
}
|
|
@@ -46,6 +84,7 @@ const getFilters = (values) => {
|
|
|
46
84
|
}
|
|
47
85
|
}
|
|
48
86
|
} else {
|
|
87
|
+
// Default: last one week
|
|
49
88
|
filters.from = new Date(new Date().setDate(new Date().getDate() - 7))
|
|
50
89
|
.toISOString()
|
|
51
90
|
.split('T')[0];
|
|
@@ -54,8 +93,13 @@ const getFilters = (values) => {
|
|
|
54
93
|
|
|
55
94
|
return filters;
|
|
56
95
|
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Fetches GitLab activities for a specific date
|
|
99
|
+
* @param {string} date - Date in YYYY-MM-DD format
|
|
100
|
+
* @returns {Array} Array of activity objects
|
|
101
|
+
*/
|
|
57
102
|
const getGitlabActivitiesByDate = async (date) => {
|
|
58
|
-
const { removeEmpty } = require('./utils');
|
|
59
103
|
const gitlabUrl = `${gitlabConfig.url}/users/${gitlabConfig.userId}/calendar_activities?date=${date}`;
|
|
60
104
|
const headers = {
|
|
61
105
|
accept: 'application/json, text/plain, */*',
|
|
@@ -71,7 +115,6 @@ const getGitlabActivitiesByDate = async (date) => {
|
|
|
71
115
|
'x-csrf-token': gitlabConfig.xCsrfToken,
|
|
72
116
|
'x-requested-with': 'XMLHttpRequest',
|
|
73
117
|
};
|
|
74
|
-
// let config = { method: 'get', maxBodyLength: Infinity, url: `${gitlabUrl}2024-5-10`, headers };
|
|
75
118
|
|
|
76
119
|
try {
|
|
77
120
|
const response = await axios.get(gitlabUrl, { headers });
|
|
@@ -79,45 +122,39 @@ const getGitlabActivitiesByDate = async (date) => {
|
|
|
79
122
|
const $ = cheerio.load(response.data);
|
|
80
123
|
|
|
81
124
|
// Initialize an empty array to store the JSON objects
|
|
82
|
-
|
|
125
|
+
const contributions = [];
|
|
83
126
|
|
|
84
127
|
// Iterate over each list item
|
|
85
|
-
$('
|
|
128
|
+
$('.event-item').each((i, element) => {
|
|
86
129
|
const $element = $(element);
|
|
87
|
-
const time = $element.find('
|
|
88
|
-
const action = $element
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (item.title === 'medica-portal-client') {
|
|
109
|
-
acc.repo = item.title;
|
|
110
|
-
} else {
|
|
111
|
-
acc = removeEmpty(item);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return acc;
|
|
115
|
-
}, {});
|
|
130
|
+
const time = $element.find('.event-item-timestamp').text()?.trim();
|
|
131
|
+
const action = $element.find('.event-title-block .event-type').text()?.trim();
|
|
132
|
+
const project = $element.find('.event-title-block .project-name').text()?.trim();
|
|
133
|
+
const $refLink = $element.find('.event-title-block a.ref-name');
|
|
134
|
+
const $targetLink = $element.find('.event-title-block a.event-target-link');
|
|
135
|
+
|
|
136
|
+
let href, text;
|
|
137
|
+
|
|
138
|
+
if ($refLink.length > 0) {
|
|
139
|
+
href = $refLink.attr('href');
|
|
140
|
+
text = $refLink.text()?.trim();
|
|
141
|
+
} else if ($targetLink.length > 0) {
|
|
142
|
+
href = $targetLink.attr('href');
|
|
143
|
+
text = $targetLink.text()?.trim();
|
|
144
|
+
} else {
|
|
145
|
+
const $eventTypeSpan = $element.find('.event-title-block .event-type');
|
|
146
|
+
const nextSpan = $eventTypeSpan.next('span');
|
|
147
|
+
|
|
148
|
+
text = nextSpan.text()?.trim();
|
|
149
|
+
href = undefined;
|
|
150
|
+
}
|
|
116
151
|
|
|
117
152
|
contributions.push({
|
|
118
153
|
time,
|
|
119
154
|
action,
|
|
120
|
-
|
|
155
|
+
text,
|
|
156
|
+
project,
|
|
157
|
+
date,
|
|
121
158
|
});
|
|
122
159
|
});
|
|
123
160
|
|
|
@@ -126,12 +163,5 @@ const getGitlabActivitiesByDate = async (date) => {
|
|
|
126
163
|
console.log(error.message);
|
|
127
164
|
}
|
|
128
165
|
};
|
|
129
|
-
const arrayToObjectByKey = (arr, key) => {
|
|
130
|
-
return arr.reduce((acc, obj) => {
|
|
131
|
-
acc[obj[key]] = obj;
|
|
132
|
-
|
|
133
|
-
return acc;
|
|
134
|
-
}, {});
|
|
135
|
-
};
|
|
136
166
|
|
|
137
167
|
module.exports = { getGitlabActivities };
|
package/services/utils.js
CHANGED
|
@@ -17,6 +17,7 @@ const gitlabConfig = {
|
|
|
17
17
|
xCsrfToken: process.env.GITLAB_XCSRF_TOKEN,
|
|
18
18
|
userId: process.env.GITLAB_USERID,
|
|
19
19
|
token: process.env.GITLAB_TOKEN,
|
|
20
|
+
defaultProjectId: process.env.GITLAB_DEFAULT_PROJECT_ID,
|
|
20
21
|
};
|
|
21
22
|
const zohoConfig = {
|
|
22
23
|
cookie: process.env.ZOHO_COOKIE,
|
|
@@ -58,6 +59,8 @@ const keyMap = {
|
|
|
58
59
|
ta: 'target',
|
|
59
60
|
docker: 'docker',
|
|
60
61
|
do: 'docker',
|
|
62
|
+
sprint: 'sprint',
|
|
63
|
+
s: 'sprint',
|
|
61
64
|
};
|
|
62
65
|
const projectMap = {
|
|
63
66
|
portal: 'medica-portal',
|
|
@@ -299,26 +302,16 @@ const generateDeployConfigs = (values = {}) => {
|
|
|
299
302
|
return { configs: removeEmpty(configs, true) };
|
|
300
303
|
};
|
|
301
304
|
const convertParamsToMap = async (item, type) => {
|
|
302
|
-
const
|
|
303
|
-
ACTIONS.
|
|
304
|
-
ACTIONS.
|
|
305
|
-
ACTIONS.
|
|
306
|
-
ACTIONS.REFACTOR,
|
|
307
|
-
ACTIONS.BACKUP,
|
|
305
|
+
const itemsToCheckLiveness = [
|
|
306
|
+
ACTIONS.BUILD_DEPLOY,
|
|
307
|
+
ACTIONS.BUILD,
|
|
308
|
+
ACTIONS.DEPLOY,
|
|
308
309
|
ACTIONS.MERGE,
|
|
309
|
-
ACTIONS.
|
|
310
|
-
ACTIONS.TIME_UPDATE,
|
|
311
|
-
ACTIONS.TIME_DELETE,
|
|
312
|
-
ACTIONS.TIME_STATUS,
|
|
313
|
-
ACTIONS.TIME_ENTRIES,
|
|
314
|
-
ACTIONS.TIME_ZOHO,
|
|
315
|
-
ACTIONS.TIME_GITLAB,
|
|
316
|
-
ACTIONS.TIME_MERGE,
|
|
310
|
+
ACTIONS.REVIEW,
|
|
317
311
|
];
|
|
318
|
-
const skipCheck = itemsToSkipCheck.includes(type);
|
|
319
312
|
let live = false;
|
|
320
313
|
|
|
321
|
-
if (
|
|
314
|
+
if (itemsToCheckLiveness.includes(type)) {
|
|
322
315
|
if (!gitlabConfig.token) {
|
|
323
316
|
console.log('Configurations are missing...!');
|
|
324
317
|
return null;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
ACTIONS: {
|
|
3
|
-
INIT: 'init',
|
|
4
|
-
SWITCH: 'switch',
|
|
5
|
-
ADD: 'add',
|
|
6
|
-
UPDATE: 'update',
|
|
7
|
-
DELETE: 'delete',
|
|
8
|
-
STATUS: 'status',
|
|
9
|
-
ENTRIES: 'entries',
|
|
10
|
-
ZOHO: 'zoho',
|
|
11
|
-
GITLAB: 'gitlab',
|
|
12
|
-
MERGE: 'merge',
|
|
13
|
-
VERSION: 'version',
|
|
14
|
-
HELP: 'help',
|
|
15
|
-
},
|
|
16
|
-
};
|