mirror of https://github.com/nextcloud/server.git
Add Status Dashboard
Signed-off-by: Georg Ehrke <developer@georgehrke.com>pull/22284/head
parent
03603db486
commit
bd6a6cf3bf
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2020, Georg Ehrke
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCA\UserStatus\Dashboard;
|
||||
|
||||
use OCA\UserStatus\AppInfo\Application;
|
||||
use OCA\UserStatus\Db\UserStatus;
|
||||
use OCA\UserStatus\Service\StatusService;
|
||||
use OCP\Dashboard\IWidget;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
|
||||
/**
|
||||
* Class UserStatusWidget
|
||||
*
|
||||
* @package OCA\UserStatus
|
||||
*/
|
||||
class UserStatusWidget implements IWidget {
|
||||
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
|
||||
/** @var IInitialStateService */
|
||||
private $initialStateService;
|
||||
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
|
||||
/** @var IUserSession */
|
||||
private $userSession;
|
||||
|
||||
/** @var StatusService */
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* UserStatusWidget constructor
|
||||
*
|
||||
* @param IL10N $l10n
|
||||
* @param IInitialStateService $initialStateService
|
||||
* @param IUserManager $userManager
|
||||
* @param IUserSession $userSession
|
||||
* @param StatusService $service
|
||||
*/
|
||||
public function __construct(IL10N $l10n,
|
||||
IInitialStateService $initialStateService,
|
||||
IUserManager $userManager,
|
||||
IUserSession $userSession,
|
||||
StatusService $service) {
|
||||
$this->l10n = $l10n;
|
||||
$this->initialStateService = $initialStateService;
|
||||
$this->userManager = $userManager;
|
||||
$this->userSession = $userSession;
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getId(): string {
|
||||
return Application::APP_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getTitle(): string {
|
||||
return $this->l10n->t('Recent statuses');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getOrder(): int {
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getIconClass(): string {
|
||||
return 'icon-user-status';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getUrl(): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function load(): void {
|
||||
\OCP\Util::addScript(Application::APP_ID, 'dashboard');
|
||||
|
||||
$currentUser = $this->userSession->getUser();
|
||||
if ($currentUser === null) {
|
||||
$this->initialStateService->provideInitialState(Application::APP_ID, 'dashboard_data', []);
|
||||
return;
|
||||
}
|
||||
$currentUserId = $currentUser->getUID();
|
||||
|
||||
// Fetch status updates and filter current user
|
||||
$recentStatusUpdates = array_slice(
|
||||
array_filter(
|
||||
$this->service->findAllRecentStatusChanges(8, 0),
|
||||
static function (UserStatus $status) use ($currentUserId): bool {
|
||||
return $status->getUserId() !== $currentUserId;
|
||||
}
|
||||
),
|
||||
0,
|
||||
7
|
||||
);
|
||||
|
||||
$this->initialStateService->provideInitialState(Application::APP_ID, 'dashboard_data', array_map(function (UserStatus $status): array {
|
||||
$user = $this->userManager->get($status->getUserId());
|
||||
$displayName = $status->getUserId();
|
||||
if ($user !== null) {
|
||||
$displayName = $user->getDisplayName();
|
||||
}
|
||||
|
||||
return [
|
||||
'userId' => $status->getUserId(),
|
||||
'displayName' => $displayName,
|
||||
'status' => $status->getStatus() === 'invisible' ? 'offline' : $status->getStatus(),
|
||||
'icon' => $status->getCustomIcon(),
|
||||
'message' => $status->getCustomMessage(),
|
||||
'timestamp' => $status->getStatusTimestamp(),
|
||||
];
|
||||
}, $recentStatusUpdates));
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import { generateFilePath } from '@nextcloud/router'
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import { translate, translatePlural } from '@nextcloud/l10n'
|
||||
import Dashboard from './views/Dashboard'
|
||||
|
||||
// eslint-disable-next-line
|
||||
__webpack_nonce__ = btoa(getRequestToken())
|
||||
|
||||
// eslint-disable-next-line
|
||||
__webpack_public_path__ = generateFilePath('user_status', '', 'js/')
|
||||
|
||||
Vue.prototype.t = translate
|
||||
Vue.prototype.n = translatePlural
|
||||
Vue.prototype.OC = OC
|
||||
Vue.prototype.OCA = OCA
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
OCA.Dashboard.register('user_status', (el) => {
|
||||
const View = Vue.extend(Dashboard)
|
||||
new View({
|
||||
propsData: {},
|
||||
}).$mount(el)
|
||||
})
|
||||
|
||||
})
|
@ -0,0 +1,100 @@
|
||||
<!--
|
||||
- @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com>
|
||||
- @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<DashboardWidget
|
||||
id="user-status_panel"
|
||||
:items="items"
|
||||
:loading="loading">
|
||||
<template v-slot:empty-content>
|
||||
<EmptyContent
|
||||
id="user_status-widget-empty-content"
|
||||
icon="icon-user-status">
|
||||
{{ t('user_status', 'No recent status changes') }}
|
||||
</EmptyContent>
|
||||
</template>
|
||||
</DashboardWidget>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DashboardWidget } from '@nextcloud/vue-dashboard'
|
||||
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import moment from '@nextcloud/moment'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
DashboardWidget,
|
||||
EmptyContent,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statuses: [],
|
||||
loading: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
items() {
|
||||
return this.statuses.map((item) => {
|
||||
const icon = item.icon || ''
|
||||
const message = item.message || ''
|
||||
const status = `${icon} ${message}`
|
||||
|
||||
let subText
|
||||
if (item.icon === null && item.message === null && item.timestamp === null) {
|
||||
subText = ''
|
||||
} else if (item.icon === null && item.message === null && item.timestamp !== null) {
|
||||
subText = moment(item.timestamp, 'X').fromNow()
|
||||
} else if (item.timestamp !== null) {
|
||||
subText = this.t('user_status', '{status}, {timestamp}', {
|
||||
status,
|
||||
timestamp: moment(item.timestamp, 'X').fromNow(),
|
||||
})
|
||||
} else {
|
||||
subText = status
|
||||
}
|
||||
|
||||
return {
|
||||
mainText: item.displayName,
|
||||
subText,
|
||||
avatarUsername: item.userId,
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
try {
|
||||
this.statuses = loadState('user_status', 'dashboard_data')
|
||||
this.loading = false
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#user_status-widget-empty-content {
|
||||
text-align: center;
|
||||
margin-top: 5vh;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2020, Georg Ehrke
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\UserStatus\Tests\Dashboard;
|
||||
|
||||
use OCA\UserStatus\Dashboard\UserStatusWidget;
|
||||
use OCA\UserStatus\Db\UserStatus;
|
||||
use OCA\UserStatus\Service\StatusService;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use Test\TestCase;
|
||||
|
||||
class UserStatusWidgetTest extends TestCase {
|
||||
|
||||
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $l10n;
|
||||
|
||||
/** @var IInitialStateService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $initialState;
|
||||
|
||||
/** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $userManager;
|
||||
|
||||
/** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $userSession;
|
||||
|
||||
/** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $service;
|
||||
|
||||
/** @var UserStatusWidget */
|
||||
private $widget;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->initialState = $this->createMock(IInitialStateService::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->userSession = $this->createMock(IUserSession::class);
|
||||
$this->service = $this->createMock(StatusService::class);
|
||||
|
||||
$this->widget = new UserStatusWidget($this->l10n, $this->initialState, $this->userManager, $this->userSession, $this->service);
|
||||
}
|
||||
|
||||
public function testGetId(): void {
|
||||
$this->assertEquals('user_status', $this->widget->getId());
|
||||
}
|
||||
|
||||
public function testGetTitle(): void {
|
||||
$this->l10n->expects($this->exactly(1))
|
||||
->method('t')
|
||||
->willReturnArgument(0);
|
||||
|
||||
$this->assertEquals('Recent statuses', $this->widget->getTitle());
|
||||
}
|
||||
|
||||
public function testGetOrder(): void {
|
||||
$this->assertEquals(5, $this->widget->getOrder());
|
||||
}
|
||||
|
||||
public function testGetIconClass(): void {
|
||||
$this->assertEquals('icon-user-status', $this->widget->getIconClass());
|
||||
}
|
||||
|
||||
public function testGetUrl(): void {
|
||||
$this->assertNull($this->widget->getUrl());
|
||||
}
|
||||
|
||||
public function testLoadNoUserSession(): void {
|
||||
$this->userSession->expects($this->once())
|
||||
->method('getUser')
|
||||
->willReturn(null);
|
||||
|
||||
$this->initialState->expects($this->once())
|
||||
->method('provideInitialState')
|
||||
->with('user_status', 'dashboard_data', []);
|
||||
|
||||
$this->service->expects($this->never())
|
||||
->method('findAllRecentStatusChanges');
|
||||
|
||||
$this->widget->load();
|
||||
}
|
||||
|
||||
public function testLoadWithCurrentUser(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUid')->willReturn('john.doe');
|
||||
$this->userSession->expects($this->once())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
|
||||
$user1 = $this->createMock(IUser::class);
|
||||
$user1->method('getDisplayName')->willReturn('User No. 1');
|
||||
|
||||
$this->userManager
|
||||
->method('get')
|
||||
->willReturnMap([
|
||||
['user_1', $user1],
|
||||
['user_2', null],
|
||||
['user_3', null],
|
||||
['user_4', null],
|
||||
['user_5', null],
|
||||
['user_6', null],
|
||||
['user_7', null],
|
||||
]);
|
||||
|
||||
$userStatuses = [
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_1',
|
||||
'status' => 'online',
|
||||
'customIcon' => '💻',
|
||||
'customMessage' => 'Working',
|
||||
'statusTimestamp' => 5000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_2',
|
||||
'status' => 'away',
|
||||
'customIcon' => '☕️',
|
||||
'customMessage' => 'Office Hangout',
|
||||
'statusTimestamp' => 6000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_3',
|
||||
'status' => 'dnd',
|
||||
'customIcon' => null,
|
||||
'customMessage' => null,
|
||||
'statusTimestamp' => 7000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'john.doe',
|
||||
'status' => 'away',
|
||||
'customIcon' => '☕️',
|
||||
'customMessage' => 'Office Hangout',
|
||||
'statusTimestamp' => 90000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_4',
|
||||
'status' => 'dnd',
|
||||
'customIcon' => null,
|
||||
'customMessage' => null,
|
||||
'statusTimestamp' => 7000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_5',
|
||||
'status' => 'invisible',
|
||||
'customIcon' => '🏝',
|
||||
'customMessage' => 'On vacation',
|
||||
'statusTimestamp' => 7000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_6',
|
||||
'status' => 'offline',
|
||||
'customIcon' => null,
|
||||
'customMessage' => null,
|
||||
'statusTimestamp' => 7000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_7',
|
||||
'status' => 'invisible',
|
||||
'customIcon' => null,
|
||||
'customMessage' => null,
|
||||
'statusTimestamp' => 7000,
|
||||
]),
|
||||
];
|
||||
|
||||
$this->service->expects($this->once())
|
||||
->method('findAllRecentStatusChanges')
|
||||
->with(8, 0)
|
||||
->willReturn($userStatuses);
|
||||
|
||||
$this->initialState->expects($this->once())
|
||||
->method('provideInitialState')
|
||||
->with('user_status', 'dashboard_data', $this->callback(function ($data): bool {
|
||||
$this->assertEquals([
|
||||
[
|
||||
'userId' => 'user_1',
|
||||
'displayName' => 'User No. 1',
|
||||
'status' => 'online',
|
||||
'icon' => '💻',
|
||||
'message' => 'Working',
|
||||
'timestamp' => 5000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_2',
|
||||
'displayName' => 'user_2',
|
||||
'status' => 'away',
|
||||
'icon' => '☕️',
|
||||
'message' => 'Office Hangout',
|
||||
'timestamp' => 6000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_3',
|
||||
'displayName' => 'user_3',
|
||||
'status' => 'dnd',
|
||||
'icon' => null,
|
||||
'message' => null,
|
||||
'timestamp' => 7000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_4',
|
||||
'displayName' => 'user_4',
|
||||
'status' => 'dnd',
|
||||
'icon' => null,
|
||||
'message' => null,
|
||||
'timestamp' => 7000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_5',
|
||||
'displayName' => 'user_5',
|
||||
'status' => 'offline',
|
||||
'icon' => '🏝',
|
||||
'message' => 'On vacation',
|
||||
'timestamp' => 7000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_6',
|
||||
'displayName' => 'user_6',
|
||||
'status' => 'offline',
|
||||
'icon' => null,
|
||||
'message' => null,
|
||||
'timestamp' => 7000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_7',
|
||||
'displayName' => 'user_7',
|
||||
'status' => 'offline',
|
||||
'icon' => null,
|
||||
'message' => null,
|
||||
'timestamp' => 7000,
|
||||
],
|
||||
], $data);
|
||||
return true;
|
||||
}));
|
||||
|
||||
$this->widget->load();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue