Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add time entry, include start with create #165

Merged
merged 6 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions cmds/addTimeEntry.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* eslint-disable no-unused-expressions */
import Client from '../client.js'
import { defaultWorkspaceId, getProjectByName, getProjectById, appName, displayTimeEntry, parseTime } from '../utils.js'
import dayjs from 'dayjs'
import debugClient from 'debug'
import utc from 'dayjs/plugin/utc.js'
import timezone from 'dayjs/plugin/timezone.js'
dayjs.extend(utc)
dayjs.extend(timezone)

const debug = debugClient('toggl-cli-add');

export const command = 'add [startTime] [endTime] [description]'
export const desc = 'Create a time entry. Time must be parsable by dayjs, e.g. 4:50PM or \'12:00 AM\'.'

export const builder = {
d: { alias: ['description'], describe: 'Time entry name', type: 'string:', demandOption: true},
p: { alias: ['projectId', 'project'], describe: 'The case insensitive project name or project id.', type: 'string', demandOption: false },
s: { alias: ['start', 'startTime'], describe: 'The start time for the task, e.g. 13:00 12:45AM.', type: 'string', demandOption: false },
e: { alias: ['end', 'endTime'], describe: 'The end time for the task, e.g. 13:00 12:45AM.', type: 'string', demandOption: false }
}

export const handler = async function (argv) {
const client = await Client()
const params = {}

params.workspace_id = +defaultWorkspaceId
let project
if (argv.projectId) {
if (isNaN(argv.projectId)) {
project = await getProjectByName(params.workspace_id, argv.projectId)
} else {
project = await getProjectById(params.workspace_id, argv.projectId)
}
}

let startTime, endTime
if (dayjs(argv.startTime).isValid()) {
startTime = argv.startTime
} else {
// Parse the time and set it based upon the current time
startTime = parseTime(argv.startTime)
}

if (dayjs(argv.endTime).isValid()) {
endTime = argv.endTime
} else {
// Parse the time and set it based upon the current time
endTime = parseTime(argv.endTime)
}

params.created_with = appName
project ? params.project_id = +project.id : undefined
startTime ? params.start = startTime.toISOString() : undefined
endTime ? params.stop = endTime.toISOString() : undefined
if (startTime || endTime) {
const startTimeUnix = startTime.unix()
const endTimeUnix = endTime.unix()
let duration = endTimeUnix - startTimeUnix
duration = endTime ? duration : startTimeUnix * -1
params.duration = duration
}
argv.description ? params.description = argv.description : undefined
debug(params)
const timeEntry = await client.timeEntries.create(params)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a createTimeEntry() in the utils, but it doesn't support creating a time entry with an endTime. I'll open a new issue to extend that function and use it here too. I see that you started with the edit command which does its own interaction with the client. I'm not very good about repeating myself or abstracting methods.

await displayTimeEntry(timeEntry)
}

20 changes: 1 addition & 19 deletions cmds/edit.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-unused-expressions */
import Client from '../client.js'
import { defaultWorkspaceId, getProjectByName, getProjectById, appName, displayTimeEntry } from '../utils.js'
import { defaultWorkspaceId, getProjectByName, getProjectById, appName, displayTimeEntry, parseTime } from '../utils.js'
import dayjs from 'dayjs'
import debugClient from 'debug'
import utc from 'dayjs/plugin/utc.js'
Expand Down Expand Up @@ -73,21 +73,3 @@ export const handler = async function (argv) {
const timeEntry = await client.timeEntries.update(currentTimeEntry.id, params)
await displayTimeEntry(timeEntry)
}

