diff --git a/fda-gateway-service/src/main/java/at/tuwien/gatewayservice/config/GatewayConfig.java b/fda-gateway-service/src/main/java/at/tuwien/gatewayservice/config/GatewayConfig.java index 54063f12a9a528713100db7f732dcb4ff210429f..47182f176a71de21fdeff19d20b58ca70ff28b8d 100644 --- a/fda-gateway-service/src/main/java/at/tuwien/gatewayservice/config/GatewayConfig.java +++ b/fda-gateway-service/src/main/java/at/tuwien/gatewayservice/config/GatewayConfig.java @@ -31,6 +31,11 @@ public class GatewayConfig { .method("POST","GET","PUT","DELETE") .and() .uri("lb://fda-query-service")) + .route("fda-query-service", r -> r.path("/api/database/**/querystore/**") + .and() + .method("POST","GET","PUT","DELETE") + .and() + .uri("lb://fda-query-service")) .route("fda-table-service", r -> r.path("/api/database/**/table/**") .and() .method("POST","GET","PUT","DELETE") diff --git a/fda-ui/components/DBToolbar.vue b/fda-ui/components/DBToolbar.vue index efdad60e18758a397eddf526d3d7d19466463d06..cd0a5769e97a0577ff06d4ca449b347258a11dc0 100644 --- a/fda-ui/components/DBToolbar.vue +++ b/fda-ui/components/DBToolbar.vue @@ -9,6 +9,9 @@ <v-btn :to="`/databases/${$route.params.database_id}/tables/import`" class="mr-2"> <v-icon left>mdi-cloud-upload</v-icon> Import CSV </v-btn> + <v-btn color="blue-grey" :to="`/databases/${$route.params.database_id}/queries/create`" class="mr-2 white--text"> + <v-icon left>mdi-wrench</v-icon> Query Builder + </v-btn> <v-btn color="primary" :to="`/databases/${$route.params.database_id}/tables/create`"> <v-icon left>mdi-table-large-plus</v-icon> Create Table </v-btn> diff --git a/fda-ui/components/QueryList.vue b/fda-ui/components/QueryList.vue new file mode 100644 index 0000000000000000000000000000000000000000..4e2c5a6eb8e3a4b328634b21261794dab3356637 --- /dev/null +++ b/fda-ui/components/QueryList.vue @@ -0,0 +1,123 @@ +<template> + <div> + <v-card v-if="queries.length === 0" flat> + <v-card-title> + (no queries) + </v-card-title> + </v-card> + <v-expansion-panels v-if="queries.length > 0" accordion> + <v-expansion-panel v-for="(item, i) in queries" :key="i" @click="details(item)"> + <v-expansion-panel-header> + {{ item.query }} + </v-expansion-panel-header> + <v-expansion-panel-content> + <v-row dense> + <v-col> + <v-list dense> + <v-list-item> + <v-list-item-icon> + <v-icon>mdi-information-variant</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title> + ID: {{ queryDetails.id }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> + <v-list-item> + <v-list-item-icon> + <v-icon>mdi-fingerprint</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title> + DOI: <code v-if="queryDetails.doi">{{ queryDetails.doi }}</code> + <span v-if="!queryDetails.doi">(no identifier issued)</span> + </v-list-item-title> + </v-list-item-content> + </v-list-item> + <v-list-item> + <v-list-item-icon> + <v-icon>mdi-api</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title> + Query Hash: <code>{{ queryDetails.queryHash }}</code> + </v-list-item-title> + </v-list-item-content> + </v-list-item> + <v-list-item> + <v-list-item-icon> + <v-icon>mdi-clock-outline</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title> + Execution Timestamp: {{ queryDetails.executionTimestamp }} + </v-list-item-title> + </v-list-item-content> + </v-list-item> + <v-list-item> + <v-list-item-icon> + <v-icon>mdi-content-save</v-icon> + </v-list-item-icon> + <v-list-item-content> + <v-list-item-title> + Query: <code>{{ queryDetails.query }}</code> + </v-list-item-title> + </v-list-item-content> + </v-list-item> + </v-list> + </v-col> + </v-row> + <v-row dense> + <v-col> + <v-btn color="primary" :to="`/databases/${$route.params.database_id}/tables/${item.id}`" disabled> + <v-icon left>mdi-run</v-icon> Execute Again + </v-btn> + </v-col> + </v-row> + </v-expansion-panel-content> + </v-expansion-panel> + </v-expansion-panels> + </div> +</template> + +<script> +export default { + data () { + return { + queries: [], + queryDetails: { + id: null, + doi: null, + queryHash: null, + executionTimestamp: null, + columns: [] + } + } + }, + mounted () { + this.$root.$on('query-create', this.refresh) + this.refresh() + }, + methods: { + async refresh () { + // XXX same as in QueryBuilder + let res + try { + res = await this.$axios.get(`/api/database/${this.$route.params.database_id}/querystore`) + console.debug('queries', res) + this.queries = res.data + } catch (err) { + this.$toast.error('Could not list queries.') + } + }, + details (query) { + this.queryDetails = query + } + } +} +</script> + +<style> + +</style> diff --git a/fda-ui/components/query/Builder.vue b/fda-ui/components/query/Builder.vue index a45d278c5d8185c5e4f49ce3affce848f49f2eb3..aa2a9f7c60206e620e0bb20e4725867818eef213 100644 --- a/fda-ui/components/query/Builder.vue +++ b/fda-ui/components/query/Builder.vue @@ -22,21 +22,32 @@ @change="buildQuery" /> </v-col> </v-row> - <QueryFilters - v-if="table" - v-model="clauses" - :columns="columnNames" /> + <!-- <QueryFilters--> + <!-- v-if="table"--> + <!-- v-model="clauses"--> + <!-- :columns="columnNames" />--> <v-row v-if="query.formatted"> <v-col> <highlightjs autodetect :code="query.formatted" /> </v-col> <v-col cols="3" class="actions"> - <v-btn class="execute" color="primary"> + <v-btn class="execute" color="primary" @click="execute"> <v-icon>mdi-refresh</v-icon> Execute </v-btn> </v-col> </v-row> + <v-row> + <v-col> + <p>Results</p> + <v-data-table + :headers="result.headers" + :items="result.rows" + :loading="loading" + :items-per-page="30" + class="elevation-1" /> + </v-col> + </v-row> </div> </template> @@ -49,7 +60,12 @@ export default { tableDetails: null, query: {}, select: [], - clauses: [] + clauses: [], + result: { + headers: [], + rows: [] + }, + loading: false } }, computed: { @@ -80,6 +96,26 @@ export default { } }, methods: { + async execute () { + const query = this.query.sql.replaceAll('"', '') + this.loading = true + try { + const res = await this.$axios.put(`/api/database/${this.$route.params.database_id}/query`, { + Query: query + }) + console.debug('query result', res) + this.$toast.success('Successfully executed query') + this.loading = false + this.result.headers = this.select.map((s) => { + return { text: s.name, value: 'mdb_' + s.name, sortable: false } + }) + this.result.rows = res.data.result + } catch (err) { + console.error('query execute', err) + this.$toast.error('Could not execute query') + this.loading = false + } + }, async buildQuery () { if (!this.table) { return @@ -87,7 +123,7 @@ export default { const url = '/server-middleware/query/build' const data = { table: this.table.internalName, - select: this.select.map(s => s.name), + select: this.select.map(s => 'mdb_' + s.name), clauses: this.clauses } try { diff --git a/fda-ui/pages/databases/_database_id/queries/create.vue b/fda-ui/pages/databases/_database_id/queries/create.vue new file mode 100644 index 0000000000000000000000000000000000000000..c2390fb100fc31aae393c887670de537524e16b0 --- /dev/null +++ b/fda-ui/pages/databases/_database_id/queries/create.vue @@ -0,0 +1,20 @@ +<template> + <div> + <v-card class="pb-2"> + <v-card-title class="pb-0"> + Query Builder + </v-card-title> + <v-card-text> + <QueryBuilder /> + </v-card-text> + </v-card> + </div> +</template> + +<script> +export default { + data () { + return {} + } +} +</script> diff --git a/fda-ui/pages/databases/_database_id/queries/index.vue b/fda-ui/pages/databases/_database_id/queries/index.vue index a0bcd835956bfabbd7095b7438d747da8ef9b09e..ff90d731ff4275336480f3eb528518a66b4b00f0 100644 --- a/fda-ui/pages/databases/_database_id/queries/index.vue +++ b/fda-ui/pages/databases/_database_id/queries/index.vue @@ -1,14 +1,7 @@ <template> <div> <DBToolbar v-model="db" /> - <v-card> - <v-card-title> - Query Builder - </v-card-title> - <v-card-text> - <QueryBuilder /> - </v-card-text> - </v-card> + <QueryList /> </div> </template> diff --git a/fda-ui/yarn.lock b/fda-ui/yarn.lock index 31a4d9d4314ca3fbc11c5f9b5efe49ffa62d9520..31dcd208e937faeed12a5ae9100e93489dede6a8 100644 --- a/fda-ui/yarn.lock +++ b/fda-ui/yarn.lock @@ -1622,7 +1622,7 @@ "webpack-node-externals" "^3.0.0" "webpackbar" "^4.0.0" -"@nuxtjs/axios@^5.12.2": +"@nuxtjs/axios@^5.13.6": "integrity" "sha512-XS+pOE0xsDODs1zAIbo95A0LKlilvJi8YW0NoXYuq3/jjxGgWDxizZ6Yx0AIIjZOoGsXJOPc0/BcnSEUQ2mFBA==" "resolved" "https://registry.npmjs.org/@nuxtjs/axios/-/axios-5.13.6.tgz" "version" "5.13.6" @@ -8579,7 +8579,7 @@ "ufo" "^0.7.7" "vue-i18n" "^8.25.0" -"nuxt@^2.12.2": +"nuxt@^2.15.8": "integrity" "sha512-ceK3qLg/Baj7J8mK9bIxqw9AavrF+LXqwYEreBdY/a4Sj8YV4mIvhqea/6E7VTCNNGvKT2sJ/TTJjtfQ597lTA==" "resolved" "https://registry.npmjs.org/nuxt/-/nuxt-2.15.8.tgz" "version" "2.15.8"