Skip to content
Merged
Next Next commit
feat: add Dashboard projects filters, add enforce instance URL setting
Signed-off-by: Andrey Borysenko <andrey18106x@gmail.com>
  • Loading branch information
andrey18106 committed May 20, 2025
commit 97e9211b0eabc982a8328a5c0b171b6e121293b2
4 changes: 3 additions & 1 deletion appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
['name' => 'config#oauthRedirect', 'url' => '/oauth-redirect', 'verb' => 'GET'],
['name' => 'config#setConfig', 'url' => '/config', 'verb' => 'PUT'],
['name' => 'config#addAccount', 'url' => '/account', 'verb' => 'POST'],
['name' => 'config#updateAccount', 'url' => '/account/{id}', 'verb' => 'PUT'],
['name' => 'config#updateAccountFilters', 'url' => '/account/{id}/filters', 'verb' => 'PUT'],
['name' => 'config#deleteAccount', 'url' => '/account/{id}', 'verb' => 'DELETE'],
['name' => 'config#setAdminConfig', 'url' => '/admin-config', 'verb' => 'PUT'],
['name' => 'config#setSensitiveAdminConfig', 'url' => '/sensitive-admin-config', 'verb' => 'PUT'],

['name' => 'gitlabAPI#getTodos', 'url' => '/gitlab/{accountId}/todos', 'verb' => 'GET'],
['name' => 'gitlabAPI#getProjectsList', 'url' => '/gitlab/{accountId}/projects', 'verb' => 'GET'],
['name' => 'gitlabAPI#getGroupsList', 'url' => '/gitlab/{accountId}/groups', 'verb' => 'GET'],
['name' => 'gitlabAPI#getProjectAvatar', 'url' => '/gitlab/{accountId}/avatar/project', 'verb' => 'GET'],
['name' => 'gitlabAPI#getUserAvatar', 'url' => '/gitlab/{accountId}/avatar/user/{userId}', 'verb' => 'GET'],
]
Expand Down
17 changes: 17 additions & 0 deletions lib/Controller/ConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,23 @@ public function addAccount(string $url, string $token) {
}
}

public function updateAccountFilters(int $id, array $projects = [], array $groups = []) {
try {
$result = $this->accountMapper->updateFilters($id, $projects, $groups) === 1;

if (!$result) {
return new DataResponse([], 500);
}
return new DataResponse([]);
} catch (DoesNotExistException $e) {
$this->logger->error('Requested Gitlab account with id ' . $id . 'not found');
return new DataResponse([], 404);
} catch (Exception $e) {
$this->logger->error('Failed to update Gitlab account: ' . $e->getMessage(), ['exception' => $e]);
return new DataResponse([], 500);
}
}

