From ddce31eb041f050c5aef906f7c6e9b18ff28b213 Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Thu, 26 Dec 2024 06:40:01 +0000 Subject: [PATCH] Dev --- .docs/concepts/authentication.md | 6 +- .docs/concepts/data-versioning.md | 39 +++ .docs/concepts/data-visibility.md | 33 ++ .docs/concepts/messaging.md | 8 +- .docs/concepts/pid.md | 46 +-- .docs/images/architecture.drawio | 47 ++- .docs/images/private-embargo.svg | 3 + .docs/{redirect.html => index.html.tpl} | 8 +- .docs/installation.md | 6 +- .docs/kubernetes.md | 2 +- .gitlab-ci.yml | 5 +- .gitlab/gen-badge.sh | 4 +- README.md | 2 +- build-docs.sh | 2 +- .../dashboards/system.json | 287 +++++++++--------- .../at/tuwien/endpoints/SubsetEndpoint.java | 13 +- .../at/tuwien/endpoints/TableEndpoint.java | 20 +- .../at/tuwien/endpoints/ViewEndpoint.java | 26 +- .../src/main/resources/application.yml | 1 + .../java/at/tuwien/config/CacheConfig.java | 51 +++- .../java/at/tuwien/config/MetricsConfig.java | 24 +- .../gateway/impl/KeycloakGatewayImpl.java | 17 +- .../java/at/tuwien/mapper/MetadataMapper.java | 18 ++ .../at/tuwien/service/MetricsService.java | 10 + .../impl/MetricsServicePrometheusImpl.java | 47 +++ .../impl/QueueServiceRabbitMqImpl.java | 7 +- .../java/at/tuwien/mapper/MetadataMapper.java | 5 +- .../at/tuwien/endpoints/TableEndpoint.java | 3 +- .../tuwien/service/impl/TableServiceImpl.java | 2 +- dbrepo-ui/components/subset/SubsetList.vue | 46 ++- dbrepo-ui/components/view/ViewList.vue | 3 - dbrepo-ui/components/view/ViewToolbar.vue | 2 +- .../[database_id]/view/[view_id]/data.vue | 2 +- helm/dbrepo/README.md | 12 +- lib/python/README.md | 12 +- mkdocs.yml | 8 +- versions.json | 5 + 37 files changed, 526 insertions(+), 306 deletions(-) create mode 100644 .docs/concepts/data-versioning.md create mode 100644 .docs/concepts/data-visibility.md create mode 100644 .docs/images/private-embargo.svg rename .docs/{redirect.html => index.html.tpl} (58%) create mode 100644 dbrepo-data-service/services/src/main/java/at/tuwien/service/MetricsService.java create mode 100644 dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/MetricsServicePrometheusImpl.java diff --git a/.docs/concepts/authentication.md b/.docs/concepts/authentication.md index c49abdf86e..c085e54e15 100644 --- a/.docs/concepts/authentication.md +++ b/.docs/concepts/authentication.md @@ -27,7 +27,7 @@ The [User Interface](../../api/ui) for example refreshes the token on-the-fly by of an expired access token, requests a new one without having to terminate the request. This happens only once after the access token has expired (after e.g. 15 minutes). -## OpenID Connect (OIDC) +## OpenID Connect -We use the widely accepted authentication protocol OIDC for client authentication. Other protocols are e.g. SAML2 which -are not used by DBRepo. \ No newline at end of file +We use the widely accepted authentication protocol OpenID Connect for client authentication. Other protocols are, e.g. +SAML2 which are not used by default in DBRepo. \ No newline at end of file diff --git a/.docs/concepts/data-versioning.md b/.docs/concepts/data-versioning.md new file mode 100644 index 0000000000..8a9abc667b --- /dev/null +++ b/.docs/concepts/data-versioning.md @@ -0,0 +1,39 @@ +--- +author: Martin Weise +--- + +Data is getting bigger and so are expectations of data provisioning in regards to data availability (i.e. immediately +after quality check and not in snapshot intervals), cost-effectiveness (i.e. no duplication of data), transparent, +precise citation and many more. + +[System-versioned](https://mariadb.com/kb/en/system-versioned-tables/) tables in MariaDB are improved data structures +that keep track of historical data. For each entry in a system-versioned table, a time period is maintained that denotes +the validity time span of this tuple from its start to end. Tuples in system-versioned tables are not *actually* +modified, they are marked as (in-)valid in time periods. + +<figure markdown> + +| ID | Sensor | Temp | Start | End | +|----|--------|------|-------|-----| +| 1 | A | 23.1 | t1 | | +| 2 | B | 25.8 | t2 | | + +</figure> + +Assuming that Sensor A was calibrated wrong and an updated measurement is passed to the system-versioned table, the +table contents show that the old row with Temp 23.1 is not deleted, but marked as valid in time span (t1, t3). The +updated row with Temp 22.1 is marked as valid from time span t3 onwards. + +<figure markdown> + +| ID | Sensor | Temp | Start | End | +|----|--------|------|-------|-----| +| 1 | A | 23.1 | t1 | t3 | +| 2 | B | 25.8 | t2 | | +| 1 | A | 22.1 | t3 | | + +</figure> + +System-versioned tables are part of the SQL:2011 standard and have been adopted by many database management system +vendors: MariaDB (10.5 and higher), Google BigQuery, IBM DB2 (12 and higher), SQL Server (2016 and higher), Azure SQL, +PostgreSQL with [temporal tables extension](https://github.com/nearform/temporal_tables), etc. \ No newline at end of file diff --git a/.docs/concepts/data-visibility.md b/.docs/concepts/data-visibility.md new file mode 100644 index 0000000000..e76448ec3e --- /dev/null +++ b/.docs/concepts/data-visibility.md @@ -0,0 +1,33 @@ +--- +author: Martin Weise +--- + +There are several ways to set the visibility of (meta-)data in DBRepo. It is possible to set the data to public/private +and the schema to be public/private for each database and separately for each table, each view and each subset of a +database. + +In total there are three possible scenarios: + +## Public + +!!! info "Possible use-case: data publication supplement to an open-access publication" + +Where the database's data and metadata is set to be *public*. This means everything in the database (tables, views, +subsets) are visible by anyone from the public. + +## Mixed + +!!! info "Possible use-case: private sensor measurements with timed embargo" + +Where the database's data and metadata is set to be *private*. This means everything in the database (tables, views, +subsets) are by default not visible by anyone from the public. You can however make specific views that join tables +and/or filter certain columns and apply a 14-day delay-embargo. + +<figure markdown> + +<figcaption>Figure 1: Public view that joins two private tables and applies a time-embargo</figcaption> +</figure> + +## Private + +!!! info "Possible use-case: data storage for trusted-/virtual research environments" \ No newline at end of file diff --git a/.docs/concepts/messaging.md b/.docs/concepts/messaging.md index c19e1fc2a5..819b20d8a0 100644 --- a/.docs/concepts/messaging.md +++ b/.docs/concepts/messaging.md @@ -21,4 +21,10 @@ JSON, a tuple looks like this in RabbitMQ: ## AMQP DBRepo uses AMQP to route messages which allows for both Basic/Bearer authentication. For more information please -consult the [RabbitMQ AMQP](https://www.rabbitmq.com/tutorials/amqp-concepts) documentation. \ No newline at end of file +consult the [RabbitMQ AMQP](https://www.rabbitmq.com/tutorials/amqp-concepts) documentation. + +## MQTT + +:octicons-tag-16:{ title="Minimum version" } 1.5.0 + +DBRepo supports MQTT for IoT with Basic authentication. \ No newline at end of file diff --git a/.docs/concepts/pid.md b/.docs/concepts/pid.md index e58ed03ac4..892bd65dea 100644 --- a/.docs/concepts/pid.md +++ b/.docs/concepts/pid.md @@ -2,46 +2,6 @@ author: Martin Weise --- -## Data Versioning - -Data is getting bigger and so are expectations of data provisioning in regards to data availability (i.e. immediately -after quality check and not in snapshot intervals), cost-effectiveness (i.e. no duplication of data), transparent, -precise citation and many more. - -[System-versioned](https://mariadb.com/kb/en/system-versioned-tables/) tables in MariaDB are improved data structures -that keep track of historical data. For each entry in a system-versioned table, a time period is maintained that denotes -the validity time span of this tuple from its start to end. Tuples in system-versioned tables are not *actually* -modified, they are marked as (in-)valid in time periods. - -<figure markdown> - -| ID | Sensor | Temp | Start | End | -|----|--------|------|-------|-----| -| 1 | A | 23.1 | t1 | | -| 2 | B | 25.8 | t2 | | - -</figure> - -Assuming that Sensor A was calibrated wrong and an updated measurement is passed to the system-versioned table, the -table contents show that the old row with Temp 23.1 is not deleted, but marked as valid in time span (t1, t3). The -updated row with Temp 22.1 is marked as valid from time span t3 onwards. - -<figure markdown> - -| ID | Sensor | Temp | Start | End | -|----|--------|------|-------|-----| -| 1 | A | 23.1 | t1 | t3 | -| 2 | B | 25.8 | t2 | | -| 1 | A | 22.1 | t3 | | - -</figure> - -System-versioned tables are part of the SQL:2011 standard and have been adopted by many database management system -vendors: MariaDB (10.5 and higher), Google BigQuery, IBM DB2 (12 and higher), SQL Server (2016 and higher), Azure SQL, -PostgreSQL with [temporal tables extension](https://github.com/nearform/temporal_tables), etc. - -## Persistent Identifier - Data in DBRepo always has attached metadata (stored in the [Metadata Database](../../api/metadata-db)). This metadata is provided as machine-understandable context in various open-source formats that is available, even when the original data is not available anymore due to e.g. a retracted dataset (hence the name **persistent**). A persistent identifier @@ -54,4 +14,8 @@ globally, uniquely identifies a data record such as: Combining [data versioning](#data-versioning) and queries, subsets can be precisely identified by storing the query that creates them and the time when the query was executed. We store both in a table inside the database we call the -query store. \ No newline at end of file +query store. + +## DOI + +DBRepo uses the DOI system minted by DataCite, this is optional and not required to function for e.g. test deployments. \ No newline at end of file diff --git a/.docs/images/architecture.drawio b/.docs/images/architecture.drawio index 30bff2a6d0..71536a3212 100644 --- a/.docs/images/architecture.drawio +++ b/.docs/images/architecture.drawio @@ -1,4 +1,4 @@ -<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" version="25.0.1" pages="8"> +<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/25.0.2 Chrome/128.0.6613.186 Electron/32.2.5 Safari/537.36" version="25.0.2" pages="9"> <diagram id="mvBsv1rP8O80Qe3yGnn_" name="docker-compose"> <mxGraphModel dx="683" dy="391" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0"> <root> @@ -1108,4 +1108,49 @@ </root> </mxGraphModel> </diagram> + <diagram id="7HywRA3nQAgvNxZjCRq2" name="private-embargo"> + <mxGraphModel dx="985" dy="394" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0"> + <root> + <mxCell id="0" /> + <mxCell id="1" parent="0" /> + <mxCell id="n6nk3BLY6128t3IB6Ma7-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;curved=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="n6nk3BLY6128t3IB6Ma7-1" target="n6nk3BLY6128t3IB6Ma7-5"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="n6nk3BLY6128t3IB6Ma7-11" value="<span style="text-wrap: wrap; background-color: rgb(251, 251, 251);">value,loc_id</span>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;" vertex="1" connectable="0" parent="n6nk3BLY6128t3IB6Ma7-8"> + <mxGeometry x="0.0303" relative="1" as="geometry"> + <mxPoint as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="n6nk3BLY6128t3IB6Ma7-1" value="" style="shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1"> + <mxGeometry x="250" y="170" width="80" height="80" as="geometry" /> + </mxCell> + <mxCell id="n6nk3BLY6128t3IB6Ma7-2" value="<b>table</b>: sensor (private)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1"> + <mxGeometry x="227.5" y="150" width="125" height="20" as="geometry" /> + </mxCell> + <mxCell id="n6nk3BLY6128t3IB6Ma7-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.75;entryY=0;entryDx=0;entryDy=0;curved=1;" edge="1" parent="1" source="n6nk3BLY6128t3IB6Ma7-3" target="n6nk3BLY6128t3IB6Ma7-5"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="n6nk3BLY6128t3IB6Ma7-12" value="id,name,lat,lng" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;" vertex="1" connectable="0" parent="n6nk3BLY6128t3IB6Ma7-9"> + <mxGeometry x="0.1455" relative="1" as="geometry"> + <mxPoint as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="n6nk3BLY6128t3IB6Ma7-3" value="" style="shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1"> + <mxGeometry x="430" y="170" width="80" height="80" as="geometry" /> + </mxCell> + <mxCell id="n6nk3BLY6128t3IB6Ma7-4" value="<b>table</b>: location (private)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1"> + <mxGeometry x="405" y="150" width="130" height="20" as="geometry" /> + </mxCell> + <mxCell id="n6nk3BLY6128t3IB6Ma7-5" value="" style="shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;fontSize=8;" vertex="1" parent="1"> + <mxGeometry x="340" y="290" width="80" height="80" as="geometry" /> + </mxCell> + <mxCell id="n6nk3BLY6128t3IB6Ma7-6" value="<b>view</b>: validated_sensor (public)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1"> + <mxGeometry x="290" y="370" width="180" height="20" as="geometry" /> + </mxCell> + <mxCell id="n6nk3BLY6128t3IB6Ma7-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="n6nk3BLY6128t3IB6Ma7-4" target="n6nk3BLY6128t3IB6Ma7-4"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + </root> + </mxGraphModel> + </diagram> </mxfile> diff --git a/.docs/images/private-embargo.svg b/.docs/images/private-embargo.svg new file mode 100644 index 0000000000..2c83363264 --- /dev/null +++ b/.docs/images/private-embargo.svg @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="308px" height="240px" viewBox="-0.5 -0.5 308 240" style="background-color: rgb(255, 255, 255);"><defs/><rect fill="#ffffff" width="100%" height="100%" x="0" y="0"/><g><g data-cell-id="0"><g data-cell-id="1"><g data-cell-id="n6nk3BLY6128t3IB6Ma7-8"><g><path d="M 62 100 Q 62 120 97 120 Q 132 120 132 133.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 132 138.88 L 128.5 131.88 L 132 133.63 L 135.5 131.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/></g><g data-cell-id="n6nk3BLY6128t3IB6Ma7-11"><g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 120px; margin-left: 99px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"><span style="text-wrap: wrap; background-color: rgb(251, 251, 251);">value,loc_id</span></div></div></div></foreignObject><image x="78" y="115.5" width="42" height="12" xlink:href="data:image/png;base64,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"/></switch></g></g></g></g><g data-cell-id="n6nk3BLY6128t3IB6Ma7-1"><g><rect x="22" y="20" width="80" height="80" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 22 40 L 102 40" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 42 20 L 42 100" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/></g></g><g data-cell-id="n6nk3BLY6128t3IB6Ma7-2"><g><rect x="-0.5" y="0" width="125" height="20" fill="none" stroke="none" pointer-events="all"/></g><g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 123px; height: 1px; padding-top: 10px; margin-left: 1px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><b>table</b>: sensor (private)</div></div></div></foreignObject><image x="1" y="3.5" width="123" height="17" xlink:href="data:image/png;base64,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"/></switch></g></g></g><g data-cell-id="n6nk3BLY6128t3IB6Ma7-9"><g><path d="M 242 100 Q 242 120 207 120 Q 172 120 172 133.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 172 138.88 L 168.5 131.88 L 172 133.63 L 175.5 131.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/></g><g data-cell-id="n6nk3BLY6128t3IB6Ma7-12"><g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 120px; margin-left: 199px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">id,name,lat,lng</div></div></div></foreignObject><image x="173" y="115.5" width="52" height="12" xlink:href="data:image/png;base64,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"/></switch></g></g></g></g><g data-cell-id="n6nk3BLY6128t3IB6Ma7-3"><g><rect x="202" y="20" width="80" height="80" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 202 40 L 282 40" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 222 20 L 222 100" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/></g></g><g data-cell-id="n6nk3BLY6128t3IB6Ma7-4"><g><rect x="177" y="0" width="130" height="20" fill="none" stroke="none" pointer-events="all"/></g><g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 10px; margin-left: 178px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><b>table</b>: location (private)</div></div></div></foreignObject><image x="178" y="3.5" width="128" height="17" xlink:href="data:image/png;base64,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"/></switch></g></g></g><g data-cell-id="n6nk3BLY6128t3IB6Ma7-5"><g><rect x="112" y="140" width="80" height="80" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 112 160 L 192 160" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 132 140 L 132 220" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/></g></g><g data-cell-id="n6nk3BLY6128t3IB6Ma7-6"><g><rect x="62" y="220" width="180" height="20" fill="none" stroke="none" pointer-events="all"/></g><g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 178px; height: 1px; padding-top: 230px; margin-left: 63px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><b>view</b>: validated_sensor (public)</div></div></div></foreignObject><image x="63" y="223.5" width="178" height="17" xlink:href="data:image/png;base64,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"/></switch></g></g></g><g data-cell-id="n6nk3BLY6128t3IB6Ma7-7"><g/></g></g></g></g></svg> \ No newline at end of file diff --git a/.docs/redirect.html b/.docs/index.html.tpl similarity index 58% rename from .docs/redirect.html rename to .docs/index.html.tpl index 69ede955cd..d92d7c4b18 100644 --- a/.docs/redirect.html +++ b/.docs/index.html.tpl @@ -1,20 +1,18 @@ <!DOCTYPE html> <html lang="en"> -<!-- This file exists only once - in the final documentation --> <head> <meta charset="UTF-8"> <title>Redirect Notice</title> - <meta http-equiv="Refresh" content="0; url='/infrastructures/dbrepo/1.5/'" /> + <meta http-equiv="Refresh" content="0; url='/infrastructures/dbrepo/${DOC_VERSION}/'" /> </head> <body> <h1>Redirect Notice</h1> <p> - This page should automatically open the documentation for version <code>1.5</code>. In case this page does not load the site is + This page should automatically open the documentation for version <code>${DOC_VERSION}</code>. In case this page does not load the site is available at: </p> <p> - <a href="/infrastructures/dbrepo/1.5/">/infrastructures/dbrepo/1.5/</a> + <a href="/infrastructures/dbrepo/${DOC_VERSION}/">/infrastructures/dbrepo/${DOC_VERSION}/</a> </p> </body> </html> \ No newline at end of file diff --git a/.docs/installation.md b/.docs/installation.md index 60f8a0dd5f..ee0d9b88fa 100644 --- a/.docs/installation.md +++ b/.docs/installation.md @@ -11,7 +11,7 @@ author: Martin Weise If you have [Docker](https://docs.docker.com/engine/install/) already installed on your system, you can install DBRepo with: ```shell -curl -sSL https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.5/install.sh | bash +curl -sSL https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.6/install.sh | bash ``` !!! bug "Default installation security disclaimer" @@ -38,7 +38,7 @@ SSL/TLS certificate is recommended. Follow the [secure install](#secure-install) Execute the install script to download only the environment and save it to `dist`. ```shell -curl -sSL https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.5/install.sh | DOWNLOAD_ONLY=1 bash +curl -sSL https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.6/install.sh | DOWNLOAD_ONLY=1 bash ``` ### Static Configuration @@ -80,7 +80,7 @@ the variable `IDENTITY_SERVICE_ADMIN_PASSWORD` in `.env`. Update the client secret of the `dbrepo-client`: ```bash -curl -sSL "https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.5/.scripts/reg-client-secret.sh" | bash +curl -sSL "https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.6/.scripts/reg-client-secret.sh" | bash ``` Also, update the JWT key according to the diff --git a/.docs/kubernetes.md b/.docs/kubernetes.md index b6fd193fdf..232584ee75 100644 --- a/.docs/kubernetes.md +++ b/.docs/kubernetes.md @@ -6,7 +6,7 @@ author: Martin Weise To install DBRepo in your existing cluster, download the sample [ -`values.yaml`](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/blob/release-1.5/helm/dbrepo/values.yaml) +`values.yaml`](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/blob/release-1.6/helm/dbrepo/values.yaml) for your deployment and update the variables, especially `hostname`. ```shell diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ce79f1a01f..aba94058e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -442,7 +442,8 @@ release-docs: - "cp .docs/.swagger/custom.css ./final/${DOC_VERSION}/rest/custom.css" # swagger - "cp -r ./site/* ./final/${DOC_VERSION}" # mkdocs - "cp .docker/dist.tar.gz ./final/${APP_VERSION}/dist.tar.gz" # dist - - "cp .docs/redirect.html ./final/${APP_VERSION}/index.html" # redirect patch docs + - "cp .docs/index.html.tpl ./final/${APP_VERSION}/index.html" # redirect patch docs + - sed -i "s/\${DOC_VERSION}/${DOC_VERSION}/g" index.html - "bash ./.gitlab/gen-badge.sh" - eval $(ssh-agent -s) - "mkdir -p /root/.ssh" @@ -452,7 +453,7 @@ release-docs: - tar czf ./final.tar.gz ./final - "scp -oHostKeyAlgorithms=+ssh-rsa -oPubkeyAcceptedAlgorithms=+ssh-rsa final.tar.gz $CI_DOC_USER@$CI_DOC_IP:final.tar.gz" - "scp -oHostKeyAlgorithms=+ssh-rsa -oPubkeyAcceptedAlgorithms=+ssh-rsa versions.json $CI_DOC_USER@$CI_DOC_IP:/system/user/ifs/infrastructures/public_html/dbrepo/versions.json" - - "scp -oHostKeyAlgorithms=+ssh-rsa -oPubkeyAcceptedAlgorithms=+ssh-rsa .docs/redirect.html $CI_DOC_USER@$CI_DOC_IP:/system/user/ifs/infrastructures/public_html/dbrepo/index.html" + - "scp -oHostKeyAlgorithms=+ssh-rsa -oPubkeyAcceptedAlgorithms=+ssh-rsa .docs/index.html.tpl $CI_DOC_USER@$CI_DOC_IP:/system/user/ifs/infrastructures/public_html/dbrepo/index.html" - 'ssh -oHostKeyAlgorithms=+ssh-rsa -oPubkeyAcceptedAlgorithms=+ssh-rsa $CI_DOC_USER@$CI_DOC_IP "rm -rf /system/user/ifs/infrastructures/public_html/dbrepo/${DOC_VERSION}; tar xzf ./final.tar.gz; rm -f ./final.tar.gz; cp -r ./final/* /system/user/ifs/infrastructures/public_html/dbrepo; rm -rf ./final"' release-libs: diff --git a/.gitlab/gen-badge.sh b/.gitlab/gen-badge.sh index e5bb815915..4124b388d7 100644 --- a/.gitlab/gen-badge.sh +++ b/.gitlab/gen-badge.sh @@ -9,7 +9,7 @@ else echo "[WARNING] Skipping badge generation, displaying default badge text: unknown" fi -curl -fsSL "${CI_SONAR_URL}/api/project_badges/measure?project=${CI_SONAR_PROJECT_KEY}&metric=sqale_rating&token=${CI_SONAR_TOKEN}" > "./final/${DOC_VERSION}/images/maintainability.svg" -curl -fsSL "${CI_SONAR_URL}/api/project_badges/measure?project=${CI_SONAR_PROJECT_KEY}&metric=security_rating&token=${CI_SONAR_TOKEN}" > "./final/${DOC_VERSION}/images/security.svg" +curl -fsSL "${CI_SONAR_URL}/api/project_badges/measure?project=${CI_SONAR_PROJECT_KEY}&metric=sqale_rating&token=${CI_SONAR_BADGE_TOKEN}" > "./final/${DOC_VERSION}/images/maintainability.svg" +curl -fsSL "${CI_SONAR_URL}/api/project_badges/measure?project=${CI_SONAR_PROJECT_KEY}&metric=security_rating&token=${CI_SONAR_BADGE_TOKEN}" > "./final/${DOC_VERSION}/images/security.svg" echo "[INFO] retrieved badges" \ No newline at end of file diff --git a/README.md b/README.md index e039731977..628da3f3f3 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ If you have [Docker](https://docs.docker.com/engine/install/) already installed with: ```bash -curl -sSL https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.5/install.sh | bash +curl -sSL https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.6/install.sh | bash ``` ## Documentation diff --git a/build-docs.sh b/build-docs.sh index fba2afc766..8639b3756f 100644 --- a/build-docs.sh +++ b/build-docs.sh @@ -89,6 +89,6 @@ done # finalization echo "===================================================" echo "Moving HTML redirect and JSON versions to /" -cp ./final/${APP_VERSION}/redirect.html ./final/index.html +cp ./final/${APP_VERSION}/index.html.tpl ./final/index.html cp ./final/${APP_VERSION}/versions.json ./final/versions.json echo "===================================================" diff --git a/dbrepo-dashboard-service/dashboards/system.json b/dbrepo-dashboard-service/dashboards/system.json index edee464f62..e6f81bda40 100644 --- a/dbrepo-dashboard-service/dashboards/system.json +++ b/dbrepo-dashboard-service/dashboards/system.json @@ -18,6 +18,7 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, + "id": 3, "links": [ { "asDropdown": false, @@ -109,7 +110,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -179,7 +180,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -247,7 +248,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -315,7 +316,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -431,7 +432,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -453,13 +454,83 @@ "title": "Data Volume", "type": "stat" }, + { + "datasource": { + "type": "prometheus", + "uid": "P18F45E9DC7E75912" + }, + "description": "Top 10 by number of accesses", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 4 + }, + "id": 38, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "right", + "showLegend": false, + "values": [] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P18F45E9DC7E75912" + }, + "editorMode": "code", + "expr": "topk(10, dbrepo_datasource_data_get_total)", + "instant": false, + "legendFormat": "{{uri}}", + "range": true, + "refId": "A" + } + ], + "title": "Popular Data Sources", + "type": "piechart" + }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 4 + "y": 11 }, "id": 22, "panels": [], @@ -506,7 +577,7 @@ "h": 3, "w": 4, "x": 0, - "y": 5 + "y": 12 }, "id": 17, "options": { @@ -526,7 +597,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -588,7 +659,7 @@ "h": 3, "w": 4, "x": 4, - "y": 5 + "y": 12 }, "id": 24, "options": { @@ -608,7 +679,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -658,7 +729,7 @@ "h": 3, "w": 4, "x": 8, - "y": 5 + "y": 12 }, "id": 25, "options": { @@ -678,7 +749,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -728,7 +799,7 @@ "h": 3, "w": 4, "x": 12, - "y": 5 + "y": 12 }, "id": 26, "options": { @@ -748,7 +819,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -800,7 +871,7 @@ "h": 3, "w": 4, "x": 16, - "y": 5 + "y": 12 }, "id": 27, "options": { @@ -819,7 +890,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -881,7 +952,7 @@ "h": 7, "w": 12, "x": 0, - "y": 8 + "y": 15 }, "id": 20, "options": { @@ -902,7 +973,7 @@ "sizing": "auto", "valueMode": "color" }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -988,7 +1059,7 @@ "h": 7, "w": 12, "x": 12, - "y": 8 + "y": 15 }, "id": 21, "options": { @@ -1030,7 +1101,7 @@ "h": 1, "w": 24, "x": 0, - "y": 15 + "y": 22 }, "id": 31, "panels": [], @@ -1052,8 +1123,7 @@ "mode": "absolute", "steps": [ { - "color": "purple", - "value": null + "color": "purple" }, { "color": "red", @@ -1081,7 +1151,7 @@ "h": 3, "w": 4, "x": 0, - "y": 16 + "y": 23 }, "id": 32, "options": { @@ -1101,7 +1171,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -1138,8 +1208,7 @@ "mode": "absolute", "steps": [ { - "color": "blue", - "value": null + "color": "blue" } ] }, @@ -1151,7 +1220,7 @@ "h": 3, "w": 4, "x": 4, - "y": 16 + "y": 23 }, "id": 29, "options": { @@ -1171,7 +1240,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -1208,8 +1277,7 @@ "mode": "absolute", "steps": [ { - "color": "blue", - "value": null + "color": "blue" } ] }, @@ -1221,7 +1289,7 @@ "h": 3, "w": 4, "x": 8, - "y": 16 + "y": 23 }, "id": 30, "options": { @@ -1241,7 +1309,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -1278,8 +1346,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "#EAB839", @@ -1303,7 +1370,7 @@ "h": 3, "w": 4, "x": 12, - "y": 16 + "y": 23 }, "id": 35, "options": { @@ -1323,7 +1390,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -1360,8 +1427,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "#EAB839", @@ -1385,7 +1451,7 @@ "h": 3, "w": 4, "x": 16, - "y": 16 + "y": 23 }, "id": 36, "options": { @@ -1405,7 +1471,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -1442,8 +1508,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "#EAB839", @@ -1467,7 +1532,7 @@ "h": 3, "w": 4, "x": 20, - "y": 16 + "y": 23 }, "id": 37, "options": { @@ -1487,7 +1552,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.4.3", + "pluginVersion": "10.4.9", "targets": [ { "datasource": { @@ -1557,8 +1622,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -1601,7 +1665,7 @@ "h": 7, "w": 12, "x": 0, - "y": 19 + "y": 26 }, "id": 33, "options": { @@ -1644,7 +1708,7 @@ "h": 1, "w": 24, "x": 0, - "y": 26 + "y": 33 }, "id": 2, "panels": [], @@ -1699,8 +1763,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1715,7 +1778,7 @@ "h": 7, "w": 12, "x": 0, - "y": 27 + "y": 34 }, "id": 23, "options": { @@ -1790,8 +1853,7 @@ "mode": "absolute", "steps": [ { - "color": "red", - "value": null + "color": "red" }, { "color": "green", @@ -1806,7 +1868,7 @@ "h": 7, "w": 12, "x": 12, - "y": 27 + "y": 34 }, "id": 16, "options": { @@ -1891,8 +1953,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -1965,7 +2026,7 @@ "h": 7, "w": 12, "x": 0, - "y": 34 + "y": 41 }, "id": 6, "options": { @@ -2052,8 +2113,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -2126,7 +2186,7 @@ "h": 7, "w": 12, "x": 12, - "y": 34 + "y": 41 }, "id": 7, "options": { @@ -2169,104 +2229,52 @@ "type": "prometheus", "uid": "P18F45E9DC7E75912" }, + "description": "Top 10 by frequency of access", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 25, - "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false - }, - "insertNulls": false, - "lineInterpolation": "smooth", - "lineWidth": 2, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" } }, "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, "unit": "reqps" }, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "/.*search-service.*/" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "orange", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byRegexp", - "options": "/.*analyse-service.*/" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-orange", - "mode": "fixed" - } - } - ] - } - ] + "overrides": [] }, "gridPos": { "h": 7, "w": 12, "x": 0, - "y": 41 + "y": 48 }, "id": 18, "options": { + "displayLabels": [ + "percent" + ], "legend": { "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "displayMode": "hidden", + "placement": "right", + "showLegend": false, + "values": [] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, "tooltip": { - "mode": "multi", + "mode": "single", "sort": "none" } }, @@ -2277,15 +2285,15 @@ "uid": "P18F45E9DC7E75912" }, "editorMode": "code", - "expr": "rate(flask_http_request_duration_seconds_count{status=~\"200|201|202\",path!=\"/health\"}[$__rate_interval])", + "expr": "topk(10, rate(dbrepo_table_data_get_total[$__range]))", "instant": false, - "legendFormat": "{{method}} {{instance}} {{path}} ({{status}})", + "legendFormat": "__auto", "range": true, "refId": "A" } ], - "title": "Successful API Requests", - "type": "timeseries" + "title": "Popular Datasources", + "type": "piechart" }, { "datasource": { @@ -2334,8 +2342,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -2378,7 +2385,7 @@ "h": 7, "w": 12, "x": 12, - "y": 41 + "y": 48 }, "id": 19, "options": { @@ -2421,13 +2428,13 @@ "list": [] }, "time": { - "from": "now-30m", + "from": "now-1h", "to": "now" }, "timepicker": {}, "timezone": "browser", "title": "DBRepo - Overview", "uid": "bdz20owu8zn5se", - "version": 1, + "version": 8, "weekStart": "" } \ No newline at end of file diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java index 13cfc5c560..4f1b5d59c9 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java @@ -10,10 +10,7 @@ import at.tuwien.api.database.query.QueryPersistDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.exception.*; import at.tuwien.mapper.MetadataMapper; -import at.tuwien.service.CredentialService; -import at.tuwien.service.SchemaService; -import at.tuwien.service.StorageService; -import at.tuwien.service.SubsetService; +import at.tuwien.service.*; import at.tuwien.utils.UserUtil; import at.tuwien.validation.EndpointValidator; import io.micrometer.observation.annotation.Observed; @@ -55,17 +52,19 @@ public class SubsetEndpoint extends AbstractEndpoint { private final SchemaService schemaService; private final SubsetService subsetService; private final MetadataMapper metadataMapper; + private final MetricsService metricsService; private final StorageService storageService; private final CredentialService credentialService; private final EndpointValidator endpointValidator; @Autowired public SubsetEndpoint(SchemaService schemaService, SubsetService subsetService, MetadataMapper metadataMapper, - StorageService storageService, CredentialService credentialService, - EndpointValidator endpointValidator) { + MetricsService metricsService, StorageService storageService, + CredentialService credentialService, EndpointValidator endpointValidator) { this.schemaService = schemaService; this.subsetService = subsetService; this.metadataMapper = metadataMapper; + this.metricsService = metricsService; this.storageService = storageService; this.credentialService = credentialService; this.endpointValidator = endpointValidator; @@ -188,6 +187,7 @@ public class SubsetEndpoint extends AbstractEndpoint { log.trace("accept header matches csv"); try { final Dataset<Row> dataset = subsetService.getData(database, subset, null, null); + metricsService.countSubsetGetData(databaseId, subsetId); final ExportResourceDto resource = storageService.transformDataset(dataset); final HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); @@ -366,6 +366,7 @@ public class SubsetEndpoint extends AbstractEndpoint { .build(); } final Dataset<Row> dataset = subsetService.getData(database, subset, page, size); + metricsService.countSubsetGetData(databaseId, subsetId); final ViewDto view = schemaService.inspectView(database, metadataMapper.queryDtoToViewName(subset)); headers.set("Access-Control-Expose-Headers", "X-Id X-Headers"); headers.set("X-Headers", String.join(",", view.getColumns().stream().map(ViewColumnDto::getInternalName).toList())); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index 4191726dd9..11720b4906 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -12,10 +12,7 @@ import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; -import at.tuwien.service.CredentialService; -import at.tuwien.service.SchemaService; -import at.tuwien.service.StorageService; -import at.tuwien.service.TableService; +import at.tuwien.service.*; import at.tuwien.utils.UserUtil; import at.tuwien.validation.EndpointValidator; import io.micrometer.observation.annotation.Observed; @@ -55,17 +52,19 @@ public class TableEndpoint extends AbstractEndpoint { private final TableService tableService; private final SchemaService schemaService; + private final MetricsService metricsService; private final StorageService storageService; private final CredentialService credentialService; private final EndpointValidator endpointValidator; private final MetadataServiceGateway metadataServiceGateway; @Autowired - public TableEndpoint(TableService tableService, SchemaService schemaService, StorageService storageService, - CredentialService credentialService, EndpointValidator endpointValidator, - MetadataServiceGateway metadataServiceGateway) { + public TableEndpoint(TableService tableService, SchemaService schemaService, MetricsService metricsService, + StorageService storageService, CredentialService credentialService, + EndpointValidator endpointValidator, MetadataServiceGateway metadataServiceGateway) { this.tableService = tableService; this.schemaService = schemaService; + this.metricsService = metricsService; this.storageService = storageService; this.credentialService = credentialService; this.endpointValidator = endpointValidator; @@ -291,10 +290,12 @@ public class TableEndpoint extends AbstractEndpoint { } headers.set("Access-Control-Expose-Headers", "X-Headers"); headers.set("X-Headers", String.join(",", table.getColumns().stream().map(ColumnDto::getInternalName).toList())); + final Dataset<Row> dataset = tableService.getData(table.getDatabase(), table.getInternalName(), timestamp, + null, null, null, null); + metricsService.countTableGetData(databaseId, tableId); return ResponseEntity.ok() .headers(headers) - .body(transform(tableService.getData(table.getDatabase(), table.getInternalName(), timestamp, - null, null, null, null))); + .body(transform(dataset)); } catch (SQLException | QueryMalformedException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); @@ -625,6 +626,7 @@ public class TableEndpoint extends AbstractEndpoint { } final Dataset<Row> dataset = tableService.getData(table.getDatabase(), table.getInternalName(), timestamp, null, null, null, null); + metricsService.countTableGetData(databaseId, tableId); final ExportResourceDto resource = storageService.transformDataset(dataset); final HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java index b08c300b45..c8da423986 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java @@ -8,10 +8,7 @@ import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.database.internal.PrivilegedViewDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.exception.*; -import at.tuwien.service.CredentialService; -import at.tuwien.service.StorageService; -import at.tuwien.service.TableService; -import at.tuwien.service.ViewService; +import at.tuwien.service.*; import at.tuwien.utils.UserUtil; import at.tuwien.validation.EndpointValidator; import io.micrometer.observation.annotation.Observed; @@ -26,6 +23,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; @@ -48,15 +47,18 @@ public class ViewEndpoint extends AbstractEndpoint { private final ViewService viewService; private final TableService tableService; + private final MetricsService metricsService; private final StorageService storageService; private final CredentialService credentialService; private final EndpointValidator endpointValidator; @Autowired - public ViewEndpoint(ViewService viewService, TableService tableService, StorageService storageService, - CredentialService credentialService, EndpointValidator endpointValidator) { + public ViewEndpoint(ViewService viewService, TableService tableService, MetricsService metricsService, + StorageService storageService, CredentialService credentialService, + EndpointValidator endpointValidator) { this.viewService = viewService; this.tableService = tableService; + this.metricsService = metricsService; this.storageService = storageService; this.credentialService = credentialService; this.endpointValidator = endpointValidator; @@ -286,10 +288,12 @@ public class ViewEndpoint extends AbstractEndpoint { } headers.set("Access-Control-Expose-Headers", "X-Headers"); headers.set("X-Headers", String.join(",", view.getColumns().stream().map(ViewColumnDto::getInternalName).toList())); + final Dataset<Row> dataset = tableService.getData(view.getDatabase(), view.getInternalName(), timestamp, + page, size, null, null); + metricsService.countViewGetData(databaseId, viewId); return ResponseEntity.ok() .headers(headers) - .body(transform(tableService.getData(view.getDatabase(), view.getInternalName(), timestamp, page, - size, null, null))); + .body(transform(dataset)); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); @@ -349,9 +353,11 @@ public class ViewEndpoint extends AbstractEndpoint { } credentialService.getAccess(databaseId, UserUtil.getId(principal)); } + final Dataset<Row> dataset = tableService.getData(view.getDatabase(), view.getInternalName(), timestamp, null, + null, null, null); + metricsService.countViewGetData(databaseId, viewId); + final ExportResourceDto resource = storageService.transformDataset(dataset); final HttpHeaders headers = new HttpHeaders(); - final ExportResourceDto resource = storageService.transformDataset(tableService.getData(view.getDatabase(), - view.getInternalName(), timestamp, null, null, null, null)); headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); log.trace("export table resulted in resource {}", resource); return ResponseEntity.ok() diff --git a/dbrepo-data-service/rest-service/src/main/resources/application.yml b/dbrepo-data-service/rest-service/src/main/resources/application.yml index 9848928b7b..03d89895cb 100644 --- a/dbrepo-data-service/rest-service/src/main/resources/application.yml +++ b/dbrepo-data-service/rest-service/src/main/resources/application.yml @@ -82,3 +82,4 @@ dbrepo: exchangeName: "${BROKER_EXCHANGE_NAME:dbrepo}" routingKey: "${BROKER_ROUTING_KEY:#}" connectionTimeout: "${SPARQL_CONNECTION_TIMEOUT:10000}" + baseUrl: "${BASE_URL:http://localhost}" diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/config/CacheConfig.java b/dbrepo-data-service/services/src/main/java/at/tuwien/config/CacheConfig.java index a77af353d3..45654157d1 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/config/CacheConfig.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/config/CacheConfig.java @@ -1,25 +1,64 @@ package at.tuwien.config; -import at.tuwien.api.PrivilegedObjectDto; +import at.tuwien.api.container.internal.PrivilegedContainerDto; +import at.tuwien.api.database.DatabaseAccessDto; +import at.tuwien.api.database.internal.PrivilegedDatabaseDto; +import at.tuwien.api.database.internal.PrivilegedViewDto; +import at.tuwien.api.database.table.internal.PrivilegedTableDto; +import at.tuwien.api.user.internal.PrivilegedUserDto; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.util.UUID; import java.util.concurrent.TimeUnit; @Configuration -public class CacheConfig<K, T extends PrivilegedObjectDto> { +public class CacheConfig { @Value("${dbrepo.credentialCacheTimeout}") private Long credentialCacheTimeout; @Bean - public Cache<K, T> cache() { - return Caffeine.newBuilder() - .expireAfterWrite(credentialCacheTimeout, TimeUnit.SECONDS) - .build(); + public Cache<UUID, PrivilegedUserDto> userCache() { + return new ExpiryCache<UUID, PrivilegedUserDto>().build(); + } + + @Bean + public Cache<Long, PrivilegedViewDto> viewCache() { + return new ExpiryCache<Long, PrivilegedViewDto>().build(); + } + + @Bean + public Cache<Long, DatabaseAccessDto> accessCache() { + return new ExpiryCache<Long, DatabaseAccessDto>().build(); + } + + @Bean + public Cache<Long, PrivilegedTableDto> tableCache() { + return new ExpiryCache<Long, PrivilegedTableDto>().build(); + } + + @Bean + public Cache<Long, PrivilegedDatabaseDto> databaseCache() { + return new ExpiryCache<Long, PrivilegedDatabaseDto>().build(); + } + + @Bean + public Cache<Long, PrivilegedContainerDto> containerCache() { + return new ExpiryCache<Long, PrivilegedContainerDto>().build(); + } + + class ExpiryCache<K, T> { + + Cache<K, T> build() { + return Caffeine.newBuilder() + .expireAfterWrite(credentialCacheTimeout, TimeUnit.SECONDS) + .build(); + } + } } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/config/MetricsConfig.java b/dbrepo-data-service/services/src/main/java/at/tuwien/config/MetricsConfig.java index 9ff09ab42b..af9ea49f9f 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/config/MetricsConfig.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/config/MetricsConfig.java @@ -1,33 +1,21 @@ package at.tuwien.config; -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.Metrics; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.aop.ObservedAspect; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +@Getter @Configuration public class MetricsConfig { + @Value("${dbrepo.baseUrl}") + private String baseUrl; + @Bean public ObservedAspect observedAspect(ObservationRegistry observationRegistry) { return new ObservedAspect(observationRegistry); } - - @Bean - public Counter httpDataAccessCounter() { - return Counter.builder("dbrepo.data.access") - .tag("protocol", "http") - .description("The total number of accessed data sources") - .register(Metrics.globalRegistry); - } - - @Bean - public Counter amqpDataAccessCounter() { - return Counter.builder("dbrepo.data.access") - .tag("protocol", "amqp") - .description("The total number of accessed data sources") - .register(Metrics.globalRegistry); - } } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/KeycloakGatewayImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/KeycloakGatewayImpl.java index fe1b2e3611..1e235de4c6 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/KeycloakGatewayImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/KeycloakGatewayImpl.java @@ -1,9 +1,10 @@ package at.tuwien.gateway.impl; -import at.tuwien.api.auth.KeycloakErrorDto; import at.tuwien.api.keycloak.TokenDto; import at.tuwien.config.KeycloakConfig; -import at.tuwien.exception.*; +import at.tuwien.exception.AccountNotSetupException; +import at.tuwien.exception.AuthServiceConnectionException; +import at.tuwien.exception.CredentialsInvalidException; import at.tuwien.gateway.KeycloakGateway; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; @@ -40,18 +41,6 @@ public class KeycloakGatewayImpl implements KeycloakGateway { payload.add("client_secret", keycloakConfig.getKeycloakClientSecret()); final String url = keycloakConfig.getKeycloakEndpoint() + "/realms/dbrepo/protocol/openid-connect/token"; log.trace("request user token from url: {}", url); - log.trace("request username: {}", username); - if (password.isEmpty() || password.isBlank()) { - log.warn("request password: (empty)"); - } else { - log.trace("request password: (set)"); - } - log.trace("request client_id: {}", keycloakConfig.getKeycloakClient()); - if (keycloakConfig.getKeycloakClientSecret().isEmpty() || keycloakConfig.getKeycloakClientSecret().isBlank()) { - log.warn("request client_secret: (empty)"); - } else { - log.trace("request client_secret: (set)"); - } final ResponseEntity<TokenDto> response; try { response = new RestTemplate() diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java index 884732bdc1..0adfafa8f9 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java @@ -64,4 +64,22 @@ public interface MetadataMapper { IdentifierBriefDto identifierDtoToIdentifierBriefDto(IdentifierDto data); + default String metricToUri(String baseUrl, Long databaseId, Long tableId, Long subsetId, Long viewId) { + final StringBuilder uri = new StringBuilder(baseUrl) + .append("/database/") + .append(databaseId); + if (tableId != null) { + uri.append("/table/") + .append(tableId); + } else if (subsetId != null) { + uri.append("/subset/") + .append(subsetId); + } else if (viewId != null) { + uri.append("/view/") + .append(viewId); + } + log.trace("count uri: {}", uri); + return uri.toString(); + } + } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/MetricsService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/MetricsService.java new file mode 100644 index 0000000000..131bae7287 --- /dev/null +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/MetricsService.java @@ -0,0 +1,10 @@ +package at.tuwien.service; + +public interface MetricsService { + + void countTableGetData(Long databaseId, Long tableId); + + void countSubsetGetData(Long databaseId, Long subsetId); + + void countViewGetData(Long databaseId, Long viewId); +} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/MetricsServicePrometheusImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/MetricsServicePrometheusImpl.java new file mode 100644 index 0000000000..73754c9f5f --- /dev/null +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/MetricsServicePrometheusImpl.java @@ -0,0 +1,47 @@ +package at.tuwien.service.impl; + +import at.tuwien.config.MetricsConfig; +import at.tuwien.mapper.MetadataMapper; +import at.tuwien.service.MetricsService; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Metrics; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +public class MetricsServicePrometheusImpl implements MetricsService { + + private final MetricsConfig metricsConfig; + private final MetadataMapper metadataMapper; + + public MetricsServicePrometheusImpl(MetricsConfig metricsConfig, MetadataMapper metadataMapper) { + this.metricsConfig = metricsConfig; + this.metadataMapper = metadataMapper; + } + + @Override + public void countTableGetData(Long databaseId, Long tableId) { + countGetData(databaseId, tableId, null, null); + } + + @Override + public void countSubsetGetData(Long databaseId, Long subsetId) { + countGetData(databaseId, null, subsetId, null); + } + + @Override + public void countViewGetData(Long databaseId, Long viewId) { + countGetData(databaseId, null, null, viewId); + } + + public void countGetData(Long databaseId, Long tableId, Long subsetId, Long viewId) { + Counter.builder("dbrepo.datasource.data.get") + .tag("uri", metadataMapper.metricToUri(metricsConfig.getBaseUrl(), databaseId, tableId, subsetId, viewId)) + .tag("protocol", "http") + .description("The total number of accessed data sources") + .register(Metrics.globalRegistry) + .increment(); + } + +} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceRabbitMqImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceRabbitMqImpl.java index aff8513787..d5127e050e 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceRabbitMqImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceRabbitMqImpl.java @@ -6,7 +6,6 @@ import at.tuwien.mapper.DataMapper; import at.tuwien.mapper.MetadataMapper; import at.tuwien.service.QueueService; import com.mchange.v2.c3p0.ComboPooledDataSource; -import io.micrometer.core.instrument.Counter; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -21,14 +20,11 @@ import java.util.Optional; @Service public class QueueServiceRabbitMqImpl extends HibernateConnector implements QueueService { - private final Counter amqpDataAccessCounter; private final DataMapper dataMapper; private final MetadataMapper metadataMapper; @Autowired - public QueueServiceRabbitMqImpl(Counter amqpDataAccessCounter, DataMapper dataMapper, - MetadataMapper metadataMapper) { - this.amqpDataAccessCounter = amqpDataAccessCounter; + public QueueServiceRabbitMqImpl(DataMapper dataMapper, MetadataMapper metadataMapper) { this.dataMapper = dataMapper; this.metadataMapper = metadataMapper; } @@ -54,7 +50,6 @@ public class QueueServiceRabbitMqImpl extends HibernateConnector implements Queu preparedStatement.executeUpdate(); log.trace("executed statement in {} ms", System.currentTimeMillis() - start); log.trace("successfully inserted tuple"); - amqpDataAccessCounter.increment(); } finally { dataSource.close(); } diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java index 98c77c7b0d..f63c387c98 100644 --- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java +++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java @@ -483,7 +483,6 @@ public interface MetadataMapper { @Mappings({ @Mapping(target = "databaseId", source = "tdbid"), - @Mapping(target = "isPublic", source = "database.isPublic"), }) TableBriefDto tableToTableBriefDto(Table data); @@ -535,7 +534,7 @@ public interface MetadataMapper { .internalName(data.getInternalName()) .owner(userToUserBriefDto(data.getOwner())) .tdbid(data.getTdbid()) - .isPublic(data.getDatabase().getIsPublic()) + .isPublic(data.getIsPublic()) .isSchemaPublic(data.getIsSchemaPublic()) .isVersioned(true) .description(data.getDescription()) @@ -720,7 +719,7 @@ public interface MetadataMapper { @Mappings({ @Mapping(target = "tableId", source = "table.id"), @Mapping(target = "databaseId", source = "table.database.id"), - @Mapping(target = "isPublic", source = "table.database.isPublic"), + @Mapping(target = "isPublic", source = "table.isSchemaPublic"), @Mapping(target = "description", source = "description"), @Mapping(target = "table", ignore = true), @Mapping(target = "views", ignore = true) diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index 3dbc450735..a517c39d10 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -403,7 +403,8 @@ public class TableEndpoint { @NotNull Principal principal) throws NotAllowedException, DataServiceException, DataServiceConnectionException, DatabaseNotFoundException, TableNotFoundException, SearchServiceException, SearchServiceConnectionException { - log.debug("endpoint update table, databaseId={}, data.is_public={}", databaseId, data.getIsPublic()); + log.debug("endpoint update table, databaseId={}, data.is_public={}, data.is_schema_public={}", databaseId, + data.getIsPublic(), data.getIsSchemaPublic()); final Table table = tableService.findById(databaseId, tableId); if (!table.getOwner().equals(principal)) { log.error("Failed to update table: not owner"); diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java index 4a4a9ccaca..57b546fcea 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java @@ -220,8 +220,8 @@ public class TableServiceImpl implements TableService { } final Table tableEntity = optional.get(); tableEntity.setIsPublic(data.getIsPublic()); - tableEntity.setDescription(data.getDescription()); tableEntity.setIsSchemaPublic(data.getIsSchemaPublic()); + tableEntity.setDescription(data.getDescription()); final Database database = databaseRepository.save(table.getDatabase()); /* update in search service */ searchServiceGateway.update(database); diff --git a/dbrepo-ui/components/subset/SubsetList.vue b/dbrepo-ui/components/subset/SubsetList.vue index b977daffa5..f57dc68a88 100644 --- a/dbrepo-ui/components/subset/SubsetList.vue +++ b/dbrepo-ui/components/subset/SubsetList.vue @@ -26,6 +26,21 @@ :to="link(item)" :href="link(item)"> <template v-slot:append> + <v-chip + v-if="database.is_public" + size="small" + class="ml-2" + color="success" + :text="$t('toolbars.database.public')" + variant="outlined" /> + <v-chip + v-if="!database.is_public" + size="small" + class="ml-2" + :color="colorVariant" + variant="outlined" + :text="$t('toolbars.database.private')" + flat /> <v-tooltip v-if="hasPublishedIdentifier(item)" :text="$t('pages.identifier.pid.title')" @@ -65,6 +80,15 @@ export default { }, database () { return this.cacheStore.getDatabase + }, + isContrastTheme () { + return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast') + }, + isDarkTheme () { + return this.$vuetify.theme.global.name.toLowerCase().startsWith('dark') + }, + colorVariant () { + return this.isContrastTheme ? '' : (this.isDarkTheme ? 'tertiary' : 'secondary') } }, mounted () { @@ -88,25 +112,25 @@ export default { toast.error(this.$t(code)) }) }, - title (query) { - if (query.identifiers.length === 0) { - return formatTimestampUTCLabel(query.created) + title (subset) { + if (subset.identifiers.length === 0) { + return subset.query } const identifierService = useIdentifierService() - return identifierService.identifierPreferEnglishTitle(query.identifiers[0]) + return identifierService.identifierPreferEnglishTitle(subset.identifiers[0]) }, - subtitle (query) { - if (query.identifiers.length === 0) { + subtitle (subset) { + if (subset.identifiers.length === 0) { return null } const identifierService = useIdentifierService() - return identifierService.identifierPreferEnglishDescription(query.identifiers[0]) + return identifierService.identifierPreferEnglishDescription(subset.identifiers[0]) }, - link (query) { - return `/database/${this.$route.params.database_id}/subset/${query.id}/info` + link (subset) { + return `/database/${this.$route.params.database_id}/subset/${subset.id}/info` }, - clazz (view) { - return this.hasPublishedIdentifier(view) ? 'primary-text' : null + clazz (subset) { + return this.hasPublishedIdentifier(subset) ? 'primary-text' : null }, hasPublishedIdentifier (subset) { if (!subset.identifiers) { diff --git a/dbrepo-ui/components/view/ViewList.vue b/dbrepo-ui/components/view/ViewList.vue index 6fe8451903..543a8746af 100644 --- a/dbrepo-ui/components/view/ViewList.vue +++ b/dbrepo-ui/components/view/ViewList.vue @@ -61,9 +61,6 @@ export default { } }, computed: { - loadingColor () { - return this.error ? 'red lighten-2' : 'primary' - }, user () { return this.userStore.getUser }, diff --git a/dbrepo-ui/components/view/ViewToolbar.vue b/dbrepo-ui/components/view/ViewToolbar.vue index 1f122cc7a4..64ea3f1029 100644 --- a/dbrepo-ui/components/view/ViewToolbar.vue +++ b/dbrepo-ui/components/view/ViewToolbar.vue @@ -187,7 +187,7 @@ export default { return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all' }, canReadData () { - if (!this.view) { + if (!this.cachedView) { return false } if (this.cachedView.is_public) { diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue index 6aed9307c4..9751fce5b7 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue @@ -96,7 +96,7 @@ export default { return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all' }, canReadData () { - if (!this.view) { + if (!this.cachedView) { return false } if (this.cachedView.is_public) { diff --git a/helm/dbrepo/README.md b/helm/dbrepo/README.md index f8c6b12d75..b995791a15 100644 --- a/helm/dbrepo/README.md +++ b/helm/dbrepo/README.md @@ -1,13 +1,13 @@ # DBRepo Helm chart -[DBRepo](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/) is a database repository system that +[DBRepo](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/) is a database repository system that allows researchers to ingest data into a central, versioned repository through common interfaces. ## TL;DR Download the sample [ -`values.yaml`](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.5/helm-charts/dbrepo/values.yaml?inline=true) +`values.yaml`](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/raw/release-1.6/helm-charts/dbrepo/values.yaml?inline=true) for your deployment and update the variables, especially `hostname`. ```bash @@ -65,10 +65,10 @@ The command removes all the Kubernetes components associated with the chart and ### Metadata Database | Name | Description | Value | -| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| ---------------------------------------- |----------------------------------------------------------------------------------------------------------------------------------------| ---------------------------------------------------------------------- | | `metadatadb.enabled` | Enable the Metadata datadb. | `true` | | `metadatadb.host` | The hostname for the microservices. | `metadata-db` | -| `metadatadb.extraFlags` | Extra flags to ensure the query store works as intended, ref https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/api/data-db/#data | `--character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci` | +| `metadatadb.extraFlags` | Extra flags to ensure the query store works as intended, ref https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/api/data-db/#data | `--character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci` | | `metadatadb.rootUser.user` | The root username. | `root` | | `metadatadb.rootUser.password` | The root user password. | `dbrepo` | | `metadatadb.db.name` | The database name. | `dbrepo` | @@ -96,9 +96,9 @@ The command removes all the Kubernetes components associated with the chart and ### Data Database | Name | Description | Value | -| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| ------------------------------------ |----------------------------------------------------------------------------------------------------------------------------------------| ---------------------------------------------------------------------- | | `datadb.host` | The hostname for the microservices. | `data-db` | -| `datadb.extraFlags` | Extra flags to ensure the query store works as intended, ref https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/api/data-db/#data | `--character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci` | +| `datadb.extraFlags` | Extra flags to ensure the query store works as intended, ref https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/api/data-db/#data | `--character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci` | | `datadb.rootUser.user` | The root username. | `root` | | `datadb.rootUser.password` | The root user password. | `dbrepo` | | `datadb.db.name` | The database name. | `dbrepo` | diff --git a/lib/python/README.md b/lib/python/README.md index 7b5b7d6da7..443787cbb4 100644 --- a/lib/python/README.md +++ b/lib/python/README.md @@ -48,17 +48,17 @@ client.import_table_data(database_id=7, table_id=13, file_name_or_data_frame=df) ## Supported Features & Best-Practices - Manage user - account ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/api/#create-user-account)) + account ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/api/#create-user-account)) - Manage - databases ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/usage-overview/#create-database)) + databases ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/usage-overview/#create-database)) - Manage database access & - visibility ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/api/#create-database)) + visibility ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/api/#create-database)) - Import - dataset ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/api/#import-dataset)) + dataset ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/api/#import-dataset)) - Create persistent - identifiers ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/api/#assign-database-pid)) + identifiers ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/api/#assign-database-pid)) - Execute - queries ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/api/#export-subset)) + queries ([docs](https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/api/#export-subset)) - Get data from tables/views/subsets ## Configure diff --git a/mkdocs.yml b/mkdocs.yml index b8e3ed5a6d..5ec3a92799 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: Database Repository -site_url: https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/ +site_url: https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/ repo_url: https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services repo_name: fda-services site_author: Research Unit Data Science, Technische Universität Wien @@ -17,6 +17,8 @@ nav: - Concepts: - Overview: concepts/index.md - Authentication: concepts/authentication.md + - Data Versioning: concepts/data-versioning.md + - Data Visibility: concepts/data-visibility.md - Messaging: concepts/messaging.md - Monitoring: concepts/monitoring.md - Persistent Identifier: concepts/pid.md @@ -118,9 +120,9 @@ markdown_extensions: custom_icons: - .docs/overrides/.icons extra: - homepage: https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/ + homepage: https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/ version: - default: 1.5 + default: 1.6 provider: mike social: - icon: simple/artifacthub diff --git a/versions.json b/versions.json index 8ba789267d..b42a3dfd7d 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,9 @@ [ + { + "version": "1.6", + "title": "1.6", + "aliases": [] + }, { "version": "1.5", "title": "1.5", -- GitLab