Skip to content

Commit a01210f

Browse files
committed
feat(dashboard): implement widget item api v2
This API enables the dashboard to render all widgets from the API data alone without having apps to provide their own bundles. This saves a lot of traffic and execution time as a lot less javascript has to be parsed on the frontend. Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
1 parent 4c2c53e commit a01210f

43 files changed

Lines changed: 702 additions & 438 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/dashboard/appinfo/routes.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*
88
* @author Julien Veyssier <eneiluj@posteo.net>
99
* @author Julius Härtl <jus@bitgrid.net>
10+
* @author Richard Steinmetz <richard@steinmetz.cloud>
1011
*
1112
* @license GNU AGPL version 3 or any later version
1213
*
@@ -33,5 +34,6 @@
3334
'ocs' => [
3435
['name' => 'dashboardApi#getWidgets', 'url' => '/api/v1/widgets', 'verb' => 'GET'],
3536
['name' => 'dashboardApi#getWidgetItems', 'url' => '/api/v1/widget-items', 'verb' => 'GET'],
37+
['name' => 'dashboardApi#getWidgetItemsV2', 'url' => '/api/v2/widget-items', 'verb' => 'GET'],
3638
]
3739
];

apps/dashboard/lib/Controller/DashboardApiController.php

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*
88
* @author Julien Veyssier <eneiluj@posteo.net>
99
* @author Kate Döen <kate.doeen@nextcloud.com>
10+
* @author Richard Steinmetz <richard@steinmetz.cloud>
1011
*
1112
* @license GNU AGPL version 3 or any later version
1213
*
@@ -35,18 +36,22 @@
3536
use OCP\Dashboard\IIconWidget;
3637
use OCP\Dashboard\IOptionWidget;
3738
use OCP\Dashboard\IManager;
39+
use OCP\Dashboard\IReloadableWidget;
3840
use OCP\Dashboard\IWidget;
3941
use OCP\Dashboard\Model\WidgetButton;
4042
use OCP\Dashboard\Model\WidgetOptions;
4143
use OCP\IConfig;
4244
use OCP\IRequest;
4345

4446
use OCP\Dashboard\IAPIWidget;
47+
use OCP\Dashboard\IAPIWidgetV2;
4548
use OCP\Dashboard\Model\WidgetItem;
49+
use OCP\Dashboard\Model\WidgetItems;
4650