/**
* @PasswordConfirmationRequired
* @NoAdminRequired
Expand Down
48 changes: 46 additions & 2 deletions lib/Controller/GitlabAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,58 @@ public function getProjectAvatar(int $accountId, int $projectId) {
* @return DataResponse
* @throws Exception
*/
public function getTodos(int $accountId, ?string $since = null): DataResponse {
public function getTodos(int $accountId, ?string $since = null, ?string $groupId = null): DataResponse {
try {
$account = $this->accountMapper->findById($this->userId, $accountId);
if ($account->getClearToken() === '') {
return new DataResponse('', 400);
}

$result = $this->gitlabAPIService->getTodos($account, $since);
$result = $this->gitlabAPIService->getTodos($account, $since, $groupId);
if (isset($result['error'])) {
return new DataResponse($result, 401);
}

return new DataResponse($result);
} catch (DoesNotExistException $e) {
$this->logger->error('Requested Gitlab account with id ' . $accountId . 'not found');
return new DataResponse([], 404);
} catch (\OCP\DB\Exception $e) {
$this->logger->error('Failed to query Gitlab account with id ' . $accountId . ': ' . $e->getMessage(), ['exception' => $e]);
return new DataResponse([], 500);
}
}

public function getProjectsList(int $accountId): DataResponse {
try {
$account = $this->accountMapper->findById($this->userId, $accountId);
if ($account->getClearToken() === '') {
return new DataResponse('', 400);
}

$result = $this->gitlabAPIService->getProjectsList($account);
if (isset($result['error'])) {
return new DataResponse($result, 401);
}

return new DataResponse($result);
} catch (DoesNotExistException $e) {
$this->logger->error('Requested Gitlab account with id ' . $accountId . 'not found');
return new DataResponse([], 404);
} catch (\OCP\DB\Exception $e) {
$this->logger->error('Failed to query Gitlab account with id ' . $accountId . ': ' . $e->getMessage(), ['exception' => $e]);
return new DataResponse([], 500);
}
}

public function getGroupsList(int $accountId): DataResponse {
try {
$account = $this->accountMapper->findById($this->userId, $accountId);
if ($account->getClearToken() === '') {
return new DataResponse('', 400);
}

$result = $this->gitlabAPIService->getGroupsList($account);
if (isset($result['error'])) {
return new DataResponse($result, 401);
}
Expand Down
15 changes: 14 additions & 1 deletion lib/Dashboard/GitlabWidget.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
namespace OCA\Gitlab\Dashboard;

use OCA\Gitlab\AppInfo\Application;
use OCA\Gitlab\Db\GitlabAccountMapper;
use OCA\Gitlab\Model\UserConfig;
use OCA\Gitlab\Service\ConfigService;
use OCP\AppFramework\Services\IInitialState;
Expand All @@ -39,6 +40,7 @@ public function __construct(
private ConfigService $config,
private IURLGenerator $url,
private IInitialState $initialStateService,
private GitlabAccountMapper $accountMapper,
private string $userId,
) {
}
Expand Down Expand Up @@ -82,7 +84,18 @@ public function getUrl(): ?string {
* @inheritDoc
*/
public function load(): void {
$this->initialStateService->provideInitialState('user-config', UserConfig::loadConfig($this->userId, $this->config)->toArray());
$userConfig = UserConfig::loadConfig($this->userId, $this->config)->toArray();

try {
$account = $this->accountMapper->findById($this->userId, $userConfig['widget_account_id'])->jsonSerialize();
$userConfig['widget_projects'] = $account['widgetProjects'];
$userConfig['widget_groups'] = $account['widgetGroups'];
} catch (\Exception) {
$userConfig['widget_projects'] = [];
$userConfig['widget_groups'] = [];
}

$this->initialStateService->provideInitialState('user-config', $userConfig);
Util::addScript(Application::APP_ID, Application::APP_ID . '-dashboard');
Util::addStyle(Application::APP_ID, 'dashboard');
}
Expand Down
10 changes: 10 additions & 0 deletions lib/Db/GitlabAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
* @method void setUserInfoName(string $userInfoName)
* @method string|null getUserInfoDisplayName()
* @method void setUserInfoDisplayName(string $userInfoDisplayName)
* @method array|null getWidgetProjects()
* @method void setWidgetProjects(array $widgetProjects)
* @method array|null getWidgetGroups()
* @method void setWidgetGroups(array $widgetGroups)
*/
class GitlabAccount extends Entity implements JsonSerializable {
protected $userId;
Expand All @@ -34,6 +38,8 @@ class GitlabAccount extends Entity implements JsonSerializable {
protected $refreshToken;
protected $userInfoName;
protected $userInfoDisplayName;
protected $widgetProjects;
protected $widgetGroups;

private ICrypto $crypto;

Expand All @@ -46,6 +52,8 @@ public function __construct() {
$this->addType('refresh_token', 'string');
$this->addType('user_info_name', 'string');
$this->addType('user_info_display_name', 'string');
$this->addType('widget_projects', 'array');
$this->addType('widget_groups', 'array');

$this->crypto = \OC::$server->get(ICrypto::class);
}
Expand All @@ -60,6 +68,8 @@ public function jsonSerialize(): array {
'refreshToken' => $this->refreshToken !== '' ? 'dummyToken' : '',
'userInfoName' => $this->userInfoName,
'userInfoDisplayName' => $this->userInfoDisplayName,
'widgetProjects' => $this->widgetProjects && !is_array($this->widgetProjects) ? json_decode($this->widgetProjects) : [],
'widgetGroups' => $this->widgetGroups && !is_array($this->widgetGroups) ? json_decode($this->widgetGroups) : [],
];
}

Expand Down
16 changes: 16 additions & 0 deletions lib/Db/GitlabAccountMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,20 @@ public function findById(string $userId, int $id): GitlabAccount {

return $this->findEntity($qb);
}

/**
* @throws Exception
*/
public function updateFilters(string $accountId, array $widget_projects, array $widget_groups): int {
$qb = $this->db->getQueryBuilder();

$qb->update('gitlab_accounts')
->set('widget_projects', $qb->createNamedParameter($widget_projects, IQueryBuilder::PARAM_JSON))
->set('widget_groups', $qb->createNamedParameter($widget_groups, IQueryBuilder::PARAM_JSON))
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($accountId, IQueryBuilder::PARAM_INT))
);

return $qb->executeStatement();
}
}
7 changes: 7 additions & 0 deletions lib/Model/AdminConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ private function __construct(
public ?string $client_secret,
public ?string $oauth_instance_url,
public ?bool $link_preview_enabled,
public ?string $force_gitlab_instance_url,
) {
}

Expand All @@ -21,6 +22,7 @@ public static function loadConfig(ConfigService $config): AdminConfig {
client_secret: $config->getAdminClientSecret(),
oauth_instance_url: $config->getAdminOauthUrl(),
link_preview_enabled: $config->getAdminLinkPreviewEnabled(),
force_gitlab_instance_url: $config->getAdminForceGitlabInstanceUrl(),
);
}

Expand All @@ -37,6 +39,9 @@ public function saveConfig(ConfigService $config): void {
if ($this->link_preview_enabled !== null) {
$config->setAdminLinkPreviewEnabled($this->link_preview_enabled);
}
if ($this->force_gitlab_instance_url !== null) {
$config->setAdminForceGitlabInstanceUrl($this->force_gitlab_instance_url);
}
}

public static function fromArray(array $config): AdminConfig {
Expand All @@ -45,6 +50,7 @@ public static function fromArray(array $config): AdminConfig {
client_secret: $config['client_secret'] ?? null,
oauth_instance_url: $config['oauth_instance_url'] ?? null,
link_preview_enabled: $config['link_preview_enabled'] ?? null,
force_gitlab_instance_url: $config['force_gitlab_instance_url'] ?? null,
);
}

Expand All @@ -54,6 +60,7 @@ public function toArray(): array {
'client_secret' => $this->client_secret !== null && $this->client_secret !== '' ? 'dummyToken' : $this->client_secret,
'oauth_instance_url' => $this->oauth_instance_url,
'link_preview_enabled' => $this->link_preview_enabled,
'force_gitlab_instance_url' => $this->force_gitlab_instance_url,
];
}
}
4 changes: 4 additions & 0 deletions lib/Model/UserConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ private function __construct(
public ?string $redirect_uri,
public ?string $oauth_origin,
public ?int $widget_account_id,
public ?string $force_gitlab_instance_url,
) {
}

