diff --git a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java index 1997759a28d1777842aa9573de1957fba2ea14e5..51d8cc4d5a993ad33fefddac1d5907c2298e7906 100644 --- a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java +++ b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java @@ -59,6 +59,7 @@ public class IdentifierEndpoint { } @GetMapping("/{id}") + @Deprecated @Transactional(readOnly = true) @Timed(value = "identifier.export", description = "Time needed to export an identifier") @Operation(summary = "Export some identifier metadata") diff --git a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java index 133dd4abc1ab032643d0dc35f2dd59943b20bd24..6150c9313bb7d77a415346ea0d60a833526cf9ff 100644 --- a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java +++ b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java @@ -1,5 +1,6 @@ package at.tuwien.endpoints; +import at.tuwien.ExportResource; import at.tuwien.config.EndpointConfig; import at.tuwien.entities.identifier.Identifier; import at.tuwien.exception.IdentifierNotFoundException; @@ -43,7 +44,7 @@ public class PersistenceEndpoint { @Operation(summary = "Find some identifier") public ResponseEntity<?> find(@Valid @PathVariable("pid") Long pid, @RequestHeader(HttpHeaders.ACCEPT) String accept) throws IdentifierNotFoundException, - QueryNotFoundException, RemoteUnavailableException, IdentifierRequestException { + QueryNotFoundException, RemoteUnavailableException { log.debug("find identifier endpoint, pid={}, accept={}", pid, accept); final Identifier identifier = identifierService.find(pid); log.info("Found persistent identifier with id {}", identifier.getId()); @@ -56,6 +57,13 @@ public class PersistenceEndpoint { } else if (accept.equals("text/csv")) { log.trace("accept header matches csv"); return ResponseEntity.ok(identifierService.exportResource(pid)); + } else if (accept.equals("text/xml")) { + final HttpHeaders headers = new HttpHeaders(); + final ExportResource resource = identifierService.exportMetadata(pid); + headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); + return ResponseEntity.ok() + .headers(headers) + .body(resource.getResource()); } } log.trace("no accept header present, serving http redirect"); diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/gateway/QueryServiceGateway.java b/fda-identifier-service/services/src/main/java/at/tuwien/gateway/QueryServiceGateway.java index af0926bdb07f3a9f63e5464bf30bb391924e4e4a..1224179b96e9c0d0bb41ff8d0b74df9af4fd2c7b 100644 --- a/fda-identifier-service/services/src/main/java/at/tuwien/gateway/QueryServiceGateway.java +++ b/fda-identifier-service/services/src/main/java/at/tuwien/gateway/QueryServiceGateway.java @@ -25,6 +25,16 @@ public interface QueryServiceGateway { QueryDto find(Long containerId, Long databaseId, IdentifierCreateDto identifier, String authorization) throws QueryNotFoundException, RemoteUnavailableException; - ExportDto export(Long containerId, Long databaseId, Long queryId) throws RemoteUnavailableException, + /** + * Exports a query by given id. + * + * @param containerId The container id. + * @param databaseId The database id. + * @param queryId The query id. + * @return The exported resource as bytes. + * @throws RemoteUnavailableException The remote service is not available. + * @throws QueryNotFoundException The query was not found. + */ + byte[] export(Long containerId, Long databaseId, Long queryId) throws RemoteUnavailableException, QueryNotFoundException; } diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/gateway/impl/QueryServiceGatewayImpl.java b/fda-identifier-service/services/src/main/java/at/tuwien/gateway/impl/QueryServiceGatewayImpl.java index 10ffefb8523af81c6ccdbfb45167463cb0921f72..de0a2d408881ffecf452b65beac0fc70853b3c8f 100644 --- a/fda-identifier-service/services/src/main/java/at/tuwien/gateway/impl/QueryServiceGatewayImpl.java +++ b/fda-identifier-service/services/src/main/java/at/tuwien/gateway/impl/QueryServiceGatewayImpl.java @@ -57,13 +57,15 @@ public class QueryServiceGatewayImpl implements QueryServiceGateway { } @Override - public ExportDto export(Long containerId, Long databaseId, Long queryId) + public byte[] export(Long containerId, Long databaseId, Long queryId) throws RemoteUnavailableException, QueryNotFoundException { final String url = "/api/container/" + containerId + "/database/" + databaseId + "/query/" + queryId + "/export"; - final ResponseEntity<ExportDto> response; + final HttpHeaders headers = new HttpHeaders(); + headers.add("Accept", "text/csv"); + final ResponseEntity<byte[]> response; try { log.trace("call gateway path {}", url); - response = restTemplate.exchange(url, HttpMethod.GET, null, ExportDto.class); + response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, headers), byte[].class); } catch (HttpServerErrorException.ServiceUnavailable e) { log.error("Query service not available: {}", e.getMessage()); throw new RemoteUnavailableException("Query service not available", e); diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java b/fda-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java index e220c40c54fe07d1dcc7d09ee889543067fe7bb1..261b5b71af3c407ec631dae7638cc9baff110a3e 100644 --- a/fda-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java +++ b/fda-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java @@ -79,13 +79,12 @@ public interface IdentifierService { * * @param identifierId The identifier id. * @return The XML resource, if successful. - * @throws IdentifierNotFoundException - * @throws QueryNotFoundException + * @throws IdentifierNotFoundException The identifier was not found in the metadata database or was deleted. + * @throws QueryNotFoundException The query was not found in the metadata database or was deleted. * @throws RemoteUnavailableException - * @throws IdentifierRequestException */ InputStreamResource exportResource(Long identifierId) - throws IdentifierNotFoundException, QueryNotFoundException, RemoteUnavailableException, IdentifierRequestException; + throws IdentifierNotFoundException, QueryNotFoundException, RemoteUnavailableException; /** * Updated the metadata (only) on the identifier for a given id in the metadata database. diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java b/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java index 61b44d1d462cff854381b3b1cdf0812a1a8ac9e7..d2fba7a6fb7450db5fba58d9c1ec454b69b4a92a 100644 --- a/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java +++ b/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java @@ -25,6 +25,7 @@ import org.springframework.core.io.InputStreamResource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.security.Principal; @@ -184,8 +185,7 @@ public class IdentifierServiceImpl implements IdentifierService { @Override @Transactional(readOnly = true) public InputStreamResource exportResource(Long identifierId) - throws IdentifierNotFoundException, QueryNotFoundException, RemoteUnavailableException, - IdentifierRequestException { + throws IdentifierNotFoundException, QueryNotFoundException, RemoteUnavailableException { /* check */ final Identifier identifier = find(identifierId); if (identifier.getType().equals(IdentifierType.DATABASE)) { @@ -194,15 +194,9 @@ public class IdentifierServiceImpl implements IdentifierService { throw new IdentifierNotFoundException("Failed to find identifier"); } /* export */ - final ExportDto export = queryServiceGateway.export(identifier.getContainerId(), + final byte[] file = queryServiceGateway.export(identifier.getContainerId(), identifier.getDatabaseId(), identifier.getQueryId()); - final InputStreamResource resource; - try { - resource = new InputStreamResource(FileUtils.openInputStream(new File("/tmp/" + export.getLocation()))); - } catch (IOException e) { - log.error("Failed to open export file: {}", e.getMessage()); - throw new IdentifierRequestException("Failed to open export file", e); - } + final InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(file)); log.trace("found resource {}", resource); return resource; } diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java index aec3139b2d79ec316e9d8b1272a53c39aa45329b..57f0ba802bc89204e3ce9b9f6f3da5d33e3ee6c9 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java @@ -112,13 +112,13 @@ public class QueryEndpoint extends AbstractEndpoint { public ResponseEntity<?> export(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("queryId") Long queryId, - @RequestParam(value = "download", required = false) String download, + @RequestHeader(HttpHeaders.ACCEPT) String accept, Principal principal) throws QueryStoreException, QueryNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, TableMalformedException, FileStorageException, NotAllowedException, QueryMalformedException, DatabaseConnectionException { - log.debug("endpoint export query, containerId={}, databaseId={}, queryId={}, download={}, principal={}", - containerId, databaseId, queryId, download, principal); + log.debug("endpoint export query, containerId={}, databaseId={}, queryId={}, accept={}, principal={}", + containerId, databaseId, queryId, accept, principal); if (!hasQueryPermission(containerId, databaseId, queryId, "QUERY_EXPORT", principal)) { log.error("Missing export query permission"); throw new NotAllowedException("Missing export query permission"); @@ -127,7 +127,7 @@ public class QueryEndpoint extends AbstractEndpoint { final Query query = storeService.findOne(containerId, databaseId, queryId); log.trace("querystore returned query {}", query); final ExportResource resource = queryService.findOne(containerId, databaseId, queryId); - if (download != null) { + if (accept.equals("text/csv")) { final HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); log.trace("export query resulted in resource {}", resource); @@ -135,12 +135,9 @@ public class QueryEndpoint extends AbstractEndpoint { .headers(headers) .body(resource.getResource()); } - final ExportDto dto = ExportDto.builder() - .location(resource.getFilename()) + log.error("Failed to export, non-csv exports are not supported"); + return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED) .build(); - log.trace("export query resulted in export file {}", dto); - return ResponseEntity.ok() - .body(dto); } } diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java index a615194e6418a377ffdb7180a8713b5263c517d1..85120ecf9077b22e217b65b8080bad004d9c2eeb 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java @@ -61,6 +61,7 @@ public class TableDataEndpoint extends AbstractEndpoint { } @PutMapping + @Deprecated @Transactional @Timed(value = "data.update", description = "Time needed to update data in a table") @Operation(summary = "Update data", security = @SecurityRequirement(name = "bearerAuth")) diff --git a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java index 3bf708d08eba45bc1f601310ed56bc6ceed4f114..87ba422bb1758f065cfaa3721a3dcf8de2094fa1 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java +++ b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java @@ -323,6 +323,16 @@ public interface QueryMapper { .append("`"); idx[0]++; }); + statement.append(" UNION ALL SELECT "); + int[] jdx = new int[]{0}; + table.getColumns() + .forEach(column -> { + statement.append(jdx[0] != 0 ? "," : "") + .append("`") + .append(column.getInternalName()) + .append("`"); + jdx[0]++; + }); statement.append("FROM `") .append(table.getInternalName()) .append("`"); diff --git a/fda-ui/components/TableList.vue b/fda-ui/components/TableList.vue index 7cbbead84be3ae81c580680de4cc515e9a3c1983..b97fc336d51e0818dc24bc12099b7d961eed16ab 100644 --- a/fda-ui/components/TableList.vue +++ b/fda-ui/components/TableList.vue @@ -96,6 +96,7 @@ disable-sort :loading="loadingDetails" hide-default-footer + items-per-page="-1" :headers="headers" :items="tableDetails.columns"> <template v-slot:item.is_null_allowed="{ item }"> diff --git a/fda-ui/components/query/Builder.vue b/fda-ui/components/query/Builder.vue index 142b635728340ee225618df73fafd0fc9dc3c5af..81888b813f9c6b9b69dfb433740147d1ddc73f21 100644 --- a/fda-ui/components/query/Builder.vue +++ b/fda-ui/components/query/Builder.vue @@ -97,6 +97,15 @@ </v-row> </v-tab-item> <v-tab-item> + <v-row> + <v-col> + <v-alert + border="left" + color="info"> + Currently, comments in the query (e.g. <code>-- Comment</code>) are not supported! + </v-alert> + </v-col> + </v-row> <v-row> <v-col> <QueryRaw @@ -165,9 +174,6 @@ export default { columnNames () { return this.selectItems && this.selectItems.map(s => s.internal_name) }, - defaultRawSqlText () { - return '-- MariaDB 10.5 Query' - }, tableId () { return this.table.id }, @@ -198,10 +204,7 @@ export default { return null }, canExecute () { - if (!this.sql || this.sql.length === 0) { - return false - } - return this.sql.trim() !== this.defaultRawSqlText + return !(!this.sql || this.sql.length === 0) }, backTo () { return `/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/` + (this.isView ? 'view' : 'query') @@ -293,13 +296,8 @@ export default { this.resultId = res.data.id console.debug('view', res.data) } catch (err) { - if (err.response.status === 423) { - console.error('View name exist', err) - this.$toast.error('View name already exists') - return - } console.error('Failed to create view', err) - this.$toast.error('Failed to create view ' + err.response.text) + this.$toast.error(err.response.data.message) } this.loadingQuery = false await this.$refs.queryResults.reExecute(this.resultId) diff --git a/fda-ui/components/query/Raw.vue b/fda-ui/components/query/Raw.vue index 6c3a6fbe847501a3fe31b81d73735e37ac179b4c..5cad541c147e3fa6469c2cdbdde3432a3bf6c33d 100644 --- a/fda-ui/components/query/Raw.vue +++ b/fda-ui/components/query/Raw.vue @@ -29,7 +29,7 @@ export default { }, data () { return { - content: this.value || '-- MariaDB 10.5 Query\n' + content: this.value } }, computed: { diff --git a/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue b/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue index 00e0279269079b23be4da88af392a8d25e6b8479..fcfe0476215d16cc33d77c068f32cd06fb15a227 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue @@ -18,7 +18,7 @@ <v-btn v-if="token && query.is_persisted && !identifier.id && !loadingIdentifier && is_owner" class="mb-1 mr-2" color="primary" :disabled="error || erroneous || !executionUTC" @click.stop="openDialog()"> <v-icon left>mdi-content-save-outline</v-icon> Get PID </v-btn> - <v-btn v-if="result_visibility" class="mb-1" :loading="downloadLoading" @click.stop="download"> + <v-btn v-if="result_visibility" class="mb-1" :loading="downloadLoading" @click.stop="download('text/csv')"> <v-icon left>mdi-download</v-icon> Data .csv </v-btn> <v-btn @@ -26,7 +26,7 @@ color="secondary" class="ml-2" :loading="metadataLoading" - @click.stop="metadata"> + @click.stop="download('text/xml')"> <v-icon left>mdi-code-tags</v-icon> Metadata .xml </v-btn> </v-toolbar-title> @@ -39,7 +39,7 @@ <v-list dense> <v-list-item> <v-list-item-icon> - <v-icon :color="database_visibility ? 'success' : 'error'">mdi-database-outline</v-icon> + <v-icon v-if="database_visibility" :color="database_visibility ? 'success' : 'error'">mdi-database-outline</v-icon> </v-list-item-icon> <v-list-item-content> <v-list-item-title> @@ -187,7 +187,7 @@ </v-list-item> <v-list-item> <v-list-item-icon> - <v-icon :color="result_visibility_icon ? 'success' : 'error'">{{ result_icon }}</v-icon> + <v-icon v-if="result_visibility_icon" :color="result_visibility_icon ? 'success' : 'error'">{{ result_icon }}</v-icon> </v-list-item-icon> <v-list-item-content v-if="!erroneous"> <v-list-item-title> @@ -365,7 +365,7 @@ export default { return this.$store.state.user && this.$store.state.user.username }, database_visibility () { - return this.database.is_public + return this.database.is_public !== null ? this.database.is_public : false }, is_owner () { return this.token && this.query.creator.username === this.user.username @@ -377,6 +377,9 @@ export default { if (this.erroneous) { return false } + if (this.database.is_public === null) { + return false + } if (this.database.is_public) { return true } @@ -389,6 +392,9 @@ export default { if (this.erroneous) { return false } + if (this.database.is_public === null) { + return false + } if (this.database.is_public) { return true } @@ -439,41 +445,29 @@ export default { .then(() => this.loadMetadata()) }, methods: { - async metadata () { - this.metadataLoading = true - try { - const res = await this.$axios.get(`/api/identifier/${this.identifier.id}`, this.config) - console.debug('identifier result', res) - const url = window.URL.createObjectURL(new Blob([res.data])) - const link = document.createElement('a') - link.href = url - link.setAttribute('download', 'metadata.xml') - document.body.appendChild(link) - link.click() - } catch (err) { - console.error('Could not export metadata', err) - this.$toast.error('Could not export metadata') - this.error = true - } - this.metadataLoading = false - }, loadResult () { this.$refs.queryResults.reExecute(this.query.id) }, - async download () { + async download (mime) { this.downloadLoading = true try { - const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/query/${this.$route.params.query_id}/export`, this.config) - console.debug('export query result', res) + const config = this.config + config.headers.Accept = mime + const res = await this.$axios.get(`/api/pid/${this.identifier.id}`, config) + console.debug('export identifier', res) const url = window.URL.createObjectURL(new Blob([res.data])) const link = document.createElement('a') link.href = url - link.setAttribute('download', 'query.csv') + if (mime === 'text/csv') { + link.setAttribute('download', 'subset.csv') + } else if (mime === 'text/xml') { + link.setAttribute('download', 'identifier.xml') + } document.body.appendChild(link) link.click() } catch (err) { - console.error('Could not export query result', err) - this.$toast.error('Could not export query result') + console.error('Could not export identifier', err) + this.$toast.error('Could not export identifier') this.error = true } this.downloadLoading = false