Skip to content

Commit

Permalink
[#50658] Connect to OpenProject Button in Smart Picker (#516)
Browse files Browse the repository at this point in the history
https://community.openproject.org/work_packages/50658

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>
  • Loading branch information
SwikritiT committed Dec 7, 2023
1 parent 4cf509f commit 7b39edf
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 20 deletions.
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
2 changes: 2 additions & 0 deletions lib/Controller/ConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ' .
Expand Down
23 changes: 23 additions & 0 deletions lib/Controller/OpenProjectAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
25 changes: 25 additions & 0 deletions lib/Service/OpenProjectAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1361,4 +1361,29 @@ public function createWorkPackage(string $userId, array $body): array {
}
return $result;
}

/**
* @param string $userId
*
* @return array<mixed>
* @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;
}
}
3 changes: 3 additions & 0 deletions src/components/OAuthConnectButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
9 changes: 5 additions & 4 deletions src/components/tab/EmptyContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
<div class="empty-content--icon">
<CheckIcon v-if="isStateOk && dashboard" :size="60" />
<LinkPlusIcon v-else-if="!!isAdminConfigOk && isStateOk && !dashboard && !isSmartPicker" :size="60" />
<OpenProjectIcon v-else-if="!!isSmartPicker" class="empty-content--icon--openproject" />
<OpenProjectIcon v-else-if="!!isSmartPicker && isStateOk" class="empty-content--icon--openproject" />
<LinkOffIcon v-else :size="60" />
</div>
<div v-if="!!isAdminConfigOk && !isSmartPicker" class="empty-content--message">
<div class="empty-content--message--title">
<div v-if="!!isAdminConfigOk" class="empty-content--message">
<div v-if="!!isStateOk && isSmartPicker" class="empty-content--message--title" />
<div v-else class="empty-content--message--title">
{{ emptyContentTitleMessage }}
</div>
<div v-if="!!emptyContentSubTitleMessage" class="empty-content--message--sub-title">
<div v-if="!!emptyContentSubTitleMessage && !isSmartPicker" class="empty-content--message--sub-title">
{{ emptyContentSubTitleMessage }}
</div>
</div>
Expand Down
5 changes: 5 additions & 0 deletions src/components/tab/SearchInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
:loading="isStateLoading"
:filterable="false"
:clear-search-on-blur="() => false"
:disabled="isDisabled"
@search="asyncFind"
@option:selected="linkWorkPackageToFile">
<template #option="option">
Expand Down Expand Up @@ -100,6 +101,10 @@ export default {
required: false,
default: null,
},
isDisabled: {
type: Boolean,
default: false,
},
},
data: () => ({
state: STATE.OK,
Expand Down
87 changes: 71 additions & 16 deletions src/views/WorkPackagePickerElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,22 @@
<h2 class="work-package-picker__header">
{{ t('integration_openproject', 'OpenProject work package picker') }}
</h2>
<SearchInput ref="linkPicker"
<SearchInput
ref="linkPicker"
:is-smart-picker="true"
:file-info="fileInfo"
:is-disabled="isLoading || !isAdminConfigOk || !isStateOk"
:linked-work-packages="linkedWorkPackages"
@submit="onSubmit" />
<EmptyContent id="openproject-empty-content"
:state="state"
:file-info="fileInfo"
:is-smart-picker="true"
:is-admin-config-ok="isAdminConfigOk" />
<div id="openproject-empty-content">
<NcLoadingIcon v-if="isLoading" class="loading-spinner" :size="90" />
<EmptyContent
v-else
:state="state"
:file-info="fileInfo"
:is-smart-picker="true"
:is-admin-config-ok="isAdminConfigOk" />
</div>
</div>
</template>

Expand All @@ -42,13 +48,17 @@ 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',
components: {
EmptyContent,
SearchInput,
NcLoadingIcon,
},
props: {
Expand All @@ -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
}
}
},
},
}
</script>
Expand All @@ -92,5 +140,12 @@ export default {
align-items: center;
align-self: center;
}
#openproject-empty-content {
height: 400px !important;
}
.loading-spinner {
margin-top: 150px;
}
}
</style>

0 comments on commit 7b39edf

Please sign in to comment.