Expand All @@ -40,6 +41,7 @@ public static function loadConfig(string $userId, ConfigService $config): UserCo
redirect_uri: $config->getUserRedirectUri($userId),
oauth_origin: $config->getUserOauthOrigin($userId),
widget_account_id: $config->getUserWidgetAccountId($userId),
force_gitlab_instance_url: $config->getAdminForceGitlabInstanceUrl(),
);
}

Expand Down Expand Up @@ -93,6 +95,7 @@ public static function fromArray(array $config): UserConfig {
redirect_uri: $config['redirect_uri'] ?? null,
oauth_origin: $config['oauth_origin'] ?? null,
widget_account_id: $config['widget_account_id'] ?? null,
force_gitlab_instance_url: $config['force_gitlab_instance_url'] ?? null,
);
}

Expand All @@ -110,6 +113,7 @@ public function toArray(): array {
'redirect_uri' => $this->redirect_uri,
'oauth_origin' => $this->oauth_origin,
'widget_account_id' => $this->widget_account_id,
'force_gitlab_instance_url' => $this->force_gitlab_instance_url,
];
}
}
8 changes: 8 additions & 0 deletions lib/Service/ConfigService.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ public function setAdminLinkPreviewEnabled(bool $enabled): void {
$this->config->setAppValue(Application::APP_ID, 'link_preview_enabled', $enabled ? '1' : '0');
}

