diff --git a/.jupyter/load_test.py b/.jupyter/load_test.py index 4f395a950b82a9bde1010cb4c6a4a4c7672aa248..0a60af96b754a22996968b1194ddb25822608f4f 100644 --- a/.jupyter/load_test.py +++ b/.jupyter/load_test.py @@ -36,7 +36,7 @@ def create_user(username): "password": username, "email": username + "@gmail.com" }) - print("created user") + print("created user with id %d" % response.id) return response @@ -45,7 +45,7 @@ def auth_user(username): "username": username, "password": username }) - print("authenticated user") + print("authenticated user with id %d" % response.id) token = response.token container.api_client.default_headers = {"Authorization": "Bearer " + token} database.api_client.default_headers = {"Authorization": "Bearer " + token} @@ -64,7 +64,7 @@ def create_container(): "repository": "mariadb", "tag": "10.5" }) - print("created container") + print("created container with id %d" % response.id) return response @@ -72,8 +72,9 @@ def start_container(container_id): response = container.modify({ "action": "start" }, container_id) + print("... starting") time.sleep(5) - print("started container") + print("started container with id %d" % response.id) return response @@ -83,7 +84,7 @@ def create_database(container_id, is_public=True): "description": "Hourly measurements in Zürich, Switzerland", "is_public": is_public }, container_id) - print("created database") + print("created database with id %d" % response.id) return response @@ -99,7 +100,7 @@ def update_database(container_id, database_id, is_public=True): "is_public": is_public, "publication": "2022-07-19" }, container_id, database_id) - print("updated database") + print("updated database with id %d" % response.id) return response @@ -154,13 +155,13 @@ def create_table(container_id, database_id, columns=None): "description": "Airquality in Zürich, Switzerland", "columns": columns }, container_id, database_id) - print("created table") + print("created table with id %d" % response.id) return response def find_table(container_id, database_id, table_id): response = table.find_by_id(container_id, database_id, table_id) - print("found table") + print("found table with id %d" % response.id) return response @@ -172,7 +173,7 @@ def fill_table(container_id, database_id, table_id): "quote": "\"", "skip_lines": 1 }, container_id, database_id, table_id) - print("filled table") + print("filled table with id %d" % table_id) return response @@ -181,7 +182,7 @@ def create_query(container_id, database_id, statement, page=0, size=3): response = query.execute({ "statement": statement }, container_id, database_id, page=page, size=size) - print("executed query") + print("executed query with id %d" % response.id) return response except api_query.rest.ApiException as e: print(e) @@ -209,7 +210,7 @@ def create_identifier(container_id, database_id, query_id, visibility="everyone" "relation": "IsCitedBy" }] }, token, container_id, database_id) - print("created identifier") + print("created identifier with id %d" % response.id) return response @@ -271,3 +272,24 @@ if __name__ == '__main__': "primary_key": True, "null_allowed": False, }]) + # + # create 1 user and 1 container and issue queries to own and foreign database + # + create_user("test2") + auth_user("test2") + # container 4 + cid = create_container().id + start_container(cid) + dbid = create_database(cid).id + update_database(cid, dbid) + tid = create_table(cid, dbid).id + tname = find_table(cid, dbid, tid).internal_name + fill_table(cid, dbid, tid) + create_query(cid, dbid, "select `id` from `" + tname + "`") + create_query(cid, dbid, "select `date` from `" + tname + "`") + qid = create_query(cid, dbid, "select `date`, `location`, `status` from `" + tname + "`").id + create_identifier(cid, dbid, qid) + # container 1 + tname = find_table(1, 1, 1).internal_name + qid = create_query(1, 1, "select `id` from `" + tname + "`").id + create_identifier(1, 1, qid) diff --git a/fda-query-service/pom.xml b/fda-query-service/pom.xml index f5cd96d75d1c927cd8a4aa456e691658478c7be0..cf40c9d8032ec0bbf7132f47b4f9378a61765d91 100644 --- a/fda-query-service/pom.xml +++ b/fda-query-service/pom.xml @@ -79,6 +79,10 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> 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 906f268a8a7be84f4e5ad284271bca6f7fe1f596..d37e7c0869db988f24cd198d11eb2e62d1d0e3be 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 @@ -65,13 +65,13 @@ public class QueryEndpoint extends AbstractEndpoint { @PutMapping("/{queryId}") @Transactional(readOnly = true) - @Operation(summary = "Re-execute some query") + @Operation(summary = "Re-execute some query", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<QueryResultDto> reExecute(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("queryId") Long queryId, @RequestParam(value = "page", required = false) Long page, @RequestParam(value = "size", required = false) Long size, - @NotNull Principal principal) + Principal principal) throws QueryStoreException, QueryNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, TableNotFoundException, QueryMalformedException, ContainerNotFoundException, TableMalformedException, ColumnParseException, NotAllowedException, DatabaseConnectionException { @@ -92,7 +92,7 @@ public class QueryEndpoint extends AbstractEndpoint { public ResponseEntity<InputStreamResource> export(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("queryId") Long queryId, - @NotNull Principal principal) + Principal principal) throws QueryStoreException, QueryNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, TableMalformedException, FileStorageException, NotAllowedException, QueryMalformedException, DatabaseConnectionException { diff --git a/fda-query-service/rest-service/src/main/resources/application.yml b/fda-query-service/rest-service/src/main/resources/application.yml index b8d3af3a2750bb1aaa09a88dafd409bbdaca89f3..030d622c37f65f471942aa4e96dcfc21d82af5cf 100644 --- a/fda-query-service/rest-service/src/main/resources/application.yml +++ b/fda-query-service/rest-service/src/main/resources/application.yml @@ -28,7 +28,7 @@ server.port: 9093 logging: pattern.console: "%d %highlight(%-5level) %msg%n" level: - root: warn + root: debug at.tuwien.: trace at.tuwien.mapper.: trace at.tuwien.service.: trace diff --git a/fda-query-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/fda-query-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java index ab731b43ce96129a060d84e3ac9dfc37fa1c5dd1..df5b4c73a637f07aa590a5483cf8b14776462b17 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java +++ b/fda-query-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java @@ -70,6 +70,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .antMatchers(HttpMethod.GET, "/api/container/**/database/**/table/**/history/**").permitAll() .antMatchers(HttpMethod.GET, "/api/container/**/database/**/table/**/export/**").permitAll() .antMatchers(HttpMethod.GET, "/api/container/**/database/**/query/**").permitAll() + .antMatchers(HttpMethod.GET, "/api/container/**/database/**/query/**/export").permitAll() + .antMatchers(HttpMethod.PUT, "/api/container/**/database/**/query/**").permitAll() .antMatchers("/v3/api-docs.yaml", "/v3/api-docs/**", "/swagger-ui/**", diff --git a/fda-ui/components/DBToolbar.vue b/fda-ui/components/DBToolbar.vue index 7ffba3adc339d338b9e5f0964c0bb44bb3b150b0..6b72889c176f9e2ef42a3097e74727175e5d6417 100644 --- a/fda-ui/components/DBToolbar.vue +++ b/fda-ui/components/DBToolbar.vue @@ -1,9 +1,10 @@ <template> <div> - <v-progress-linear v-if="loading" :color="loadingColor" /> - <v-toolbar v-if="db" flat> + <v-toolbar v-if="cached_database" flat> <v-toolbar-title> - {{ db.name }} + <span>{{ cached_database.name }}</span> + <v-icon v-if="!cached_database.is_public" color="primary" class="mb-1" right>mdi-lock-outline</v-icon> + <v-icon v-if="cached_database.is_public" class="mb-1" right>mdi-lock-open-outline</v-icon> </v-toolbar-title> <v-spacer /> <v-toolbar-title> @@ -40,11 +41,15 @@ export default { return { tab: null, loading: false, - error: false + error: false, + database: { + id: null, + is_public: null + } } }, computed: { - db () { + cached_database () { return this.$store.state.db }, databaseId () { @@ -65,17 +70,28 @@ export default { } } }, + watch: { + $route () { + if (this.database.id !== this.$route.params.database_id) { + this.loadDatabase() + } + } + }, mounted () { - this.init() + if (this.database.id) { + return + } + if (this.cached_database && this.cached_database.id === this.$route.params.database_id) { + return + } + this.loadDatabase() }, methods: { - async init () { - if (this.db != null) { - return - } + async loadDatabase () { try { this.loading = true const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}`, this.config) + this.database = res.data console.debug('database', res.data) this.$store.commit('SET_DATABASE', res.data) this.loading = false diff --git a/fda-ui/components/QueryList.vue b/fda-ui/components/QueryList.vue index 3f8ac4066302e91c886cc825f578cada68f1718a..3190fa08db47dc3a5f48120afede349bf532066d 100644 --- a/fda-ui/components/QueryList.vue +++ b/fda-ui/components/QueryList.vue @@ -117,6 +117,9 @@ export default { }, executionUTC () { return formatTimestampUTCLabel(this.queryDetails.execution) + }, + creator () { + return this.queryDetails.creator } }, mounted () { 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 456c774cdb992ff0607feba958e49ae7b3a9b8a6..5d55f4df5fe2738f666673557336657acaf82d22 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 @@ -207,7 +207,7 @@ </v-list-item-title> <v-list-item-content> <v-alert - v-if="!loadingQuery && erroneous" + v-if="!error && !loadingQuery && erroneous" border="left" color="error"> This query failed to execute and did not produce a subset. @@ -425,10 +425,7 @@ export default { async download () { 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`, { - headers: { Authorization: `Bearer ${this.token}` }, - responseType: 'text' - }) + 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 url = window.URL.createObjectURL(new Blob([res.data])) const link = document.createElement('a') diff --git a/fda-ui/pages/container/index.vue b/fda-ui/pages/container/index.vue index b855d8dda518eb7c6be34c1f82288add90b58321..a8e29244e05906b5b704d28698d1f59fecb27855 100644 --- a/fda-ui/pages/container/index.vue +++ b/fda-ui/pages/container/index.vue @@ -18,43 +18,43 @@ <thead> <tr> <th>Name</th> - <th>Visibility</th> <th>Engine</th> <th>Creator</th> + <th>Visibility</th> <th>Created</th> </tr> </thead> - <tbody> - <tr v-if="containers.length === 0" aria-readonly="true"> + <tbody + v-if="!loading"> + <tr v-if="databases.length === 0" aria-readonly="true"> <td colspan="5"> - <span v-if="!loading">(no databases)</span> + <span>(no databases)</span> </td> </tr> <tr - v-for="item in containers" + v-for="item in databases" :key="item.id" class="database" @click="loadDatabase(item)"> - <td>{{ item.name }}</td> <td> - <v-skeleton-loader v-if="!item.database" type="text" width="50" class="mt-1" /> - <span v-if="item.database"> - <span v-if="item.database.is_public">Public</span> - <span v-if="!item.database.is_public">Private <v-icon right>mdi-eye-off</v-icon></span> - </span> + <span>{{ item.name }}</span> </td> <td> - <span v-if="item.database">{{ item.database.engine }}</span> - <v-skeleton-loader v-if="!item.database" type="text" width="100" class="mt-1" /> + <span>{{ item.engine }}</span> </td> <td> - <span v-if="item.database">{{ formatCreator(item.database.creator) }}</span> - <v-skeleton-loader v-if="!item.database" type="text" width="100" class="mt-1" /> - <sup v-if="item.database"> - <v-icon v-if="item.database.creator.email_verified" small color="primary">mdi-check-decagram</v-icon> + <span>{{ formatCreator(item.creator) }}</span> + <sup> + <v-icon v-if="item.creator.email_verified" small color="primary">mdi-check-decagram</v-icon> </sup> </td> - <td>{{ createdUTC(item.created) }}</td> + <td> + <v-icon v-if="!item.is_public" color="primary" class="private-icon" right>mdi-lock-outline</v-icon> + <v-icon v-if="item.is_public" class="private-icon" right>mdi-lock-open-outline</v-icon> + </td> + <td> + {{ createdUTC(item.created) }} + </td> </tr> </tbody> </template> @@ -81,12 +81,12 @@ export default { data () { return { createDbDialog: false, + databases: [], containers: [], searchQuery: null, items: [ { text: 'Databases', to: '/container', activeClass: '' } ], - loadingContainers: false, loading: false, error: false, iconSelect: mdiDatabaseArrowRightOutline @@ -122,7 +122,7 @@ export default { async loadContainers () { this.createDbDialog = false try { - this.loadingContainers = true + this.loading = true const res = await this.$axios.get('/api/container/') this.containers = res.data console.debug('containers', this.containers) @@ -130,20 +130,20 @@ export default { } catch (err) { console.error('containers', err) this.error = true + this.loading = false } - this.loadingContainers = false }, async loadDatabases () { if (this.containers.length === 0) { return } - const containers = [] + this.loading = true for (const container of this.containers) { try { const res = await this.$axios.get(`/api/container/${container.id}/database`, this.config) - for (const database of res.data) { - container.database = database - containers.push(container) + for (const info of res.data) { + info.container_id = container.id + this.databases.push(info) } } catch (err) { if (err.response === undefined || err.response.status === undefined || err.response.status !== 401) { @@ -151,18 +151,14 @@ export default { } } } - this.containers = containers - console.debug('databases loaded', this.containers) + this.loading = false + console.debug('databases', this.databases) }, createdUTC (str) { return formatTimestampUTCLabel(str) }, - loadDatabase (container) { - if (!container.id || !container.database) { - console.error('container id', container.id, 'or database id missing') - return - } - this.$router.push(`/container/${container.id}/database/${container.database.id}/info`) + loadDatabase (database) { + this.$router.push(`/container/${database.container_id}/database/${database.id}/info`) } } } @@ -184,4 +180,8 @@ export default { .v-progress-circular { margin-left: 8px; } + .private-icon { + flex: 0 !important; + margin-right: 16px; + } </style>