nitor 1.0.0 → 1.2.0

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.
@@ -0,0 +1,50 @@
1
+ const path = require('path');
2
+ const {
3
+ contentTableHeading,
4
+ contentTableSeparator,
5
+ convertToTaskData,
6
+ readFileSync,
7
+ accessFileSync,
8
+ writeFileSync,
9
+ userHomeDir,
10
+ appendFileSync,
11
+ } = require('./utils');
12
+ const checkDateEntry = async (date, entryContent) => {
13
+ const isExists = { file: false, entry: false };
14
+
15
+ try {
16
+ if (accessFileSync(path.join(userHomeDir, `.${date}`))) {
17
+ const entries = readFileSync(path.join(userHomeDir, `.${date}`));
18
+
19
+ isExists.file = entries.includes(`### ${date}`);
20
+ isExists.entry = entries.includes(entryContent);
21
+ }
22
+
23
+ return isExists;
24
+ } catch {
25
+ return isExists;
26
+ }
27
+ };
28
+ const addNewTask = async (values) => {
29
+ const { project, sprint, task, date, work, duration, remarks } = await convertToTaskData(values);
30
+
31
+ try {
32
+ const entryHeading = `---\ntags: [Time entry task]\ntitle: ${date}\ncreated: ${new Date().toISOString()}\n---\n\n### ${date}\n${contentTableHeading}\n${contentTableSeparator}`;
33
+ const entryContent = `\n| ${new Date().getTime()} | ${project} | ${sprint} | ${date} | ${task} | ${work} | ${duration} | ${remarks} | false |`;
34
+ const isExists = await checkDateEntry(date, entryContent);
35
+
36
+ if (isExists.file && isExists.entry) {
37
+ console.log('Same entry already exists');
38
+ } else if (isExists.file && !isExists.entry) {
39
+ console.log('New entry added');
40
+ appendFileSync(path.join(userHomeDir, `.${date}`), `${entryContent}`);
41
+ } else {
42
+ console.log('New entry added');
43
+ writeFileSync(path.join(userHomeDir, `.${date}`), `${entryHeading}${entryContent}`);
44
+ }
45
+ } catch (error) {
46
+ console.log(error);
47
+ }
48
+ };
49
+
50
+ module.exports = { addNewTask };
@@ -0,0 +1,16 @@
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
+ };
@@ -0,0 +1,137 @@
1
+ const axios = require('axios');
2
+ const cheerio = require('cheerio');
3
+ const { gitlabConfig } = require('../utils');
4
+
5
+ const getGitlabActivities = async (values) => {
6
+ const resp = {};
7
+ const filters = getFilters(values);
8
+ const startDate = new Date(filters.from);
9
+
10
+ while (startDate <= new Date(filters.to)) {
11
+ const date = startDate.toISOString().split('T')[0];
12
+ const activities = await getGitlabActivitiesByDate(date);
13
+
14
+ if (activities?.length) {
15
+ resp[date] = arrayToObjectByKey(activities, 'action');
16
+ }
17
+
18
+ startDate.setDate(startDate.getDate() + 1);
19
+ }
20
+
21
+ console.log(resp);
22
+ return resp;
23
+ };
24
+ const getFilters = (values) => {
25
+ const filters = {};
26
+ const keyMap = {
27
+ f: 'from',
28
+ from: 'from',
29
+ t: 'to',
30
+ to: 'to',
31
+ };
32
+
33
+ if (values?.length) {
34
+ for (const item of values) {
35
+ let [key, ...itemValues] = item.split(' ');
36
+ const itemValue = itemValues.join(' ');
37
+
38
+ if (key.charAt(0) === '-') {
39
+ key = key.substring(1);
40
+ }
41
+
42
+ const dt = new Date(itemValue).toISOString().split('T')[0];
43
+
44
+ if (dt) {
45
+ filters[keyMap[key]] = dt;
46
+ }
47
+ }
48
+ } else {
49
+ filters.from = new Date(new Date().setDate(new Date().getDate() - 7))
50
+ .toISOString()
51
+ .split('T')[0];
52
+ filters.to = new Date().toISOString().split('T')[0];
53
+ }
54
+
55
+ return filters;
56
+ };
57
+ const getGitlabActivitiesByDate = async (date) => {
58
+ const { removeEmpty } = require('./utils');
59
+ const gitlabUrl = `${gitlabConfig.url}/users/${gitlabConfig.userId}/calendar_activities?date=${date}`;
60
+ const headers = {
61
+ accept: 'application/json, text/plain, */*',
62
+ 'accept-language': 'en-GB,en-US;q=0.9,en;q=0.8,ml;q=0.7',
63
+ 'cache-control': 'no-cache',
64
+ cookie: gitlabConfig.cookie,
65
+ pragma: 'no-cache',
66
+ referer: `${gitlabConfig.url}/${gitlabConfig.userId}`,
67
+ 'sec-fetch-mode': 'cors',
68
+ 'sec-fetch-site': 'same-origin',
69
+ 'user-agent':
70
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
71
+ 'x-csrf-token': gitlabConfig.xCsrfToken,
72
+ 'x-requested-with': 'XMLHttpRequest',
73
+ };
74
+ // let config = { method: 'get', maxBodyLength: Infinity, url: `${gitlabUrl}2024-5-10`, headers };
75
+
76
+ try {
77
+ const response = await axios.get(gitlabUrl, { headers });
78
+
79
+ const $ = cheerio.load(response.data);
80
+
81
+ // Initialize an empty array to store the JSON objects
82
+ let contributions = [];
83
+
84
+ // Iterate over each list item
85
+ $('ul.bordered-list li').each((i, element) => {
86
+ const $element = $(element);
87
+ const time = $element.find('span.js-localtime').text().trim();
88
+ const action = $element
89
+ .contents()
90
+ .filter(function () {
91
+ return this.nodeType === 3; // Text nodes
92
+ })
93
+ .text()
94
+ .trim()
95
+ .split(/\n/g)[0]
96
+ .trim();
97
+ const detailsArray = $element
98
+ .find('a')
99
+ .map((i, el) => {
100
+ return {
101
+ title: $(el).attr('title'),
102
+ href: $(el).attr('href').split(/\/-\//g)[1],
103
+ text: $(el).text().trim().replace(/!/g, ''),
104
+ };
105
+ })
106
+ .get();
107
+ const details = detailsArray.reduce((acc, item) => {
108
+ if (item.title === 'medica-portal-client') {
109
+ acc.repo = item.title;
110
+ } else {
111
+ acc = removeEmpty(item);
112
+ }
113
+
114
+ return acc;
115
+ }, {});
116
+
117
+ contributions.push({
118
+ time,
119
+ action,
120
+ ...details,
121
+ });
122
+ });
123
+
124
+ return contributions;
125
+ } catch (error) {
126
+ console.log(error.message);
127
+ }
128
+ };
129
+ const arrayToObjectByKey = (arr, key) => {
130
+ return arr.reduce((acc, obj) => {
131
+ acc[obj[key]] = obj;
132
+
133
+ return acc;
134
+ }, {});
135
+ };
136
+
137
+ module.exports = { getGitlabActivities };
@@ -0,0 +1,142 @@
1
+ const {
2
+ contentTableHeading,
3
+ contentTableSeparator,
4
+ path,
5
+ readFileSync,
6
+ userHomeDir,
7
+ removeEmpty,
8
+ } = require('./utils');
9
+ const getTasksByDate = async (filters) => {
10
+ try {
11
+ if (!(filters?.from || filters?.to)) {
12
+ filters = getFilters(filters);
13
+ }
14
+
15
+ let data = [];
16
+ let startDate = new Date(filters.from);
17
+ const itemHeading = contentTableHeading.split('|').reduce((acc, val) => {
18
+ const item = val.trim();
19
+
20
+ if (item && item !== '--') {
21
+ acc.push(item);
22
+ }
23
+
24
+ return acc;
25
+ }, []);
26
+
27
+ while (startDate <= new Date(filters.to)) {
28
+ const stringDate = startDate.toISOString().split('T')[0];
29
+ const item = readFileSync(path.join(userHomeDir, `.${stringDate}`));
30
+
31
+ if (item) {
32
+ const timeEntries = removeEmpty(item.split(contentTableSeparator)[1].split('\n'));
33
+
34
+ data = data.concat(
35
+ timeEntries.map((entry) =>
36
+ entry.split('|').reduce((acc, val, i) => {
37
+ const item = val.trim();
38
+
39
+ if (item && item !== '--') {
40
+ acc[itemHeading[i - 1]] = item;
41
+ }
42
+
43
+ return acc;
44
+ }, {}),
45
+ ),
46
+ );
47
+ }
48
+
49
+ startDate.setDate(startDate.getDate() + 1);
50
+ }
51
+
52
+ return data.filter((item) => item);
53
+ } catch (error) {
54
+ console.error(error.message);
55
+ }
56
+ };
57
+ const getTasksBySynced = async (synced = 'false') => {
58
+ try {
59
+ const resp = await getTasksByDate();
60
+
61
+ return (resp || []).filter((item) => item.synced === synced);
62
+ } catch (error) {
63
+ console.error(error.message);
64
+ }
65
+ };
66
+ const getReport = async (filters) => {
67
+ try {
68
+ const resp = await getTasksByDate(filters);
69
+ const adhocTasks = ['adhoc', 'internal', 'call', 'calls', 'code', 'review'];
70
+
71
+ return (resp || []).reduce((res, { date, work, synced, duration }) => {
72
+ res[date] = {
73
+ synced: res[date]?.synced || 0,
74
+ open: res[date]?.open || 0,
75
+ task: res[date]?.task || 0,
76
+ adhoc: res[date]?.adhoc || 0,
77
+ total: res[date]?.total || 0,
78
+ pending: res[date]?.pending || 480,
79
+ };
80
+
81
+ if (synced === 'true') {
82
+ res[date].synced += +duration;
83
+ } else {
84
+ res[date].open += +duration;
85
+ }
86
+
87
+ if (adhocTasks.some((adhoc) => work.toLowerCase().includes(adhoc))) {
88
+ res[date].adhoc += +duration;
89
+ } else {
90
+ res[date].task += +duration;
91
+ }
92
+
93
+ res[date].total = res[date].task + res[date].adhoc;
94
+ res[date].pending = res[date].total - 480;
95
+
96
+ return res;
97
+ }, {});
98
+ } catch (error) {
99
+ console.error(error.message);
100
+ }
101
+ };
102
+ const getStatus = async (values) => {
103
+ const data = getFilters(values);
104
+
105
+ console.table(await getReport(data));
106
+ };
107
+ const getFilters = (values) => {
108
+ const filters = {};
109
+ const keyMap = {
110
+ f: 'from',
111
+ '-f': 'from',
112
+ from: 'from',
113
+ t: 'to',
114
+ to: 'to',
115
+ };
116
+
117
+ if (values?.length) {
118
+ for (const item of values) {
119
+ let [key, ...itemValues] = item.split(' ');
120
+ const itemValue = itemValues.join(' ');
121
+
122
+ if (key.charAt(0) === '-') {
123
+ key = key.substring(1);
124
+ }
125
+
126
+ const dt = new Date(itemValue).toISOString().split('T')[0];
127
+
128
+ if (dt) {
129
+ filters[keyMap[key]] = dt;
130
+ }
131
+ }
132
+ } else {
133
+ filters.from = new Date(new Date().setDate(new Date().getDate() - 7))
134
+ .toISOString()
135
+ .split('T')[0];
136
+ filters.to = new Date().toISOString().split('T')[0];
137
+ }
138
+
139
+ return filters;
140
+ };
141
+
142
+ module.exports = { getReport, getStatus, getTasksByDate, getTasksBySynced };
@@ -0,0 +1,81 @@
1
+ const axios = require('axios');
2
+ const { getHeaders, getSprints, userId } = require('./utils');
3
+ const { zohoConfig } = require('../utils');
4
+ const getZohoTasks = async ({ params }) => {
5
+ const headers = await getHeaders();
6
+ let activeSprint;
7
+
8
+ if (!params?.sprint) {
9
+ if (params?.type) {
10
+ if (typeof params.type === 'string') {
11
+ params.type = params.type.split(',');
12
+ }
13
+ } else {
14
+ params.type = ['2'];
15
+ }
16
+
17
+ activeSprint = await getSprints({ params });
18
+
19
+ params.sprint = activeSprint?.[0]?.value;
20
+ } else {
21
+ const isValidSprint = await checkSprintId(params.sprint, { headers, params });
22
+
23
+ if (!isValidSprint) {
24
+ console.error('Invalid sprint ID');
25
+ }
26
+ }
27
+
28
+ if (activeSprint?.length > 1) {
29
+ const resp = [];
30
+
31
+ for (const sprint of activeSprint) {
32
+ params.sprint = sprint.value;
33
+
34
+ const tasks = await getTasksBySprint({ params, headers });
35
+
36
+ resp.push(...(tasks || []));
37
+ }
38
+
39
+ return resp;
40
+ } else {
41
+ return getTasksBySprint({ params, headers });
42
+ }
43
+ };
44
+ const getTasksBySprint = async ({ params, headers }) => {
45
+ const p = {
46
+ action: 'data',
47
+ index: params.index || 1,
48
+ range: params.range || 300,
49
+ sprint: params.sprint || null,
50
+ project: params.project || zohoConfig.defaultProjectId,
51
+ subitem: params.subitem || false,
52
+ };
53
+ const parentUrl = `${zohoConfig.url}/zsapi/team/${zohoConfig.team}/projects/${p.project}/sprints/${p.sprint}/item/?action=${p.action}&range=${p.range}&index=${p.index}&subitem=${p.subitem}&customviewid=${zohoConfig.customViewId}`;
54
+
55
+ try {
56
+ const response = await axios.get(parentUrl, { headers });
57
+
58
+ return Object.entries(response.data.itemJObj).reduce((acc, [key, values]) => {
59
+ if (params.filterUser) {
60
+ for (const value of values) {
61
+ if (typeof value === 'object' && value.length && value.includes(userId)) {
62
+ acc.push({ value: key, label: values[0], taskId: values[1] });
63
+ }
64
+ }
65
+ } else {
66
+ acc.push({ value: key, label: values[0], taskId: values[1] });
67
+ }
68
+
69
+ return acc.sort((a, b) => a.taskId - b.taskId);
70
+ }, []);
71
+ } catch (error) {
72
+ console.error(error.message);
73
+ }
74
+ };
75
+ const checkSprintId = async (id, req) => {
76
+ const sprints = await getSprints(req);
77
+
78
+ return sprints.some(({ value }) => value === id);
79
+ };
80
+
81
+ module.exports = { getZohoTasks };
@@ -0,0 +1,81 @@
1
+ const axios = require('axios');
2
+ const FormData = require('form-data');
3
+ const { format, addMinutes, startOfDay } = require('date-fns');
4
+ const { utcToZonedTime } = require('date-fns-tz');
5
+ const { getTasksBySynced } = require('./get-report');
6
+ const { getZohoTasks } = require('./get-zoho-tasks');
7
+ const { updateTaskStatus } = require('./update-delete-task');
8
+ const { getHeaders, getSprints } = require('./utils');
9
+ const { zohoConfig } = require('../utils');
10
+ const logTaskHoursAndSync = async () => {
11
+ const tasks = await getTasksBySynced();
12
+ const response = {
13
+ success: 0,
14
+ failed: 0,
15
+ total: tasks.length,
16
+ pending: tasks.length,
17
+ erroredTasks: [],
18
+ successTasks: [],
19
+ };
20
+
21
+ for (const task of tasks) {
22
+ let status = 'failed';
23
+ const project = zohoConfig.projects[task.project]?.value || zohoConfig.defaultProjectId;
24
+ const activeSprint = await getSprints({ params: { type: '2', project } });
25
+ const sprint = activeSprint.find(
26
+ ({ label }) => label.toLowerCase() === task.sprint.toLowerCase(),
27
+ )?.value;
28
+ const zohoTasks = await getZohoTasks({ params: { project, sprint, subitem: true } });
29
+ const headers = await getHeaders();
30
+ const zohoTask = zohoTasks.find(({ taskId }) => taskId === task.task);
31
+ const newData = new FormData();
32
+ const taskDate = new Date(task.date);
33
+ const duration = format(addMinutes(startOfDay(new Date()), task.duration), 'HH:mm');
34
+
35
+ if (!zohoTask) {
36
+ response.failed++;
37
+ response.erroredTasks.push(task.task);
38
+ console.log('No tasks found', task.task);
39
+
40
+ continue;
41
+ }
42
+
43
+ taskDate.setHours(0, 0, 0, 0);
44
+
45
+ const zonedDate = utcToZonedTime(taskDate, 'Asia/Kolkata');
46
+ const date = format(zonedDate, "yyyy-MM-dd'T'HH:mm:ssXXX");
47
+ const parentUrl = `${zohoConfig.url}/zsapi/team/${zohoConfig.team}/projects/${project}/sprints/${activeSprint?.[0]?.value}/item/${zohoTask?.value}/timesheet/?action=additemlog`;
48
+
49
+ newData.append('users', zohoConfig.userId);
50
+ newData.append('duration', duration);
51
+ newData.append('isbillable', 1);
52
+ newData.append('date', date);
53
+ newData.append('notes', task.remarks ? `<div>${task.remarks}</div>` : '');
54
+
55
+ try {
56
+ const item = await axios.post(parentUrl, newData, { headers });
57
+
58
+ if (item.data.status === 'success') {
59
+ status = 'success';
60
+
61
+ response.success++;
62
+ response.successTasks.push(task.task);
63
+ await updateTaskStatus(task.id, task.date);
64
+ } else {
65
+ response.failed++;
66
+ response.erroredTasks.push(task.task);
67
+ }
68
+ } catch (error) {
69
+ response.failed++;
70
+ response.erroredTasks.push(task.task);
71
+ console.log(error.message);
72
+ }
73
+
74
+ response.pending--;
75
+ console.log(`Task ${task.task} is ${status}`);
76
+ }
77
+
78
+ return response;
79
+ };
80
+
81
+ module.exports = { logTaskHoursAndSync };
@@ -0,0 +1,85 @@
1
+ const { getTasksByDate } = require('./get-report');
2
+ const {
3
+ contentTableSeparator,
4
+ readFileSync,
5
+ path,
6
+ userHomeDir,
7
+ writeFileSync,
8
+ convertToTaskData,
9
+ } = require('./utils');
10
+ const updateTaskStatus = async (taskId, date, taskStatus = true) => {
11
+ try {
12
+ const entries = await getTasksByDate([`f ${date}`, `t ${date}`]);
13
+ const file = readFileSync(path.join(userHomeDir, `.${date}`));
14
+ let fileHeading = getFileHeading(file.split(contentTableSeparator)[0]);
15
+
16
+ for (const entry of entries) {
17
+ if (entry) {
18
+ const { id, project, sprint, task, date, work, duration, remarks, synced } = entry;
19
+
20
+ fileHeading += `\n| ${id} | ${project} | ${sprint} | ${date} | ${task} | ${work} | ${duration} | ${remarks} | ${taskId === id ? taskStatus : synced} |`;
21
+ }
22
+ }
23
+
24
+ writeFileSync(path.join(userHomeDir, `.${date}`), fileHeading);
25
+ } catch (error) {
26
+ console.log(error.message);
27
+ }
28
+ };
29
+ const updateTask = async (values) => {
30
+ try {
31
+ const item = await convertToTaskData(values);
32
+ const entries = await getTasksByDate([`f ${item.date}`, `t ${item.date}`]);
33
+ const file = readFileSync(path.join(userHomeDir, `.${item.date}`));
34
+ let fileHeading = getFileHeading(file.split(contentTableSeparator)[0]);
35
+
36
+ for (const entry of entries) {
37
+ if (entry) {
38
+ const { id, project, sprint, task, date, work, duration, remarks, synced } = entry;
39
+
40
+ if (id === item.id) {
41
+ fileHeading += `| ${id} | ${project} | ${sprint} | ${item.date} | ${item.task} | ${item.work} | ${item.duration} | ${item.remarks} | ${synced} |\n`;
42
+ } else {
43
+ fileHeading += `| ${id} | ${project} | ${sprint} | ${date} | ${task} | ${work} | ${duration} | ${remarks} | ${synced} |\n`;
44
+ }
45
+ }
46
+ }
47
+
48
+ writeFileSync(path.join(userHomeDir, `.${item.date}`), fileHeading);
49
+ } catch (error) {
50
+ console.log(error.message);
51
+ }
52
+ };
53
+ const deleteTask = async (values) => {
54
+ try {
55
+ const item = await convertToTaskData(values);
56
+ const entries = await getTasksByDate(item.date, item.date);
57
+ const file = readFileSync(path.join(userHomeDir, `.${item.date}`));
58
+ let fileHeading = getFileHeading(file.split(contentTableSeparator)[0]);
59
+
60
+ for (const entry of entries) {
61
+ if (entry) {
62
+ const { id, project, sprint, task, date, work, duration, remarks, synced } = entry;
63
+
64
+ if (id !== item.id) {
65
+ fileHeading += `| ${id} | ${project} | ${sprint} | ${date} | ${task} | ${work} | ${duration} | ${remarks} | ${synced} |\n`;
66
+ }
67
+ }
68
+ }
69
+
70
+ writeFileSync(path.join(userHomeDir, `.${item.date}`), fileHeading);
71
+ } catch (error) {
72
+ console.log(error.message);
73
+ }
74
+ };
75
+ const getFileHeading = (topHeading) => {
76
+ const lastIndex = topHeading.lastIndexOf('\n');
77
+
78
+ if (lastIndex !== -1) {
79
+ topHeading = topHeading.substring(0, lastIndex) + topHeading.substring(lastIndex + 1);
80
+ }
81
+
82
+ return `${topHeading}\n${contentTableSeparator}\n`;
83
+ };
84
+
85
+ module.exports = { updateTaskStatus, updateTask, deleteTask };