Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Semi-persistent local filtering on table through URL parameters #1402

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
['name' => 'view#create', 'url' => '/view', 'verb' => 'POST'],
['name' => 'view#update', 'url' => '/view/{id}', 'verb' => 'PUT'],
['name' => 'view#destroy', 'url' => '/view/{id}', 'verb' => 'DELETE'],
['name' => 'view#createTemporaryView', 'url' => '/view/{tableId}/temporary-view', 'verb' => 'POST'],

// columns
['name' => 'column#indexTableByView', 'url' => '/column/table/{tableId}/view/{viewId}', 'verb' => 'GET'],
Expand Down
9 changes: 9 additions & 0 deletions lib/Controller/ViewController.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ public function update(int $id, array $data): DataResponse {
});
}

/**
* @NoAdminRequired
*/
public function createTemporaryView(int $tableId, array $data): DataResponse {
return $this->handleError(function () use ($tableId, $data) {
return $this->service->createTemporaryView($tableId, $data, $this->userId);
});
}

/**
* @NoAdminRequired
*/
Expand Down
26 changes: 26 additions & 0 deletions lib/Db/View.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
* @method setSort(string $sort)
* @method getOwnerDisplayName(): string
* @method setOwnerDisplayName(string $ownerDisplayName)
* @method getRows(): string
* @method setRows(string $rows)
* @method getColumnValues(): string
* @method setColumnValues(string $columnValues)
*/
class View extends Entity implements JsonSerializable {
protected ?string $title = null;
Expand All @@ -73,6 +77,8 @@
protected ?int $rowsCount = 0;
protected ?string $ownership = null;
protected ?string $ownerDisplayName = null;
protected ?string $rows = null;
protected ?string $columnValues = null;

public function __construct() {
$this->addType('id', 'integer');
Expand All @@ -87,6 +93,16 @@
return $this->getArray($this->getColumns());
}


public function getRowsArray(): array {
return $this->getArray($this->getRows());
}

public function getColumnValuesArray(): array {
return $this->getArray($this->getColumnValues());
}


/**
* @psalm-suppress MismatchingDocblockReturnType
* @return list<array{columnId: int, mode: 'ASC'|'DESC'}>
Expand Down Expand Up @@ -125,6 +141,14 @@
$this->setColumns(\json_encode($array));
}

public function setRowsArray(array $array):void {
$this->setRows(\json_encode($array));
}

public function setColumnValuesArray(array $array):void {
$this->setColumnValues(\json_encode($array));
}

public function setSortArray(array $array):void {
$this->setSort(\json_encode($array));
}
Expand All @@ -146,7 +170,7 @@
}

/**
* @psalm-return TablesView

Check failure on line 173 in lib/Db/View.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

InvalidReturnType

lib/Db/View.php:173:19: InvalidReturnType: The declared return type 'array{columns: array<array-key, int>, createdAt: string, createdBy: string, description: null|string, emoji: null|string, favorite: bool, filter: list<list<array{columnId: int, operator: 'begins-with'|'contains'|'ends-with'|'is-empty'|'is-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal', value: float|int|string}>>, hasShares: bool, id: int, isShared: bool, lastEditAt: string, lastEditBy: string, onSharePermissions: array{create: bool, delete: bool, manage: bool, read: bool, update: bool}|null, ownerDisplayName: null|string, ownership: string, rowsCount: int, sort: list<array{columnId: int, mode: 'ASC'|'DESC'}>, tableId: int, title: string}' for OCA\Tables\Db\View::jsonSerialize is incorrect, got 'array{columnValues: array<array-key, mixed>, columns: array<array-key, int>, createdAt: string, createdBy: string, description: null|string, emoji: null|string, favorite: bool, filter: list<list<array{columnId: int, operator: 'begins-with'|'contains'|'ends-with'|'is-empty'|'is-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal', value: float|int|string}>>, hasShares: bool, id: int, isShared: bool, lastEditAt: string, lastEditBy: string, onSharePermissions: array{create: bool, delete: bool, manage: bool, read: bool, update: bool}|null, ownerDisplayName: null|string, ownership: string, rows: array<array-key, mixed>, rowsCount: int, sort: list<array{columnId: int, mode: 'ASC'|'DESC'}>, tableId: int, title: string}' which is different due to additional array shape fields (rows, columnValues) (see https://psalm.dev/011)

Check failure on line 173 in lib/Db/View.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable28

InvalidReturnType

lib/Db/View.php:173:19: InvalidReturnType: The declared return type 'array{columns: array<array-key, int>, createdAt: string, createdBy: string, description: null|string, emoji: null|string, favorite: bool, filter: list<list<array{columnId: int, operator: 'begins-with'|'contains'|'ends-with'|'is-empty'|'is-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal', value: float|int|string}>>, hasShares: bool, id: int, isShared: bool, lastEditAt: string, lastEditBy: string, onSharePermissions: array{create: bool, delete: bool, manage: bool, read: bool, update: bool}|null, ownerDisplayName: null|string, ownership: string, rowsCount: int, sort: list<array{columnId: int, mode: 'ASC'|'DESC'}>, tableId: int, title: string}' for OCA\Tables\Db\View::jsonSerialize is incorrect, got 'array{columnValues: array<array-key, mixed>, columns: array<array-key, int>, createdAt: string, createdBy: string, description: null|string, emoji: null|string, favorite: bool, filter: list<list<array{columnId: int, operator: 'begins-with'|'contains'|'ends-with'|'is-empty'|'is-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal', value: float|int|string}>>, hasShares: bool, id: int, isShared: bool, lastEditAt: string, lastEditBy: string, onSharePermissions: array{create: bool, delete: bool, manage: bool, read: bool, update: bool}|null, ownerDisplayName: null|string, ownership: string, rows: array<array-key, mixed>, rowsCount: int, sort: list<array{columnId: int, mode: 'ASC'|'DESC'}>, tableId: int, title: string}' which is different due to additional array shape fields (rows, columnValues) (see https://psalm.dev/011)
*/
public function jsonSerialize(): array {
$serialisedJson = [
Expand All @@ -168,9 +192,11 @@
'hasShares' => !!$this->hasShares,
'rowsCount' => $this->rowsCount ?: 0,
'ownerDisplayName' => $this->ownerDisplayName,
'rows' => $this->getRowsArray(),
'columnValues' => $this->getColumnValuesArray(),
];
$serialisedJson['filter'] = $this->getFilterArray();

return $serialisedJson;

Check failure on line 200 in lib/Db/View.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

InvalidReturnStatement

lib/Db/View.php:200:10: InvalidReturnStatement: The inferred type 'array{columnValues: array<array-key, mixed>, columns: array<array-key, int>, createdAt: string, createdBy: string, description: null|string, emoji: null|string, favorite: bool, filter: list<list<array{columnId: int, operator: 'begins-with'|'contains'|'ends-with'|'is-empty'|'is-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal', value: float|int|string}>>, hasShares: bool, id: int, isShared: bool, lastEditAt: string, lastEditBy: string, onSharePermissions: array{create: bool, delete: bool, manage: bool, read: bool, update: bool}|null, ownerDisplayName: null|string, ownership: string, rows: array<array-key, mixed>, rowsCount: int, sort: list<array{columnId: int, mode: 'ASC'|'DESC'}>, tableId: int, title: string}' does not match the declared return type 'array{columns: array<array-key, int>, createdAt: string, createdBy: string, description: null|string, emoji: null|string, favorite: bool, filter: list<list<array{columnId: int, operator: 'begins-with'|'contains'|'ends-with'|'is-empty'|'is-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal', value: float|int|string}>>, hasShares: bool, id: int, isShared: bool, lastEditAt: string, lastEditBy: string, onSharePermissions: array{create: bool, delete: bool, manage: bool, read: bool, update: bool}|null, ownerDisplayName: null|string, ownership: string, rowsCount: int, sort: list<array{columnId: int, mode: 'ASC'|'DESC'}>, tableId: int, title: string}' for OCA\Tables\Db\View::jsonSerialize due to additional array shape fields (rows, columnValues) (see https://psalm.dev/128)

Check failure on line 200 in lib/Db/View.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable28

InvalidReturnStatement

lib/Db/View.php:200:10: InvalidReturnStatement: The inferred type 'array{columnValues: array<array-key, mixed>, columns: array<array-key, int>, createdAt: string, createdBy: string, description: null|string, emoji: null|string, favorite: bool, filter: list<list<array{columnId: int, operator: 'begins-with'|'contains'|'ends-with'|'is-empty'|'is-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal', value: float|int|string}>>, hasShares: bool, id: int, isShared: bool, lastEditAt: string, lastEditBy: string, onSharePermissions: array{create: bool, delete: bool, manage: bool, read: bool, update: bool}|null, ownerDisplayName: null|string, ownership: string, rows: array<array-key, mixed>, rowsCount: int, sort: list<array{columnId: int, mode: 'ASC'|'DESC'}>, tableId: int, title: string}' does not match the declared return type 'array{columns: array<array-key, int>, createdAt: string, createdBy: string, description: null|string, emoji: null|string, favorite: bool, filter: list<list<array{columnId: int, operator: 'begins-with'|'contains'|'ends-with'|'is-empty'|'is-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal', value: float|int|string}>>, hasShares: bool, id: int, isShared: bool, lastEditAt: string, lastEditBy: string, onSharePermissions: array{create: bool, delete: bool, manage: bool, read: bool, update: bool}|null, ownerDisplayName: null|string, ownership: string, rowsCount: int, sort: list<array{columnId: int, mode: 'ASC'|'DESC'}>, tableId: int, title: string}' for OCA\Tables\Db\View::jsonSerialize due to additional array shape fields (rows, columnValues) (see https://psalm.dev/128)
}
}
129 changes: 129 additions & 0 deletions lib/Service/ViewService.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
use OCA\Tables\Db\Table;
use OCA\Tables\Db\View;
use OCA\Tables\Db\ViewMapper;
use OCA\Tables\Db\ColumnMapper;
use OCA\Tables\Db\Row2;
use OCA\Tables\Db\Row2Mapper;
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\Errors\PermissionError;
Expand All @@ -40,6 +43,9 @@ class ViewService extends SuperService {

private RowService $rowService;

private ColumnMapper $columnMapper;
private Row2Mapper $row2Mapper;

protected UserHelper $userHelper;

protected FavoritesService $favoritesService;
Expand All @@ -54,6 +60,8 @@ public function __construct(
LoggerInterface $logger,
?string $userId,
ViewMapper $mapper,
ColumnMapper $columnMapper,
Row2Mapper $row2Mapper,
ShareService $shareService,
RowService $rowService,
UserHelper $userHelper,
Expand All @@ -67,6 +75,8 @@ public function __construct(
$this->mapper = $mapper;
$this->shareService = $shareService;
$this->rowService = $rowService;
$this->columnMapper = $columnMapper;
$this->row2Mapper = $row2Mapper;
$this->userHelper = $userHelper;
$this->favoritesService = $favoritesService;
$this->eventDispatcher = $eventDispatcher;
Expand Down Expand Up @@ -550,4 +560,123 @@ public function search(string $term, int $limit = 100, int $offset = 0, ?string
return [];
}
}

/**
* @param int $tableId
* @param array $data
* @param string|null $userId
* @return View
* @throws InternalError
* @throws PermissionError
* @throws InvalidArgumentException
*/
public function createTemporaryView(int $tableId, array $data, ?string $userId = null): View {
$userId = $this->permissionsService->preCheckUserId($userId);

try {
// $table = $this->tableService->find($tableId);

// // security
// if (!$this->permissionsService->canReadTable($table, $userId)) {
// throw new PermissionError('PermissionError: can not read table with id ' . $tableId);
// }
$this->logger->warning((string)$tableId.' , '.$userId);

$view = new View();
$view->setTableId($tableId);
$view->setOwnership($userId);

$updatableParameter = ['title', 'emoji', 'description', 'columns', 'sort', 'filter'];

foreach ($data as $key => $value) {
if (!in_array($key, $updatableParameter)) {
throw new InvalidArgumentException('Invalid parameter: ' . $key);
}

switch ($key) {
case 'title':
$view->setTitle($value);
break;
case 'emoji':
$view->setEmoji($value);
break;
case 'description':
$view->setDescription($value);
break;
case 'columns':
$view->setColumnsArray($value);
break;
case 'sort':
$view->setSortArray($value);
break;
case 'filter':
$view->setFilterArray($value);
break;
}
}
$rows = $this->findRowsByView($tableId, $userId, $view);
$view->setRowsArray($rows);
$columnValues = $this->findColumnsByView($tableId, $view, $userId);
$view->setColumnValuesArray($columnValues);

// $this->enhanceTemporaryView($view, $userId);

return $view;
} catch (NotFoundError $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError('Table not found: ' . $tableId);
} catch (PermissionError $e) {
$this->logger->debug('permission error during creating temporary view', ['exception' => $e]);
throw $e;
} catch (Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError($e->getMessage());
}
}

/**
* @param int $tableId
* @param string $userId
* @param View view
* @param int|null $limit
* @param int|null $offset
* @return Row2[]
* @throws DoesNotExistException
* @throws InternalError
* @throws MultipleObjectsReturnedException
* @throws PermissionError
*/
public function findRowsByView(int $tableId, string $userId, View $view, ?int $limit = null, ?int $offset = null): array {
try {
if ($this->permissionsService->canReadRowsByElementId($tableId, 'table', $userId)) {
$columnsArray = $view->getColumnsArray();
$filterColumns = $this->columnMapper->findAll($columnsArray);
$tableColumns = $this->columnMapper->findAllByTable($view->getTableId());

return $this->row2Mapper->findAll($tableColumns, $filterColumns, $view->getTableId(), $limit, $offset, $view->getFilterArray(), $view->getSortArray(), $userId);
} else {
throw new PermissionError('no read access to table id = '.$tableId);
}
} catch (Exception $e) {
$this->logger->error($e->getMessage());
throw new InternalError($e->getMessage());
}
}


/**
* @param int $tableId
* @param View view
* @param string|null $userId
* @return array
* @throws NotFoundError
* @throws PermissionError
* @throws InternalError
*/
public function findColumnsByView(int $tableId, View $view, ?string $userId = null): array {
$viewColumnIds = $view->getColumnsArray();
$viewColumns = $this->columnMapper->findAll($viewColumnIds);
return $viewColumns;
}

}
69 changes: 69 additions & 0 deletions src/modules/main/sections/Temp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!--
- SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<!-- <div v-if="!view" class="icon-loading" />
<div v-else class="main-view-view"> -->
<div>
<ElementTitle :active-element="view" :is-table="false" :view-setting.sync="localViewSetting" />
<div class="table-wrapper">
<EmptyView v-if="columns.length === 0" :view="view" />
<TableView v-else
:rows="rows"
:columns="columns"
:element="view"
:view-setting.sync="localViewSetting"
:is-view="true"
:can-read-rows="true"
:can-create-rows="true"
:can-edit-rows="true"
:can-delete-rows="false"
:can-create-columns="false"
:can-edit-columns="false"
:can-delete-columns="false"
:can-delete-table="false" />
</div>
</div>
</template>

<script>
import TableView from '../partials/TableView.vue'

import EmptyView from './EmptyView.vue'
import permissionsMixin from '../../../shared/components/ncTable/mixins/permissionsMixin.js'
import ElementTitle from './ElementTitle.vue'

export default {
components: {
EmptyView,
TableView,
ElementTitle,
},

mixins: [permissionsMixin],

props: {
view: {
type: Object,
default: null,
},
columns: {
type: Array,
default: null,
},
rows: {
type: Array,
default: null,
},
},

data() {
return {
localLoading: false,
lastActiveViewId: null,
localViewSetting: {},
}
},
}
</script>
Loading
Loading