Skip to content

Commit

Permalink
Merge release2.4 to master (#458)
Browse files Browse the repository at this point in the history
* [OP#48106] use same component as project tab for searching in smart picker (#448)

* reuse component

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

reuse

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

emit event

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

add op icon vue

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* fix failing tests

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* check for access token

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* add unit test

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* address review

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

---------

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* Fix the lable of managed project folder (#452)

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* [OP#49182] Fix the form heading index colour in different themes (#453)

* Fix the colour of the indexes in different themes

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* update snapshots

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* fix style lint

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

---------

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* [OP#49621] fix app password form becomes inactive (#456)

* Check and remove the app password with name OpenProject

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* add phpunit test

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* add api test to cover the bug

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* add unit test for multiple token

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* add unit test for delete token

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

---------

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* [OP#49435] Nextcloud oauth section should not be disabled when the user deletes the oauth credentials (#457)

* [#49435] Nextcloud oauth section should not be disabled when the user deletes the oauth credentials

https://community.openproject.org/work_packages/49435
Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* fix project folder form

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* fix and add unit tests

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* fix the feature

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

---------

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* Fix link previewd

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* fix link on front end and fix test

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* fix failing php unit test and add more patterns'

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* add php test

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* ignore path

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>

* Support global work package URL without split view

Signed-off-by: Wieland Lindenthal <w.lindenthal@forkmerge.com>

---------

Signed-off-by: Swikriti Tripathi <swikriti808@gmail.com>
Signed-off-by: Wieland Lindenthal <w.lindenthal@forkmerge.com>
Co-authored-by: Wieland Lindenthal <w.lindenthal@forkmerge.com>
  • Loading branch information
SwikritiT and wielinde committed Aug 21, 2023
1 parent 0da766a commit 1c6097e
Show file tree
Hide file tree
Showing 21 changed files with 723 additions and 85 deletions.
7 changes: 4 additions & 3 deletions lib/Controller/OpenProjectAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,18 @@ public function getNotifications(): DataResponse {
*
* @return DataResponse
*/
public function getSearchedWorkPackages(?string $searchQuery = null, ?int $fileId = null): DataResponse {
public function getSearchedWorkPackages(?string $searchQuery = null, ?int $fileId = null, bool $isSmartPicker = false): DataResponse {
if ($this->accessToken === '') {
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
return new DataResponse('', Http::STATUS_BAD_REQUEST);
}

// when the search is done through smart picker we don't want to check if the work package is linkable
$result = $this->openprojectAPIService->searchWorkPackage(
$this->userId,
$searchQuery,
$fileId
$fileId,
!$isSmartPicker
);

if (!isset($result['error'])) {
Expand Down
27 changes: 27 additions & 0 deletions lib/Listener/OpenProjectReferenceListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,48 @@
namespace OCA\OpenProject\Listener;

use OCA\OpenProject\AppInfo\Application;
use OCA\OpenProject\Service\OpenProjectAPIService;
use OCP\AppFramework\Services\IInitialState;
use OCP\Collaboration\Reference\RenderReferenceEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IConfig;
use OCP\Util;

/**
* @template-implements IEventListener<Event>
*/
class OpenProjectReferenceListener implements IEventListener {

/**
* @var IInitialState
*/
private $initialStateService;

/**
* @var IConfig
*/
private $config;

public function __construct(
IInitialState $initialStateService,
IConfig $config
) {
$this->initialStateService = $initialStateService;
$this->config = $config;
}
public function handle(Event $event): void {
// @phpstan-ignore-next-line - make phpstan not complain in nextcloud version other than 26
if (!$event instanceof RenderReferenceEvent) {
return;
}

Util::addScript(Application::APP_ID, Application::APP_ID . '-reference');
$this->initialStateService->provideInitialState('admin-config-status', OpenProjectAPIService::isAdminConfigOk($this->config));

$this->initialStateService->provideInitialState(
'openproject-url',
$this->config->getAppValue(Application::APP_ID, 'openproject_instance_url')
);
}
}
32 changes: 16 additions & 16 deletions lib/Reference/WorkPackageReferenceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

use OCA\OpenProject\Service\OpenProjectAPIService;
use OCP\Collaboration\Reference\ADiscoverableReferenceProvider;
use OCP\Collaboration\Reference\ISearchableReferenceProvider;
use OCP\Collaboration\Reference\Reference;
use OC\Collaboration\Reference\ReferenceManager;
use OCA\OpenProject\AppInfo\Application;
Expand All @@ -34,7 +33,7 @@
use OCP\IL10N;
use OCP\IURLGenerator;

class WorkPackageReferenceProvider extends ADiscoverableReferenceProvider implements ISearchableReferenceProvider {
class WorkPackageReferenceProvider extends ADiscoverableReferenceProvider {
private const RICH_OBJECT_TYPE = Application::APP_ID . '_work_package';

// as we know we are on NC >= 26, we can use Php 8 syntax for class attributes
Expand Down Expand Up @@ -76,29 +75,30 @@ public function getIconUrl(): string {
);
}

/**
* @inheritDoc
*/
public function getSupportedSearchProviderIds(): array {
return ['openproject-search'];
}

/**
* Parse a link to find a work package ID
*
* @param string $referenceText
*
* @return int|null
*/
private function getWorkPackageIdFromUrl(string $referenceText): ?int {
public function getWorkPackageIdFromUrl(string $referenceText): ?int {
$patterns = array(
'\/wp\/([0-9]+)/',
'\/projects\/[^\/\?]+\/(?:work_packages|bcf)(?:\/details)?\/([0-9]+)/',
'\/(?:work_packages|notifications)\/details\/([0-9]+)/',
'\/work_packages\/([0-9]+)/',
'\/projects\/[^\/\?]+\/(?:boards|calendars|team_planners)\/[^\/\?]+\/details\/([0-9]+)/');
// example links
// https://community.openproject.org/projects/nextcloud-integration/work_packages/40070
$openProjectUrl = $this->config->getAppValue(Application::APP_ID, 'openproject_instance_url');
preg_match('/^' . preg_quote($openProjectUrl, '/') . '\/projects\/[^\/\?]+\/work_packages\/([0-9]+)/', $referenceText, $matches);
if (count($matches) > 1) {
return (int) $matches[1];
$openProjectUrl = rtrim($this->config->getAppValue(Application::APP_ID, 'openproject_instance_url'),'/');
foreach ($patterns as $pattern) {
$patternString ='/^' . preg_quote($openProjectUrl, '/') . $pattern;
preg_match($patternString, $referenceText, $patternMatches);
if (count($patternMatches) > 1) {
return (int) $patternMatches[1];
}
}

return null;
}

Expand All @@ -124,7 +124,7 @@ public function matchReference(string $referenceText): bool {
* @inheritDoc
*/
public function resolveReference(string $referenceText): ?IReference {
if ($this->matchReference($referenceText)) {
if ($this->matchReference($referenceText) && OpenProjectAPIService::isAdminConfigOk($this->config)) {
$wpId = $this->getWorkPackageIdFromUrl($referenceText);
if ($wpId !== null) {
$wpInfo = $this->openProjectAPIService->getWorkPackageInfo($this->userId, $wpId);
Expand Down
20 changes: 5 additions & 15 deletions lib/Search/OpenProjectSearchProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,24 +110,14 @@ public function search(IUser $user, ISearchQuery $query): SearchResult {
$offset = $offset ? intval($offset) : 0;
$openprojectUrl = OpenProjectAPIService::sanitizeUrl($this->config->getAppValue(Application::APP_ID, 'openproject_instance_url'));
$accessToken = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'token');

if ($accessToken === '') {
$searchEnabled = $this->config->getUserValue(
$user->getUID(),
Application::APP_ID, 'search_enabled',
$this->config->getAppValue(Application::APP_ID, 'default_enable_unified_search', '0')) === '1';
if ($accessToken === '' || !$searchEnabled) {
return SearchResult::paginated($this->getName(), [], 0);
}

$routeFrom = $query->getRoute();
$requestedFromSmartPicker = $routeFrom === '' || $routeFrom === 'smart-picker';

if (!$requestedFromSmartPicker) {
$searchEnabled = $this->config->getUserValue(
$user->getUID(),
Application::APP_ID, 'search_enabled',
$this->config->getAppValue(Application::APP_ID, 'default_enable_unified_search', '0')) === '1';
if (!$searchEnabled) {
return SearchResult::paginated($this->getName(), [], 0);
}
}

$searchResults = $this->service->searchWorkPackage($user->getUID(), $term, null, false);
$searchResults = array_slice($searchResults, $offset, $limit);

Expand Down
29 changes: 21 additions & 8 deletions lib/Service/OpenProjectAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1129,8 +1129,12 @@ public function generateAppPasswordTokenForUser(): string {
*/
public function deleteAppPassword(): void {
if ($this->hasAppPassword()) {
$tokenId = $this->tokenProvider->getTokenByUser(Application::OPEN_PROJECT_ENTITIES_NAME)[0]->getId();
$this->tokenProvider->invalidateTokenById(Application::OPEN_PROJECT_ENTITIES_NAME, $tokenId);
$tokens = $this->tokenProvider->getTokenByUser(Application::OPEN_PROJECT_ENTITIES_NAME);
foreach ($tokens as $token) {
if ($token->getName() === Application::OPEN_PROJECT_ENTITIES_NAME) {
$this->tokenProvider->invalidateTokenById(Application::OPEN_PROJECT_ENTITIES_NAME, $token->getId());
}
}
}
}

Expand All @@ -1140,7 +1144,13 @@ public function deleteAppPassword(): void {
* @return bool
*/
public function hasAppPassword(): bool {
return sizeof($this->tokenProvider->getTokenByUser(Application::OPEN_PROJECT_ENTITIES_NAME)) === 1;
$tokens = $this->tokenProvider->getTokenByUser(Application::OPEN_PROJECT_ENTITIES_NAME);
foreach ($tokens as $token) {
if ($token->getName() === Application::OPEN_PROJECT_ENTITIES_NAME) {
return true;
}
}
return false;
}

/**
Expand All @@ -1151,11 +1161,14 @@ public function hasAppPassword(): bool {
*/
public function getWorkPackageInfo(string $userId, int $wpId): array {
$result[] = null;
$searchResult = $this->searchWorkPackage($userId, null, null, false, $wpId);
$result['title'] = $this->getSubline($searchResult[0]);
$result['description'] = $this->getMainText($searchResult[0]);
$result['imageUrl'] = $this->getOpenProjectUserAvatarUrl($searchResult[0]);
$result['entry'] = $searchResult[0];
$accessToken = $this->config->getUserValue($userId, Application::APP_ID, 'token');
if ($accessToken) {
$searchResult = $this->searchWorkPackage($userId, null, null, false, $wpId);
$result['title'] = $this->getSubline($searchResult[0]);
$result['description'] = $this->getMainText($searchResult[0]);
$result['imageUrl'] = $this->getOpenProjectUserAvatarUrl($searchResult[0]);
$result['entry'] = $searchResult[0];
}
return $result;
}

Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ parameters:
reportUnmatchedIgnoredErrors: false
excludePaths:
- './lib/Reference/WorkPackageReferenceProvider.php'
- './tests/lib/Reference/WorkPackageReferenceProviderTest.php'
41 changes: 32 additions & 9 deletions src/components/AdminSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
<div class="openproject-server-host">
<FormHeading index="1"
:title="t('integration_openproject', 'OpenProject server')"
:is-complete="isServerHostFormComplete" />
:is-complete="isServerHostFormComplete"
:is-dark-theme="isDarkTheme" />

<FieldValue v-if="isServerHostFormInView"
is-required
class="pb-1"
Expand Down Expand Up @@ -56,7 +58,8 @@
<FormHeading index="2"
:title="t('integration_openproject', 'OpenProject OAuth settings')"
:is-complete="isOPOAuthFormComplete"
:is-disabled="isOPOAuthFormInDisableMode" />
:is-disabled="isOPOAuthFormInDisableMode"
:is-dark-theme="isDarkTheme" />
<div v-if="isServerHostFormComplete">
<FieldValue v-if="isOPOAuthFormInView"
is-required
Expand Down Expand Up @@ -109,7 +112,8 @@
<FormHeading index="3"
:title="t('integration_openproject', 'Nextcloud OAuth client')"
:is-complete="isNcOAuthFormComplete"
:is-disabled="isNcOAuthFormInDisableMode" />
:is-disabled="isNcOAuthFormInDisableMode"
:is-dark-theme="isDarkTheme" />
<div v-if="state.nc_oauth_client">
<TextInput v-if="isNcOAuthFormInEdit"
id="nextcloud-oauth-client-id"
Expand Down Expand Up @@ -159,6 +163,15 @@
</NcButton>
</div>
</div>
<div v-if="!state.nc_oauth_client && isOPOAuthFormComplete && isOPOAuthFormInView && showDefaultManagedProjectFolders">
<NcButton data-test-id="reset-nc-oauth-btn"
@click="resetNcOauthValues">
<template #icon>
<AutoRenewIcon :size="20" />
</template>
{{ t('integration_openproject', 'Create Nextcloud OAuth values') }}
</NcButton>
</div>
</div>
<div class="project-folder-setup">
<FormHeading index="4"
Expand Down Expand Up @@ -239,7 +252,7 @@
</div>
<div v-else class="project-folder-status">
<div class="project-folder-status-value">
<b>Automatic managed folders:</b> {{ opUserAppPassword ? t('integration_openproject', 'Active') : t('integration_openproject', 'Inactive') }}
<b>Automatically managed folders:</b> {{ opUserAppPassword ? t('integration_openproject', 'Active') : t('integration_openproject', 'Inactive') }}
</div>
<ProjectFolderError
v-if="state.app_password_set && !isProjectFolderSetupCorrect"
Expand All @@ -262,7 +275,8 @@
<FormHeading index="5"
:title="t('integration_openproject', 'Project folders application connection')"
:is-complete="isOPUserAppPasswordFormComplete"
:is-disabled="isOPUserAppPasswordInDisableMode" />
:is-disabled="isOPUserAppPasswordInDisableMode"
:is-dark-theme="isDarkTheme" />
<div v-if="state.app_password_set">
<TextInput v-if="isOPUserAppPasswordFormInEdit"
id="openproject-system-password"
Expand Down Expand Up @@ -408,6 +422,7 @@ export default {
textLabelProjectFolderSetupButton: null,
// pointer for which form the request is coming
isFormStep: null,
isDarkTheme: null,
}
},
computed: {
Expand Down Expand Up @@ -528,14 +543,13 @@ export default {
isResetButtonDisabled() {
return !(this.state.openproject_client_id || this.state.openproject_client_secret || this.state.openproject_instance_url)
},
isDarkTheme() {
return !!document.querySelector("body[data-themes*='dark']")
},
},
created() {
this.init()
},
mounted() {
this.isDarkTheme = window.getComputedStyle(this.$el).getPropertyValue('--background-invert-if-dark') === 'invert(100%)'
},
methods: {
init() {
if (this.state) {
Expand Down Expand Up @@ -571,6 +585,15 @@ export default {
this.formMode.ncOauth = F_MODES.VIEW
this.isFormCompleted.ncOauth = true
}
if (!this.state.nc_oauth_client
&& this.state.openproject_instance_url
&& this.state.openproject_client_id
&& this.state.openproject_client_secret
&& this.textLabelProjectFolderSetupButton === 'Keep current setup') {
this.showDefaultManagedProjectFolders = true
this.formMode.projectFolderSetUp = F_MODES.VIEW
this.isFormCompleted.projectFolderSetUp = true
}
if (this.formMode.ncOauth === F_MODES.VIEW) {
this.showDefaultManagedProjectFolders = true
}
Expand Down
18 changes: 12 additions & 6 deletions src/components/admin/FormHeading.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
<div v-else-if="isComplete" class="complete">
<CheckBoldIcon fill-color="#FFFFFF" :size="12" />
</div>
<div v-else class="index">
<div v-else
class="index"
:class="{
'index-dark-mode': isDarkTheme,
'index-light-mode': !isDarkTheme
}">
{{ index }}
</div>
<div class="title"
Expand Down Expand Up @@ -124,7 +129,6 @@ export default {
text-align: center;
border-radius: 50%;
background: var(--color-loading-dark);
color: white;
}
.title {
font-weight: 700;
Expand All @@ -134,10 +138,12 @@ export default {
}
}
body[data-themes*='dark'] {
.index {
color: #000000;
}
.index-dark-mode {
color: #000000;
}
.index-light-mode {
color: #FFFFFF;
}
.form-heading.disabled {
Expand Down
Loading

0 comments on commit 1c6097e

Please sign in to comment.