Skip to content
Snippets Groups Projects
Verified Commit e8c3269e authored by Martin Weise's avatar Martin Weise
Browse files

Fixed UI for private views and also library

parent ebc88122
Branches
Tags
2 merge requests!365Hotfix/ui view,!364Hotfix/ui view
......@@ -270,6 +270,10 @@ public class ViewEndpoint extends AbstractEndpoint {
// TODO improve with a single operation that checks if user xyz has access to view abc
final PrivilegedViewDto view = metadataServiceGateway.getViewById(databaseId, viewId);
if (!view.getIsPublic()) {
if (principal == null) {
log.error("Failed to get data from view: unauthorized");
throw new NotAllowedException("Failed to get data from view: unauthorized");
}
metadataServiceGateway.getAccess(databaseId, UserUtil.getId(principal));
}
try {
......
......@@ -34,6 +34,7 @@
:text="$t('navigation.info')"
:to="`/database/${$route.params.database_id}/view/${$route.params.view_id}/info`" />
<v-tab
v-if="canReadData"
:text="$t('navigation.data')"
:to="`/database/${$route.params.database_id}/view/${$route.params.view_id}/data`" />
</v-tabs>
......@@ -96,6 +97,24 @@ export default {
roles () {
return this.userStore.getRoles
},
hasReadAccess () {
if (!this.access) {
return false
}
return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all'
},
canReadData () {
if (!this.view) {
return false
}
if (this.view.is_public) {
return true
}
if (!this.user) {
return false
}
return this.view.owner.id === this.user.id || this.hasReadAccess
},
identifiers () {
if (!this.view) {
return []
......
<template>
<div>
<div
v-if="canReadData">
<ViewToolbar
v-if="view" />
<v-toolbar
......@@ -7,7 +8,6 @@
:title="$t('toolbars.database.current')"
flat>
<v-btn
v-if="canDownload"
:prepend-icon="$vuetify.display.lgAndUp ? 'mdi-download' : null"
variant="flat"
:loading="downloadLoading"
......@@ -85,18 +85,24 @@ export default {
access () {
return this.userStore.getAccess
},
canDownload () {
hasReadAccess () {
if (!this.access) {
return false
}
return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all'
},
canReadData () {
if (!this.view) {
return false
}
if (this.view.is_public) {
return true
}
if (!this.access) {
if (!this.user) {
return false
}
return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all'
}
return this.view.owner.id === this.user.id || this.hasReadAccess
},
},
mounted () {
this.reload()
......
......@@ -957,8 +957,7 @@ class RestClient:
raise ResponseCodeError(f'Failed to delete view: response code: {response.status_code} is not '
f'202 (ACCEPTED): {response.text}')
def get_view_data(self, database_id: int, view_id: int, page: int = 0, size: int = 10,
df: bool = False) -> Result | DataFrame:
def get_view_data(self, database_id: int, view_id: int, page: int = 0, size: int = 10) -> DataFrame:
"""
Get data of a view in a database with given database id and view id.
......@@ -966,9 +965,8 @@ class RestClient:
:param view_id: The view id.
:param page: The result pagination number. Optional. Default: 0.
:param size: The result pagination size. Optional. Default: 10.
:param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False.
:returns: The result of the view query, if successful.
:returns: The view data, if successful.
:raises MalformedError: If the payload is rejected by the service.
:raises ForbiddenError: If something went wrong with the authorization.
......@@ -984,11 +982,7 @@ class RestClient:
params.append(('size', size))
response = self._wrapper(method="get", url=url, params=params)
if response.status_code == 200:
body = response.json()
res = Result.model_validate(body)
if df:
return DataFrame.from_records(res.result)
return res
return DataFrame.from_records(response.json())
if response.status_code == 400:
raise MalformedError(f'Failed to get view data: {response.text}')
if response.status_code == 403:
......@@ -1026,7 +1020,7 @@ class RestClient:
f'200 (OK): {response.text}')
def get_table_data(self, database_id: int, table_id: int, page: int = 0, size: int = 10,
timestamp: datetime.datetime = None, df: bool = False) -> Result | DataFrame:
timestamp: datetime.datetime = None) -> DataFrame:
"""
Get data of a table in a database with given database id and table id.
......@@ -1035,9 +1029,8 @@ class RestClient:
:param page: The result pagination number. Optional. Default: 0.
:param size: The result pagination size. Optional. Default: 10.
:param timestamp: The query execution time. Optional.
:param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False.
:returns: The result of the view query, if successful.
:returns: The table data, if successful.
:raises MalformedError: If the payload is rejected by the service.
:raises ForbiddenError: If something went wrong with the authorization.
......@@ -1054,11 +1047,7 @@ class RestClient:
params.append(('timestamp', timestamp))
response = self._wrapper(method="get", url=url, params=params)
if response.status_code == 200:
body = response.json()
res = Result.model_validate(body)
if df:
return DataFrame.from_records(res.result)
return res
return DataFrame.from_records(response.json())
if response.status_code == 400:
raise MalformedError(f'Failed to get table data: {response.text}')
if response.status_code == 403:
......@@ -1551,7 +1540,7 @@ class RestClient:
f'201 (CREATED): {response.text}')
def create_subset(self, database_id: int, query: str, page: int = 0, size: int = 10,
timestamp: datetime.datetime = None, df: bool = False) -> Result | DataFrame:
timestamp: datetime.datetime = None) -> DataFrame:
"""
Executes a SQL query in a database where the current user has at least read access with given database id. The
result set can be paginated with setting page and size (both). Historic data can be queried by setting
......@@ -1562,7 +1551,6 @@ class RestClient:
:param page: The result pagination number. Optional. Default: 0.
:param size: The result pagination size. Optional. Default: 10.
:param timestamp: The timestamp at which the data validity is set. Optional. Default: <current timestamp>.
:param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False.
:returns: The result set, if successful.
......@@ -1585,11 +1573,8 @@ class RestClient:
response = self._wrapper(method="post", url=url, headers={"Accept": "application/json"},
payload=ExecuteQuery(statement=query))
if response.status_code == 201:
body = response.json()
res = Result.model_validate(body)
if df:
return DataFrame.from_records(res.result)
return res
logging.info(f'Created subset with id: {response.headers["X-Id"]}')
return DataFrame.from_records(response.json())
if response.status_code == 400:
raise MalformedError(f'Failed to create subset: {response.text}')
if response.status_code == 403:
......@@ -1605,8 +1590,7 @@ class RestClient:
raise ResponseCodeError(f'Failed to create subset: response code: {response.status_code} is not '
f'201 (CREATED): {response.text}')
def get_subset_data(self, database_id: int, subset_id: int, page: int = 0, size: int = 10,
df: bool = False) -> Result | DataFrame:
def get_subset_data(self, database_id: int, subset_id: int, page: int = 0, size: int = 10) -> DataFrame:
"""
Re-executes a query in a database with given database id and query id.
......@@ -1615,9 +1599,8 @@ class RestClient:
:param page: The result pagination number. Optional. Default: 0.
:param size: The result pagination size. Optional. Default: 10.
:param size: The result pagination size. Optional. Default: 10.
:param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False.
:returns: The result set, if successful.
:returns: The subset data, if successful.
:raises MalformedError: If the payload is rejected by the service.
:raises ForbiddenError: If something went wrong with the authorization.
......@@ -1631,11 +1614,7 @@ class RestClient:
url += f'?page={page}&size={size}'
response = self._wrapper(method="get", url=url, headers=headers)
if response.status_code == 200:
body = response.json()
res = Result.model_validate(body)
if df:
return DataFrame.from_records(res.result)
return res
return DataFrame.from_records(response.json())
if response.status_code == 400:
raise MalformedError(f'Failed to get query data: {response.text}')
if response.status_code == 403:
......@@ -1936,7 +1915,7 @@ class RestClient:
:returns: List of licenses, if successful.
"""
url = f'/api/database/license'
url = f'/api/license'
response = self._wrapper(method="get", url=url)
if response.status_code == 200:
body = response.json()
......
from __future__ import annotations
import datetime
from dataclasses import field
from enum import Enum
import datetime
from typing import List, Optional, Any, Annotated
from pydantic import BaseModel, ConfigDict, PlainSerializer, Field
from typing import List, Optional, Annotated
from pydantic import BaseModel, PlainSerializer, Field
Timestamp = Annotated[
datetime.datetime, PlainSerializer(lambda v: v.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z', return_type=str)
......@@ -638,12 +639,6 @@ class CreateView(BaseModel):
is_public: bool
class Result(BaseModel):
result: Any
headers: Any
id: Optional[int] = None
class ViewBrief(BaseModel):
id: int
database_id: int
......@@ -874,8 +869,8 @@ class DataType(BaseModel):
display_name: str
value: str
documentation: str
is_quoted: bool
is_buildable: bool
is_quoted: bool
is_buildable: bool
size_min: Optional[int] = None
size_max: Optional[int] = None
size_default: Optional[int] = None
......@@ -884,8 +879,8 @@ class DataType(BaseModel):
d_max: Optional[int] = None
d_default: Optional[int] = None
d_required: Optional[bool] = None
data_hint: Optional[str] = None
type_hint: Optional[str] = None
data_hint: Optional[str] = None
type_hint: Optional[str] = None
class Column(BaseModel):
......
......@@ -12,7 +12,7 @@ class DatabaseUnitTest(unittest.TestCase):
def test_get_licenses_empty_succeeds(self):
with requests_mock.Mocker() as mock:
# mock
mock.get('/api/database/license', json=[])
mock.get('/api/license', json=[])
# test
response = RestClient().get_licenses()
self.assertEqual([], response)
......@@ -22,7 +22,7 @@ class DatabaseUnitTest(unittest.TestCase):
exp = [License(identifier='CC-BY-4.0', uri='https://creativecommons.org/licenses/by/4.0/',
description='The Creative Commons Attribution license allows re-distribution and re-use of a licensed work on the condition that the creator is appropriately credited.')]
# mock
mock.get('/api/database/license', json=[exp[0].model_dump()])
mock.get('/api/license', json=[exp[0].model_dump()])
# test
response = RestClient().get_licenses()
self.assertEqual(exp, response)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment