mirror of
https://github.com/nextcloud/server.git
synced 2026-02-18 18:28:50 -05:00
Feat: Allow users to select another user as their out-of-office replacement
Signed-off-by: Hamza Mahjoubi <hamzamahjoubi221@gmail.com>
This commit is contained in:
parent
3b75c5b98c
commit
a9774741e8
17 changed files with 321 additions and 12 deletions
|
|
@ -10,7 +10,7 @@
|
|||
<name>WebDAV</name>
|
||||
<summary>WebDAV endpoint</summary>
|
||||
<description>WebDAV endpoint</description>
|
||||
<version>1.31.0</version>
|
||||
<version>1.31.1</version>
|
||||
<licence>agpl</licence>
|
||||
<author>owncloud.org</author>
|
||||
<namespace>DAV</namespace>
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ return array(
|
|||
'OCA\\DAV\\Migration\\Version1029Date20221114151721' => $baseDir . '/../lib/Migration/Version1029Date20221114151721.php',
|
||||
'OCA\\DAV\\Migration\\Version1029Date20231004091403' => $baseDir . '/../lib/Migration/Version1029Date20231004091403.php',
|
||||
'OCA\\DAV\\Migration\\Version1030Date20240205103243' => $baseDir . '/../lib/Migration/Version1030Date20240205103243.php',
|
||||
'OCA\\DAV\\Migration\\Version1031Date20240610134258' => $baseDir . '/../lib/Migration/Version1031Date20240610134258.php',
|
||||
'OCA\\DAV\\Profiler\\ProfilerPlugin' => $baseDir . '/../lib/Profiler/ProfilerPlugin.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
|
||||
|
|
|
|||
|
|
@ -341,6 +341,7 @@ class ComposerStaticInitDAV
|
|||
'OCA\\DAV\\Migration\\Version1029Date20221114151721' => __DIR__ . '/..' . '/../lib/Migration/Version1029Date20221114151721.php',
|
||||
'OCA\\DAV\\Migration\\Version1029Date20231004091403' => __DIR__ . '/..' . '/../lib/Migration/Version1029Date20231004091403.php',
|
||||
'OCA\\DAV\\Migration\\Version1030Date20240205103243' => __DIR__ . '/..' . '/../lib/Migration/Version1030Date20240205103243.php',
|
||||
'OCA\\DAV\\Migration\\Version1031Date20240610134258' => __DIR__ . '/..' . '/../lib/Migration/Version1031Date20240610134258.php',
|
||||
'OCA\\DAV\\Profiler\\ProfilerPlugin' => __DIR__ . '/..' . '/../lib/Profiler/ProfilerPlugin.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
|
||||
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ class OutOfOfficeController extends OCSController {
|
|||
'lastDay' => $data->getLastDay(),
|
||||
'status' => $data->getStatus(),
|
||||
'message' => $data->getMessage(),
|
||||
'replacementUserId' => $data->getReplacementUserId(),
|
||||
'replacementUserDisplayName' => $data->getReplacementUserDisplayName(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -103,11 +105,14 @@ class OutOfOfficeController extends OCSController {
|
|||
* @param string $lastDay Last day of the absence in format `YYYY-MM-DD`
|
||||
* @param string $status Short text that is set as user status during the absence
|
||||
* @param string $message Longer multiline message that is shown to others during the absence
|
||||
* @return DataResponse<Http::STATUS_OK, DAVOutOfOfficeData, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: 'firstDay'}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, null, array{}>
|
||||
* @param string $replacementUserId User id of the replacement user
|
||||
* @param string $replacementUserDisplayName Display name of the replacement user
|
||||
* @return DataResponse<Http::STATUS_OK, DAVOutOfOfficeData, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: 'firstDay'}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, null, array{}>|DataResponse<Http::STATUS_NOT_FOUND, null, array{}>
|
||||
*
|
||||
* 200: Absence data
|
||||
* 400: When the first day is not before the last day
|
||||
* 401: When the user is not logged in
|
||||
* 404: When the replacementUserId was provided but replacement user was not found
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function setOutOfOffice(
|
||||
|
|
@ -115,12 +120,22 @@ class OutOfOfficeController extends OCSController {
|
|||
string $lastDay,
|
||||
string $status,
|
||||
string $message,
|
||||
string $replacementUserId = '',
|
||||
string $replacementUserDisplayName = ''
|
||||
|
||||
): DataResponse {
|
||||
$user = $this->userSession?->getUser();
|
||||
if ($user === null) {
|
||||
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($replacementUserId !== '') {
|
||||
$replacementUser = $this->userManager->get($replacementUserId);
|
||||
if ($replacementUser === null) {
|
||||
return new DataResponse(null, Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
$parsedFirstDay = new DateTimeImmutable($firstDay);
|
||||
$parsedLastDay = new DateTimeImmutable($lastDay);
|
||||
if ($parsedFirstDay->getTimestamp() > $parsedLastDay->getTimestamp()) {
|
||||
|
|
@ -133,6 +148,8 @@ class OutOfOfficeController extends OCSController {
|
|||
$lastDay,
|
||||
$status,
|
||||
$message,
|
||||
$replacementUserId,
|
||||
$replacementUserDisplayName
|
||||
);
|
||||
$this->coordinator->clearCache($user->getUID());
|
||||
|
||||
|
|
@ -143,6 +160,8 @@ class OutOfOfficeController extends OCSController {
|
|||
'lastDay' => $data->getLastDay(),
|
||||
'status' => $data->getStatus(),
|
||||
'message' => $data->getMessage(),
|
||||
'replacementUserId' => $data->getReplacementUserId(),
|
||||
'replacementUserDisplayName' => $data->getReplacementUserDisplayName(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ use OCP\User\IOutOfOfficeData;
|
|||
* @method void setStatus(string $status)
|
||||
* @method string getMessage()
|
||||
* @method void setMessage(string $message)
|
||||
* @method string getReplacementUserId()
|
||||
* @method void setReplacementUserId(string $replacementUserId)
|
||||
* @method string getReplacementUserDisplayName()
|
||||
* @method void setReplacementUserDisplayName(string $replacementUserDisplayName)
|
||||
*/
|
||||
class Absence extends Entity implements JsonSerializable {
|
||||
protected string $userId = '';
|
||||
|
|
@ -43,12 +47,18 @@ class Absence extends Entity implements JsonSerializable {
|
|||
|
||||
protected string $message = '';
|
||||
|
||||
protected string $replacementUserId = '';
|
||||
|
||||
protected string $replacementUserDisplayName = '';
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('userId', 'string');
|
||||
$this->addType('firstDay', 'string');
|
||||
$this->addType('lastDay', 'string');
|
||||
$this->addType('status', 'string');
|
||||
$this->addType('message', 'string');
|
||||
$this->addType('replacementUserId', 'string');
|
||||
$this->addType('replacementUserDisplayName', 'string');
|
||||
}
|
||||
|
||||
public function toOutOufOfficeData(IUser $user, string $timezone): IOutOfOfficeData {
|
||||
|
|
@ -70,6 +80,8 @@ class Absence extends Entity implements JsonSerializable {
|
|||
$endDate->getTimestamp(),
|
||||
$this->getStatus(),
|
||||
$this->getMessage(),
|
||||
$this->getReplacementUserId(),
|
||||
$this->getReplacementUserDisplayName(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -80,6 +92,8 @@ class Absence extends Entity implements JsonSerializable {
|
|||
'lastDay' => $this->lastDay,
|
||||
'status' => $this->status,
|
||||
'message' => $this->message,
|
||||
'replacementUserId' => $this->replacementUserId,
|
||||
'replacementUserDisplayName' => $this->replacementUserDisplayName,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
apps/dav/lib/Migration/Version1031Date20240610134258.php
Normal file
44
apps/dav/lib/Migration/Version1031Date20240610134258.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
class Version1031Date20240610134258 extends SimpleMigrationStep {
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
$tableDavAbsence = $schema->getTable('dav_absence');
|
||||
|
||||
if (!$tableDavAbsence->hasColumn('replacement_user_id')) {
|
||||
$tableDavAbsence->addColumn('replacement_user_id', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'default' => '',
|
||||
'length' => 64,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$tableDavAbsence->hasColumn('replacement_user_display_name')) {
|
||||
$tableDavAbsence->addColumn('replacement_user_display_name', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'default' => '',
|
||||
'length' => 64,
|
||||
]);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ namespace OCA\DAV;
|
|||
* @psalm-type DAVOutOfOfficeDataCommon = array{
|
||||
* userId: string,
|
||||
* message: string,
|
||||
* replacementUserId: string,
|
||||
* replacementUserDisplayName: string,
|
||||
* }
|
||||
*
|
||||
* @psalm-type DAVOutOfOfficeData = DAVOutOfOfficeDataCommon&array{
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ class AbsenceService {
|
|||
string $lastDay,
|
||||
string $status,
|
||||
string $message,
|
||||
?string $replacementUserId = null,
|
||||
?string $replacementUserDisplayName = null,
|
||||
): Absence {
|
||||
try {
|
||||
$absence = $this->absenceMapper->findByUserId($user->getUID());
|
||||
|
|
@ -59,6 +61,8 @@ class AbsenceService {
|
|||
$absence->setLastDay($lastDay);
|
||||
$absence->setStatus($status);
|
||||
$absence->setMessage($message);
|
||||
$absence->setReplacementUserId($replacementUserId ?? '');
|
||||
$absence->setReplacementUserDisplayName($replacementUserDisplayName ?? '');
|
||||
|
||||
if ($absence->getId() === null) {
|
||||
$absence = $this->absenceMapper->insert($absence);
|
||||
|
|
|
|||
|
|
@ -133,7 +133,9 @@
|
|||
"type": "object",
|
||||
"required": [
|
||||
"userId",
|
||||
"message"
|
||||
"message",
|
||||
"replacementUserId",
|
||||
"replacementUserDisplayName"
|
||||
],
|
||||
"properties": {
|
||||
"userId": {
|
||||
|
|
@ -141,6 +143,12 @@
|
|||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"replacementUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"replacementUserDisplayName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -570,6 +578,24 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "replacementUserId",
|
||||
"in": "query",
|
||||
"description": "User id of the replacement user",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "replacementUserDisplayName",
|
||||
"in": "query",
|
||||
"description": "Display name of the replacement user",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "userId",
|
||||
"in": "path",
|
||||
|
|
@ -690,6 +716,36 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "When the replacementUserId was provided but replacement user was not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -17,6 +17,21 @@
|
|||
class="absence__dates__picker"
|
||||
:required="true" />
|
||||
</div>
|
||||
<label for="replacement-search-input">{{ $t('dav', 'Out of office replacement (optional)') }}</label>
|
||||
<NcSelect ref="select"
|
||||
v-model="replacementUser"
|
||||
input-id="replacement-search-input"
|
||||
:loading="searchLoading"
|
||||
:placeholder="$t('dav', 'Name of the replacement')"
|
||||
:clear-search-on-blur="() => false"
|
||||
:user-select="true"
|
||||
:options="options"
|
||||
@search="asyncFind"
|
||||
>
|
||||
<template #no-options="{ search }">
|
||||
{{ search ?$t('dav', 'No results.') : $t('dav', 'Start typing.') }}
|
||||
</template>
|
||||
</NcSelect>
|
||||
<NcTextField :value.sync="status" :label="$t('dav', 'Short absence status')" :required="true" />
|
||||
<NcTextArea :value.sync="message" :label="$t('dav', 'Long absence Message')" :required="true" />
|
||||
|
||||
|
|
@ -39,13 +54,16 @@
|
|||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
||||
import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js'
|
||||
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
|
||||
import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import debounce from 'debounce'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { formatDateAsYMD } from '../utils/date.js'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { Type as ShareTypes } from '@nextcloud/sharing'
|
||||
|
||||
import logger from '../service/logger.js'
|
||||
|
||||
|
|
@ -56,16 +74,20 @@ export default {
|
|||
NcTextField,
|
||||
NcTextArea,
|
||||
NcDateTimePickerNative,
|
||||
NcSelect
|
||||
},
|
||||
data() {
|
||||
const { firstDay, lastDay, status, message } = loadState('dav', 'absence', {})
|
||||
|
||||
const { firstDay, lastDay, status, message ,replacementUserId ,replacementUserDisplayName } = loadState('dav', 'absence', {})
|
||||
return {
|
||||
loading: false,
|
||||
status: status ?? '',
|
||||
message: message ?? '',
|
||||
firstDay: firstDay ? new Date(firstDay) : new Date(),
|
||||
lastDay: lastDay ? new Date(lastDay) : null,
|
||||
replacementUserId: replacementUserId ,
|
||||
replacementUser: replacementUserId ? { user: replacementUserId, displayName: replacementUserDisplayName } : null,
|
||||
searchLoading: false,
|
||||
options: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -93,6 +115,99 @@ export default {
|
|||
this.firstDay = new Date()
|
||||
this.lastDay = null
|
||||
},
|
||||
|
||||
/**
|
||||
* Format shares for the multiselect options
|
||||
*
|
||||
* @param {object} result select entry item
|
||||
* @return {object}
|
||||
*/
|
||||
formatForMultiselect(result) {
|
||||
return {
|
||||
user: result.uuid || result.value.shareWith,
|
||||
displayName: result.name || result.label,
|
||||
subtitle: result.dsc | ''
|
||||
}
|
||||
},
|
||||
|
||||
async asyncFind(query) {
|
||||
this.searchLoading = true
|
||||
await this.debounceGetSuggestions(query.trim())
|
||||
},
|
||||
/**
|
||||
* Get suggestions
|
||||
*
|
||||
* @param {string} search the search query
|
||||
*/
|
||||
async getSuggestions(search) {
|
||||
|
||||
const shareType = [
|
||||
ShareTypes.SHARE_TYPE_USER,
|
||||
]
|
||||
|
||||
let request = null
|
||||
try {
|
||||
request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1/sharees'), {
|
||||
params: {
|
||||
format: 'json',
|
||||
itemType: 'file',
|
||||
search,
|
||||
shareType,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching suggestions', error)
|
||||
return
|
||||
}
|
||||
|
||||
const data = request.data.ocs.data
|
||||
const exact = request.data.ocs.data.exact
|
||||
data.exact = [] // removing exact from general results
|
||||
const rawExactSuggestions = exact.users
|
||||
const rawSuggestions = data.users
|
||||
console.info('rawExactSuggestions', rawExactSuggestions)
|
||||
console.info('rawSuggestions', rawSuggestions)
|
||||
// remove invalid data and format to user-select layout
|
||||
const exactSuggestions = rawExactSuggestions
|
||||
.map(share => this.formatForMultiselect(share))
|
||||
const suggestions = rawSuggestions
|
||||
.map(share => this.formatForMultiselect(share))
|
||||
|
||||
const allSuggestions = exactSuggestions.concat(suggestions)
|
||||
|
||||
// Count occurrences of display names in order to provide a distinguishable description if needed
|
||||
const nameCounts = allSuggestions.reduce((nameCounts, result) => {
|
||||
if (!result.displayName) {
|
||||
return nameCounts
|
||||
}
|
||||
if (!nameCounts[result.displayName]) {
|
||||
nameCounts[result.displayName] = 0
|
||||
}
|
||||
nameCounts[result.displayName]++
|
||||
return nameCounts
|
||||
}, {})
|
||||
|
||||
this.options = allSuggestions.map(item => {
|
||||
// Make sure that items with duplicate displayName get the shareWith applied as a description
|
||||
if (nameCounts[item.displayName] > 1 && !item.desc) {
|
||||
return { ...item, desc: item.shareWithDisplayNameUnique }
|
||||
}
|
||||
return item
|
||||
})
|
||||
|
||||
this.searchLoading = false
|
||||
console.info('suggestions', this.options)
|
||||
},
|
||||
|
||||
/**
|
||||
* Debounce getSuggestions
|
||||
*
|
||||
* @param {...*} args the arguments
|
||||
*/
|
||||
debounceGetSuggestions: debounce(function(...args) {
|
||||
this.getSuggestions(...args)
|
||||
}, 300),
|
||||
|
||||
async saveForm() {
|
||||
if (!this.valid) {
|
||||
return
|
||||
|
|
@ -105,6 +220,8 @@ export default {
|
|||
lastDay: formatDateAsYMD(this.lastDay),
|
||||
status: this.status,
|
||||
message: this.message,
|
||||
replacementUserId: this.replacementUser?.user ?? null,
|
||||
replacementUserDisplayName: this.replacementUser?.displayName ?? null,
|
||||
})
|
||||
showSuccess(this.$t('dav', 'Absence saved'))
|
||||
} catch (error) {
|
||||
|
|
|
|||
4
dist/dav-settings-personal-availability.js
vendored
4
dist/dav-settings-personal-availability.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -13,6 +13,7 @@ SPDX-FileCopyrightText: jden <jason@denizac.org>
|
|||
SPDX-FileCopyrightText: inherits developers
|
||||
SPDX-FileCopyrightText: escape-html developers
|
||||
SPDX-FileCopyrightText: defunctzombie
|
||||
SPDX-FileCopyrightText: debounce developers
|
||||
SPDX-FileCopyrightText: atomiks
|
||||
SPDX-FileCopyrightText: assert developers
|
||||
SPDX-FileCopyrightText: Varun A P
|
||||
|
|
@ -110,6 +111,9 @@ This file is generated from multiple sources. Included packages:
|
|||
- @nextcloud/router
|
||||
- version: 3.0.1
|
||||
- license: GPL-3.0-or-later
|
||||
- @nextcloud/sharing
|
||||
- version: 0.1.0
|
||||
- license: GPL-3.0-or-later
|
||||
- @nextcloud/vue-select
|
||||
- version: 3.25.0
|
||||
- license: MIT
|
||||
|
|
@ -179,6 +183,9 @@ This file is generated from multiple sources. Included packages:
|
|||
- css-loader
|
||||
- version: 6.10.0
|
||||
- license: MIT
|
||||
- debounce
|
||||
- version: 2.1.0
|
||||
- license: MIT
|
||||
- define-data-property
|
||||
- version: 1.1.4
|
||||
- license: MIT
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -61,6 +61,8 @@ class AvailabilityCoordinator implements IAvailabilityCoordinator {
|
|||
$cachedData['endDate'],
|
||||
$cachedData['shortMessage'],
|
||||
$cachedData['message'],
|
||||
$cachedData['replacementUserId'],
|
||||
$cachedData['replacementUserDisplayName'],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +74,8 @@ class AvailabilityCoordinator implements IAvailabilityCoordinator {
|
|||
'endDate' => $data->getEndDate(),
|
||||
'shortMessage' => $data->getShortMessage(),
|
||||
'message' => $data->getMessage(),
|
||||
'replacementUserId' => $data->getReplacementUserId(),
|
||||
'replacementUserDisplayName' => $data->getReplacementUserDisplayName(),
|
||||
], JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
$this->logger->error('Failed to serialize out-of-office data: ' . $e->getMessage(), [
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ class OutOfOfficeData implements IOutOfOfficeData {
|
|||
private int $startDate,
|
||||
private int $endDate,
|
||||
private string $shortMessage,
|
||||
private string $message) {
|
||||
private string $message,
|
||||
private string $replacementUserId,
|
||||
private string $replacementUserDisplayName) {
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
@ -45,6 +47,14 @@ class OutOfOfficeData implements IOutOfOfficeData {
|
|||
return $this->message;
|
||||
}
|
||||
|
||||
public function getReplacementUserId(): string {
|
||||
return $this->replacementUserId;
|
||||
}
|
||||
|
||||
public function getReplacementUserDisplayName(): string {
|
||||
return $this->replacementUserDisplayName;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
|
|
@ -53,6 +63,8 @@ class OutOfOfficeData implements IOutOfOfficeData {
|
|||
'endDate' => $this->getEndDate(),
|
||||
'shortMessage' => $this->getShortMessage(),
|
||||
'message' => $this->getMessage(),
|
||||
'replacementUserId' => $this->getReplacementUserId(),
|
||||
'replacementUserDisplayName' => $this->getReplacementUserDisplayName(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ use OCP\IUser;
|
|||
* endDate: int,
|
||||
* shortMessage: string,
|
||||
* message: string,
|
||||
* replacementUserId: string,
|
||||
* replacementUserDisplayName: string
|
||||
* }
|
||||
*
|
||||
* @since 28.0.0
|
||||
|
|
@ -69,6 +71,20 @@ interface IOutOfOfficeData extends JsonSerializable {
|
|||
*/
|
||||
public function getMessage(): string;
|
||||
|
||||
/**
|
||||
* Get the replacement user id for auto responders and similar
|
||||
*
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getReplacementUserId(): string;
|
||||
|
||||
/**
|
||||
* Get the replacement user displayName for auto responders and similar
|
||||
*
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getReplacementUserDisplayName(): string;
|
||||
|
||||
/**
|
||||
* @return OutOfOfficeData
|
||||
*
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ class AvailabilityCoordinatorTest extends TestCase {
|
|||
$absence->setLastDay('2023-10-08');
|
||||
$absence->setStatus('Vacation');
|
||||
$absence->setMessage('On vacation');
|
||||
$absence->setReplacementUserId('batman');
|
||||
$absence->setReplacementUserDisplayName('Bruce Wayne');
|
||||
$this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin');
|
||||
|
||||
$user = $this->createMock(IUser::class);
|
||||
|
|
@ -89,7 +91,7 @@ class AvailabilityCoordinatorTest extends TestCase {
|
|||
$this->cache->expects(self::exactly(2))
|
||||
->method('set')
|
||||
->withConsecutive([$user->getUID() . '_timezone', 'Europe/Berlin', 3600],
|
||||
[$user->getUID(), '{"id":"420","startDate":1696111200,"endDate":1696802340,"shortMessage":"Vacation","message":"On vacation"}', 300]);
|
||||
[$user->getUID(), '{"id":"420","startDate":1696111200,"endDate":1696802340,"shortMessage":"Vacation","message":"On vacation","replacementUserId":"batman","replacementUserDisplayName":"Bruce Wayne"}', 300]);
|
||||
|
||||
$expected = new OutOfOfficeData(
|
||||
'420',
|
||||
|
|
@ -98,6 +100,8 @@ class AvailabilityCoordinatorTest extends TestCase {
|
|||
1696802340,
|
||||
'Vacation',
|
||||
'On vacation',
|
||||
'batman',
|
||||
'Bruce Wayne',
|
||||
);
|
||||
$actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
|
||||
self::assertEquals($expected, $actual);
|
||||
|
|
@ -111,6 +115,8 @@ class AvailabilityCoordinatorTest extends TestCase {
|
|||
$absence->setLastDay('2023-10-08');
|
||||
$absence->setStatus('Vacation');
|
||||
$absence->setMessage('On vacation');
|
||||
$absence->setReplacementUserId('batman');
|
||||
$absence->setReplacementUserDisplayName('Bruce Wayne');
|
||||
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')
|
||||
|
|
@ -118,7 +124,7 @@ class AvailabilityCoordinatorTest extends TestCase {
|
|||
|
||||
$this->cache->expects(self::exactly(2))
|
||||
->method('get')
|
||||
->willReturnOnConsecutiveCalls('UTC', '{"id":"420","startDate":1696118400,"endDate":1696809540,"shortMessage":"Vacation","message":"On vacation"}');
|
||||
->willReturnOnConsecutiveCalls('UTC', '{"id":"420","startDate":1696118400,"endDate":1696809540,"shortMessage":"Vacation","message":"On vacation","replacementUserId":"batman","replacementUserDisplayName":"Bruce Wayne"}');
|
||||
$this->absenceService->expects(self::never())
|
||||
->method('getAbsence');
|
||||
$this->cache->expects(self::exactly(1))
|
||||
|
|
@ -131,6 +137,8 @@ class AvailabilityCoordinatorTest extends TestCase {
|
|||
1696809540,
|
||||
'Vacation',
|
||||
'On vacation',
|
||||
'batman',
|
||||
'Bruce Wayne'
|
||||
);
|
||||
$actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
|
||||
self::assertEquals($expected, $actual);
|
||||
|
|
@ -170,6 +178,8 @@ class AvailabilityCoordinatorTest extends TestCase {
|
|||
$absence->setLastDay('2023-10-08');
|
||||
$absence->setStatus('Vacation');
|
||||
$absence->setMessage('On vacation');
|
||||
$absence->setReplacementUserId('batman');
|
||||
$absence->setReplacementUserDisplayName('Bruce Wayne');
|
||||
$this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin');
|
||||
|
||||
$user = $this->createMock(IUser::class);
|
||||
|
|
@ -185,7 +195,7 @@ class AvailabilityCoordinatorTest extends TestCase {
|
|||
->willReturn($absence);
|
||||
$this->cache->expects(self::once())
|
||||
->method('set')
|
||||
->with('user', '{"id":"420","startDate":1696118400,"endDate":1696809540,"shortMessage":"Vacation","message":"On vacation"}', 300);
|
||||
->with('user', '{"id":"420","startDate":1696118400,"endDate":1696809540,"shortMessage":"Vacation","message":"On vacation","replacementUserId":"batman","replacementUserDisplayName":"Bruce Wayne"}', 300);
|
||||
|
||||
$expected = new OutOfOfficeData(
|
||||
'420',
|
||||
|
|
@ -194,6 +204,8 @@ class AvailabilityCoordinatorTest extends TestCase {
|
|||
1696809540,
|
||||
'Vacation',
|
||||
'On vacation',
|
||||
'batman',
|
||||
'Bruce Wayne'
|
||||
);
|
||||
$actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
|
||||
self::assertEquals($expected, $actual);
|
||||
|
|
|
|||
Loading…
Reference in a new issue