From 34b9175c6af030789f5da87010db0426a84800f0 Mon Sep 17 00:00:00 2001 From: 0w0miki Date: Thu, 8 Feb 2024 23:37:39 +0800 Subject: [PATCH] Update jira-time-tracking extension (#10355) Co-authored-by: xzong --- extensions/jira-time-tracking/.gitignore | 13 ++ extensions/jira-time-tracking/CHANGELOG.md | 3 + extensions/jira-time-tracking/README.md | 10 + .../jira-time-tracking/package-lock.json | 193 ++++++++---------- extensions/jira-time-tracking/package.json | 7 +- .../jira-time-tracking/src/controllers.ts | 34 ++- extensions/jira-time-tracking/src/index.tsx | 122 +++++++---- extensions/jira-time-tracking/src/types.ts | 11 + .../jira-time-tracking/src/validators.ts | 6 +- 9 files changed, 238 insertions(+), 161 deletions(-) create mode 100644 extensions/jira-time-tracking/.gitignore create mode 100644 extensions/jira-time-tracking/README.md diff --git a/extensions/jira-time-tracking/.gitignore b/extensions/jira-time-tracking/.gitignore new file mode 100644 index 00000000000..9ff34e3e9f3 --- /dev/null +++ b/extensions/jira-time-tracking/.gitignore @@ -0,0 +1,13 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# Raycast specific files +raycast-env.d.ts +.raycast-swift-build +.swiftpm +compiled_raycast_swift + +# misc +.DS_Store diff --git a/extensions/jira-time-tracking/CHANGELOG.md b/extensions/jira-time-tracking/CHANGELOG.md index 15d4088e467..528a9b8358f 100644 --- a/extensions/jira-time-tracking/CHANGELOG.md +++ b/extensions/jira-time-tracking/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## [Pagination support] - 2024-01-26 +- Support load all projects/issues with Jira pagination API. + ## [Initial release] - 2022-04-26 - Released first version of Jira Time Tracking diff --git a/extensions/jira-time-tracking/README.md b/extensions/jira-time-tracking/README.md new file mode 100644 index 00000000000..957a6723202 --- /dev/null +++ b/extensions/jira-time-tracking/README.md @@ -0,0 +1,10 @@ +# Jira Time Tracking + +This extension helps you to quickly add time log to specific issue. + +## Setup + +To use this extension, you need to fill the following preference: +- **Jira Domain**: The domain of your Jira instance, e.g. `company.atlassian.net`. +- **Jira Username**: Jira user name. +- **API Token**: An API token created as described in [Manage API tokens for your Atlassian account](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/). \ No newline at end of file diff --git a/extensions/jira-time-tracking/package-lock.json b/extensions/jira-time-tracking/package-lock.json index 451fbd32f13..999e365c312 100644 --- a/extensions/jira-time-tracking/package-lock.json +++ b/extensions/jira-time-tracking/package-lock.json @@ -7,13 +7,13 @@ "name": "jira-time-tracking", "license": "MIT", "dependencies": { - "@raycast/api": "^1.26.3", + "@raycast/api": "^1.66.1", "@types/node-fetch": "^3.0.3", "node-fetch": "^3.2.0" }, "devDependencies": { "@types/node": "~16.10.0", - "@types/react": "^17.0.28", + "@types/react": "^18.2.21", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^7.32.0", @@ -209,15 +209,41 @@ } }, "node_modules/@raycast/api": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.27.0.tgz", - "integrity": "sha512-WaoKkgxkdAkUbvpQFjdmAG40VppIhVnSxZ2q9mXi/V3alypo8c5qMWrAfRbw1pf7EQN6GOwEvEoPvzcu41Nfww==", + "version": "1.66.1", + "resolved": "https://registry.npmmirror.com/@raycast/api/-/api-1.66.1.tgz", + "integrity": "sha512-lFM5dxEeIumaACBONFbG37wBTxxcVRksqx23kWb4/S69aj3rXFB2Vun3pTGXaPU1lD4PFJ0I0qMskqM5pxuPXA==", + "hasInstallScript": true, + "dependencies": { + "@types/node": "^20.8.10", + "@types/react": "^18.2.27", + "react": "18.2.0" + }, "bin": { "ray": "bin/ray" }, "peerDependencies": { - "react": "17.0.2", - "react-reconciler": "0.26.2" + "@types/node": "20.8.10", + "@types/react": "18.2.27", + "react-devtools": "4.28.4" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "react-devtools": { + "optional": true + } + } + }, + "node_modules/@raycast/api/node_modules/@types/node": { + "version": "20.11.5", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.11.5.tgz", + "integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==", + "dependencies": { + "undici-types": "~5.26.4" } }, "node_modules/@types/json-schema": { @@ -244,14 +270,12 @@ "node_modules/@types/prop-types": { "version": "15.7.4", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "dev": true + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, "node_modules/@types/react": { - "version": "17.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", - "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", - "dev": true, + "version": "18.2.48", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.2.48.tgz", + "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -261,8 +285,7 @@ "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.10.0", @@ -666,8 +689,7 @@ "node_modules/csstype": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", - "dev": true + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" }, "node_modules/data-uri-to-buffer": { "version": "4.0.0", @@ -1342,9 +1364,8 @@ }, "node_modules/loose-envify": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -1445,15 +1466,6 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1579,35 +1591,16 @@ ] }, "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "peer": true, + "version": "18.2.0", + "resolved": "https://registry.npmmirror.com/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/react-reconciler": { - "version": "0.26.2", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", - "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "react": "^17.0.2" - } - }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -1686,16 +1679,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -1928,6 +1911,11 @@ "node": ">=4.2.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2142,10 +2130,24 @@ } }, "@raycast/api": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.27.0.tgz", - "integrity": "sha512-WaoKkgxkdAkUbvpQFjdmAG40VppIhVnSxZ2q9mXi/V3alypo8c5qMWrAfRbw1pf7EQN6GOwEvEoPvzcu41Nfww==", - "requires": {} + "version": "1.66.1", + "resolved": "https://registry.npmmirror.com/@raycast/api/-/api-1.66.1.tgz", + "integrity": "sha512-lFM5dxEeIumaACBONFbG37wBTxxcVRksqx23kWb4/S69aj3rXFB2Vun3pTGXaPU1lD4PFJ0I0qMskqM5pxuPXA==", + "requires": { + "@types/node": "^20.8.10", + "@types/react": "^18.2.27", + "react": "18.2.0" + }, + "dependencies": { + "@types/node": { + "version": "20.11.5", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.11.5.tgz", + "integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==", + "requires": { + "undici-types": "~5.26.4" + } + } + } }, "@types/json-schema": { "version": "7.0.9", @@ -2170,14 +2172,12 @@ "@types/prop-types": { "version": "15.7.4", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "dev": true + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, "@types/react": { - "version": "17.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", - "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", - "dev": true, + "version": "18.2.48", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.2.48.tgz", + "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2187,8 +2187,7 @@ "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "@typescript-eslint/eslint-plugin": { "version": "5.10.0", @@ -2447,8 +2446,7 @@ "csstype": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", - "dev": true + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" }, "data-uri-to-buffer": { "version": "4.0.0", @@ -2959,9 +2957,8 @@ }, "loose-envify": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -3027,12 +3024,6 @@ "formdata-polyfill": "^4.0.10" } }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "peer": true - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3114,24 +3105,11 @@ "dev": true }, "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "react-reconciler": { - "version": "0.26.2", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", - "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", - "peer": true, + "version": "18.2.0", + "resolved": "https://registry.npmmirror.com/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "loose-envify": "^1.1.0" } }, "regexpp": { @@ -3176,16 +3154,6 @@ "queue-microtask": "^1.2.2" } }, - "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -3352,6 +3320,11 @@ "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/extensions/jira-time-tracking/package.json b/extensions/jira-time-tracking/package.json index e87d908e586..a1989cb602f 100644 --- a/extensions/jira-time-tracking/package.json +++ b/extensions/jira-time-tracking/package.json @@ -5,6 +5,9 @@ "description": "Log time against Jira issues", "icon": "command-icon.png", "author": "niallpaterson", + "contributors": [ + "0w0miki" + ], "license": "MIT", "commands": [ { @@ -41,13 +44,13 @@ } ], "dependencies": { - "@raycast/api": "^1.26.3", + "@raycast/api": "^1.66.1", "@types/node-fetch": "^3.0.3", "node-fetch": "^3.2.0" }, "devDependencies": { "@types/node": "~16.10.0", - "@types/react": "^17.0.28", + "@types/react": "^18.2.21", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^7.32.0", diff --git a/extensions/jira-time-tracking/src/controllers.ts b/extensions/jira-time-tracking/src/controllers.ts index 43a24e87f1d..bae7bfda6d2 100644 --- a/extensions/jira-time-tracking/src/controllers.ts +++ b/extensions/jira-time-tracking/src/controllers.ts @@ -1,18 +1,36 @@ import { parseDate } from "./utils"; import { jiraRequest } from "./requests"; -import { issuesValidator, projectsValidator } from "./validators"; +import { issuesValidator, paginationValidator, projectsValidator } from "./validators"; -export const getProjects = async () => { - const response = await jiraRequest(`/rest/api/3/project/search`); - return projectsValidator(response) ? response.values : []; +export const getProjects = async (begin: number) => { + const response = await jiraRequest(`/rest/api/3/project/search?maxResults=500&startAt=${begin}`); + return { + total: handlePaginationResp(response), + data: handleProjectResp(response), + }; }; -export const getIssues = async (projectId?: string) => { - const endpoint = `/rest/api/3/search?fields=summary,parent,project&maxResults=500&startAt=0&jql=${ - projectId ? "project=" + projectId : "" +export const getIssues = async (begin: number, projectId?: string) => { + const endpoint = `/rest/api/3/search?fields=summary,parent,project&maxResults=500&startAt=${begin}&jql=${ + projectId ? `project=${projectId}` : "" }`; const response = await jiraRequest(endpoint); - return issuesValidator(response) ? response.issues : []; + return { + total: handlePaginationResp(response), + data: handleIssueResp(response), + }; +}; + +const handlePaginationResp = (resp: unknown) => { + return paginationValidator(resp) ? resp.total : 0; +}; + +const handleProjectResp = (resp: unknown) => { + return projectsValidator(resp) ? resp.values : []; +}; + +const handleIssueResp = (resp: unknown) => { + return issuesValidator(resp) ? resp.issues : []; }; export const postTimeLog = async (timeSpentSeconds: number, issueId: string, description: string, startedAt: Date) => { diff --git a/extensions/jira-time-tracking/src/index.tsx b/extensions/jira-time-tracking/src/index.tsx index 95d06bcb30e..a531d99e7ef 100644 --- a/extensions/jira-time-tracking/src/index.tsx +++ b/extensions/jira-time-tracking/src/index.tsx @@ -1,12 +1,12 @@ -import { useState, useEffect } from "react"; -import { Form, Detail, ActionPanel, SubmitFormAction, showToast, ToastStyle } from "@raycast/api"; +import { useState, useEffect, useRef } from "react"; +import { Form, Detail, ActionPanel, Action, showToast, Toast } from "@raycast/api"; import { getIssues, getProjects, postTimeLog } from "./controllers"; import { toSeconds, createTimeLogSuccessMessage } from "./utils"; import { Project, Issue } from "./types"; export default function Command() { const [issues, setIssues] = useState([]); - const [projects, setProjects] = useState(); + const [projects, setProjects] = useState([]); const [selectedIssue, setSelectedIssue] = useState(); const [hours, setHours] = useState("0"); const [minutes, setMinutes] = useState("0"); @@ -15,16 +15,20 @@ export default function Command() { const [selectedProject, setSelectedProject] = useState(); const [startedAt, setStartedAt] = useState(new Date()); const [loading, setLoading] = useState(true); + const [issueCache, setIssueCache] = useState(new Map()); + + const pageGot = useRef(0); + const pageTotal = useRef(1); async function handleSubmit() { const totalTimeWorked = toSeconds(Number(seconds), Number(minutes), Number(hours)); if (!selectedIssue) { - showToast(ToastStyle.Failure, "Error logging time: issue not found"); + showToast(Toast.Style.Failure, "Error logging time: issue not found"); return; } if (!totalTimeWorked) { - showToast(ToastStyle.Failure, "Error logging time: no time entered."); + showToast(Toast.Style.Failure, "Error logging time: no time entered."); return; } @@ -33,11 +37,11 @@ export default function Command() { try { await postTimeLog(totalTimeWorked, selectedIssue.key, description, startedAt); const successMessage = createTimeLogSuccessMessage(selectedIssue.key, hours, minutes, seconds); - showToast(ToastStyle.Success, successMessage); + showToast(Toast.Style.Success, successMessage); cleanUp(); } catch (e) { const message = e instanceof Error ? e.message : "Error Logging Time"; - showToast(ToastStyle.Failure, message); + showToast(Toast.Style.Failure, message); } finally { setLoading(false); } @@ -45,40 +49,73 @@ export default function Command() { // fetch projects on mount useEffect(() => { - const fetchProjects = async () => { - try { - const projects = await getProjects(); - if (projects.length) { - setProjects(projects); - } - } catch (e) { - const message = e instanceof Error ? e.message : "Error Logging Time"; - showToast(ToastStyle.Failure, message); - } finally { - setLoading(false); + if (!selectedProject) { + if (pageGot.current < pageTotal.current) { + const fetchProjects = async () => { + console.debug("fetch project"); + try { + const result = await getProjects(pageGot.current); + pageTotal.current = result.total; + pageGot.current = pageGot.current + result.data.length; + if (result.data.length > 0) { + setProjects([...projects, ...result.data]); + } + showToast(Toast.Style.Animated, `Loading projects ${pageGot.current}/${pageTotal.current}`); + } catch (e) { + const message = e instanceof Error ? e.message : "Error Logging Time"; + showToast(Toast.Style.Failure, message); + } finally { + setLoading(false); + } + }; + fetchProjects(); + } else { + showToast(Toast.Style.Success, `Project loaded ${pageGot.current}/${pageTotal.current}`); } - }; - - fetchProjects(); - }, []); + } + }, [projects]); // fetch issues after project is selected useEffect(() => { if (selectedProject) { - const fetchIssues = async () => { - const issues = await getIssues(selectedProject); - if (issues) { - setIssues(issues); - setSelectedIssue(issues.at(-1)); - } - }; - fetchIssues(); + if (pageGot.current < pageTotal.current) { + const fetchIssues = async (project: string) => { + const result = await getIssues(pageGot.current, project); + const oldIssues = issueCache.get(project) ?? []; + setIssueCache((prev) => new Map(prev).set(project, [...oldIssues, ...result.data])); + pageTotal.current = result.total; + showToast(Toast.Style.Animated, `Loading issues ${pageGot.current}/${pageTotal.current}`); + }; + fetchIssues(selectedProject); + } else { + showToast(Toast.Style.Success, `Issue loaded ${pageGot.current}/${pageTotal.current}`); + } } + }, [issues]); + + const resetIssue = (resetLength: boolean) => { + const list = issueCache.get(selectedProject) ?? []; + setIssues([...list]); + setSelectedIssue(list[0]); + pageGot.current = list.length; + if (resetLength) { + pageTotal.current = list.length + 1; + } + }; + + useEffect(() => { + resetIssue(true); }, [selectedProject]); + useEffect(() => { + resetIssue(false); + }, [issueCache]); + const handleSelectIssue = (key: string) => { const issue = issues.find((issue) => issue.key === key); - if (issue) setSelectedIssue(issue); + if (issue) { + setSelectedIssue(issue); + } }; const cleanUp = () => { @@ -113,41 +150,46 @@ Please check your permissions, jira account or credentials and try again. navigationTitle="Log Time" actions={ - + } > - {projects?.map((item) => ( - - ))} + {projects?.map((item) => )} {issues.map((item) => ( - + ))} - + { + date && setStartedAt(date); + }} + /> {Array(25) .fill(null) .map((_, i) => ( - + ))} {Array(60) .fill(null) .map((_, i) => ( - + ))} {Array(60) .fill(null) .map((_, i) => ( - + ))} { if (typeof body === "object" && body !== null && "values" in body) { @@ -16,5 +16,9 @@ export const issuesValidator = (body: unknown): body is IssueBody => { return false; }; +export const paginationValidator = (body: unknown): body is PaginationBody => { + return typeof body === "object" && body !== null && "total" in body && "startAt" in body && "maxResults" in body; +}; + export const isJiraErrorResponseBody = (body: unknown): body is JiraErrorResponseBody => typeof body === "object" && body !== null && ("message" in body || "messages" in body);