teamwork-cli
Version:
Command-line-interface for the Teamwork (https://www.teamwork.com/time-tracking) time entry system.
521 lines (457 loc) • 13.4 kB
JavaScript
const request = require('sync-request');
const userData = require('./user-data.js');
/************************************************************************************
* Teamwork API function wrappers
************************************************************************************/
let TEAMWORK_URL;
let BASIC_AUTH_TOKEN;
let USER_ID;
const init = () => {
const persistedData = userData.get();
let {key, url, userId} = persistedData.teamwork;
if (typeof key !== 'string' || key.length === 0) {
throw "Teamwork key is not defined! Use 'hours --key <key>' to save it."
}
if (typeof url !== 'string' || url.length === 0) {
throw "Teamwork url is not defined! Use 'hours --url <url>' to save it."
}
if (url.startsWith('http')) {
TEAMWORK_URL = url;
} else {
TEAMWORK_URL = 'https://' + url;
}
BASIC_AUTH_TOKEN = new Buffer(key + ":xxx").toString("base64");
USER_ID = userId;
};
const getAuthHeader = () => {
if (!BASIC_AUTH_TOKEN) {
init();
}
return "BASIC " + BASIC_AUTH_TOKEN;
};
const getTeamworkUrl = () => {
if (!TEAMWORK_URL) {
init();
}
return TEAMWORK_URL;
};
/**
* Make a GET request to a teamwork API.
*
* @param endpoint Path to API (after base URL)
* @returns Object body of response
*/
const teamworkGET = (endpoint) => {
let body = request('GET', getTeamworkUrl() + endpoint, {
headers: {
"Authorization": getAuthHeader(),
"Accept": "application/json"
}
}).getBody('utf8');
if (!body) {
throw new Error("There was no body.");
}
return JSON.parse(body);
};
/**
* Make a POST request to a teamwork API.
*
* @param endpoint Path to API (after base URL)
* @param body Object to attach to request
* @returns Object body of response
*/
const teamworkPOST = (endpoint, body) => {
let resp = request('POST', getTeamworkUrl() + endpoint, {
headers: {
"Authorization": getAuthHeader(),
"Accept": "application/json"
},
json: body
}).getBody('utf8');
if (!resp) {
return;
} else {
return JSON.parse(resp);
}
};
/**
* Make a PUT request to a teamwork API.
*
* @param endpoint Path to API (after base URL)
* @param body Object to attach to request
* @returns Object body of response
*/
const teamworkPUT = (endpoint, body) => {
let resp = request('PUT', getTeamworkUrl() + endpoint, {
headers: {
"Authorization": getAuthHeader(),
"Accept": "application/json"
},
json: body
}).getBody('utf8');
if (!resp) {
return;
} else {
return JSON.parse(resp);
}
};
/**
* Make a DELETE request to a teamwork API.
*
* @param endpoint Path to API (after base URL)
*/
const teamworkDELETE = (endpoint) => {
request('DELETE', getTeamworkUrl() + endpoint, {
headers: {
"Authorization": getAuthHeader()
}
});
};
/**
* Get teamwork user information from API key
*/
const getMe = () => {
return teamworkGET('/me.json');
};
/**
* Get teamwork user id from API key
*/
const getUserId = () => {
if (!USER_ID) {
const me = getMe();
USER_ID = me.person.id;
userData.get().teamwork.userId = USER_ID;
}
return USER_ID;
};
/**
* Get list of projects for user
*/
const getProjects = () => {
return teamworkGET('/projects.json').projects;
};
/**
* Get collection of Task lists for the given project
*/
const getTasklists = (projectId) => {
return teamworkGET(`/projects/${projectId}/tasklists.json`).tasklists;
};
/**
* Get a single Task list
*/
const getTasklist = (taskListId) => {
return teamworkGET(`/tasklists/${taskListId}.json`)['todo-list'];
};
/**
* Get collection of tasks for the given task list
*/
const getTasks = (tasklistId) => {
return teamworkGET(`/tasklists/${tasklistId}/tasks.json`)['todo-items'];
};
/**
* Get collection of tasks for the given project
*/
const getProjectTasks = (projectId) => {
return teamworkGET(`/projects/${projectId}/tasks.json`)['todo-items'];
};
/**
* Get all tasks
*/
const getAllTasks = () => {
return teamworkGET(`/tasks.json`)['todo-items'];
};
/**
* Get collection of tasks for the given task list
*/
const getTask = (taskId) => {
return teamworkGET(`/tasks/${taskId}.json`)['todo-item'];
};
/**
* Get collection of time entries
*/
const getAllEntries = () => {
const userId = getUserId();
let entries = [];
let page = 1;
let lastSize = 500;
while (lastSize === 500) {
console.log('requesting page ' + page + ' last size: ' + lastSize);
const result = teamworkGET(`/time_entries.json?pageSize=500&page=${page}`)['time-entries'];
lastSize = result.length;
page += 1;
entries = entries.concat(result);
}
return entries.filter(entry => entry['person-id'] === userId);
};
/**
* Get collection of time entries for the given project
*/
const getProjectEntries = (projectId) => {
const userId = getUserId();
let entries = [];
let page = 1;
let lastSize = 500;
while (lastSize === 500) {
const result = teamworkGET(`/projects/${projectId}/time_entries.json?pageSize=500&page=${page}`)['time-entries'];
lastSize = result.length;
page += 1;
entries = entries.concat(result);
}
return entries.filter(entry => entry['person-id'] === userId);
};
/**
* Get collection of time entries for the given task list
*/
const getTaskListEntries = (taskListId) => {
let entryList = [];
getTasks(taskListId).forEach(task => {
entryList = entryList.concat(getTaskEntries(task.id));
});
return entryList;
};
/**
* Get collection of time entries for the given task
*/
const getTaskEntries = (taskId) => {
const userId = getUserId();
return teamworkGET(`/todo_items/${taskId}/time_entries.json`)['time-entries']
.filter(entry => entry['person-id'] === userId);
};
/**
* Add a task to the tasklist
*
* @param tasklistId ID of task list to add task to
* @param newTask.content Name of task - required
* @param newTask.estimatedMinutes Estimated minutes
* @param newTask.description Longer description of task
* @param newTask.parentTaskId ID of parent task
* @param newTask.progress Percent complete 0-90
* @param newTask.owner ID of assignees (csv)
* @param newTask.startDate Date to start on
* @param newTask.dueDate Date needs completed by
* @param newTask.priority (low, medium, high)
* @param newTask.predecessors tasks that need completed first
* @param newTask.positionAfterTask Position in list (-1 top, 0 bottom, taskId after)
* @param newTask.tags csv of tags
*/
const addTask = (tasklistId, newTask) => {
const {
content, description, parentTaskId, progress, priority, predecessors, positionAfterTask, tags,
estimatedMinutes, owner, startDate, dueDate
} = newTask;
const todoItem = {
"todo-item": {
'estimated-minutes': estimatedMinutes,
'responsible-party-id': owner,
'start-date': startDate,
'due-date': dueDate,
content,
description,
parentTaskId,
progress,
priority,
predecessors,
positionAfterTask,
tags
}
};
console.log(newTask);
return teamworkPOST(`/tasklists/${tasklistId}/tasks.json`, todoItem);
};
/**
* Edit a task
*
* @param taskId ID of task to edit
* @param task.content Name of task - required
* @param task.estimatedMinutes Estimated minutes
* @param task.description Longer description of task
* @param task.parentTaskId ID of parent task
* @param task.progress Percent complete 0-90
* @param task.owner ID of assignees (csv)
* @param task.startDate Date to start on
* @param task.dueDate Date needs completed by
* @param task.priority (low, medium, high)
* @param task.predecessors tasks that need completed first
* @param task.positionAfterTask Position in list (-1 top, 0 bottom, taskId after)
* @param task.tags csv of tags
*/
const editTask = (taskId, task) => {
const {
content, description, parentTaskId, progress, priority, predecessors, positionAfterTask, tags,
estimatedMinutes, owner, startDate, dueDate
} = task;
const todoItem = {
"todo-item": {
'estimated-minutes': estimatedMinutes,
'responsible-party-id': owner,
'start-date': startDate,
'due-date': dueDate,
content,
description,
parentTaskId,
progress,
priority,
predecessors,
positionAfterTask,
tags
}
};
console.log(task);
return teamworkPUT(`/tasks/${taskId}.json`, todoItem);
};
/**
* Deletes the task
*/
const deleteTask = (taskId) => {
teamworkDELETE(`/tasks/${taskId}.json`);
};
/**
* Deletes the time entry
*/
const deleteTimeEntry = (entryId) => {
teamworkDELETE(`/time_entries/${entryId}.json`);
};
/**
* Get user's time entries between the given dates
*/
const getTimeEntries = (fromDate, toDate) => {
const userId = getUserId();
const args = {userId, fromDate, toDate, pageSize: 500};
const argStr = Object.keys(args).filter(k => args[k]).map(k => k + '=' + args[k]).join('&');
return teamworkGET('/time_entries.json?' + argStr)['time-entries'];
};
/**
* Search for tasks using given searchTerm
* @param searchTerm text to search for
* @param projectId (optional) limit search to project
* @param taskListId (optional) limit search to task list
*/
const searchForTask = (searchTerm, projectId, taskListId) => {
let args = 'searchTerm=' + searchTerm;
if (projectId) {
args += '&projectId=' + projectId;
}
let results = teamworkGET('/search.json?pageSize=100&searchFor=tasks&' + args).searchResult.tasks;
if (taskListId) {
results = results.filter(r => r.taskListId === taskListId);
}
return results;
};
/**
* Get the total time spent on a project
* @param projectId ID of project
*/
const getProjectTime = (projectId) => {
return getTimeFrom(teamworkGET(`/projects/${projectId}/time/total.json`).projects[0]);
};
/**
* Get the total time spent on a task list
* @param taskListId ID of task list
*/
const getTaskListTime = (taskListId) => {
return getTimeFrom(teamworkGET(`/tasklists/${taskListId}/time/total.json`).projects[0].tasklist);
};
/**
* Get the total time spent on a task
* @param taskId ID of task
*/
const getTaskTime = (taskId) => {
return getTimeFrom(teamworkGET(`/tasks/${taskId}/time/total.json`).projects[0].tasklist.task);
};
const getTimeFrom = (response) => {
// maybe extend to return object
return response['time-totals']['total-hours-sum'];
};
const getTimeEntry = (entryId) => {
return teamworkGET(`/time_entries/${entryId}.json`)['time-entry'];
};
const prettyJson = (json) => {
if (json) {
console.log(JSON.stringify(json, null, 2));
} else {
console.log('undefined');
}
};
/**
* Send a time entry to log
*
* @param entry.taskId ID of task to enter time for
* @param entry.description Text describing the entry
* @param entry.date Date the entry is for
* @param entry.hours Number of hours to log
* @param entry.minutes Number of minutes to log
* @param entry.isbillable 1 if time is billable
* @param entry.time The start time of the entry
*/
const sendTimeEntry = (entry) => {
const timeEntry = {
'time-entry': {
taskId: entry.taskId,
description: entry.description,
date: entry.date,
hours: entry.hours,
minutes: entry.minutes,
isbillable: entry.isbillable,
'person-id': getUserId(),
time: entry.time,
tags: entry.tags,
}
};
prettyJson(timeEntry);
return teamworkPOST(`/tasks/${entry.taskId}/time_entries.json`, timeEntry);
};
const updateTimeEntry = (entry) => {
const timeEntry = {
'time-entry': {
description: entry.description,
date: entry.date,
hours: entry.hours,
minutes: entry.minutes,
isbillable: entry.isbillable
}
};
return teamworkPUT(`/time_entries/${entry.id}.json`, timeEntry);
};
const getProjectNotebooks = (projectId) => {
return teamworkGET(`/projects/${projectId}/notebooks.json`).project.notebooks;
};
const getAllNotebooks = () => {
return teamworkGET(`/notebooks.json`).projects;
};
const getNotebook = (notebookId) => {
return teamworkGET(`/notebooks/${notebookId}.json`).notebook;
};
module.exports = {
addTask,
deleteTask,
deleteTimeEntry,
editTask,
getAllEntries,
getAllNotebooks,
getAllTasks,
getMe,
getNotebook,
getProjectEntries,
getProjectNotebooks,
getProjectTasks,
getProjectTime,
getProjects,
getTask,
getTaskEntries,
getTaskListEntries,
getTaskListTime,
getTaskTime,
getTasklist,
getTasklists,
getTasks,
getTimeEntries,
getTimeEntry,
getUserId,
searchForTask,
sendTimeEntry,
teamworkDELETE,
teamworkGET,
teamworkPOST,
teamworkPUT,
updateTimeEntry,
};