/**
* Parses a timelike string into a dayjs object of the current date and that time
* @param {string} timeString timelike string e.g. 4:50PM '12:00 AM' etc.
* @returns {object} dayjs object
*/
function parseTime (timeString) {
let h, m
// Assumes time in format 4:50 PM
const time = timeString.split(':', 2)
h = time[0]
m = time[1].match(/[0-9]+/)[0]
if (timeString.match(/PM/i) && h <= 12) {
// + in front of string converts to a number, cool!
h = +h + 12
}
return dayjs().hour(h).minute(m).second(0)
}
2 changes: 2 additions & 0 deletions cmds/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as edit from './edit.mjs'
import * as web from './web.mjs'
import * as startTimeEntry from './startTimeEntry.mjs'
import * as stopTimeEntry from './stopTimeEntry.mjs'
import * as addTimeEntry from './addTimeEntry.mjs'
import * as removeTimeEntry from './removeTimeEntry.mjs'
import * as today from './today.mjs'
import * as weekly from './weekly.mjs'
Expand All @@ -21,6 +22,7 @@ export const commands = [
projects,
startTimeEntry,
stopTimeEntry,
addTimeEntry,
removeTimeEntry,
today,
web,
Expand Down
19 changes: 17 additions & 2 deletions cmds/startTimeEntry.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { defaultWorkspaceId,getProjectByName, createTimeEntry, getProjectById, defaultProjectId } from '../utils.js'
import { defaultWorkspaceId, getProjectByName, createTimeEntry, getProjectById, defaultProjectId, parseTime } from '../utils.js'
import dayjs from 'dayjs'

export const command = 'start'
export const desc = 'Starts a time entry'
Expand All @@ -10,7 +11,8 @@ export const builder = {
},
p: { alias: ['projectId', 'project'], describe: 'The case insensitive project name or project id.', type: 'string', demandOption: false },
// TODO default to default workspace
w: { alias: ['workspaceId', 'workspace'], describe: 'The case insensitive workspace name or workspace id.', type: 'number', demandOption: false }
w: { alias: ['workspaceId', 'workspace'], describe: 'The case insensitive workspace name or workspace id.', type: 'number', demandOption: false },
s: { alias: ['start', 'startTime'], describe: 'The start time for the task, e.g. 13:00 12:45AM.', type: 'string', demandOption: false },
}

export const handler = async function (argv) {
Expand All @@ -31,6 +33,19 @@ export const handler = async function (argv) {
}
}

if (argv.startTime) {
let startTime;
if (dayjs(argv.startTime).isValid()) {
startTime = argv.startTime
} else {
// Parse the time and set it based upon the current time
startTime = parseTime(argv.startTime)
}

params.start = startTime.toISOString()
params.duration = -1
}

params.projectId = project?.id || defaultProjectId || null
// TODO check for invalid projectId or catch the error when creating fails
const timeEntry = await createTimeEntry(params)
Expand Down
34 changes: 29 additions & 5 deletions utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const createTimeEntry = async function (params) {
description: params.description,
workspace_id: +params.workspaceId,
project_id: +params.projectId,
start: dayjs().toISOString(),
start: params.start ? params.start : dayjs().toISOString(),
duration: -1 * dayjs().unix(),
created_with: appName,
at: dayjs().toISOString()
Expand Down Expand Up @@ -113,11 +113,15 @@ export const displayTimeEntry = async function (timeEntry) {
console.info(`${chalk.blueBright(timeEntry.description ? timeEntry.description : 'no description')} ${chalk.yellow('#'+timeEntry.id)}`)
console.info(`Billable: ${chalk.gray(timeEntry.billable)}`)

// TODO this should be abstracted for reuse
const startTime = dayjs.unix(timeEntry.duration * -1)
const duration = dayjs().diff(startTime, 's')
const durationFormatted = dayjs.duration(duration * 1000).format('H[h] m[m]')
let stopTime;
if (timeEntry.stop == null) {
stopTime = dayjs()
} else {
stopTime = dayjs(timeEntry.stop)
}

const duration = stopTime.diff(dayjs(timeEntry.start))
const durationFormatted = dayjs.duration(duration).format('H[h] m[m]')
console.info(`Duration: ${chalk.green(durationFormatted)}`)

const projects = await getProjects(timeEntry.wid)
Expand All @@ -138,3 +142,23 @@ export const displayTimeEntry = async function (timeEntry) {
console.info(`Workspace: ${workspace.name} (#${timeEntry.wid})`)
}
}

/**
* Parses a timelike string into a dayjs object of the current date and that time
* @param {string} timeString timelike string e.g. 4:50PM '12:00 AM' etc.
* @returns {object} dayjs object
*/
export function parseTime (timeString) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for moving this here, this really should have been available for reuse.

let h, m
// Assumes time in format 4:50 PM
const time = timeString.split(':', 2)
h = time[0]
m = time[1].match(/[0-9]+/)[0]
if (timeString.match(/PM/i) && h <= 12) {
// + in front of string converts to a number, cool!
h = +h + 12
} else if (h == 12) {
h = 0
}
return dayjs().hour(h).minute(m).second(0).millisecond(0)
}
Loading