From 7b39edf5170f68996aedf9f178f40312196f6739 Mon Sep 17 00:00:00 2001 From: Swikriti Tripathi <41103328+SwikritiT@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:05:52 +0545 Subject: [PATCH] [#50658] Connect to OpenProject Button in Smart Picker (#516) https://community.openproject.org/work_packages/50658 Signed-off-by: Swikriti Tripathi --- appinfo/routes.php | 1 + lib/Controller/ConfigController.php | 2 + lib/Controller/OpenProjectAPIController.php | 23 ++++++ lib/Service/OpenProjectAPIService.php | 25 ++++++ src/components/OAuthConnectButton.vue | 3 + src/components/tab/EmptyContent.vue | 9 ++- src/components/tab/SearchInput.vue | 5 ++ src/views/WorkPackagePickerElement.vue | 87 +++++++++++++++++---- 8 files changed, 135 insertions(+), 20 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 3bc4107c7..acbc841a6 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -44,6 +44,7 @@ ['name' => 'openProjectAPI#getOpenProjectWorkPackageForm', 'url' => '/projects/{projectId}/work-packages/form','verb' => 'POST'], ['name' => 'openProjectAPI#getAvailableAssigneesOfAProject', 'url' => '/projects/{projectId}/available-assignees','verb' => 'GET'], ['name' => 'openProjectAPI#createWorkPackage', 'url' => '/create/work-packages','verb' => 'POST'], + ['name' => 'openProjectAPI#getOpenProjectConfiguration', 'url' => '/configuration', 'verb' => 'GET'], ], 'ocs' => [ ['name' => 'files#getFileInfo', 'url' => '/fileinfo/{fileId}', 'verb' => 'GET'], diff --git a/lib/Controller/ConfigController.php b/lib/Controller/ConfigController.php index 7c1a5844c..2c9b303f1 100755 --- a/lib/Controller/ConfigController.php +++ b/lib/Controller/ConfigController.php @@ -416,6 +416,8 @@ public function oauthRedirect(string $code = '', string $state = ''): RedirectRe 'dir' => $oauthJourneyStartingPageDecoded->file->dir, 'scrollto' => $oauthJourneyStartingPageDecoded->file->name ]); + } elseif ($oauthJourneyStartingPageDecoded->page === 'spreed') { + $newUrl = $oauthJourneyStartingPageDecoded->callUrl; } else { $this->logger->error( 'could not determine where the OAuth journey ' . diff --git a/lib/Controller/OpenProjectAPIController.php b/lib/Controller/OpenProjectAPIController.php index ccd38827a..5760780c8 100644 --- a/lib/Controller/OpenProjectAPIController.php +++ b/lib/Controller/OpenProjectAPIController.php @@ -507,6 +507,29 @@ public function createWorkPackage(array $body): DataResponse { return new DataResponse($result, Http::STATUS_CREATED); } + /** + * get OpenProject configuration + * + * @NoAdminRequired + * + * @return DataResponse + */ + public function getOpenProjectConfiguration(): DataResponse { + if ($this->accessToken === '') { + return new DataResponse('', Http::STATUS_UNAUTHORIZED); + } elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) { + return new DataResponse('', Http::STATUS_BAD_REQUEST); + } + try { + $result = $this->openprojectAPIService->getOpenProjectConfiguration($this->userId); + } catch (OpenprojectErrorException $e) { + return new DataResponse($e->getMessage(), $e->getCode()); + } catch (\Exception $e) { + return new DataResponse($e->getMessage(), Http::STATUS_INTERNAL_SERVER_ERROR); + } + return new DataResponse($result); + } + /** * check if there is a OpenProject behind a certain URL * diff --git a/lib/Service/OpenProjectAPIService.php b/lib/Service/OpenProjectAPIService.php index 8635b97b3..f78799555 100644 --- a/lib/Service/OpenProjectAPIService.php +++ b/lib/Service/OpenProjectAPIService.php @@ -1361,4 +1361,29 @@ public function createWorkPackage(string $userId, array $body): array { } return $result; } + + /** + * @param string $userId + * + * @return array + * @throws OpenprojectErrorException + * @throws OpenprojectResponseException + */ + public function getOpenProjectConfiguration(string $userId): array { + $result = $this->request( + $userId, '/configuration' + ); + + if (isset($result['error'])) { + throw new OpenprojectErrorException($result['error'], $result['statusCode']); + } + + if ( + !isset($result['_type']) || + $result['_type'] !== 'Configuration' + ) { + throw new OpenprojectResponseException('Malformed response'); + } + return $result; + } } diff --git a/src/components/OAuthConnectButton.vue b/src/components/OAuthConnectButton.vue index 190136716..65425426d 100644 --- a/src/components/OAuthConnectButton.vue +++ b/src/components/OAuthConnectButton.vue @@ -67,6 +67,9 @@ export default { if (window.location.pathname.includes('apps/files') && Object.keys(this.fileInfo).length === 0) { return { page: 'files' } } + if (window.location.pathname.includes('call')) { + return { page: 'spreed', callUrl: window.location.href } + } return { page: 'settings' } }, async onOAuthClick() { diff --git a/src/components/tab/EmptyContent.vue b/src/components/tab/EmptyContent.vue index 34fc97bf0..1e59a2ba2 100644 --- a/src/components/tab/EmptyContent.vue +++ b/src/components/tab/EmptyContent.vue @@ -4,14 +4,15 @@
- +
-
-
+
+
+
{{ emptyContentTitleMessage }}
-
+
{{ emptyContentSubTitleMessage }}
diff --git a/src/components/tab/SearchInput.vue b/src/components/tab/SearchInput.vue index 8a2c80ef0..69a3e7389 100644 --- a/src/components/tab/SearchInput.vue +++ b/src/components/tab/SearchInput.vue @@ -11,6 +11,7 @@ :loading="isStateLoading" :filterable="false" :clear-search-on-blur="() => false" + :disabled="isDisabled" @search="asyncFind" @option:selected="linkWorkPackageToFile"> @@ -42,6 +48,9 @@ import SearchInput from '../components/tab/SearchInput.vue' import EmptyContent from '../components/tab/EmptyContent.vue' import { STATE } from '../utils.js' import { loadState } from '@nextcloud/initial-state' +import { generateUrl } from '@nextcloud/router' +import axios from '@nextcloud/axios' +import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' export default { name: 'WorkPackagePickerElement', @@ -49,6 +58,7 @@ export default { components: { EmptyContent, SearchInput, + NcLoadingIcon, }, props: { @@ -61,22 +71,60 @@ export default { default: false, }, }, - - data: () => ({ - fileInfo: {}, - linkedWorkPackages: [], - state: STATE.OK, - isAdminConfigOk: loadState('integration_openproject', 'admin-config-status'), - }), - mounted() { - if (this.$refs.linkPicker?.$refs?.workPackageSelect) { - document.getElementById(`${this.$refs.linkPicker?.$refs?.workPackageSelect?.inputId}`).focus() + data() { + return { + fileInfo: {}, + linkedWorkPackages: [], + state: STATE.LOADING, + isAdminConfigOk: loadState('integration_openproject', 'admin-config-status'), } }, + computed: { + isStateOk() { + return this.state === STATE.OK + }, + isLoading() { + return this.state === STATE.LOADING + }, + }, + mounted() { + this.checkIfOpenProjectIsAvailable() + }, methods: { onSubmit(data) { this.$emit('submit', data) }, + async checkIfOpenProjectIsAvailable() { + if (!this.isAdminConfigOk) { + this.state = STATE.ERROR + return + } + const configurationUrl = generateUrl('/apps/integration_openproject/configuration') + let response = null + try { + // send an axios request to fetch configuration to see if the connection is there + response = await axios.get(configurationUrl) + if (response.data) { + this.state = STATE.OK + if (this.$refs.linkPicker?.$refs?.workPackageSelect) { + this.$nextTick(() => { + document.getElementById(`${this.$refs.linkPicker?.$refs?.workPackageSelect?.inputId}`).focus() + }) + } + } else { + this.state = STATE.ERROR + } + } catch (error) { + document.activeElement?.blur() + if (error.response && (error.response.status === 404 || error.response.status === 503)) { + this.state = STATE.CONNECTION_ERROR + } else if (error.response && error.response.status === 401) { + this.state = STATE.NO_TOKEN + } else { + this.state = STATE.ERROR + } + } + }, }, } @@ -92,5 +140,12 @@ export default { align-items: center; align-self: center; } + #openproject-empty-content { + height: 400px !important; + } + .loading-spinner { + margin-top: 150px; + } } +