@beauraines/toggl-cli 0.10.10 → 0.10.12
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/.env.foo +3 -0
- package/.github/dependabot.yml +5 -0
- package/.github/workflows/main.yml +1 -1
- package/CHANGELOG.md +9 -0
- package/cmds/continue.mjs +3 -2
- package/cmds/ls.mjs +27 -7
- package/cmds/ls.test.js +6 -6
- package/database.js +70 -0
- package/helpers.js +72 -0
- package/package.json +1 -1
- package/toggl.credentials +5 -0
package/.env.foo
ADDED
package/.github/dependabot.yml
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.10.12](https://github.com/beauraines/toggl-cli/compare/v0.10.11...v0.10.12) (2023-03-23)
|
|
6
|
+
|
|
7
|
+
### [0.10.11](https://github.com/beauraines/toggl-cli/compare/v0.10.10...v0.10.11) (2023-03-19)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* improvements to the ls command ([#52](https://github.com/beauraines/toggl-cli/issues/52)) ([5c9effc](https://github.com/beauraines/toggl-cli/commit/5c9effc10f2c85435441d1cb3bdbb1e821df88b3))
|
|
13
|
+
|
|
5
14
|
### [0.10.10](https://github.com/beauraines/toggl-cli/compare/v0.10.9...v0.10.10) (2023-03-16)
|
|
6
15
|
|
|
7
16
|
|
package/cmds/continue.mjs
CHANGED
|
@@ -24,12 +24,13 @@ export const handler = async function (argv) {
|
|
|
24
24
|
}
|
|
25
25
|
) // Gets time entries for last 14 days, up to 1000 entries
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
// Sort to guarantee its most recent to oldest)
|
|
28
|
+
timeEntries.sort((a, b) => dayjs(b.start).toDate() - dayjs(a.start).toDate())
|
|
28
29
|
|
|
29
30
|
let matchingTimeEntry
|
|
30
31
|
switch (argv.description) {
|
|
31
32
|
case undefined:
|
|
32
|
-
matchingTimeEntry = timeEntries
|
|
33
|
+
matchingTimeEntry = timeEntries[0]
|
|
33
34
|
break
|
|
34
35
|
default:
|
|
35
36
|
{ const searchName = argv.description.toLowerCase()
|
package/cmds/ls.mjs
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
import Client from '../client.js'
|
|
2
2
|
import { convertUtcTime, formatDuration } from '../utils.js'
|
|
3
3
|
import dayjs from 'dayjs'
|
|
4
|
+
import debugClient from 'debug'
|
|
5
|
+
import Table from 'cli-table3'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
export const desc = 'Lists time entries'
|
|
7
|
+
const debug = debugClient('toggl-cli-ls')
|
|
7
8
|
|
|
8
|
-
export const
|
|
9
|
+
export const command = 'ls [searchStrings...]'
|
|
10
|
+
export const desc = 'Lists recent time entries. Defaults to the last 14 days.'
|
|
9
11
|
|
|
12
|
+
export const builder = {
|
|
13
|
+
d: { alias: ['days'], describe: 'The number of days to return.', type: 'number', demandOption: false, default: 14 }
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
export const handler = async function (argv) {
|
|
17
|
+
debug(argv)
|
|
13
18
|
const client = Client()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
const days = argv.days
|
|
20
|
+
let timeEntries = await client.timeEntries.list({
|
|
21
|
+
start_date: dayjs().subtract(days, 'days').startOf('day').toISOString(),
|
|
22
|
+
end_date: dayjs().toISOString()
|
|
23
|
+
})
|
|
24
|
+
timeEntries.sort((a, b) => (a.start > b.start) ? 1 : -1)
|
|
25
|
+
if (argv.searchStrings) {
|
|
26
|
+
const searchString = argv.searchStrings.join(' ')
|
|
27
|
+
debug(searchString)
|
|
28
|
+
timeEntries = timeEntries.filter(x => x.description.includes(searchString))
|
|
29
|
+
}
|
|
17
30
|
const report = []
|
|
18
31
|
timeEntries.forEach(element => {
|
|
19
32
|
report.push(
|
|
@@ -26,5 +39,12 @@ export const handler = async function (argv) {
|
|
|
26
39
|
)
|
|
27
40
|
})
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
const table = new Table({
|
|
43
|
+
head: ['description', 'start', 'stop', 'duration']
|
|
44
|
+
})
|
|
45
|
+
for (const entry of report) {
|
|
46
|
+
table.push([entry.description, entry.start, entry.stop, entry.duration])
|
|
47
|
+
}
|
|
48
|
+
console.log(table.toString())
|
|
49
|
+
// console.table(report, ['description', 'start', 'stop', 'duration'])
|
|
30
50
|
}
|
package/cmds/ls.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
describe('ls command',() => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
})
|
|
1
|
+
describe('ls command', () => {
|
|
2
|
+
it.todo('should get tasks for the last 14 days')
|
|
3
|
+
// get time entries
|
|
4
|
+
// sort them
|
|
5
|
+
// check that the earliest is within 14 days
|
|
6
|
+
})
|
package/database.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const homedir = require('os').homedir();
|
|
2
|
+
const sqlite = require('sqlite');
|
|
3
|
+
const sqlite3 = require('sqlite3');
|
|
4
|
+
const {fileExists} = require('./helpers');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Opens the BurnDownStatus SQLite3 Database
|
|
8
|
+
* @param file file name of the SQLite3 DB. If not provided, defaults to ${homedir}/BurnDownStatus.db
|
|
9
|
+
* @returns SQLite database connection
|
|
10
|
+
*/
|
|
11
|
+
async function getDBConnection(file) {
|
|
12
|
+
file = file ? file : `${homedir}/BurnDownStatus.db`;
|
|
13
|
+
if (!await fileExists(file)){
|
|
14
|
+
console.error(`${file} not found`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const db = await sqlite.open({
|
|
18
|
+
filename: `${homedir}/BurnDownStatus.db`,
|
|
19
|
+
driver: sqlite3.Database
|
|
20
|
+
});
|
|
21
|
+
return db;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} data The burndown data to be inserted into the Burndown database
|
|
27
|
+
*
|
|
28
|
+
* @returns {Object} The result of the database running the provided query
|
|
29
|
+
*/
|
|
30
|
+
async function writeToDb(data) {
|
|
31
|
+
// TODO should take db, query and values as parameters
|
|
32
|
+
let db = await getDBConnection();
|
|
33
|
+
let query = `insert into qa_burndown (date, qa_review_count, qa_validated_count) values (?,?,?) on conflict do update set qa_review_count = excluded.qa_review_count, qa_validated_count = excluded.qa_validated_count`;
|
|
34
|
+
|
|
35
|
+
let values = [data.date, data.qa_review_count, data.qa_validated_count];
|
|
36
|
+
let result = await db.run(query, values);
|
|
37
|
+
await db.close();
|
|
38
|
+
return result;
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async function insert(database,table,data) {
|
|
44
|
+
let db = await getDBConnection(database);
|
|
45
|
+
|
|
46
|
+
let fields = Object.keys(data[0]).toString();
|
|
47
|
+
let fieldsCount = Object.keys(data[0]).length;
|
|
48
|
+
let valueSubstitution = Array(fieldsCount).fill('?').toString();
|
|
49
|
+
|
|
50
|
+
//TODO figure out how to build upsert
|
|
51
|
+
// on conflict do update set qa_review_count = excluded.qa_review_count, qa_validated_count = excluded.qa_validated_count`;
|
|
52
|
+
let query = `insert into ${table} (${fields}) values (${valueSubstitution});`
|
|
53
|
+
let result = []
|
|
54
|
+
data.forEach(async (row) => {
|
|
55
|
+
// TODO add try/catch for SQL error errno and code
|
|
56
|
+
// TODO switch to db.exec() for more meaningful response
|
|
57
|
+
let response = await db.run(query,Object.values(row));
|
|
58
|
+
result.push(response);
|
|
59
|
+
});
|
|
60
|
+
await db.close();
|
|
61
|
+
// TODO clean up result
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
getDBConnection,
|
|
68
|
+
writeToDb,
|
|
69
|
+
insert
|
|
70
|
+
}
|
package/helpers.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts a string to Title Case, using whitespace as the delimiter
|
|
5
|
+
* @param {String} str String to convert
|
|
6
|
+
* @returns The string converted to title case
|
|
7
|
+
*/
|
|
8
|
+
function toTitleCase(str) {
|
|
9
|
+
return str.replace(
|
|
10
|
+
/\w\S*/g,
|
|
11
|
+
function(txt) {
|
|
12
|
+
return txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Removes newline characters \r and/or \n from a string
|
|
19
|
+
* @param {string} string to remove newlines from
|
|
20
|
+
* @returns string
|
|
21
|
+
*/
|
|
22
|
+
function stripNewLines(string) {
|
|
23
|
+
return string.replace(/\r|\n/g, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Checks to see if the specified file exists
|
|
28
|
+
* @param {string} filePath fully qualified path and filename
|
|
29
|
+
* @returns Boolean
|
|
30
|
+
*/
|
|
31
|
+
async function fileExists(filePath) {
|
|
32
|
+
return fs.existsSync(filePath);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Asynchronously reads the entire file contents and returns it.
|
|
37
|
+
* @param {string} filePath fully qualified path and filename
|
|
38
|
+
* @returns any
|
|
39
|
+
*/
|
|
40
|
+
async function readFile(filePath) {
|
|
41
|
+
return fs.readFileSync(filePath,{ encoding: 'utf8', flag: 'r' });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Groups an array by specified properties and sums other specified properties
|
|
46
|
+
*
|
|
47
|
+
* https://stackoverflow.com/questions/46794232/group-objects-by-multiple-properties-in-array-then-sum-up-their-values
|
|
48
|
+
*
|
|
49
|
+
* @param {Array} arr Array of Objects to aggregate
|
|
50
|
+
* @param {Array} groupKeys keys to group by
|
|
51
|
+
* @param {Array} sumKeys keys of properties to sum by
|
|
52
|
+
* @returns Array of Objects
|
|
53
|
+
*/
|
|
54
|
+
function groupAndSum(arr, groupKeys, sumKeys){
|
|
55
|
+
return Object.values(
|
|
56
|
+
arr.reduce((acc,curr)=>{
|
|
57
|
+
const group = groupKeys.map(k => curr[k]).join('-');
|
|
58
|
+
acc[group] = acc[group] || Object.fromEntries(
|
|
59
|
+
groupKeys.map(k => [k, curr[k]]).concat(sumKeys.map(k => [k, 0])));
|
|
60
|
+
sumKeys.forEach(k => acc[group][k] += curr[k]);
|
|
61
|
+
return acc;
|
|
62
|
+
}, {})
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
groupAndSum,
|
|
68
|
+
toTitleCase,
|
|
69
|
+
fileExists,
|
|
70
|
+
stripNewLines,
|
|
71
|
+
readFile
|
|
72
|
+
}
|
package/package.json
CHANGED