Skip to content

Commit

Permalink
feat: Adds startClock, stopClock, and changeClockDuration
Browse files Browse the repository at this point in the history
* Adds put, post, and delete HTTP verb support.
  • Loading branch information
tannerbaum committed Apr 25, 2018
1 parent 59ec713 commit 5db9894
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ typings/

.DS_Store
.vscode
lib/
lib/
index.js
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Dependency Status](https://david-dm.org/peerigon/clockodo.svg)](https://david-dm.org/peerigon/clockodo)
[![Build Status](https://travis-ci.org/peerigon/clockodo.svg?branch=master)](https://travis-ci.org/peerigon/clockodo)

We have provided get methods for each of the endpoints available by the Clockodo API. We also renamed the request parameters from what you will see in the Clockodo docs, removing symbols, camel casing, and in some instances shortening their names. If you are interested, you can find the mappings in the mapParams.js file.
We have provided get methods for each of the endpoints available by the Clockodo API. We also renamed the request parameters from what you will see in the Clockodo docs, removing symbols, camel casing, and in some instances shortening their names. If you are interested, you can find the mappings in the mapKeys.js file.

Further features and endpoints can be added on request. Please feel free to submit an issue or pull request.

Expand Down Expand Up @@ -61,7 +61,7 @@ clockodo.getUsers()
* [getCustomers()](#getcustomers)
* [getEntry()](#getentryid)
* [getEntries()](#getentriesbegin-end-filters)
* [getEntryGroups()](#getentrygroupsbegin-end-grouping-options)
* [getEntryGroups()](#getentrygroupsbegin-end-grouping-options)
* [getProject()](#getprojectid)
* [getSearchTexts()](#getsearchtextsparams)
* [getService()](#getserviceid)
Expand All @@ -72,6 +72,7 @@ clockodo.getUsers()
* [getUserReports()](#getuserreportsid-params)
* [getTasks()](#gettasksparams)
* [getTaskDuration()](#gettaskdurationparams)

---

### getAbsence(id)
Expand Down
18 changes: 18 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const REQUIRED_PARAMS_GET_ENTRIES = ["begin", "end"];
const REQUIRED_PARAMS_GET_ENTRY_GROUPS = ["begin", "end", "grouping"];
const REQUIRED_PARAMS_GET_USER_REPORTS = ["year"];
const REQUIRED_PARAMS_GET_ABSENCES = ["year"];
const REQUIRED_PARAMS_START_CLOCK = ["customerId", "serviceId", "billable"];
const REQUIRED_PARAMS_CHANGE_CLOCK_DURATION = ["durationBefore", "duration"];

class Clockodo {
constructor({ user, apiKey }) {
Expand Down Expand Up @@ -114,6 +116,22 @@ class Clockodo {

return this[clockodoApi].get("/userreports", params);
}

changeClockDuration(entryId, params) {
_checkRequired(params, REQUIRED_PARAMS_CHANGE_CLOCK_DURATION);

return this[clockodoApi].put("/clock/" + entryId, params);
}

startClock(params) {
_checkRequired(params, REQUIRED_PARAMS_START_CLOCK);

return this[clockodoApi].post("/clock", params);
}

stopClock(entryId, params) {
return this[clockodoApi].delete("/clock/" + entryId, params);
}
}

function _checkRequired(params, requiredList) {
Expand Down
22 changes: 20 additions & 2 deletions src/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const axios = require("axios");
const camelCase = require("camelcase");
const deepMapKeys = require("deep-map-keys");
const qs = require("qs");
const mapParams = require("./mapParams");
const mapKeys = require("./mapKeys");

const ENDPOINT = "https://my.clockodo.com/api";
const axiosClient = Symbol("axiosClient");
Expand All @@ -21,12 +21,30 @@ class ClockodoLib {
}
async get(resource, params = {}) {
const response = await this[axiosClient].get(resource, {
params: mapParams(params),
params: mapKeys(params),
paramsSerializer(params) {
return qs.stringify(params, { arrayFormat: "brackets" });
},
});

return deepMapKeys(response.data, key => camelCase(key));
}
async post(resource, params = {}) {
const mappedObj = mapKeys(params);
const response = await this[axiosClient].post(resource, mappedObj);

return deepMapKeys(response.data, key => camelCase(key));
}
async put(resource, params = {}) {
const mappedObj = mapKeys(params);
const response = await this[axiosClient].put(resource, mappedObj);

return deepMapKeys(response.data, key => camelCase(key));
}
async delete(resource, params = {}) {
const mappedObj = mapKeys(params);
const response = await this[axiosClient].delete(resource, mappedObj);

return deepMapKeys(response.data, key => camelCase(key));
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/mapParams.js → src/mapKeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ const paramMapping = {
roundBy: "round_to_minutes",
prependCustomer: "prepend_customer_to_project_name",
calcHardBudgetRevenues: "calc_also_revenues_for_projects_with_hard_budget",
searchCustomerId: "customers_id",
searchProjectId: "projects_id",
searchServiceId: "services_id",
customerId: "customers_id",
projectId: "projects_id",
serviceId: "services_id",
userId: "users_id",
searchTerm: "term",
durationBefore: "duration_before",
offsetBefore: "offset_before",
};

module.exports = function mapParams(userParams) {
module.exports = function mapKeys(userParams) {
const apiParams = {};

for (const [userParamName, value] of entries(userParams)) {
Expand Down
90 changes: 87 additions & 3 deletions tests/unit/unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ describe("Clockodo (instance)", () => {
describe("getSearchTexts()", () => {
it("correctly builds getSearchTexts() request", async () => {
const givenParameters = {
searchProjectId: "300",
projectId: "300",
};
const expectedParameters = {
projects_id: "300",
Expand Down Expand Up @@ -264,15 +264,15 @@ describe("Clockodo (instance)", () => {
it("throws an error when getTaskDuration() is missing param", async () => {
expect.assertions(1);

const parameters = {
const badParams = {
taskCustomerId: "23",
taskProjectId: "25",
taskServiceId: "42",
taskText: "clean the dishes",
};

try {
await clockodo.getTaskDuration(parameters);
await clockodo.getTaskDuration(badParams);
} catch (error) {
expect(error.message).toEqual('Missing required parameter "taskBillable"');
}
Expand Down Expand Up @@ -343,6 +343,90 @@ describe("Clockodo (instance)", () => {
});
});

describe("changeClockDuration()", () => {
it("correctly builds changeClockDuration() request", async () => {
const params = {
durationBefore: "300",
duration: "540",
offsetBefore: "60",
};
const expectedParameters = {
duration_before: "300",
duration: "540",
offset_before: "60",
};
const nockScope = nock(CLOCKODO_API)
.put("/clock/7082", expectedParameters)
.reply(200);

await clockodo.changeClockDuration("7082", params);

nockScope.done();
});
it("throws an error when getUserReports() is missing param", async () => {
expect.assertions(1);

const badParams = {
durationBefore: "300",
offsetBefore: "60",
};

try {
await clockodo.changeClockDuration("7082", badParams);
} catch (error) {
expect(error.message).toEqual('Missing required parameter "duration"');
}
});
});
describe("startClock()", () => {
it("correctly builds startClock() request", async () => {
const params = {
customerId: "24",
serviceId: "7",
projectId: "365",
billable: Clockodo.ENTRY_NOT_BILLED,
};
const expectedParameters = {
customers_id: "24",
services_id: "7",
projects_id: "365",
billable: Clockodo.ENTRY_NOT_BILLED,
};
const nockScope = nock(CLOCKODO_API)
.post("/clock", expectedParameters)
.reply(200);

await clockodo.startClock(params);

nockScope.done();
});
it("throws an error when getUserReports() is missing param", async () => {
expect.assertions(1);

const badParams = {
customerId: "24",
serviceId: "7",
};

try {
await clockodo.startClock(badParams);
} catch (error) {
expect(error.message).toEqual('Missing required parameter "billable"');
}
});
});
describe("stopClock()", () => {
it("correctly builds stopClock() request", async () => {
const nockScope = nock(CLOCKODO_API)
.delete("/clock/7082")
.reply(200);

await clockodo.stopClock("7082");

nockScope.done();
});
});

describe("Clockodo Constructor", () => {
it("throws an error when constructor is missing user email", () => {
try {
Expand Down

0 comments on commit 5db9894

Please sign in to comment.