Compare commits

...

5 Commits

Author SHA1 Message Date
provokateurin f53814b2f9 feat(dashboard): Document supported API versions and features
Signed-off-by: provokateurin <kate@provokateurin.de>
2024-07-19 09:01:32 +02:00
provokateurin 1987e4d39b feat(core): Expose app versions, API versions and features in capabilities
Signed-off-by: provokateurin <kate@provokateurin.de>
2024-07-19 09:00:02 +02:00
provokateurin 908bafc5d5 fix(core,user_ldap): Extend correct OCSController
Signed-off-by: provokateurin <kate@provokateurin.de>
2024-07-19 08:59:53 +02:00
provokateurin 815fe7fb8b feat(Capabilities): Add IFeature
Signed-off-by: provokateurin <kate@provokateurin.de>
2024-07-19 08:59:47 +02:00
provokateurin ed1d8d162c feat(appinfo): Add api-version to info.xml
Signed-off-by: provokateurin <kate@provokateurin.de>
2024-07-19 08:55:13 +02:00
23 changed files with 323 additions and 32 deletions
+3
View File
@@ -13,6 +13,9 @@
The Nextcloud Dashboard is your starting point of the day, giving you an overview of your upcoming appointments, urgent emails, chat messages, incoming tickets, latest tweets and much more! People can add the widgets they like and change the background to their liking.]]>
</description>
<version>7.10.0</version>
<api-version>1</api-version>
<api-version>2</api-version>
<api-version>3</api-version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Dashboard</namespace>
@@ -7,6 +7,8 @@ $baseDir = $vendorDir;
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'OCA\\Dashboard\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
'OCA\\Dashboard\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
'OCA\\Dashboard\\Controller\\DashboardApiController' => $baseDir . '/../lib/Controller/DashboardApiController.php',
'OCA\\Dashboard\\Controller\\DashboardController' => $baseDir . '/../lib/Controller/DashboardController.php',
'OCA\\Dashboard\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
@@ -22,6 +22,8 @@ class ComposerStaticInitDashboard
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'OCA\\Dashboard\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
'OCA\\Dashboard\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
'OCA\\Dashboard\\Controller\\DashboardApiController' => __DIR__ . '/..' . '/../lib/Controller/DashboardApiController.php',
'OCA\\Dashboard\\Controller\\DashboardController' => __DIR__ . '/..' . '/../lib/Controller/DashboardController.php',
'OCA\\Dashboard\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Dashboard\AppInfo;
use OCA\Dashboard\Capabilities;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
class Application extends App implements IBootstrap {
public const APP_ID = 'dashboard';
public function __construct(array $urlParams = []) {
parent::__construct(self::APP_ID, $urlParams);
}
public function register(IRegistrationContext $context): void {
$context->registerCapability(Capabilities::class);
}
public function boot(IBootContext $context): void {
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Dashboard;
use OCP\Capabilities\ICapability;
use OCP\Capabilities\IFeature;
class Capabilities implements ICapability, IFeature {
/**
* @return array{dashboard: array{enabled: bool}}
*/
public function getCapabilities(): array {
return [
'dashboard' => [
'enabled' => true,
],
];
}
public function getFeatures(): array {
return [
'dashboard' => [
'widgets-v1',
'widget-items-v1',
'widget-items-v2',
'layout-v3',
'statuses-v3',
],
];
}
}
+19
View File
@@ -20,6 +20,25 @@
}
},
"schemas": {
"Capabilities": {
"type": "object",
"required": [
"dashboard"
],
"properties": {
"dashboard": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "boolean"
}
}
}
}
},
"OCSMeta": {
"type": "object",
"required": [
@@ -5,9 +5,6 @@
*/
namespace OCA\User_LDAP\Controller;
use OC\CapabilitiesManager;
use OC\Core\Controller\OCSController;
use OC\Security\IdentityProof\Manager;
use OCA\User_LDAP\Configuration;
use OCA\User_LDAP\ConnectionFactory;
use OCA\User_LDAP\Helper;
@@ -16,31 +13,19 @@ use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
class ConfigAPIController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
CapabilitiesManager $capabilitiesManager,
IUserSession $userSession,
IUserManager $userManager,
Manager $keyManager,
private Helper $ldapHelper,
private LoggerInterface $logger,
private ConnectionFactory $connectionFactory
) {
parent::__construct(
$appName,
$request,
$capabilitiesManager,
$userSession,
$userManager,
$keyManager
);
parent::__construct($appName, $request);
}
/**
+13 -1
View File
@@ -5,8 +5,10 @@
*/
namespace OC\Core\Controller;
use OC\App\AppManager;
use OC\CapabilitiesManager;
use OC\Security\IdentityProof\Manager;
use OC_App;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\OpenAPI;
@@ -23,6 +25,7 @@ class OCSController extends \OCP\AppFramework\OCSController {
private IUserSession $userSession,
private IUserManager $userManager,
private Manager $keyManager,
private AppManager $appManager,
) {
parent::__construct($appName, $request);
}
@@ -49,7 +52,7 @@ class OCSController extends \OCP\AppFramework\OCSController {
*
* Get the capabilities
*
* @return DataResponse<Http::STATUS_OK, array{version: array{major: int, minor: int, micro: int, string: string, edition: '', extendedSupport: bool}, capabilities: array<string, mixed>}, array{}>
* @return DataResponse<Http::STATUS_OK, array{version: array{major: int, minor: int, micro: int, string: string, edition: '', extendedSupport: bool}, capabilities: array<string, mixed>, features: array<string, list<string>>, apps: array<string, array{version: string, api_versions: list<string>}>}, array{}>
*
* 200: Capabilities returned
*/
@@ -72,6 +75,15 @@ class OCSController extends \OCP\AppFramework\OCSController {
$result['capabilities'] = $this->capabilitiesManager->getCapabilities(true);
}
$result['features'] = $this->capabilitiesManager->getFeatures();
$result['apps'] = [];
foreach (OC_App::getEnabledApps() as $app) {
$info = $this->appManager->getAppInfo($app);
$result['apps'][$app]['version'] = (string)$info['version'];
$result['apps'][$app]['api_versions'] = array_values(array_map(static fn ($apiVersion) => (string)$apiVersion, (array)$info['api-version']));
}
$response = new DataResponse($result);
$response->setETag(md5(json_encode($result)));
return $response;
+2 -7
View File
@@ -5,17 +5,15 @@
*/
namespace OC\Core\Controller;
use OC\CapabilitiesManager;
use OC\Security\IdentityProof\Manager;
use OC\Updater\ChangesCheck;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\Defaults;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\L10N\IFactory;
@@ -23,16 +21,13 @@ class WhatsNewController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
CapabilitiesManager $capabilitiesManager,
private IUserSession $userSession,
IUserManager $userManager,
Manager $keyManager,
private IConfig $config,
private ChangesCheck $whatsNewService,
private IFactory $langFactory,
private Defaults $defaults,
) {
parent::__construct($appName, $request, $capabilitiesManager, $userSession, $userManager, $keyManager);
parent::__construct($appName, $request);
}
/**
+33 -1
View File
@@ -2724,7 +2724,9 @@
"type": "object",
"required": [
"version",
"capabilities"
"capabilities",
"features",
"apps"
],
"properties": {
"version": {
@@ -2766,6 +2768,36 @@
"additionalProperties": {
"type": "object"
}
},
"features": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"apps": {
"type": "object",
"additionalProperties": {
"type": "object",
"required": [
"version",
"api_versions"
],
"properties": {
"version": {
"type": "string"
},
"api_versions": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
+33 -1
View File
@@ -2724,7 +2724,9 @@
"type": "object",
"required": [
"version",
"capabilities"
"capabilities",
"features",
"apps"
],
"properties": {
"version": {
@@ -2766,6 +2768,36 @@
"additionalProperties": {
"type": "object"
}
},
"features": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"apps": {
"type": "object",
"additionalProperties": {
"type": "object",
"required": [
"version",
"api_versions"
],
"properties": {
"version": {
"type": "string"
},
"api_versions": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
@@ -178,6 +178,7 @@ return array(
'OCP\\Calendar\\Room\\IRoom' => $baseDir . '/lib/public/Calendar/Room/IRoom.php',
'OCP\\Calendar\\Room\\IRoomMetadata' => $baseDir . '/lib/public/Calendar/Room/IRoomMetadata.php',
'OCP\\Capabilities\\ICapability' => $baseDir . '/lib/public/Capabilities/ICapability.php',
'OCP\\Capabilities\\IFeature' => $baseDir . '/lib/public/Capabilities/IFeature.php',
'OCP\\Capabilities\\IInitialStateExcludedCapability' => $baseDir . '/lib/public/Capabilities/IInitialStateExcludedCapability.php',
'OCP\\Capabilities\\IPublicCapability' => $baseDir . '/lib/public/Capabilities/IPublicCapability.php',
'OCP\\Collaboration\\AutoComplete\\AutoCompleteEvent' => $baseDir . '/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php',
@@ -211,6 +211,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Calendar\\Room\\IRoom' => __DIR__ . '/../../..' . '/lib/public/Calendar/Room/IRoom.php',
'OCP\\Calendar\\Room\\IRoomMetadata' => __DIR__ . '/../../..' . '/lib/public/Calendar/Room/IRoomMetadata.php',
'OCP\\Capabilities\\ICapability' => __DIR__ . '/../../..' . '/lib/public/Capabilities/ICapability.php',
'OCP\\Capabilities\\IFeature' => __DIR__ . '/../../..' . '/lib/public/Capabilities/IFeature.php',
'OCP\\Capabilities\\IInitialStateExcludedCapability' => __DIR__ . '/../../..' . '/lib/public/Capabilities/IInitialStateExcludedCapability.php',
'OCP\\Capabilities\\IPublicCapability' => __DIR__ . '/../../..' . '/lib/public/Capabilities/IPublicCapability.php',
'OCP\\Collaboration\\AutoComplete\\AutoCompleteEvent' => __DIR__ . '/../../..' . '/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php',
+3
View File
@@ -119,6 +119,9 @@ class InfoParser {
if (!array_key_exists('backend', $array['dependencies'])) {
$array['dependencies']['backend'] = [];
}
if (!array_key_exists('api-version', $array)) {
$array['api-version'] = [];
}
if (array_key_exists('types', $array)) {
if (is_array($array['types'])) {
+31 -2
View File
@@ -6,10 +6,12 @@ declare(strict_types=1);
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC;
use OCP\AppFramework\QueryException;
use OCP\Capabilities\ICapability;
use OCP\Capabilities\IFeature;
use OCP\Capabilities\IInitialStateExcludedCapability;
use OCP\Capabilities\IPublicCapability;
use Psr\Log\LoggerInterface;
@@ -35,10 +37,10 @@ class CapabilitiesManager {
* Get an array of al the capabilities that are registered at this manager
*
* @param bool $public get public capabilities only
* @throws \InvalidArgumentException
* @return array<string, mixed>
* @throws \InvalidArgumentException
*/
public function getCapabilities(bool $public = false, bool $initialState = false) : array {
public function getCapabilities(bool $public = false, bool $initialState = false): array {
$capabilities = [];
foreach ($this->capabilities as $capability) {
try {
@@ -87,6 +89,33 @@ class CapabilitiesManager {
return $capabilities;
}
/**
* @return array<string, list<string>>
*/
public function getFeatures(): array {
$features = [];
foreach ($this->capabilities as $capability) {
try {
$c = $capability();
} catch (QueryException $e) {
$this->logger->error('CapabilitiesManager', [
'exception' => $e,
]);
continue;
}
if ($c instanceof ICapability) {
if ($c instanceof IFeature) {
$features = array_merge_recursive($features, $c->getFeatures());
}
} else {
throw new \InvalidArgumentException('The given Capability (' . get_class($c) . ') does not implement the ICapability interface');
}
}
return $features;
}
/**
* In order to improve lazy loading a closure can be registered which will be called in case
* capabilities are actually requested
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace OCP\Capabilities;
/**
* Interface for apps to expose their available features.
*
* @since 30.0.0
*/
interface IFeature {
/**
* Returns the available features.
*
* ```php
* return [
* 'myapp' => [
* 'feature1',
* 'feature2',
* ],
* 'otherapp' => [
* 'feature3',
* ],
* ];
* ```
*
* @return array<string, list<string>>
* @since 30.0.0
*/
public function getFeatures(): array;
}
+2
View File
@@ -18,6 +18,8 @@
maxOccurs="unbounded"/>
<xs:element name="version" type="semver"
minOccurs="1" maxOccurs="1"/>
<xs:element name="api-version" type="semver"
minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="licence" type="licence" minOccurs="1"
maxOccurs="unbounded"/>
<xs:element name="author" type="author" minOccurs="1"
+2
View File
@@ -18,6 +18,8 @@
maxOccurs="unbounded"/>
<xs:element name="version" type="semver"
minOccurs="1" maxOccurs="1"/>
<xs:element name="api-version" type="semver"
minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="licence" type="licence" minOccurs="1"
maxOccurs="unbounded"/>
<xs:element name="author" type="author" minOccurs="1"
+1
View File
@@ -79,6 +79,7 @@
},
"background-jobs": [],
"two-factor-providers": [],
"api-version": [1, 2],
"commands": [],
"activity": {
"filters": [],
+2 -1
View File
@@ -82,5 +82,6 @@
"live-migration": [],
"uninstall": []
},
"two-factor-providers": []
"two-factor-providers": [],
"api-version": []
}
+2 -1
View File
@@ -88,5 +88,6 @@
"live-migration": [],
"uninstall": []
},
"two-factor-providers": []
"two-factor-providers": [],
"api-version": []
}
+2
View File
@@ -36,4 +36,6 @@
<owncloud min-version="7.0.1" max-version="8" />
<backend>caldav</backend>
</dependencies>
<api-version>1</api-version>
<api-version>2</api-version>
</info>
+68 -1
View File
@@ -7,9 +7,11 @@
namespace Test;
use InvalidArgumentException;
use OC\CapabilitiesManager;
use OCP\AppFramework\QueryException;
use OCP\Capabilities\ICapability;
use OCP\Capabilities\IFeature;
use OCP\Capabilities\IPublicCapability;
use Psr\Log\LoggerInterface;
@@ -68,7 +70,7 @@ class CapabilitiesManagerTest extends TestCase {
* Test that we need something that implents ICapability
*/
public function testNoICapability() {
$this->expectException(\InvalidArgumentException::class);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The given Capability (Test\\NoCapability) does not implement the ICapability interface');
$this->manager->registerCapability(function () {
@@ -140,6 +142,24 @@ class CapabilitiesManagerTest extends TestCase {
$this->assertEquals([], $res);
}
public function testFeatures() {
$this->manager->registerCapability(fn () => new FeatureFoo());
$res = $this->manager->getFeatures();
$this->assertEquals(['foo' => ['123', '456']], $res);
$this->manager->registerCapability(fn () => new FeatureBar());
$res = $this->manager->getFeatures();
$this->assertEquals(['foo' => ['123', '456'], 'bar' => ['789']], $res);
$this->manager->registerCapability(fn () => new FeatureFooBar());
$res = $this->manager->getFeatures();
$this->assertEquals(['foo' => ['123', '456', '789'], 'bar' => ['789', '123', '456']], $res);
}
}
class SimpleCapability implements ICapability {
@@ -193,3 +213,50 @@ class DeepCapability implements ICapability {
];
}
}
class FeatureFoo implements ICapability, IFeature {
public function getCapabilities() {
return [];
}
public function getFeatures(): array {
return [
'foo' => [
'123',
'456',
],
];
}
}
class FeatureBar implements ICapability, IFeature {
public function getCapabilities() {
return [];
}
public function getFeatures(): array {
return [
'bar' => [
'789',
],
];
}
}
class FeatureFooBar implements ICapability, IFeature {
public function getCapabilities() {
return [];
}
public function getFeatures(): array {
return [
'foo' => [
'789',
],
'bar' => [
'123',
'456',
],
];
}
}