public function getAdminForceGitlabInstanceUrl(): string {
return $this->config->getAppValue(Application::APP_ID, 'force_gitlab_instance_url');
}

public function setAdminForceGitlabInstanceUrl(string $url): void {
$this->config->setAppValue(Application::APP_ID, 'force_gitlab_instance_url', $url);
}

public function getUserUrl(string $userId): string {
return $this->config->getUserValue($userId, Application::APP_ID, 'url') ?: $this->getAdminOauthUrl();
}
Expand Down
23 changes: 22 additions & 1 deletion lib/Service/GitlabAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,13 @@ public function searchMergeRequests(GitlabAccount $account, string $term, int $o
return array_slice($mergeRequests, $leftPadding, $limit);
}

public function getTodos(GitlabAccount $account, ?string $since = null): array {
public function getTodos(GitlabAccount $account, ?string $since = null, ?string $groupId = null): array {
$params = [
'state' => 'pending',
];
if ($groupId) {
$params['group_id'] = $groupId;
}
$result = $this->request($account, $account->getUrl(), 'todos', $params);
if (isset($result['error'])) {
return $result;
Expand Down Expand Up @@ -189,6 +192,24 @@ public function getTodos(GitlabAccount $account, ?string $since = null): array {
return $result;
}

public function getProjectsList(GitLabAccount $account, ?string $since = null): array {
$params = [
'membership' => 'true',
];
if ($since) {
$params['updated_after'] = $since;
}

return $this->request($account, $account->getUrl(), 'projects', $params);
}

public function getGroupsList(GitLabAccount $account): array {
$params = [
'membership' => 'true',
];
return $this->request($account, $account->getUrl(), 'groups', $params);
}

public function getUserAvatar(GitlabAccount $account, string $baseUrl, int $gitlabUserId): array {
$userInfo = $this->request($account, $baseUrl, 'users/' . $gitlabUserId);
if (!isset($userInfo['error']) && isset($userInfo['avatar_url'])) {
Expand Down
6 changes: 5 additions & 1 deletion lib/Settings/Personal.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ public function __construct(
public function getForm(): TemplateResponse {
$this->initialStateService->provideInitialState('accounts', array_map(static fn (GitlabAccount $account) => $account->jsonSerialize(), $this->accountMapper->find($this->userId)));
$this->initialStateService->provideInitialState('user-config', UserConfig::loadConfig($this->userId, $this->config)->toArray());
$this->initialStateService->provideInitialState('admin-config', ['oauth_is_possible' => $this->config->getAdminClientId() !== '' && $this->config->getAdminClientSecret() !== '']);
$this->initialStateService->provideInitialState('admin-config', [
'oauth_is_possible' => $this->config->getAdminClientId() !== '' && $this->config->getAdminClientSecret() !== '',
'oauth_instance_url' => $this->config->getAdminOauthUrl(),
'force_gitlab_instance_url' => $this->config->getAdminForceGitlabInstanceUrl(),
]);
return new TemplateResponse(Application::APP_ID, 'personalSettings');
}

Expand Down
12 changes: 12 additions & 0 deletions src/components/AdminSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@
:placeholder="t('integration_gitlab', 'Instance address')"
@input="onInput">
</div>
<div class="line">
<label for="gitlab-force-instance-url">
<EarthIcon :size="20" class="icon" />
{{ t('integration_gitlab', 'Restrict GitLab instance URL') }}
</label>
<input id="gitlab-force-instance-url"
v-model="state.force_gitlab_instance_url"
type="text"
:placeholder="t('integration_gitlab', 'Restrict GitLab instance URL')"
@input="onInput">
</div>
<div class="line">
<label for="gitlab-client-id">
<KeyIcon :size="20" class="icon" />
Expand Down Expand Up @@ -133,6 +144,7 @@ export default {
const values = {
client_id: this.state.client_id,
oauth_instance_url: this.state.oauth_instance_url,
force_gitlab_instance_url: this.state.force_gitlab_instance_url,
}
if (this.state.client_secret !== 'dummyToken') {
values.client_secret = this.state.client_secret
Expand Down
Loading