├── .gitattributes
├── src
├── index.js
├── utils
│ ├── isObject.js
│ ├── buildSearchParams.js
│ ├── merge.js
│ └── createRequestInstance.js
├── routes
│ ├── index.js
│ ├── Comments.js
│ ├── Webhooks.js
│ ├── KeyResults.js
│ ├── Authorization.js
│ ├── Goals.js
│ ├── Views.js
│ ├── Checklists.js
│ ├── Folders.js
│ ├── Spaces.js
│ ├── Lists.js
│ ├── Tasks.js
│ └── Teams.js
└── structures
│ ├── Clickup.js
│ └── Request.js
├── docs
├── .gitignore
├── content
│ ├── settings.json
│ └── en
│ │ ├── index.md
│ │ ├── setup.md
│ │ ├── examples
│ │ ├── authorization.md
│ │ ├── teams.md
│ │ └── tasks.md
│ │ └── usage.md
├── nuxt.config.js
├── package.json
└── README.md
├── .prettierrc.json
├── .npmignore
├── .eslintrc.json
├── .github
└── workflows
│ ├── pr-test.yml
│ └── npm-publish.yml
├── .eslintignore
├── .prettierignore
├── LICENSE
├── package.json
├── .gitignore
├── test
├── utils.spec.js
├── clickup.spec.js
└── request.spec.js
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const { Clickup } = require('./structures/Clickup');
2 |
3 | module.exports = {
4 | Clickup,
5 | };
6 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.iml
3 | .idea
4 | *.log*
5 | .nuxt
6 | .vscode
7 | .DS_Store
8 | coverage
9 | dist
10 | sw.*
11 | .env
12 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "useTabs": true,
7 | "printWidth": 120
8 | }
9 |
--------------------------------------------------------------------------------
/docs/content/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "clickup.js",
3 | "url": "https://clickup-js.netlify.app",
4 | "github": "ComfortablyCoding/clickup.js",
5 | "defaultBranch": "master"
6 | }
7 |
--------------------------------------------------------------------------------
/docs/nuxt.config.js:
--------------------------------------------------------------------------------
1 | import theme from '@nuxt/content-theme-docs'
2 |
3 | export default theme({
4 | docs: {
5 | primaryColor: '#7b68ee'
6 | },
7 | loading: { color: '#00CD81' },
8 | })
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # NPM
2 |
3 | # node
4 | node_modules
5 |
6 | # git
7 | .git
8 | .gitattributes
9 | .gitignore
10 |
11 | # vscode
12 | .vscode
13 |
14 | # ESLint
15 | .eslintrc.json
16 | .eslintignore
17 |
18 | # prettier
19 | .prettierrc.json
20 | .prettierignore
21 |
22 | # project
23 | *.log
24 | test/
25 | docs/
26 |
27 | # github
28 | .github/
29 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "nuxt",
7 | "build": "nuxt build",
8 | "start": "nuxt start",
9 | "generate": "nuxt generate"
10 | },
11 | "dependencies": {
12 | "@nuxt/content-theme-docs": "^0.11.0",
13 | "nuxt": "^2.15.8"
14 | },
15 | "devDependencies": {}
16 | }
17 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "commonjs": true,
5 | "es2020": true,
6 | "node": true,
7 | "mocha": true
8 | },
9 | "extends": ["airbnb-base", "prettier"],
10 | "plugins": ["prettier"],
11 | "parserOptions": {
12 | "ecmaVersion": 11
13 | },
14 | "rules": {
15 | "prettier/prettier": "error",
16 | "no-underscore-dangle": 0,
17 | "no-param-reassign": "off",
18 | "no-restricted-syntax": "off"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/isObject.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Determines if the passed in value is an object
3 | *
4 | * @param value The value to check.
5 | * @returns {boolean} indicating if the passed in value is an object.
6 | */
7 | const isObject = (value) => {
8 | const isObjectType = typeof value === 'object';
9 | const isArray = Array.isArray(value);
10 | const isNull = value == null;
11 | return isObjectType && !isArray && !isNull;
12 | };
13 |
14 | module.exports = {
15 | isObject,
16 | };
17 |
--------------------------------------------------------------------------------
/.github/workflows/pr-test.yml:
--------------------------------------------------------------------------------
1 | name: PR Test
2 | on: [pull_request]
3 | jobs:
4 | test-pr:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - name: Checkout branch
8 | uses: actions/checkout@v2
9 |
10 | - name: Install Node v16
11 | uses: actions/setup-node@v2
12 | with:
13 | node-version: '16.x'
14 | registry-url: 'https://registry.npmjs.org'
15 |
16 | - name: Clean install deps
17 | run: npm ci
18 |
19 | - name: Run tests
20 | run: npm test
21 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # docs
2 |
3 | ## Setup
4 |
5 | Install dependencies:
6 |
7 | ```bash
8 | yarn install
9 | ```
10 |
11 | ## Development
12 |
13 | ```bash
14 | yarn dev
15 | ```
16 |
17 | ## Static Generation
18 |
19 | This will create the `dist/` directory for publishing to static hosting:
20 |
21 | ```bash
22 | yarn generate
23 | ```
24 |
25 | To preview the static generated app, run `yarn start`
26 |
27 | For detailed explanation on how things work, checkout [nuxt/content](https://content.nuxtjs.org) and [@nuxt/content theme docs](https://content.nuxtjs.org/themes-docs).
28 |
--------------------------------------------------------------------------------
/src/utils/buildSearchParams.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts parameters for a request to URLSearchParams
3 | *
4 | * @param {Object} query parameters to be converted
5 | * @returns {URLSearchParams} searchParams The query in LHS bracket style format
6 | */
7 | const buildSearchParams = (query) => {
8 | const params = new URLSearchParams();
9 |
10 | for (const key in query) {
11 | if (key.endsWith('[]')) {
12 | query[key].forEach((entry) => {
13 | params.append(key, entry);
14 | });
15 | } else {
16 | params.set(key, query[key]);
17 | }
18 | }
19 |
20 | return params;
21 | };
22 |
23 | module.exports = {
24 | buildSearchParams,
25 | };
26 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # ESLint
2 | # git
3 | .git
4 | .gitattributes
5 | .gitignore
6 |
7 | # vscode
8 | .vscode
9 |
10 | # docs
11 | docs/
12 |
13 | # Logs
14 | logs
15 | *.log
16 | npm-debug.log*
17 | yarn-debug.log*
18 | yarn-error.log*
19 | lerna-debug.log*
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Dependency directories
29 | node_modules/
30 |
31 | # Optional npm cache directory
32 | .npm
33 |
34 | # Optional eslint cache
35 | .eslintcache
36 |
37 | # Output of 'npm pack'
38 | *.tgz
39 |
40 | # Yarn Integrity file
41 | .yarn-integrity
42 |
43 | # dotenv environment variables file
44 | .env
45 | .env.test
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Prettier
2 | # git
3 | .git
4 | .gitattributes
5 | .gitignore
6 |
7 | # vscode
8 | .vscode
9 |
10 | # docs
11 | docs/
12 |
13 | # Logs
14 | logs
15 | *.log
16 | npm-debug.log*
17 | yarn-debug.log*
18 | yarn-error.log*
19 | lerna-debug.log*
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Dependency directories
29 | node_modules/
30 |
31 | # Optional npm cache directory
32 | .npm
33 |
34 | # Optional eslint cache
35 | .eslintcache
36 |
37 | # Output of 'npm pack'
38 | *.tgz
39 |
40 | # Yarn Integrity file
41 | .yarn-integrity
42 |
43 | # dotenv environment variables file
44 | .env
45 | .env.test
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | const Authorization = require('./Authorization');
2 | const Checklists = require('./Checklists');
3 | const Comments = require('./Comments');
4 | const Folders = require('./Folders');
5 | const Goals = require('./Goals');
6 | const KeyResults = require('./KeyResults');
7 | const Lists = require('./Lists');
8 | const Spaces = require('./Spaces');
9 | const Tasks = require('./Tasks');
10 | const Teams = require('./Teams');
11 | const Views = require('./Views');
12 | const Webhooks = require('./Webhooks');
13 |
14 | module.exports = {
15 | Authorization,
16 | Checklists,
17 | Comments,
18 | Folders,
19 | Goals,
20 | KeyResults,
21 | Lists,
22 | Spaces,
23 | Tasks,
24 | Teams,
25 | Views,
26 | Webhooks,
27 | };
28 |
--------------------------------------------------------------------------------
/docs/content/en/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | description: ''
4 | position: 00
5 | category: 'Getting Started'
6 | features:
7 | - Authorization
8 | - Checklists
9 | - Comments
10 | - Folders
11 | - Goals
12 | - KeyResults
13 | - Lists
14 | - Spaces
15 | - Tasks
16 | - Teams
17 | - Views
18 | - Webhooks
19 | ---
20 |
21 | # clickup.js
22 |
23 | A Node.js wrapper for the [Clickup API](https://clickup.com/api).
24 |
25 | ## Features
26 |
27 | The available features are:
28 |
29 |
30 |
31 | ## Disclaimer
32 |
33 | The [clickup.js](https://github.com/ComfortablyCoding/clickup.js) package is **unofficial** and therefor not endorsed or affiliated with ClickUp or it's subsidiaries.
34 |
--------------------------------------------------------------------------------
/src/utils/merge.js:
--------------------------------------------------------------------------------
1 | const { isObject } = require('./isObject');
2 |
3 | /**
4 | * Performs a deep merge of source into object. It does not override properties
5 | *
6 | * @param object The destination object.
7 | * @param sources The values to be applied to the object
8 | * @returns The merged object
9 | */
10 | const merge = (object, ...sources) => {
11 | const result = object;
12 | for (const source of sources) {
13 | for (const key in source) {
14 | if (isObject(result[key])) {
15 | result[key] = merge(result[key], source[key]);
16 | } else if (!Object.prototype.hasOwnProperty.call(result, key)) {
17 | result[key] = source[key];
18 | }
19 | }
20 | }
21 |
22 | return result;
23 | };
24 |
25 | module.exports = {
26 | merge,
27 | };
28 |
--------------------------------------------------------------------------------
/docs/content/en/setup.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Setup
3 | description: ''
4 | position: 01
5 | category: 'Getting Started'
6 | ---
7 |
8 | ## Installation
9 |
10 | First we need to install `clickup.js`.
11 |
12 |
13 |
14 |
15 | ```bash
16 | yarn add clickup.js
17 | ```
18 |
19 |
20 |
21 |
22 | ```bash
23 | npm install clickup.js
24 | ```
25 |
26 |
27 |
28 |
29 | ## Obtain Access Token
30 |
31 | Before the package is able to be used we must authenticate with the Clickup API and obtain an `Access Token`. You can read how to do so at the [Clickup API docs](https://clickup.com/api).
32 |
33 | Once complete you are now ready to use the package.
34 |
--------------------------------------------------------------------------------
/src/utils/createRequestInstance.js:
--------------------------------------------------------------------------------
1 | const got = require('got');
2 | const { merge } = require('./merge');
3 |
4 | /**
5 | * Creates a got instance with clickup default config
6 | * @private
7 | * @param {got.ExtendOptions} requestOptions Options for the created got instance. All options can be found [here](https://github.com/sindresorhus/got#options)
8 | * @returns {got.Got} A got instance
9 | */
10 | const createRequestInstance = (token, requestOptions = {}) => {
11 | const requestDefaultOptions = {
12 | headers: {
13 | Authorization: token,
14 | 'Content-Type': 'application/json',
15 | },
16 | responseType: 'json',
17 | prefixUrl: 'https://api.clickup.com/api/v2',
18 | };
19 | // apply defaults where necessary
20 | const requestConfig = merge(requestOptions, requestDefaultOptions);
21 | return got.extend(requestConfig);
22 | };
23 |
24 | module.exports = {
25 | createRequestInstance,
26 | };
27 |
--------------------------------------------------------------------------------
/src/routes/Comments.js:
--------------------------------------------------------------------------------
1 | class Comments {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | /**
14 | * The main route for the collection
15 | * @type {String}
16 | */
17 | this.route = 'comment';
18 | }
19 |
20 | /**
21 | * Update a comment
22 | *
23 | * @param {Number} commentId The comment id
24 | * @param {Object} data The comment data
25 | */
26 | async update(commentId, data) {
27 | return this._request.put({
28 | endpoint: `${this.route}/${commentId}`,
29 | data,
30 | });
31 | }
32 |
33 | /**
34 | * Delete a comment
35 | *
36 | * @param {Number} commentId The comment id
37 | */
38 | async delete(commentId) {
39 | return this._request.delete({
40 | endpoint: `${this.route}/${commentId}`,
41 | });
42 | }
43 | }
44 |
45 | module.exports = Comments;
46 |
--------------------------------------------------------------------------------
/src/routes/Webhooks.js:
--------------------------------------------------------------------------------
1 | class Webhooks {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | /**
14 | * The main route for the collection
15 | * @type {String}
16 | */
17 | this.route = 'webhook';
18 | }
19 |
20 | /**
21 | * Update a webhook
22 | *
23 | * @param {String} webhookId The webhook id
24 | * @param {Object} data The webhook data
25 | */
26 | async update(webhookId, data) {
27 | return this._request.put({
28 | endpoint: `${this.route}/${webhookId}`,
29 | data,
30 | });
31 | }
32 |
33 | /**
34 | * Delete a webhook
35 | *
36 | * @param {String} webhookId The webhook id
37 | */
38 | async delete(webhookId) {
39 | return this._request.delete({
40 | endpoint: `${this.route}/${webhookId}`,
41 | });
42 | }
43 | }
44 |
45 | module.exports = Webhooks;
46 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | release:
8 | types: [published]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions/setup-node@v2
16 | with:
17 | node-version: 16
18 | - run: npm ci
19 | - run: npm test
20 |
21 | publish-npm:
22 | needs: build
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v2
26 | - uses: actions/setup-node@v2
27 | with:
28 | node-version: '16.x'
29 | registry-url: https://registry.npmjs.org/
30 | - run: npm ci
31 | - run: npm publish
32 | env:
33 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
34 |
--------------------------------------------------------------------------------
/src/routes/KeyResults.js:
--------------------------------------------------------------------------------
1 | class KeyResults {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | /**
14 | * The main route for the collection
15 | * @type {String}
16 | */
17 | this.route = 'key_result';
18 | }
19 |
20 | /**
21 | * Update a key result
22 | *
23 | * @param {String} keyResultId The key result id
24 | * @param {Object} data The key result data
25 | */
26 | updateKeyResult(keyResultId, data) {
27 | return this._request.put({
28 | endpoint: `${this.route}/${keyResultId}`,
29 | data,
30 | });
31 | }
32 |
33 | /**
34 | * Delete a key result
35 | *
36 | * @param {String} keyResultId The key result id
37 | */
38 | deleteKeyResult(keyResultId) {
39 | return this._request.delete({
40 | endpoint: `${this.route}/${keyResultId}`,
41 | });
42 | }
43 | }
44 | module.exports = KeyResults;
45 |
--------------------------------------------------------------------------------
/docs/content/en/examples/authorization.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Authorization
3 | description: 'Examples on using the Authorization endpoints'
4 | position: 00
5 | category: 'Examples'
6 | ---
7 |
8 | ### Get the authorized team(s) for this token
9 |
10 | ```js
11 | (async () => {
12 | try {
13 | // get the authorized team(s) for the given token
14 | const { body } = await clickup.authorization.getAuthorizedTeams();
15 | console.log(body);
16 | } catch (error) {
17 | if (error.response) {
18 | // The request was made and the server responded with a status code
19 | // that falls out of the range of 2xx
20 | console.log(error.response.body);
21 | console.log(error.response.statusCode);
22 | console.log(error.response.headers);
23 | } else if (error.request) {
24 | // The request was made but no response was received
25 | console.log(error.request);
26 | } else {
27 | // Something happened in setting up the request that triggered an Error
28 | console.log('Error', error.message);
29 | }
30 | console.log(error.options);
31 | }
32 | })();
33 | ```
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 @ComfortablyCoding
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/content/en/examples/teams.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Teams
3 | description: 'Examples on using the Teams endpoints'
4 | position: 02
5 | category: 'Examples'
6 | ---
7 |
8 | ### Get Filtered Team Tasks
9 |
10 | ```js
11 | (async () => {
12 | try {
13 | // return all tasks that meet the criteria
14 | const filterData = {
15 | date_created: 1508369194377,
16 | 'list_ids[]': [123],
17 | 'statuses[]': ['active', 'in progress'],
18 | };
19 | const { body } = await clickup.teams.getFilteredTasks(543, filterData);
20 | console.log(body);
21 | } catch (error) {
22 | if (error.response) {
23 | // The request was made and the server responded with a status code
24 | // that falls out of the range of 2xx
25 | console.log(error.response.body);
26 | console.log(error.response.statusCode);
27 | console.log(error.response.headers);
28 | } else if (error.request) {
29 | // The request was made but no response was received
30 | console.log(error.request);
31 | } else {
32 | // Something happened in setting up the request that triggered an Error
33 | console.log('Error', error.message);
34 | }
35 | console.log(error.options);
36 | }
37 | })();
38 | ```
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "clickup.js",
3 | "version": "3.0.2",
4 | "description": "A Node.js wrapper for the Clickup API",
5 | "main": "src/index.js",
6 | "keywords": [
7 | "clickup",
8 | "clickupjs",
9 | "clickup-js",
10 | "clickup.js",
11 | "api",
12 | "wrapper",
13 | "wrapper-api"
14 | ],
15 | "scripts": {
16 | "test": "mocha",
17 | "lint": "eslint src test --fix",
18 | "format": "prettier --write ."
19 | },
20 | "dependencies": {
21 | "form-data": "^4.0.0",
22 | "got": "^11.8.2"
23 | },
24 | "devDependencies": {
25 | "chai": "^4.3.4",
26 | "eslint": "^8.4.1",
27 | "eslint-config-airbnb-base": "^15.0.0",
28 | "eslint-config-prettier": "^8.3.0",
29 | "eslint-plugin-import": "^2.25.3",
30 | "eslint-plugin-prettier": "^4.0.0",
31 | "mocha": "^9.1.3",
32 | "prettier": "^2.5.1",
33 | "sinon": "^12.0.1"
34 | },
35 | "author": "@ComfortablyCoding",
36 | "repository": {
37 | "type": "git",
38 | "url": "https://github.com/ComfortablyCoding/clickup.js.git"
39 | },
40 | "homepage": "https://github.com/ComfortablyCoding/clickup.js#readme",
41 | "bugs": {
42 | "url": "https://github.com/ComfortablyCoding/clickup.js/issues"
43 | },
44 | "license": "MIT"
45 | }
46 |
--------------------------------------------------------------------------------
/src/routes/Authorization.js:
--------------------------------------------------------------------------------
1 | class Authorization {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | }
14 |
15 | /**
16 | * Get the access token for the given client
17 | *
18 | * @param {String} clientId Oauth app client id
19 | * @param {String} clientSecret Oauth app client secret
20 | * @param {String} code Code given in redirect url
21 | */
22 | async accessToken(clientId, clientSecret, code) {
23 | return this._request.post({
24 | endpoint: 'oauth/token',
25 | params: {
26 | client_id: clientId,
27 | client_secret: clientSecret,
28 | code,
29 | },
30 | });
31 | }
32 |
33 | /**
34 | * Get the user that this token belongs to
35 | */
36 | async getAuthorizedUser() {
37 | return this._request.get({
38 | endpoint: 'user',
39 | });
40 | }
41 |
42 | /**
43 | * Get the authorized teams for this token
44 | */
45 | async getAuthorizedTeams() {
46 | return this._request.get({
47 | endpoint: 'team',
48 | });
49 | }
50 | }
51 |
52 | module.exports = Authorization;
53 |
--------------------------------------------------------------------------------
/src/routes/Goals.js:
--------------------------------------------------------------------------------
1 | class Goals {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | /**
14 | * The main route for the collection
15 | * @type {String}
16 | */
17 | this.route = 'goal';
18 | }
19 |
20 | /**
21 | * Get a goal
22 | *
23 | * @param {String} goalId The goal id
24 | */
25 | async get(goalId) {
26 | return this._request.get({
27 | endpoint: `${this.route}/${goalId}`,
28 | });
29 | }
30 |
31 | /**
32 | * Update a goal
33 | *
34 | * @param {String} goalId The goal id
35 | * @param {Object} data The goal data
36 | */
37 | async update(goalId, data) {
38 | return this._request.put({
39 | endpoint: `${this.route}/${goalId}`,
40 | data,
41 | });
42 | }
43 |
44 | /**
45 | * Delete a goal
46 | *
47 | * @param {String} goalId The goal id
48 | */
49 | async delete(goalId) {
50 | return this._request.delete({
51 | endpoint: `${this.route}/${goalId}`,
52 | });
53 | }
54 |
55 | /**
56 | * Create a key result
57 | *
58 | * @param {String} goalId The goal id
59 | * @param {Object} data The key result data
60 | */
61 | async createKeyResult(goalId, data) {
62 | return this._request.post({
63 | endpoint: `${this.route}/${goalId}/key_result`,
64 | data,
65 | });
66 | }
67 | }
68 |
69 | module.exports = Goals;
70 |
--------------------------------------------------------------------------------
/src/routes/Views.js:
--------------------------------------------------------------------------------
1 | class Views {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | /**
14 | * The main route for the collection
15 | * @type {String}
16 | */
17 | this.route = 'view';
18 | }
19 |
20 | /**
21 | * Get a view
22 | *
23 | * @param {String} viewId The view id
24 | */
25 | async get(viewId) {
26 | return this._request.get({
27 | endpoint: `${this.route}/${viewId}`,
28 | });
29 | }
30 |
31 | /**
32 | * Update a view
33 | *
34 | * @param {String} viewId The view id
35 | * @param {Object} data The view data
36 | */
37 | async update(viewId, data) {
38 | return this._request.put({
39 | endpoint: `${this.route}/${viewId}`,
40 | data,
41 | });
42 | }
43 |
44 | /**
45 | * Delete a view
46 | *
47 | * @param {String} viewId The view id
48 | */
49 | async delete(viewId) {
50 | return this._request.delete({
51 | endpoint: `${this.route}/${viewId}`,
52 | });
53 | }
54 |
55 | /**
56 | * Add a comment on a view
57 | *
58 | * @param {String} viewId The view id
59 | * @param {Object} data The comment data
60 | */
61 | async addComment(viewId, data) {
62 | return this._request.post({
63 | endpoint: `${this.route}/${viewId}/comment`,
64 | data,
65 | });
66 | }
67 |
68 | /**
69 | * Get all comments on a view
70 | *
71 | * @param {String} viewId The view id
72 | */
73 | async getComments(viewId) {
74 | return this._request.get({
75 | endpoint: `${this.route}/${viewId}/comment`,
76 | });
77 | }
78 |
79 | /**
80 | * Get all tasks in a view
81 | *
82 | * @param {String} viewId The view id
83 | * @param {Integer} [page=0] The page to get
84 | */
85 | async getTasks(viewId, page = 0) {
86 | return this._request.get({
87 | endpoint: `${this.route}/${viewId}/task`,
88 | params: {
89 | page,
90 | },
91 | });
92 | }
93 | }
94 |
95 | module.exports = Views;
96 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/src/routes/Checklists.js:
--------------------------------------------------------------------------------
1 | class Checklists {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | /**
14 | * The main route for the collection
15 | * @type {String}
16 | */
17 | this.route = 'checklist';
18 | }
19 |
20 | /**
21 | * Update a checklist
22 | *
23 | * @param {String} checklistId The checklist id
24 | * @param {Object} data The checklist data
25 | */
26 | async update(checklistId, data) {
27 | return this._request.put({
28 | endpoint: `${this.route}/${checklistId}`,
29 | data,
30 | });
31 | }
32 |
33 | /**
34 | * Delete a checklist
35 | *
36 | * @param {String} checklistId The checklist id
37 | */
38 | async delete(checklistId) {
39 | return this._request.delete({
40 | endpoint: `${this.route}/${checklistId}`,
41 | });
42 | }
43 |
44 | /**
45 | * Create a checklist item
46 | *
47 | * @param {String} checklistId The checklist id
48 | * @param {Object} data The checklist item data
49 | */
50 | async createChecklistItem(checklistId, data) {
51 | return this._request.post({
52 | endpoint: `${this.route}/${checklistId}/checklist_item`,
53 | data,
54 | });
55 | }
56 |
57 | /**
58 | * Update a checklist item
59 | *
60 | * @param {String} checklistId The checklist id
61 | * @param {String} checklistItemId The checklist item id
62 | * @param {Object} data The checklist item data
63 | */
64 | async updateChecklistItem(checklistId, checklistItemId, data) {
65 | return this._request.put({
66 | endpoint: `${this.route}/${checklistId}/checklist_item/${checklistItemId}`,
67 | data,
68 | });
69 | }
70 |
71 | /**
72 | * Delete a checklist item
73 | *
74 | * @param {String} checklistId The checklist id
75 | * @param {String} checklistItemId The checklist item id
76 | */
77 | async deleteChecklistItem(checklistId, checklistItemId) {
78 | return this._request.delete({
79 | endpoint: `${this.route}/${checklistId}/checklist_item/${checklistItemId}`,
80 | });
81 | }
82 | }
83 |
84 | module.exports = Checklists;
85 |
--------------------------------------------------------------------------------
/test/utils.spec.js:
--------------------------------------------------------------------------------
1 | const { assert } = require('chai');
2 | const { buildSearchParams } = require('../src/utils/buildSearchParams');
3 | const { isObject } = require('../src/utils/isObject');
4 | const { merge } = require('../src/utils/merge');
5 |
6 | describe('Testing buildSearchParams util', () => {
7 | it('should construct an instance of URLSearchParams', () => {
8 | assert.instanceOf(buildSearchParams({}), URLSearchParams);
9 | });
10 |
11 | it('should build URLSearchParams to support LHS notation', () => {
12 | const params = {
13 | archive: false,
14 | order_by: 'due_date',
15 | 'statuses[]': ['in progress', 'completed'],
16 | };
17 |
18 | const expectedOutput = new URLSearchParams([
19 | ['archived', 'false'],
20 | ['order_by', 'due_date'],
21 | ['statuses[]', 'in progress'],
22 | ['statuses[]', 'completed'],
23 | ]);
24 |
25 | assert.deepEqual(buildSearchParams(params), expectedOutput);
26 | });
27 | });
28 |
29 | describe('Testing isObject util', () => {
30 | it('should be false for an array', () => {
31 | assert.isFalse(isObject([]));
32 | });
33 |
34 | it('should be false for an number', () => {
35 | assert.isFalse(isObject(1));
36 | });
37 |
38 | it('should be false for an string', () => {
39 | assert.isFalse(isObject('test'));
40 | });
41 |
42 | it('should be true for an object', () => {
43 | assert.isTrue(isObject({ id: 1 }));
44 | });
45 | });
46 |
47 | describe('Testing merge util', () => {
48 | it('should merge source into object', () => {
49 | const object = {
50 | text: 'original',
51 | headers: {
52 | 'Content-Type': 'application/json',
53 | },
54 | };
55 | const source1 = {
56 | id: 2,
57 | type: 'text',
58 | headers: {
59 | Authorization: 'token',
60 | },
61 | };
62 | const source2 = {
63 | id: 3,
64 | text: 'changed',
65 | headers: {
66 | 'Content-Type': 'text/html',
67 | },
68 | };
69 | const expectedResult = {
70 | id: 2,
71 | text: 'original',
72 | headers: {
73 | Authorization: 'token',
74 | 'Content-Type': 'application/json',
75 | },
76 | type: 'text',
77 | };
78 |
79 | const mergeResult = merge(object, source1, source2);
80 | assert.deepEqual(mergeResult, expectedResult);
81 | });
82 | });
83 |
--------------------------------------------------------------------------------
/test/clickup.spec.js:
--------------------------------------------------------------------------------
1 | const { assert } = require('chai');
2 | const { Clickup } = require('../src/index');
3 | const routes = require('../src/routes');
4 |
5 | const token = 'token';
6 |
7 | describe('Testing Clickup Client Instance', () => {
8 | let clickup;
9 | before(() => {
10 | clickup = new Clickup(token);
11 | });
12 |
13 | it('should construct a Clickup instance', () => {
14 | assert.instanceOf(clickup, Clickup);
15 | });
16 |
17 | it('should have default prefix url', () => {
18 | const { prefixUrl } = clickup._service.getOptions();
19 | assert.strictEqual(prefixUrl, 'https://api.clickup.com/api/v2/');
20 | });
21 |
22 | it('should have default headers', () => {
23 | const headers = clickup._service.getHeaders();
24 | assert.property(headers, 'authorization');
25 | assert.property(headers, 'content-type');
26 | assert.strictEqual(headers.authorization, token);
27 | assert.strictEqual(headers['content-type'], 'application/json');
28 | });
29 |
30 | it('should have the default response type', () => {
31 | const { responseType } = clickup._service.getOptions();
32 | assert.strictEqual(responseType, 'json');
33 | });
34 |
35 | it('should instantiate all routes', () => {
36 | assert.instanceOf(clickup.authorization, routes.Authorization);
37 | assert.instanceOf(clickup.checklists, routes.Checklists);
38 | assert.instanceOf(clickup.comments, routes.Comments);
39 | assert.instanceOf(clickup.folders, routes.Folders);
40 | assert.instanceOf(clickup.goals, routes.Goals);
41 | assert.instanceOf(clickup.keyResults, routes.KeyResults);
42 | assert.instanceOf(clickup.lists, routes.Lists);
43 | assert.instanceOf(clickup.spaces, routes.Spaces);
44 | assert.instanceOf(clickup.tasks, routes.Tasks);
45 | assert.instanceOf(clickup.teams, routes.Teams);
46 | assert.instanceOf(clickup.views, routes.Views);
47 | assert.instanceOf(clickup.webhooks, routes.Webhooks);
48 | });
49 | });
50 |
51 | describe('Testing Client Got Options', () => {
52 | let clickup;
53 | before(() => {
54 | clickup = new Clickup(token, {
55 | hooks: {
56 | beforeRequest: [
57 | (options) => {
58 | options.headers.foo = 'bar';
59 | },
60 | ],
61 | },
62 | });
63 | });
64 |
65 | it('should have beforeRequest hook(s)', () => {
66 | const { hooks } = clickup._service.getOptions();
67 | assert.isArray(hooks.beforeRequest);
68 | assert.lengthOf(hooks.beforeRequest, 1);
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/docs/content/en/examples/tasks.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Tasks
3 | description: 'Examples on using the Tasks endpoints'
4 | position: 01
5 | category: 'Examples'
6 | ---
7 |
8 | ### Create a task
9 |
10 | ```js
11 | (async () => {
12 | try {
13 | // create a task on a list
14 | const taskData = {
15 | name: 'New Task Name',
16 | description: 'New Task Description',
17 | assignees: [183],
18 | tags: ['new'],
19 | status: 'Open',
20 | priority: 3,
21 | due_date: 1508369194377,
22 | due_date_time: false,
23 | time_estimate: 8640000,
24 | start_date: 1567780450202,
25 | start_date_time: false,
26 | notify_all: true,
27 | parent: null,
28 | links_to: null,
29 | check_required_custom_fields: true,
30 | custom_fields: [
31 | {
32 | id: '0a52c486-5f05-403b-b4fd-c512ff05131c',
33 | value: 23,
34 | },
35 | {
36 | id: '03efda77-c7a0-42d3-8afd-fd546353c2f5',
37 | value: 'Text field input',
38 | },
39 | ],
40 | };
41 |
42 | const { body } = await clickup.lists.createTask(1234, taskData);
43 | console.log(body);
44 | } catch (error) {
45 | if (error.response) {
46 | // The request was made and the server responded with a status code
47 | // that falls out of the range of 2xx
48 | console.log(error.response.body);
49 | console.log(error.response.statusCode);
50 | console.log(error.response.headers);
51 | } else if (error.request) {
52 | // The request was made but no response was received
53 | console.log(error.request);
54 | } else {
55 | // Something happened in setting up the request that triggered an Error
56 | console.log('Error', error.message);
57 | }
58 | console.log(error.options);
59 | }
60 | })();
61 | ```
62 |
63 | ### Fetch a task
64 |
65 | ```js
66 | (async () => {
67 | try {
68 | // get a specific task
69 | const { body } = await clickup.tasks.get('9hz');
70 | console.log(body);
71 | } catch (error) {
72 | if (error.response) {
73 | // The request was made and the server responded with a status code
74 | // that falls out of the range of 2xx
75 | console.log(error.response.body);
76 | console.log(error.response.statusCode);
77 | console.log(error.response.headers);
78 | } else if (error.request) {
79 | // The request was made but no response was received
80 | console.log(error.request);
81 | } else {
82 | // Something happened in setting up the request that triggered an Error
83 | console.log('Error', error.message);
84 | }
85 | console.log(error.options);
86 | }
87 | })();
88 | ```
89 |
--------------------------------------------------------------------------------
/src/structures/Clickup.js:
--------------------------------------------------------------------------------
1 | const {
2 | Authorization,
3 | Checklists,
4 | Comments,
5 | Folders,
6 | Goals,
7 | KeyResults,
8 | Lists,
9 | Spaces,
10 | Tasks,
11 | Teams,
12 | Views,
13 | Webhooks,
14 | } = require('../routes');
15 | const { Request } = require('./Request');
16 |
17 | class Clickup {
18 | /**
19 | * Creates a client instance that connects to the Clickup API
20 | *
21 | * @constructor
22 | * @param {String} token Clickup API Access Token
23 | * @param {import('got/dist/source').ExtendOptions} requestOptions Options for the created got instance.
24 | */
25 | constructor(token, requestOptions) {
26 | // create service instance
27 | /**
28 | * The clickup request service
29 | * @type {Request}
30 | * @private
31 | */
32 | this._service = new Request(token, requestOptions);
33 |
34 | // pull in all routes
35 | /**
36 | * authorization
37 | *
38 | * @type {Authorization}
39 | * @public
40 | */
41 | this.authorization = new Authorization(this._service);
42 | /**
43 | * checklists
44 | *
45 | * @type {Checklists}
46 | * @public
47 | */
48 | this.checklists = new Checklists(this._service);
49 | /**
50 | * comments
51 | *
52 | * @type {Comments}
53 | * @public
54 | */
55 | this.comments = new Comments(this._service);
56 | /**
57 | * folders
58 | *
59 | * @type {Folder}
60 | * @public
61 | */
62 | this.folders = new Folders(this._service);
63 | /**
64 | * goals
65 | *
66 | * @type {Goals}
67 | * @public
68 | */
69 | this.goals = new Goals(this._service);
70 | /**
71 | * keyResults
72 | *
73 | * @type {KeyResults}
74 | * @public
75 | */
76 | this.keyResults = new KeyResults(this._service);
77 | /**
78 | * lists
79 | *
80 | * @type {Lists}
81 | * @public
82 | */
83 | this.lists = new Lists(this._service);
84 | /**
85 | * spaces
86 | *
87 | * @type {Spaces}
88 | * @public
89 | */
90 | this.spaces = new Spaces(this._service);
91 | /**
92 | * tasks
93 | *
94 | * @type {Tasks}
95 | * @public
96 | */
97 | this.tasks = new Tasks(this._service);
98 | /**
99 | * teams
100 | *
101 | * @type {Teams}
102 | * @public
103 | */
104 | this.teams = new Teams(this._service);
105 | /**
106 | * views
107 | *
108 | * @type {Views}
109 | * @public
110 | */
111 | this.views = new Views(this._service);
112 | /**
113 | * webhooks
114 | *
115 | * @type {Webhooks}
116 | * @public
117 | */
118 | this.webhooks = new Webhooks(this._service);
119 | }
120 | }
121 |
122 | module.exports = {
123 | Clickup,
124 | };
125 |
--------------------------------------------------------------------------------
/docs/content/en/usage.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Usage
3 | description: ''
4 | position: 02
5 | category: 'Getting Started'
6 | ---
7 |
8 | In your project, initialize an instance of clickup.js
9 |
10 | ```js
11 | const { Clickup } = require('clickup.js');
12 | const token = '...'; // API access token
13 | const clickup = new Clickup(token);
14 | ```
15 |
16 | Once you've created an instance, you can use it to access all the features provided by the wrapper, the following example fetches a task by id and displays the response to the console.
17 |
18 | ```js
19 | (async () => {
20 | try {
21 | // get a specific task
22 | const { body } = await clickup.tasks.get('9hz');
23 | console.log(body);
24 | } catch (error) {
25 | if (error.response) {
26 | // The request was made and the server responded with a status code
27 | // that falls out of the range of 2xx
28 | console.log(error.response.body);
29 | console.log(error.response.statusCode);
30 | console.log(error.response.headers);
31 | } else if (error.request) {
32 | // The request was made but no response was received
33 | console.log(error.request);
34 | } else {
35 | // Something happened in setting up the request that triggered an Error
36 | console.log('Error', error.message);
37 | }
38 | console.log(error.options);
39 | }
40 | })();
41 | ```
42 |
43 | Additional examples can be view in the [example section](./examples).
44 |
45 |
46 |
47 | Due to the HTTP request library being used each error contains an `options` property which are the options Got used to create a request - just to make debugging easier. Additionally, the errors may have `request` and `response` properties depending on which phase of the request failed. Read more about HTTP request library [Got](https://github.com/sindresorhus/got).
48 |
49 |
50 |
51 | ## Caveats
52 |
53 |
54 |
55 | The library is structured to match classes with their respective routes, **NOT** how they are sectioned in the Clickup API docs.
56 |
57 |
58 |
59 | For example adding a guest to a task is under the `Tasks` class instead of the `Guests` class as its route is via `task` and not `guest`. Due to this a request to add a guest to a task will look like so:
60 |
61 | ```js
62 | (async () => {
63 | try {
64 | // guest data
65 | const guestData = {
66 | permission_level: 'read',
67 | };
68 | // add guest to task
69 | const { body } = await clickup.tasks.addGuest('c04', 403, guestData);
70 | console.log(body);
71 | } catch (error) {
72 | if (error.response) {
73 | // The request was made and the server responded with a status code
74 | // that falls out of the range of 2xx
75 | console.log(error.response.body);
76 | console.log(error.response.statusCode);
77 | console.log(error.response.headers);
78 | } else if (error.request) {
79 | // The request was made but no response was received
80 | console.log(error.request);
81 | } else {
82 | // Something happened in setting up the request that triggered an Error
83 | console.log('Error', error.message);
84 | }
85 | console.log(error.options);
86 | }
87 | })();
88 | ```
89 |
--------------------------------------------------------------------------------
/src/routes/Folders.js:
--------------------------------------------------------------------------------
1 | class Folders {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | /**
14 | * The main route for the collection
15 | * @type {String}
16 | */
17 | this.route = 'folder';
18 | }
19 |
20 | /**
21 | * Get a folder
22 | *
23 | * @param {Number} folderId The folder id
24 | */
25 | async get(folderId) {
26 | return this._request.get({
27 | endpoint: `${this.route}/${folderId}`,
28 | });
29 | }
30 |
31 | /**
32 | * Update a folder
33 | *
34 | * @param {Number} folderId The folder id
35 | * @param {Object} data The folder data
36 | */
37 | async update(folderId, data) {
38 | return this._request.put({
39 | endpoint: `${this.route}/${folderId}`,
40 | data,
41 | });
42 | }
43 |
44 | /**
45 | * Delete a folder
46 | *
47 | * @param {Number} folderId The folder id
48 | */
49 | async delete(folderId) {
50 | return this._request.delete({
51 | endpoint: `${this.route}/${folderId}`,
52 | });
53 | }
54 |
55 | /**
56 | *Add a guest to a folder
57 | *
58 | * @param {Number} folderId The folder id
59 | * @param {Number} guestId The guest id
60 | * @param {Object} data The guest data
61 | */
62 | async addGuest(folderId, guestId, data) {
63 | return this._request.post({
64 | endpoint: `${this.route}/${folderId}/guest/${guestId}`,
65 | data,
66 | });
67 | }
68 |
69 | /**
70 | * Remove a guest from a folder
71 | *
72 | * @param {Number} folderId The folder id
73 | * @param {Number} guestId The guest id
74 | */
75 | async removeGuest(folderId, guestId) {
76 | return this._request.delete({
77 | endpoint: `${this.route}/${folderId}/guest/${guestId}`,
78 | });
79 | }
80 |
81 | /**
82 | * Create a list
83 | *
84 | * @param {Number} folderId The folder id
85 | * @param {Object} data The list data
86 | */
87 | async createList(folderId, data) {
88 | return this._request.post({
89 | endpoint: `${this.route}/${folderId}/list`,
90 | data,
91 | });
92 | }
93 |
94 | /**
95 | * Get all lists in a folder
96 | *
97 | * @param {Number} folderId The folder id
98 | * @param {Boolean} [archived=false] If archived lists should be returned or not
99 | */
100 | async getLists(folderId, archived = false) {
101 | return this._request.get({
102 | endpoint: `${this.route}/${folderId}/list`,
103 | params: {
104 | archived,
105 | },
106 | });
107 | }
108 |
109 | /**
110 | * Create a view for a folder
111 | *
112 | * @param {Number} folderId The folder id
113 | * @param {Object} data The view data
114 | */
115 | async createView(folderId, data) {
116 | return this._request.post({
117 | endpoint: `${this.route}/${folderId}/view`,
118 | data,
119 | });
120 | }
121 |
122 | /**
123 | * Get all views for a folder
124 | *
125 | * @param {Number} folderId The folder id
126 | */
127 | async getViews(folderId) {
128 | return this._request.get({
129 | endpoint: `${this.route}/${folderId}/view`,
130 | });
131 | }
132 | }
133 |
134 | module.exports = Folders;
135 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # clickup.js
2 |
3 | A Node.js wrapper for the [Clickup API](https://clickup.com/api).
4 |
5 | [](https://www.npmjs.com/package/clickup.js)
6 | [](https://packagephobia.now.sh/result?p=clickup.js)
7 | 
8 |
9 | ## Install
10 |
11 | ```sh
12 | npm install clickup.js
13 | ```
14 |
15 | or
16 |
17 | ```sh
18 | yarn add clickup.js
19 | ```
20 |
21 | ## Usage
22 |
23 | Before you can use this library you will need to first authenticate with the Clickup API and obtain an `Access Token`. You can read how to do so at the [Clickup API docs](https://clickup.com/api).
24 |
25 | In your project, initialize an instance of clickup.js:
26 |
27 | ```js
28 | const { Clickup } = require('clickup.js');
29 | const token = '...'; // API access token
30 | const clickup = new Clickup(token);
31 | ```
32 |
33 | Once you've created an instance, you can use it to access all the features provided by the wrapper, the following example fetches a task by id and displays the response to the console:
34 |
35 | ```js
36 | (async () => {
37 | try {
38 | // get a specific task
39 | const { body } = await clickup.tasks.get('9hz');
40 | console.log(body);
41 | } catch (error) {
42 | if (error.response) {
43 | // The request was made and the server responded with a status code
44 | // that falls out of the range of 2xx
45 | console.log(error.response.body);
46 | console.log(error.response.statusCode);
47 | console.log(error.response.headers);
48 | } else if (error.request) {
49 | // The request was made but no response was received
50 | console.log(error.request);
51 | } else {
52 | // Something happened in setting up the request that triggered an Error
53 | console.log('Error', error.message);
54 | }
55 | console.log(error.options);
56 | }
57 | })();
58 | ```
59 |
60 | **Note:** Due to the HTTP request library being used each error contains an `options` property which are the options Got used to create a request - just to make debugging easier. Additionally, the errors may have `request` and `response` properties depending on which phase of the request failed. Read more about HTTP request library [Got](https://github.com/sindresorhus/got).
61 |
62 | ## Important Note
63 |
64 | The library is structured to match classes with their respective routes, **NOT** how they are sectioned in the Clickup API docs. For example adding a guest to a task is under the `Tasks` class instead of the `Guests` class as its route is via `task` and not `guest`. Due to this a request to add a guest to a task will look like so:
65 |
66 | ```js
67 | (async () => {
68 | try {
69 | // guest data
70 | const guestData = {
71 | permission_level: 'read',
72 | };
73 | // add guest to task
74 | const { body } = await clickup.tasks.addGuest('c04', 403, guestData);
75 | console.log(body);
76 | } catch (error) {
77 | if (error.response) {
78 | // The request was made and the server responded with a status code
79 | // that falls out of the range of 2xx
80 | console.log(error.response.body);
81 | console.log(error.response.statusCode);
82 | console.log(error.response.headers);
83 | } else if (error.request) {
84 | // The request was made but no response was received
85 | console.log(error.request);
86 | } else {
87 | // Something happened in setting up the request that triggered an Error
88 | console.log('Error', error.message);
89 | }
90 | console.log(error.options);
91 | }
92 | })();
93 | ```
94 |
95 | ## Documentation
96 |
97 | You can read the library documentation at the [clickup.js docs](https://clickup-js.netlify.app)
98 |
99 | ## Features
100 |
101 | The available features are:
102 |
103 | - `Authorization`
104 | - `Checklists`
105 | - `Comments`
106 | - `Folders`
107 | - `Goals`
108 | - `KeyResults`
109 | - `Lists`
110 | - `Spaces`
111 | - `Tasks`
112 | - `Teams`
113 | - `Views`
114 | - `Webhooks`
115 |
116 | ## Disclaimer
117 |
118 | The [clickup.js](https://github.com/ComfortablyCoding/clickup.js) package is **unofficial** and therefor not endorsed or affiliated with ClickUp or it's subsidiaries.
119 |
--------------------------------------------------------------------------------
/src/routes/Spaces.js:
--------------------------------------------------------------------------------
1 | class Spaces {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | /**
14 | * The main route for the collection
15 | * @type {String}
16 | */
17 | this.route = 'space';
18 | }
19 |
20 | /**
21 | * Get a space
22 | *
23 | * @param {Number} spaceId The space id
24 | */
25 | async get(spaceId) {
26 | return this._request.get({
27 | endpoint: `${this.route}/${spaceId}`,
28 | });
29 | }
30 |
31 | /**
32 | * Update a space
33 | *
34 | * @param {Number} spaceId The space id
35 | * @param {Object} data The space data
36 | */
37 | async update(spaceId, data) {
38 | return this._request.put({
39 | endpoint: `${this.route}/${spaceId}`,
40 | data,
41 | });
42 | }
43 |
44 | /**
45 | * Delete a space
46 | *
47 | * @param {Numnber} spaceId The space id
48 | */
49 | async delete(spaceId) {
50 | return this._request.delete({
51 | endpoint: `${this.route}/${spaceId}`,
52 | });
53 | }
54 |
55 | /**
56 | * Create a folder
57 | *
58 | * @param {Number} spaceId The space id
59 | * @param {Object} data The folder data
60 | */
61 | async createFolder(spaceId, data) {
62 | return this._request.post({
63 | endpoint: `${this.route}/${spaceId}/folder`,
64 | data,
65 | });
66 | }
67 |
68 | /**
69 | * Get all folders in a space
70 | *
71 | * @param {Number} spaceId The space id
72 | * @param {Boolean} [archived=false] If archived folders should be returned or not
73 | */
74 | async getFolders(spaceId, archived = false) {
75 | return this._request.get({
76 | endpoint: `${this.route}/${spaceId}/folder`,
77 | params: {
78 | archived,
79 | },
80 | });
81 | }
82 |
83 | /**
84 | * Create a folderless list
85 | *
86 | * @param {Number} spaceId The space id
87 | * @param {Object} data The folderless list data
88 | */
89 | async createFolderlessList(spaceId, data) {
90 | return this._request.post({
91 | endpoint: `${this.route}/${spaceId}/list`,
92 | data,
93 | });
94 | }
95 |
96 | /**
97 | * Get all folderless lists in a space
98 | *
99 | * @param {Number} spaceId The space id
100 | * @param {Boolean} [archived=false] If archived folderless lists should be returned or not
101 | */
102 | async getFolderlessLists(spaceId, archived = false) {
103 | return this._request.get({
104 | endpoint: `${this.route}/${spaceId}/list`,
105 | params: {
106 | archived,
107 | },
108 | });
109 | }
110 |
111 | /**
112 | * Get all tags in a space
113 | *
114 | * @param {Number} spaceId The space id
115 | */
116 | async getTags(spaceId) {
117 | return this._request.get({
118 | endpoint: `${this.route}/${spaceId}/tag`,
119 | });
120 | }
121 |
122 | /**
123 | * Create a space tag
124 | *
125 | * @param {Number} spaceId The space id
126 | * @param {Object} data The space tag data
127 | */
128 | async createTag(spaceId, data) {
129 | return this._request.post({
130 | endpoint: `${this.route}/${spaceId}/tag`,
131 | data,
132 | });
133 | }
134 |
135 | /**
136 | * Update a space tag
137 | *
138 | * @param {Number} spaceId The space id
139 | * @param {String} tagName The tag name
140 | */
141 | async updateTag(spaceId, tagName) {
142 | return this._request.put({
143 | endpoint: `${this.route}/${spaceId}/tag/${tagName}`,
144 | });
145 | }
146 |
147 | /**
148 | * Delete a space tag
149 | *
150 | * @param {Number} spaceId The space id
151 | * @param {String} tagName The tag name
152 | */
153 | async deleteTag(spaceId, tagName) {
154 | return this._request.delete({
155 | endpoint: `${this.route}/${spaceId}/tag/${tagName}`,
156 | });
157 | }
158 |
159 | /**
160 | * Create a view for a space
161 | *
162 | * @param {Number} spaceId The space id
163 | * @param {Object} data The view data
164 | */
165 | async createView(spaceId, data) {
166 | return this._request.post({
167 | endpoint: `${this.route}/${spaceId}/view`,
168 | data,
169 | });
170 | }
171 |
172 | /**
173 | * Get all views for a space
174 | *
175 | * @param {Number} spaceId The space id
176 | */
177 | async getViews(spaceId) {
178 | return this._request.get({
179 | endpoint: `${this.route}/${spaceId}/view`,
180 | });
181 | }
182 | }
183 |
184 | module.exports = Spaces;
185 |
--------------------------------------------------------------------------------
/src/structures/Request.js:
--------------------------------------------------------------------------------
1 | const { createRequestInstance } = require('../utils/createRequestInstance');
2 | const { buildSearchParams } = require('../utils/buildSearchParams');
3 |
4 | class Request {
5 | /**
6 | * Creates a client instance that connects to the Clickup API
7 | *
8 | * @constructor
9 | * @param {import('got/dist/source').ExtendOptions} requestOptions Options for the created request instance.
10 | */
11 | constructor(token, requestOptions) {
12 | /**
13 | * The access token
14 | */
15 | this._token = token;
16 | // create service instance
17 | /**
18 | * The request instance
19 | * @private
20 | */
21 | this._instance = createRequestInstance(token, requestOptions);
22 | }
23 |
24 | /**
25 | * Makes an HTTP GET request
26 | *
27 | * @param {Object} options Options to pass to the api call
28 | * @param {String} options.endpoint The endpoint to make a request to
29 | * @param {Object} options.params The parameters to add to the endpoint
30 | */
31 | async get({ endpoint, params }) {
32 | return this._request({ endpoint, method: 'GET', params });
33 | }
34 |
35 | /**
36 | * Makes an HTTP POST request
37 | *
38 | * @param {Object} options Options to pass to the api call
39 | * @param {String} options.endpoint The endpoint to make a request to
40 | * @param {Object} options.params The query parameters to add to the request
41 | * @param {Object} options.data The data to send in the body of the request
42 | * @param {Object} options.headers The headers to send along with the request
43 | */
44 | async post({ endpoint, params, data, headers }) {
45 | return this._request({ endpoint, method: 'POST', params, data, headers });
46 | }
47 |
48 | /**
49 | * Makes an HTTP PUT request
50 | *
51 | * @param {Object} options Options to pass to the api call
52 | * @param {String} options.endpoint The endpoint to make a request to
53 | * @param {Object} options.params The query parameters to add to the request
54 | * @param {Object} options.data The data to send in the body of the request
55 | */
56 | async put({ endpoint, params, data }) {
57 | return this._request({ endpoint, method: 'PUT', params, data });
58 | }
59 |
60 | /**
61 | * Makes an HTTP DELETE request
62 | *
63 | * @param {Object} options Options to pass to the api call
64 | * @param {String} options.endpoint The endpoint to make a request to
65 | * @param {Object} options.params The query parameters to add to the request
66 | */
67 | async delete({ endpoint, params }) {
68 | return this._request({ endpoint, method: 'DELETE', params });
69 | }
70 |
71 | /**
72 | * Makes an HTTP request
73 | *
74 | * @param {Object} options Options to pass to the api call
75 | * @param {String} options.endpoint The endpoint to make a request to
76 | * @param {String} options.method The request method to use in the request
77 | * @param {Object} options.params The query parameters to add to the request
78 | * @param {Object} options.data The data to send in the body of the request
79 | * @param {Object} options.headers The headers to send along with the request
80 | */
81 | async _request({ endpoint, method = 'GET', params, data = {}, headers }) {
82 | const options = {
83 | method,
84 | };
85 |
86 | if (params) {
87 | options.searchParams = buildSearchParams(params);
88 | }
89 |
90 | let contentType = this.getHeader('content-type');
91 |
92 | if (headers) {
93 | options.headers = headers;
94 | if (headers['content-type']) {
95 | contentType = headers['content-type'];
96 | }
97 | }
98 |
99 | if (method !== 'GET') {
100 | // json data must be sent via json property, all others are sent via body
101 | const dataType = contentType === 'application/json' ? 'json' : 'body';
102 | options[dataType] = data;
103 | }
104 |
105 | return this._instance(endpoint, options);
106 | }
107 |
108 | /**
109 | * Helper to obtain the instance headers
110 | */
111 | getHeaders() {
112 | const options = this.getOptions();
113 | return options.headers;
114 | }
115 |
116 | /**
117 | * Helper to obtain a specific header
118 | */
119 | getHeader(name) {
120 | const options = this.getOptions();
121 | return options.headers[name];
122 | }
123 |
124 | /**
125 | * Helper to obtain the access token
126 | */
127 | getToken() {
128 | return this._token;
129 | }
130 |
131 | /**
132 | * Helper to obtain the options
133 | */
134 | getOptions() {
135 | return this._instance.defaults.options;
136 | }
137 | }
138 |
139 | module.exports = { Request };
140 |
--------------------------------------------------------------------------------
/src/routes/Lists.js:
--------------------------------------------------------------------------------
1 | class Lists {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | /**
14 | * The main route for the collection
15 | * @type {String}
16 | */
17 | this.route = 'list';
18 | }
19 |
20 | /**
21 | * Get a list
22 | *
23 | * @param {Number} listId The list id
24 | */
25 | async get(listId) {
26 | return this._request.get({
27 | endpoint: `${this.route}/${listId}`,
28 | });
29 | }
30 |
31 | /**
32 | * Update a list
33 | *
34 | * @param {Number} listId The list id
35 | * @param {Object} data The list data
36 | */
37 | async update(listId, data) {
38 | return this._request.put({
39 | endpoint: `${this.route}/${listId}`,
40 | data,
41 | });
42 | }
43 |
44 | /**
45 | * Delete a list
46 | *
47 | * @param {Number} listId The list id
48 | */
49 | async delete(listId) {
50 | return this._request.delete({
51 | endpoint: `${this.route}/${listId}`,
52 | });
53 | }
54 |
55 | /**
56 | * Add a list comment
57 | *
58 | * @param {Number} listId The list id
59 | * @param {Object} data The comment data
60 | */
61 | async addComment(listId, data) {
62 | return this._request.post({
63 | endpoint: `${this.route}/${listId}/comment`,
64 | data,
65 | });
66 | }
67 |
68 | /**
69 | * Get all comments on a list
70 | *
71 | * @param {Number} listId The list id
72 | */
73 | async getComments(listId) {
74 | return this._request.get({
75 | endpoint: `${this.route}/${listId}/comment`,
76 | });
77 | }
78 |
79 | /**
80 | * Get all accessible custom fields of a list
81 | *
82 | * @param {Number} listId The list id
83 | */
84 | async getAccessibleCustomFields(listId) {
85 | return this._request.get({
86 | endpoint: `${this.route}/${listId}/field`,
87 | });
88 | }
89 |
90 | /**
91 | * Add a guest to a list
92 | *
93 | * @param {Number} listId The list id
94 | * @param {Number} guestId The guest id
95 | * @param {Object} data The guest data
96 | */
97 | async addGuest(listId, guestId, data) {
98 | return this._request.post({
99 | endpoint: `${this.route}/${listId}/guest/${guestId}`,
100 | data,
101 | });
102 | }
103 |
104 | /**
105 | * Remove a guest from a list
106 | *
107 | * @param {Number} listId The list id
108 | * @param {Number} guestId The guest id
109 | */
110 | async removeGuest(listId, guestId) {
111 | return this._request.delete({
112 | endpoint: `${this.route}/${listId}/guest/${guestId}`,
113 | });
114 | }
115 |
116 | /**
117 | * Get all members of a list
118 | *
119 | * @param {Number} listId The list id
120 | */
121 | async getMembers(listId) {
122 | return this._request.get({
123 | endpoint: `${this.route}/${listId}/member`,
124 | });
125 | }
126 |
127 | /**
128 | * Create a task
129 | *
130 | * @param {Number} listId The list id
131 | * @param {Object} data The task data
132 | */
133 | async createTask(listId, data) {
134 | return this._request.post({
135 | endpoint: `${this.route}/${listId}/task`,
136 | data,
137 | });
138 | }
139 |
140 | /**
141 | * Get all tasks in a list
142 | *
143 | * @param {Number} listId The list id
144 | * @param {Object} [options] The parameter options to pass in
145 | */
146 | async getTasks(listId, options = {}) {
147 | // eslint-disable-next-line no-param-reassign
148 | options.archived = options.archived || false;
149 | return this._request.get({
150 | endpoint: `${this.route}/${listId}/task`,
151 | params: options,
152 | });
153 | }
154 |
155 | /**
156 | * Create a task from a template
157 | *
158 | * @param {Number} listId The list id
159 | * @param {String} templateId The template id
160 | * @param {Object} data The task data
161 | */
162 | async createTaskFromTemplate(listId, templateId, data) {
163 | return this._request.post({
164 | endpoint: `${this.route}/${listId}/taskTemplate/${templateId}`,
165 | data,
166 | });
167 | }
168 |
169 | /**
170 | * Create a view for a list
171 | *
172 | * @param {Number} listId The list id
173 | * @param {Object} data The view data
174 | */
175 | async createView(listId, data) {
176 | return this._request.post({
177 | endpoint: `${this.route}/${listId}/view`,
178 | data,
179 | });
180 | }
181 |
182 | /**
183 | * Get all views for a list
184 | *
185 | * @param {Number} listId The list id
186 | */
187 | async getViews(listId) {
188 | return this._request.get({
189 | endpoint: `${this.route}/${listId}/view`,
190 | });
191 | }
192 |
193 | /**
194 | * Add task to a list
195 | *
196 | * @param {String} listId The list id
197 | * @param {String} taskId The task id
198 | */
199 | async addTaskToList(listId, taskId) {
200 | return this._request.post({
201 | endpoint: `${this.route}/${listId}/task/${taskId}`,
202 | });
203 | }
204 |
205 | /**
206 | * Remove a task from a list
207 | *
208 | * @param {Sting} listId The list id
209 | * @param {String} taskId The task id
210 | */
211 | async removeTaskFromList(listId, taskId) {
212 | return this._request.delete({
213 | endpoint: `${this.route}/${listId}/task/${taskId}`,
214 | });
215 | }
216 |
217 | /**
218 | * Get list members
219 | *
220 | * @param {String} listId The list id
221 | */
222 | async getListMembers(listId) {
223 | return this._request.get({
224 | endpoint: `${this.route}/${listId}/member`,
225 | });
226 | }
227 | }
228 |
229 | module.exports = Lists;
230 |
--------------------------------------------------------------------------------
/test/request.spec.js:
--------------------------------------------------------------------------------
1 | const { assert } = require('chai');
2 | const sinon = require('sinon');
3 | const { Clickup } = require('../src/index');
4 |
5 | describe('Testing the Request HTTP methods', () => {
6 | let clickup;
7 | let stub;
8 | before(() => {
9 | clickup = new Clickup('token');
10 | stub = sinon.stub(clickup._service, '_instance').resolves();
11 | });
12 |
13 | afterEach(() => {
14 | stub.resetHistory();
15 | });
16 |
17 | it('should use proper request method per HTTP method helper', async () => {
18 | await clickup._service.get({
19 | endpoint: 'tasks/task_id',
20 | });
21 | await clickup._service.post({
22 | endpoint: 'tasks/task_id',
23 | });
24 | await clickup._service.put({
25 | endpoint: 'tasks/task_id',
26 | });
27 | await clickup._service.delete({
28 | endpoint: 'tasks/task_id',
29 | });
30 |
31 | const [getArgs, postArgs, putArgs, deleteArgs] = stub.args;
32 |
33 | const [, getOptions] = getArgs;
34 | const [, postOptions] = postArgs;
35 | const [, putOptions] = putArgs;
36 | const [, deleteOptions] = deleteArgs;
37 |
38 | assert.strictEqual(getOptions.method, 'GET');
39 | assert.strictEqual(postOptions.method, 'POST');
40 | assert.strictEqual(putOptions.method, 'PUT');
41 | assert.strictEqual(deleteOptions.method, 'DELETE');
42 | });
43 |
44 | it('should preserve the endpoint in request', async () => {
45 | await clickup._service.get({
46 | endpoint: 'tasks/task_id',
47 | });
48 | await clickup._service.post({
49 | endpoint: 'tasks/task_id',
50 | });
51 | await clickup._service.put({
52 | endpoint: 'tasks/task_id',
53 | });
54 | await clickup._service.delete({
55 | endpoint: 'tasks/task_id',
56 | });
57 |
58 | const [getArgs, postArgs, putArgs, deleteArgs] = stub.args;
59 |
60 | const [getEndpoint] = getArgs;
61 | const [postEndpoint] = postArgs;
62 | const [putEndpoint] = putArgs;
63 | const [deleteEndpoint] = deleteArgs;
64 |
65 | assert.strictEqual(getEndpoint, 'tasks/task_id');
66 | assert.strictEqual(postEndpoint, 'tasks/task_id');
67 | assert.strictEqual(putEndpoint, 'tasks/task_id');
68 | assert.strictEqual(deleteEndpoint, 'tasks/task_id');
69 | });
70 |
71 | it('should construct the url params correctly', async () => {
72 | await clickup._service.get({
73 | endpoint: 'tasks/task_id',
74 | params: {
75 | team_id: 123,
76 | },
77 | });
78 | await clickup._service.post({
79 | endpoint: 'tasks/task_id',
80 | params: {
81 | team_id: 123,
82 | },
83 | });
84 | await clickup._service.put({
85 | endpoint: 'tasks/task_id',
86 | params: {
87 | team_id: 123,
88 | },
89 | });
90 | await clickup._service.delete({
91 | endpoint: 'tasks/task_id',
92 | params: {
93 | team_id: 123,
94 | },
95 | });
96 | const expectedParams = new URLSearchParams([['team_id', 123]]);
97 |
98 | const [getArgs, postArgs, putArgs, deleteArgs] = stub.args;
99 |
100 | const [, getOptions] = getArgs;
101 | const [, postOptions] = postArgs;
102 | const [, putOptions] = putArgs;
103 | const [, deleteOptions] = deleteArgs;
104 |
105 | assert.deepEqual(getOptions.searchParams, expectedParams);
106 | assert.deepEqual(postOptions.searchParams, expectedParams);
107 | assert.deepEqual(putOptions.searchParams, expectedParams);
108 | assert.deepEqual(deleteOptions.searchParams, expectedParams);
109 | });
110 |
111 | it('should use correct request content placement per content type', async () => {
112 | await clickup._service.post({
113 | endpoint: 'tasks/task_id',
114 | params: {
115 | team_id: 123,
116 | },
117 | data: {
118 | priority: 1,
119 | },
120 | });
121 | await clickup._service.post({
122 | endpoint: 'tasks/task_id',
123 | params: {
124 | team_id: 123,
125 | },
126 | data: {
127 | priority: 1,
128 | },
129 | headers: {
130 | 'content-type': 'multipart/form-data',
131 | },
132 | });
133 | await clickup._service.put({
134 | endpoint: 'tasks/task_id',
135 | params: {
136 | team_id: 123,
137 | },
138 | data: {
139 | priority: 1,
140 | },
141 | });
142 | await clickup._service.delete({
143 | endpoint: 'tasks/task_id',
144 | params: {
145 | team_id: 123,
146 | },
147 | data: {
148 | priority: 1,
149 | },
150 | });
151 | const [postArgs, postWithHeaderArgs, putArgs, deleteArgs] = stub.args;
152 |
153 | const [, postOptions] = postArgs;
154 | const [, postWithHeaderOptions] = postWithHeaderArgs;
155 | const [, putOptions] = putArgs;
156 | const [, deleteOptions] = deleteArgs;
157 |
158 | assert.isObject(postOptions.json);
159 | assert.isUndefined(postOptions.body);
160 | assert.isObject(postWithHeaderOptions.body);
161 | assert.isUndefined(postWithHeaderOptions.json);
162 | assert.isObject(putOptions.json);
163 | assert.isUndefined(putOptions.body);
164 | assert.isObject(deleteOptions.json);
165 | assert.isUndefined(deleteOptions.body);
166 | });
167 | });
168 |
169 | describe('Testing the Request helper methods', () => {
170 | let clickup;
171 | before(() => {
172 | clickup = new Clickup('token');
173 | });
174 |
175 | it('should get all current request options', () => {
176 | const currentOptions = clickup._service._instance.defaults.options;
177 | const options = clickup._service.getOptions();
178 |
179 | assert.deepEqual(options, currentOptions);
180 | });
181 |
182 | it('should get all current request headers', () => {
183 | const currentHeaders = clickup._service._instance.defaults.options.headers;
184 | const headers = clickup._service.getHeaders();
185 |
186 | assert.deepEqual(headers, currentHeaders);
187 | });
188 |
189 | it('should get the current authorization header', () => {
190 | const currentAuthorizationHeader = clickup._service._instance.defaults.options.headers.authorization;
191 | const authorizationHeader = clickup._service.getHeader('authorization');
192 |
193 | assert.deepEqual(authorizationHeader, currentAuthorizationHeader);
194 | });
195 |
196 | it('should get current token', () => {
197 | const currentToken = clickup._service._token;
198 | const token = clickup._service.getToken();
199 |
200 | assert.deepEqual(token, currentToken);
201 | });
202 | });
203 |
--------------------------------------------------------------------------------
/src/routes/Tasks.js:
--------------------------------------------------------------------------------
1 | const { createReadStream } = require('fs');
2 | const FormData = require('form-data');
3 |
4 | class Tasks {
5 | /**
6 | * @constructor
7 | * @param {Request} request A request instance
8 | */
9 | constructor(request) {
10 | /**
11 | * A request instance
12 | * @type {Request}
13 | * @private
14 | */
15 | this._request = request;
16 | /**
17 | * The main route for the collection
18 | * @type {String}
19 | */
20 | this.route = 'task';
21 | }
22 |
23 | /**
24 | * Get a task
25 | *
26 | * @param {String} taskId The task id
27 | * @param {Object} [options] The parameter options to pass in
28 | */
29 | async get(taskId, options) {
30 | return this._request.get({
31 | endpoint: `${this.route}/${taskId}`,
32 | params: options,
33 | });
34 | }
35 |
36 | /**
37 | * Update a task
38 | *
39 | * @param {String} taskId The task id
40 | * @param {Object} data The task data
41 | * @param {Object} [options] The parameter options to pass in
42 | */
43 | async update(taskId, data, options) {
44 | return this._request.put({
45 | endpoint: `${this.route}/${taskId}`,
46 | params: options,
47 | data,
48 | });
49 | }
50 |
51 | /**
52 | * Delete a task
53 | *
54 | * @param {String} taskId The task id
55 | * @param {Object} [options] The parameter options to pass in
56 | */
57 | async delete(taskId, options) {
58 | return this._request.delete({
59 | endpoint: `${this.route}/${taskId}`,
60 | params: options,
61 | });
62 | }
63 |
64 | /**
65 | * Add an attachment to a task
66 | *
67 | * @param {String} taskId The task id
68 | * @param {Object} fileSettings The file settings
69 | * @param {String} fileSettings.filePath The path to the file
70 | * @param {String} fileSettings.fileName The name of the attachment file along with its extension type. Example: 'notes.txt'
71 | * @param {Object} [options] The parameter options to pass in
72 | */
73 | async addAttachment(taskId, fileSettings, options) {
74 | // ensure fileSettings are provided
75 | if (fileSettings) {
76 | if (!fileSettings.filePath) {
77 | throw new Error('A file path must be provided');
78 | }
79 | if (!fileSettings.fileName) {
80 | throw new Error('A file name must be provided');
81 | }
82 | } else {
83 | throw new Error('File settings must be provided');
84 | }
85 |
86 | // building form-data
87 | const form = new FormData();
88 | form.append('filename', fileSettings.fileName);
89 | form.append('attachment', createReadStream(fileSettings.filePath));
90 |
91 | // setting headers
92 | const headers = form.getHeaders();
93 | headers.authorization = this._request.getToken();
94 |
95 | return this._request.post({
96 | endpoint: `${this.route}/${taskId}/attachment`,
97 | params: options,
98 | data: form,
99 | headers,
100 | });
101 | }
102 |
103 | /**
104 | * Add a comment to as task
105 | *
106 | * @param {String} taskId The task id
107 | * @param {Object} data The comment data
108 | * @param {Object} [options] The parameter options to pass in
109 | */
110 | async addComment(taskId, data, options) {
111 | return this._request.post({
112 | endpoint: `${this.route}/${taskId}/comment`,
113 | params: options,
114 | data,
115 | });
116 | }
117 |
118 | /**
119 | * Get all comments on a task
120 | *
121 | * @param {String} taskId The task id
122 | * @param {Object} [options] The parameter options to pass in
123 | */
124 | async getComments(taskId, options) {
125 | return this._request.get({
126 | endpoint: `${this.route}/${taskId}/comment`,
127 | params: options,
128 | });
129 | }
130 |
131 | /**
132 | * Create a checklist in a task
133 | *
134 | * @param {String} taskId The task id
135 | * @param {Object} data The checklist data
136 | * @param {Object} [options] The parameter options to pass in
137 | */
138 | async createChecklist(taskId, data, options) {
139 | return this._request.post({
140 | endpoint: `${this.route}/${taskId}/checklist`,
141 | params: options,
142 | data,
143 | });
144 | }
145 |
146 | /**
147 | * Add a custom field value for a task
148 | *
149 | * @param {String} taskId The task id
150 | * @param {String} fieldId The custom field id
151 | * @param {Object} data The custom field data
152 | * @param {Object} [options] The parameter options to pass in
153 | */
154 | async addCustomFieldValue(taskId, fieldId, data, options) {
155 | return this._request.post({
156 | endpoint: `${this.route}/${taskId}/field/${fieldId}`,
157 | params: options,
158 | data,
159 | });
160 | }
161 |
162 | /**
163 | * Delete a custom field value for a task
164 | *
165 | * @param {String} taskId The task id
166 | * @param {String} fieldId The custom field id
167 | * @param {Object} [options] The parameter options to pass in
168 | */
169 | async deleteCustomFieldValue(taskId, fieldId, options) {
170 | return this._request.delete({
171 | endpoint: `${this.route}/${taskId}/field/${fieldId}`,
172 | params: options,
173 | });
174 | }
175 |
176 | /**
177 | * Create a dependancy for a task
178 | *
179 | * @param {String} taskId The task id
180 | * @param {Object} data The dependency data
181 | * @param {Object} [options] The parameter options to pass in
182 | */
183 | async addDependency(taskId, data, options) {
184 | return this._request.post({
185 | endpoint: `${this.route}/${taskId}/dependency`,
186 | params: options,
187 | data,
188 | });
189 | }
190 |
191 | /**
192 | * Delete a dependancy for a task
193 | *
194 | * @param {String} taskId The task id
195 | * @param {Object} options The parameter options to pass in
196 | */
197 | async deleteDependency(taskId, options) {
198 | return this._request.delete({
199 | endpoint: `${this.route}/${taskId}/dependency`,
200 | params: options,
201 | });
202 | }
203 |
204 | /**
205 | * Add a task link
206 | *
207 | * @param {String} taskId The task id
208 | * @param {String} linksTo The id of the task to link to
209 | * @param {Object} [options] The parameter options to pass in
210 | */
211 | async addTaskLink(taskId, linksTo, options) {
212 | return this._request.post({
213 | endpoint: `${this.route}/${taskId}/link/${linksTo}`,
214 | params: options,
215 | });
216 | }
217 |
218 | /**
219 | * Delete a task link
220 | *
221 | * @param {String} taskId The task id
222 | * @param {String} linksTo The id of the task to link to
223 | * @param {String} [options] The parameter options to pass in
224 | */
225 | async deleteTaskLink(taskId, linksTo, options) {
226 | return this._request.delete({
227 | endpoint: `${this.route}/${taskId}/link/${linksTo}`,
228 | params: options,
229 | });
230 | }
231 |
232 | /**
233 | * Add a guest to a task
234 | *
235 | * @param {String} taskId The task id
236 | * @param {Number} guestId The guest id
237 | * @param {Object} data The guest data
238 | * @param {Object} [options] The parameter options to pass in
239 | */
240 | async addGuest(taskId, guestId, data, options) {
241 | return this._request.post({
242 | endpoint: `${this.route}/${taskId}/guest/${guestId}`,
243 | params: options,
244 | data,
245 | });
246 | }
247 |
248 | /**
249 | * Remove a guest from a task
250 | *
251 | * @param {String} taskId The task id
252 | * @param {Number} guestId The guest id
253 | * @param {Object} [options] The parameter options to pass in
254 | */
255 | async removeGuest(taskId, guestId, options) {
256 | return this._request.delete({
257 | endpoint: `${this.route}/${taskId}/guest/${guestId}`,
258 | params: options,
259 | });
260 | }
261 |
262 | /**
263 | * Get all members of a task
264 | *
265 | * @param {String} taskId The task id
266 | */
267 | async getMembers(taskId) {
268 | return this._request.get({
269 | endpoint: `${this.route}/${taskId}/member`,
270 | });
271 | }
272 |
273 | /**
274 | * Add a tag to a task
275 | *
276 | * @param {String} taskId The task id
277 | * @param {String} tagName The tag name
278 | * @param {Object} [options] The parameter options to pass in
279 | */
280 | async addTag(taskId, tagName, options) {
281 | return this._request.post({
282 | endpoint: `${this.route}/${taskId}/tag/${tagName}`,
283 | params: options,
284 | });
285 | }
286 |
287 | /**
288 | * Remove a tag from a task
289 | *
290 | * @param {String} taskId The task id
291 | * @param {String} tagName The tag name
292 | * @param {Object} [options] The parameter options to pass in
293 | */
294 | async removeTag(taskId, tagName, options) {
295 | return this._request.delete({
296 | endpoint: `${this.route}/${taskId}/tag/${tagName}`,
297 | params: options,
298 | });
299 | }
300 |
301 | /**
302 | * Track time for a task (Time Tracking Legacy API)
303 | *
304 | * @param {String} taskId The task id
305 | * @param {Object} data The time tracking data
306 | * @param {Object} [options] The parameter options to pass in
307 | */
308 | async trackTime(taskId, data, options) {
309 | return this._request.post({
310 | endpoint: `${this.route}/${taskId}/time`,
311 | params: options,
312 | data,
313 | });
314 | }
315 |
316 | /**
317 | * Get tracked time for a task (Time Tracking Legacy API)
318 | *
319 | * @param {String} taskId The task id
320 | * @param {Object} [options] The parameter options to pass in
321 | */
322 | async getTrackedTime(taskId, options) {
323 | return this._request.get({
324 | endpoint: `${this.route}/${taskId}/time`,
325 | params: options,
326 | });
327 | }
328 |
329 | /**
330 | * Edit tracked time for a task (Time Tracking Legacy API)
331 | *
332 | * @param {String} taskId The task id
333 | * @param {String} intervalId The interval id
334 | * @param {Object} data The time tracking data
335 | * @param {Object} [options] The parameter options to pass in
336 | */
337 | async editTrackedTime(taskId, intervalId, data, options) {
338 | return this._request.put({
339 | endpoint: `${this.route}/${taskId}/time/${intervalId}`,
340 | params: options,
341 | data,
342 | });
343 | }
344 |
345 | /**
346 | * Delete tracked time for a task
347 | *
348 | * @param {String} taskId The task id
349 | * @param {String} intervalId The interval id
350 | * @param {Object} [options] The parameter options to pass in
351 | */
352 | async deleteTrackedTime(taskId, intervalId, options) {
353 | return this._request.delete({
354 | endpoint: `${this.route}/${taskId}/time/${intervalId}`,
355 | params: options,
356 | });
357 | }
358 |
359 | /**
360 | * Get tasks time in status
361 | *
362 | * @param {String} taskId The task id
363 | * @param {Object} options The parameter options to pass in
364 | */
365 | async getTimeInStatus(taskId, options) {
366 | return this._request.get({
367 | endpoint: `${this.route}/${taskId}/time_in_status`,
368 | params: options,
369 | });
370 | }
371 |
372 | /**
373 | * Get bulk tasks time in status
374 | *
375 | * @param {Object} options The parameter options to pass in
376 | */
377 | async getBulkTimeInStatus(options) {
378 | return this._request.get({
379 | endpoint: `${this.route}/bulk_time_in_status/task_ids`,
380 | params: options,
381 | });
382 | }
383 | }
384 |
385 | module.exports = Tasks;
386 |
--------------------------------------------------------------------------------
/src/routes/Teams.js:
--------------------------------------------------------------------------------
1 | class Teams {
2 | /**
3 | * @constructor
4 | * @param {Request} request A request instance
5 | */
6 | constructor(request) {
7 | /**
8 | * A request instance
9 | * @type {Request}
10 | * @private
11 | */
12 | this._request = request;
13 | /**
14 | * The main route for the collection
15 | * @type {String}
16 | */
17 | this.route = 'team';
18 | }
19 |
20 | /**
21 | * Get teams
22 | */
23 | async get() {
24 | return this._request.get({
25 | endpoint: `${this.route}`,
26 | });
27 | }
28 |
29 | /**
30 | * Create a goal
31 | *
32 | * @param {Number} teamId The team id
33 | * @param {Object} data Goal data
34 | */
35 | async createGoal(teamId, data) {
36 | return this._request.post({
37 | endpoint: `${this.route}/${teamId}/goal`,
38 | data,
39 | });
40 | }
41 |
42 | /**
43 | * Get all goals for a team
44 | *
45 | * @param {Number} teamId The team id
46 | */
47 | async getGoals(teamId) {
48 | return this._request.get({
49 | endpoint: `${this.route}/${teamId}/goal`,
50 | });
51 | }
52 |
53 | /**
54 | * Invite a guest to a workspace/team
55 | *
56 | * @param {Number} teamId The team id
57 | * @param {Object} data The guest data
58 | */
59 | async inviteGuest(teamId, data) {
60 | return this._request.post({
61 | endpoint: `${this.route}/${teamId}/guest`,
62 | data,
63 | });
64 | }
65 |
66 | /**
67 | * Get a guest in a workspace/team
68 | *
69 | * @param {Number} teamId The team id
70 | * @param {Number} guestId The guest id
71 | */
72 | async getGuest(teamId, guestId) {
73 | return this._request.get({
74 | endpoint: `${this.route}/${teamId}/guest/${guestId}`,
75 | });
76 | }
77 |
78 | /**
79 | *
80 | * @param {Number} teamId The team id
81 | * @param {Number} guestId The guest id
82 | * @param {Object} data The guest data
83 | */
84 | async editGuest(teamId, guestId, data) {
85 | return this._request.put({
86 | endpoint: `${this.route}/${teamId}/guest/${guestId}`,
87 | data,
88 | });
89 | }
90 |
91 | /**
92 | * Remove a guest from a workspace/team
93 | *
94 | * @param {Number} teamId The team id
95 | * @param {Number} guestId The guest id
96 | */
97 | async removeGuest(teamId, guestId) {
98 | return this._request.delete({
99 | endpoint: `${this.route}/${teamId}/guest/${guestId}`,
100 | });
101 | }
102 |
103 | /**
104 | * Returns all resources you have access to where you don't have access to its parent.
105 | *
106 | * @param {Number} teamId
107 | */
108 | async sharedHierarchy(teamId) {
109 | return this._request.get({
110 | endpoint: `${this.route}/${teamId}/shared`,
111 | });
112 | }
113 |
114 | /**
115 | * Create a space for a team
116 | *
117 | * @param {Number} teamId The team id
118 | * @param {Object} data The space data
119 | */
120 | async createSpace(teamId, data) {
121 | return this._request.post({
122 | endpoint: `${this.route}/${teamId}/space`,
123 | data,
124 | });
125 | }
126 |
127 | /**
128 | * Get spaces for a team
129 | *
130 | * @param {Number} teamId The team id
131 | * @param {Boolean} [archived=false] If archived spaces should be returned or not
132 | */
133 | async getSpaces(teamId, archived = false) {
134 | return this._request.get({
135 | endpoint: `${this.route}/${teamId}/space`,
136 | params: {
137 | archived,
138 | },
139 | });
140 | }
141 |
142 | /**
143 | * Get filtered tasks for a team
144 | *
145 | * @param {Number} teamId The team id
146 | * @param {Object} [options] The parameter options to pass in
147 | */
148 | async getFilteredTasks(teamId, options = {}) {
149 | // eslint-disable-next-line no-param-reassign
150 | options.page = options.page || 0;
151 | return this._request.get({
152 | endpoint: `${this.route}/${teamId}/task`,
153 | params: options,
154 | });
155 | }
156 |
157 | /**
158 | * Get task templates for a team
159 | *
160 | * @param {Number} teamId The team id
161 | * @param {Integer} [page=0] The page to get
162 | */
163 | async getTaskTemplates(teamId, page = 0) {
164 | return this._request.get({
165 | endpoint: `${this.route}/${teamId}/taskTemplate`,
166 | params: {
167 | page,
168 | },
169 | });
170 | }
171 |
172 | /**
173 | * Get a user for a team. Only available to enterprise teams
174 | *
175 | * @param {Number} teamId The team id
176 | * @param {Number} userId The user id
177 | */
178 | async getUser(teamId, userId) {
179 | return this._request.get({
180 | endpoint: `${this.route}/${teamId}/user/${userId}`,
181 | });
182 | }
183 |
184 | /**
185 | * Invite a user to a workspace/team. Only available to enterprise teams
186 | *
187 | * @param {Number} teamId The team id
188 | * @param {Object} data The user data
189 | */
190 | async inviteUser(teamId, data) {
191 | return this._request.post({
192 | endpoint: `${this.route}/${teamId}/user`,
193 | data,
194 | });
195 | }
196 |
197 | /**
198 | * Edit a user for a workspace/team. Only available to enterprise teams
199 | *
200 | * @param {Number} teamId The team id
201 | * @param {Number} userId The user id
202 | * @param {Object} data The user data
203 | */
204 | async editUser(teamId, userId, data) {
205 | return this._request.put({
206 | endpoint: `${this.route}/${teamId}/user/${userId}`,
207 | data,
208 | });
209 | }
210 |
211 | /**
212 | * Remove a user from a workspace/team. Only available to enterprise teams
213 | *
214 | * @param {Number} teamId The team id
215 | * @param {Number} userId The team id
216 | */
217 | async removeUser(teamId, userId) {
218 | return this._request.delete({
219 | endpoint: `${this.route}/${teamId}/user/${userId}`,
220 | });
221 | }
222 |
223 | /**
224 | * Create a team view for a team
225 | *
226 | * @param {Number} teamId The team id
227 | * @param {Object} data The view data
228 | */
229 | async createView(teamId, data) {
230 | return this._request.post({
231 | endpoint: `${this.route}/${teamId}/view`,
232 | data,
233 | });
234 | }
235 |
236 | /**
237 | * Get all team views for a team
238 | *
239 | * @param {Number} teamId The team id
240 | */
241 | async getViews(teamId) {
242 | return this._request.get({
243 | endpoint: `${this.route}/${teamId}/view`,
244 | });
245 | }
246 |
247 | /**
248 | * Create a webhook
249 | *
250 | * @param {Number} teamId The team id
251 | * @param {Object} data The webhook data
252 | */
253 | async createWebhook(teamId, data) {
254 | return this._request.post({
255 | endpoint: `${this.route}/${teamId}/webhook`,
256 | data,
257 | });
258 | }
259 |
260 | /**
261 | * Get all webhooks
262 | *
263 | * @param {Number} teamId the team id
264 | */
265 | async getWebhooks(teamId) {
266 | return this._request.get({
267 | endpoint: `${this.route}/${teamId}/webhook`,
268 | });
269 | }
270 |
271 | /**
272 | * Get time entries within a data range
273 | *
274 | * @param {Number} teamId The team id
275 | * @param {Object} [options] The parameter options to pass in
276 | */
277 | async getTimeEntries(teamId, options) {
278 | return this._request.get({
279 | endpoint: `${this.route}/${teamId}/time_entries`,
280 | params: options,
281 | });
282 | }
283 |
284 | /**
285 | * Get a single time entry
286 | *
287 | * @param {Number} teamId The team id
288 | * @param {String} timerId The timer id
289 | * @param {Object} [options] The parameter options to pass in
290 | */
291 | async getSingleTimeEntry(teamId, timerId, options) {
292 | return this._request.get({
293 | endpoint: `${this.route}/${teamId}/time_entries/${timerId}`,
294 | params: options,
295 | });
296 | }
297 |
298 | /**
299 | * Get running time entry
300 | *
301 | * @param {Number} teamId The team id
302 | * @param {Object} [options] The parameter options to pass in
303 | */
304 | async getRunningTimeEntry(teamId, options) {
305 | return this._request.get({
306 | endpoint: `${this.route}/${teamId}/time_entries/current`,
307 | params: options,
308 | });
309 | }
310 |
311 | /**
312 | * Create a time entry
313 | *
314 | * @param {Number} teamId The team id
315 | * @param {Object} data The time entry data
316 | * @param {Object} [options] The parameter options to pass in
317 | */
318 | async createTimeEntry(teamId, data, options) {
319 | return this._request.post({
320 | endpoint: `${this.route}/${teamId}/time_entries`,
321 | params: options,
322 | data,
323 | });
324 | }
325 |
326 | /**
327 | * Remove tags from time entries
328 | *
329 | * @param {Number} teamId The team id
330 | * @param {Object} data The time entries data
331 | */
332 | async removeTagsFromTimeEntries(teamId, data) {
333 | return this._request.delete({
334 | endpoint: `${this.route}/${teamId}/time_entries/tags`,
335 | data,
336 | });
337 | }
338 |
339 | /**
340 | * Get all tags from time entries
341 | *
342 | * @param {Number} teamId The team id
343 | */
344 | async getAllTagsFromTimeEntries(teamId) {
345 | return this._request.get({
346 | endpoint: `${this.route}/${teamId}/time_entries/tags`,
347 | });
348 | }
349 |
350 | /**
351 | * Add tags from time entries
352 | *
353 | * @param {Number} teamId The team id
354 | * @param {Object} data The time entries and tag data
355 | */
356 | async addTagsFromTimeEntries(teamId, data) {
357 | return this._request.post({
358 | endpoint: `${this.route}/${teamId}/time_entries/tags`,
359 | data,
360 | });
361 | }
362 |
363 | /**
364 | * Change tag names from time entries
365 | *
366 | * @param {Number} teamId The team id
367 | * @param {Object} data The tag data
368 | */
369 | async changeTagsFromTimeEntries(teamId, data) {
370 | return this._request.put({
371 | endpoint: `${this.route}/${teamId}/time_entries/tags`,
372 | data,
373 | });
374 | }
375 |
376 | /**
377 | * Start a time entry
378 | *
379 | * @param {Number} teamId The team id
380 | * @param {Number} timerId The timer id
381 | * @param {Object} data The time entry data
382 | * @param {Object} [options] The parameter options to pass in
383 | */
384 | async startTimeEntry(teamId, timerId, data, options) {
385 | return this._request.post({
386 | endpoint: `${this.route}/${teamId}/time_entries/start/${timerId}`,
387 | params: options,
388 | data,
389 | });
390 | }
391 |
392 | /**
393 | * Stop a time entry
394 | *
395 | * @param {Number} teamId The team id
396 | */
397 | async stopTimeEntry(teamId) {
398 | return this._request.post({
399 | endpoint: `${this.route}/${teamId}/time_entries/stop`,
400 | });
401 | }
402 |
403 | /**
404 | * Delete a time entry
405 | *
406 | * @param {Number} teamId The team id
407 | * @param {Number} timerId The timer id
408 | */
409 | async deleteTimeEntry(teamId, timerId) {
410 | return this._request.delete({
411 | endpoint: `${this.route}/${teamId}/time_entries/${timerId}`,
412 | });
413 | }
414 |
415 | /**
416 | * Update a time entry
417 | *
418 | * @param {Number} teamId The team id
419 | * @param {Number} timerId The timer id
420 | * @param {Object} data The time entry data
421 | * @param {Object} [options] The parameter options to pass in
422 | */
423 | async updateTimeEntry(teamId, timerId, data, options) {
424 | return this._request.put({
425 | endpoint: `${this.route}/${teamId}/time_entries/${timerId}`,
426 | params: options,
427 | data,
428 | });
429 | }
430 | }
431 | module.exports = Teams;
432 |
--------------------------------------------------------------------------------