4751
/**
4852
* @psalm-import-type DashboardWidget from ResponseDefinitions
4953
* @psalm-import-type DashboardWidgetItem from ResponseDefinitions
54+
* @psalm-import-type DashboardWidgetItems from ResponseDefinitions
5055
*/
5156
class DashboardApiController extends OCSController {
5257

@@ -71,6 +76,24 @@ public function __construct(
7176
$this->userId = $userId;
7277
}
7378

79+
/**
80+
* @param string[] $widgetIds Limit widgets to given ids
81+
* @return IWidget[]
82+
*/
83+
private function getShownWidgets(array $widgetIds): array {
84+
if (empty($widgetIds)) {
85+
$systemDefault = $this->config->getAppValue('dashboard', 'layout', 'recommendations,spreed,mail,calendar');
86+
$widgetIds = explode(',', $this->config->getUserValue($this->userId, 'dashboard', 'layout', $systemDefault));
87+
}
88+
89+
return array_filter(
90+
$this->dashboardManager->getWidgets(),
91+
static function (IWidget $widget) use ($widgetIds) {
92+
return in_array($widget->getId(), $widgetIds);
93+
},
94+
);
95+
}
96+
7497
/**
7598
* @NoAdminRequired
7699
* @NoCSRFRequired
@@ -83,18 +106,11 @@ public function __construct(
83106
* @return DataResponse<Http::STATUS_OK, array<string, DashboardWidgetItem[]>, array{}>
84107
*/
85108
public function getWidgetItems(array $sinceIds = [], int $limit = 7, array $widgets = []): DataResponse {
86-
$showWidgets = $widgets;
87109
$items = [];
88-
89-
if (empty($showWidgets)) {
90-
$systemDefault = $this->config->getAppValue('dashboard', 'layout', 'recommendations,spreed,mail,calendar');
91-
$showWidgets = explode(',', $this->config->getUserValue($this->userId, 'dashboard', 'layout', $systemDefault));
92-
}
93-
94-
$widgets = $this->dashboardManager->getWidgets();
110+
$widgets = $this->getShownWidgets($widgets);
95111
foreach ($widgets as $widget) {
96-
if ($widget instanceof IAPIWidget && in_array($widget->getId(), $showWidgets)) {
97-
$items[$widget->getId()] = array_map(function (WidgetItem $item) {
112+
if ($widget instanceof IAPIWidget) {
113+
$items[$widget->getId()] = array_map(static function (WidgetItem $item) {
98114
return $item->jsonSerialize();
99115
}, $widget->getItems($this->userId, $sinceIds[$widget->getId()] ?? null, $limit));
100116
}
@@ -103,6 +119,31 @@ public function getWidgetItems(array $sinceIds = [], int $limit = 7, array $widg
103119
return new DataResponse($items);
104120
}
105121

122+
/**
123+
* @NoAdminRequired
124+
* @NoCSRFRequired
125+
*
126+
* Get the items for the widgets
127+
*
128+
* @param array<string, string> $sinceIds Array indexed by widget Ids, contains date/id from which we want the new items
129+
* @param int $limit Limit number of result items per widget
130+
* @param string[] $widgets Limit results to specific widgets
131+
* @return DataResponse<Http::STATUS_OK, array<string, DashboardWidgetItems>, array{}>
132+
*/
133+
public function getWidgetItemsV2(array $sinceIds = [], int $limit = 7, array $widgets = []): DataResponse {
134+
$items = [];
135+
$widgets = $this->getShownWidgets($widgets);
136+
foreach ($widgets as $widget) {
137+
if ($widget instanceof IAPIWidgetV2) {
138+
$items[$widget->getId()] = $widget
139+
->getItemsV2($this->userId, $sinceIds[$widget->getId()] ?? null, $limit)
140+
->jsonSerialize();
141+
}
142+
}
143+
144+
return new DataResponse($items);
145+
}
146+
106147
/**
107148
* Get the widgets
108149
*
@@ -124,6 +165,8 @@ public function getWidgets(): DataResponse {
124165
'icon_url' => ($widget instanceof IIconWidget) ? $widget->getIconUrl() : '',
125166
'widget_url' => $widget->getUrl(),
126167
'item_icons_round' => $options->withRoundItemIcons(),
168+
'item_api_versions' => [],
169+
'reload_interval' => 0,
127170
];
128171
if ($widget instanceof IButtonWidget) {
129172
$data += [
@@ -136,6 +179,15 @@ public function getWidgets(): DataResponse {
136179
}, $widget->getWidgetButtons($this->userId)),
137180
];
138181
}
182+
if ($widget instanceof IReloadableWidget) {
183+
$data['reload_interval'] = $widget->getReloadInterval();
184+
}
185+
if ($widget instanceof IAPIWidget) {
186+
$data['item_api_versions'][] = 1;
187+
}
188+
if ($widget instanceof IAPIWidgetV2) {
189+
$data['item_api_versions'][] = 2;
190+
}
139191
return $data;
140192
}, $widgets);
141193

apps/dashboard/lib/ResponseDefinitions.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com>
66
*
77
* @author Kate Döen <kate.doeen@nextcloud.com>
8+
* @author Richard Steinmetz <richard@steinmetz.cloud>
89
*
910
* @license GNU AGPL version 3 or any later version
1011
*
@@ -34,6 +35,8 @@
3435
* icon_url: string,
3536
* widget_url: ?string,
3637
* item_icons_round: bool,
38+
* item_api_versions: int[],
39+
* reload_interval: int,
3740
* buttons?: array{
3841
* type: string,
3942
* text: string,
@@ -46,8 +49,15 @@
4649
* title: string,
4750
* link: string,
4851
* iconUrl: string,
52+
* overlayIconUrl: string,
4953
* sinceId: string,
5054
* }
55+
*
56+
* @psalm-type DashboardWidgetItems = array{
57+
* items: DashboardWidgetItem[],
58+
* emptyContentMessage: string,
59+
* halfEmptyContentMessage: string,
60+
* }
5161
*/
5262
class ResponseDefinitions {
5363
}

apps/dashboard/openapi.json

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
"icon_class",
5454
"icon_url",
5555
"widget_url",
56-
"item_icons_round"
56+
"item_icons_round",
57+
"item_api_versions",
58+
"reload_interval"
5759
],
5860
"properties": {
5961
"id": {
@@ -79,6 +81,17 @@
7981
"item_icons_round": {
8082
"type": "boolean"
8183
},
84+
"item_api_versions": {
85+
"type": "array",
86+
"items": {
87+
"type": "integer",
88+
"format": "int64"
89+
}
90+
},
91+
"reload_interval": {
92+
"type": "integer",
93+
"format": "int64"
94+
},
8295
"buttons": {
8396
"type": "array",
8497
"items": {
@@ -110,6 +123,7 @@
110123
"title",
111124
"link",
112125
"iconUrl",
126+
"overlayIconUrl",
113127
"sinceId"
114128
],
115129
"properties": {
@@ -125,10 +139,35 @@
125139
"iconUrl": {
126140
"type": "string"
127141
},
142+
"overlayIconUrl": {
143+
"type": "string"
144+
},
128145
"sinceId": {
129146
"type": "string"
130147
}
131148
}
149+
},
150+
"WidgetItems": {
151+
"type": "object",
152+
"required": [
153+
"items",
154+
"emptyContentMessage",
155+
"halfEmptyContentMessage"
156+
],
157+
"properties": {
158+
"items": {
159+
"type": "array",
160+
"items": {
161+
"$ref": "#/components/schemas/WidgetItem"
162+
}
163+
},
164+
"emptyContentMessage": {
165+
"type": "string"
166+
},
167+
"halfEmptyContentMessage": {
168+
"type": "string"
169+
}
170+
}
132171
}
133172
}
134173
},
@@ -291,6 +330,99 @@
291330
}
292331
}
293332
}
333+
},
334+
"/ocs/v2.php/apps/dashboard/api/v2/widget-items": {
335+
"get": {
336+
"operationId": "dashboard_api-get-widget-items-v2",
337+
"summary": "Get the items for the widgets",
338+
"tags": [
339+
"dashboard_api"
340+
],
341+
"security": [
342+
{
343+
"bearer_auth": []
344+
},
345+
{
346+
"basic_auth": []
347+
}
348+
],
349+
"parameters": [
350+
{
351+
"name": "sinceIds",
352+
"in": "query",
353+
"description": "Array indexed by widget Ids, contains date/id from which we want the new items",
354+
"schema": {
355+
"type": "string"
356+
}
357+
},
358+
{
359+
"name": "limit",
360+
"in": "query",
361+
"description": "Limit number of result items per widget",
362+
"schema": {
363+
"type": "integer",
364+
"format": "int64",
365+
"default": 7
366+
}
367+
},
368+
{
369+
"name": "widgets[]",
370+
"in": "query",
371+
"description": "Limit results to specific widgets",
372+
"schema": {
373+
"type": "array",
374+
"default": [],
375+
"items": {
376+
"type": "string"
377+
}
378+
}
379+
},
380+
{
381+
"name": "OCS-APIRequest",
382+
"in": "header",
383+
"required": true,
384+
"schema": {
385+
"type": "string",
386+
"default": "true"
387+
}
388+
}
389+
],
390+
"responses": {
391+
"200": {
392+
"description": "",
393+
"content": {
394+
"application/json": {
395+
"schema": {
396+
"type": "object",
397+
"required": [
398+
"ocs"
399+
],
400+
"properties": {
401+
"ocs": {
402+
"type": "object",
403+
"required": [
404+
"meta",
405+
"data"
406+
],
407+
"properties": {
408+
"meta": {
409+
"$ref": "#/components/schemas/OCSMeta"
410+
},
411+
"data": {
412+
"type": "object",
413+
"additionalProperties": {
414+
"$ref": "#/components/schemas/WidgetItems"
415+
}
416+
}
417+
}
418+
}
419+
}
420+
}
421+
}
422+
}
423+
}
424+
}
425+
}
294426
}
295427
},
296428
"tags": []

0 commit comments

Comments
 (0)