diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/ContainerDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/ContainerDto.java index 78435fc540721a6e695ba0346749b2827b913e3e..73a83b9217e5f2505775c605ec47194cb906dd8f 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/ContainerDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/ContainerDto.java @@ -26,34 +26,42 @@ public class ContainerDto { private Long id; @NotBlank + @Field(name = "name", type = FieldType.Keyword) @Schema(example = "Air Quality") private String name; @NotBlank @JsonProperty("internal_name") - @Field(name = "internal_name") + @Field(name = "internal_name", type = FieldType.Keyword) @Schema(example = "data-db") private String internalName; @NotBlank + @Field(name = "host", type = FieldType.Keyword) private String host; + @Field(name = "port", type = FieldType.Keyword) private Integer port; @NotBlank @JsonProperty("sidecar_host") + @Field(name = "sidecar_host", type = FieldType.Keyword) private String sidecarHost; @NotNull @JsonProperty("sidecar_port") + @Field(name = "sidecar_port", type = FieldType.Keyword) private Integer sidecarPort; @JsonProperty("ui_host") + @Field(name = "ui_host", type = FieldType.Keyword) private String uiHost; @JsonProperty("ui_port") + @Field(name = "ui_port", type = FieldType.Keyword) private Integer uiPort; + @Field(name = "image", includeInParent = true, type = FieldType.Nested) private ImageBriefDto image; @NotNull diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageBriefDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageBriefDto.java index 4d404be60bccfc0f22beb4694ddffbfdd298e298..3fbf0b99fd54b91165e1cae28e942f51742c6bb5 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageBriefDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageBriefDto.java @@ -5,6 +5,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.*; import lombok.extern.jackson.Jacksonized; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; @Getter @Setter @@ -16,13 +18,16 @@ import lombok.extern.jackson.Jacksonized; public class ImageBriefDto { @NotNull + @Field(name = "id", type = FieldType.Keyword) private Long id; @NotBlank + @Field(name = "name", type = FieldType.Keyword) @Schema(example = "mariadb") private String name; @NotBlank + @Field(name = "version", type = FieldType.Keyword) @Schema(example = "10.5") private String version; diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java index 2f01dc84c5e4b2cbf9753662d78ec37fe3c86c6c..a47cc416a5625050254f40a90d16e2b08a477c5d 100644 --- a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java +++ b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/database/Database.java @@ -31,6 +31,7 @@ import java.util.UUID; @UniqueConstraint(columnNames = {"cid", "internalName"}) }) @NamedQueries({ + @NamedQuery(name = "Database.findAll", query = "select d from Database d order by d.created desc"), @NamedQuery(name = "Database.findReadAccess", query = "select distinct d from Database d join DatabaseAccess a on a.hdbid = d.id and a.huserid = ?1"), @NamedQuery(name = "Database.findWriteAccess", query = "select distinct d from Database d join DatabaseAccess a on a.hdbid = d.id and a.huserid = ?1 where a.type = 'WRITE_OWN' or a.type = 'WRITE_ALL'"), @NamedQuery(name = "Database.findConfigureAccess", query = "select distinct d from Database d where d.ownedBy = ?1"), diff --git a/dbrepo-search-service/app/opensearch_client.py b/dbrepo-search-service/app/opensearch_client.py index 955a37ecafb9e619e79b46866aadc64a078eb759..2dcc1e0f7c4b8beae08a799c8e9dc1dd7f83e64e 100644 --- a/dbrepo-search-service/app/opensearch_client.py +++ b/dbrepo-search-service/app/opensearch_client.py @@ -1,9 +1,8 @@ """ The opensearch_client.py is used by the different API endpoints in routes.py to handle requests to the opensearch db """ -import json import logging - +import re from flask import current_app from collections.abc import MutableMapping @@ -127,7 +126,7 @@ def general_search(search_term=None, t1=None, t2=None, fieldValuePairs=None): searchable_indices = ["database", "user", "table", "column", "identifier", "view", "concept", "unit"] index = searchable_indices field_list = [ - "name", + "table.name", "identifier.titles.title", "identifier.descriptions.description", "identifier.publisher", @@ -137,13 +136,19 @@ def general_search(search_term=None, t1=None, t2=None, fieldValuePairs=None): "column.column_type", "column.is_null_allowed", "column.is_primary_key", + "unit.uri", + "unit.name", + "unit.description", + "concept.uri", + "concept.name", + "concept.description", "funders", "title", "description", "creator.username", - "concept.name", - "concept.uri", "author", + "name", + "uri", "database.*", "internal_name", "is_public", @@ -151,14 +156,23 @@ def general_search(search_term=None, t1=None, t2=None, fieldValuePairs=None): queries = [] if search_term is not None: logging.debug('query has search_term present') - text_query = { - "multi_match": { - "fields": field_list, - "query": search_term, - "fuzziness": "AUTO", + fuzzy_body = { + "query": { + "multi_match": { + "query": search_term, + "fuzziness": "AUTO", + "fuzzy_transpositions": True, + "minimum_should_match": 3 + } } } - queries.append(text_query) + logging.debug('search body: %s', fuzzy_body) + response = current_app.opensearch_client.search( + index=index, + body=fuzzy_body + ) + response["status"] = 200 + return response if t1 and t2 is not None: logging.debug('query has time range present') time_range_query = { @@ -170,28 +184,27 @@ def general_search(search_term=None, t1=None, t2=None, fieldValuePairs=None): } } queries.append(time_range_query) - if fieldValuePairs is not None: + if fieldValuePairs is not None and len(fieldValuePairs) > 0: logging.debug('query has fieldValuePairs present') musts = [] - for field, value in fieldValuePairs.items(): - if field == "type" and value in searchable_indices: + for key, value in fieldValuePairs.items(): + if key == "type" and value in searchable_indices: logging.debug("search for specific index: %s", value) index = value continue - if field in field_list: - if field.startswith(index) and "." in field: - new_field = field[field.index(".") + 1:len(field)] + if key in field_list: + if re.match(f"{key}\\.", key): + new_field = key[key.index(".") + 1:len(key)] logging.debug( - f"field name {field} starts with index name {index}: flattened field name to {new_field}") - field = new_field + f"field name {key} starts with index name {index}: flattened field name to {new_field}") + key = new_field musts.append({ "match": { - field: {"query": value, "minimum_should_match": "90%"} + key: {"query": value, "minimum_should_match": "90%"} } }) specific_query = {"bool": {"must": musts}} queries.append(specific_query) - logging.debug("queries: %s", queries) body = { "query": {"bool": {"must": queries}}, "_source": [ diff --git a/dbrepo-ui/api/search.service.js b/dbrepo-ui/api/search.service.js index 85f2315c6945442a5cd35452227feb7873d2d264..2dac62689fa3fe5cc329af3ddeeb1942fbfa5068 100644 --- a/dbrepo-ui/api/search.service.js +++ b/dbrepo-ui/api/search.service.js @@ -23,7 +23,7 @@ class SearchService { // transform values to what the search API expects const searchTerm = searchData.search_term delete searchData.search_term - searchData = Object.fromEntries(Object.entries(searchData).filter(([_, v]) => v != null)) // https://stackoverflow.com/questions/286141/remove-blank-attributes-from-an-object-in-javascript + searchData = Object.fromEntries(Object.entries(searchData).filter(([_, v]) => v != null && v !== '')) // https://stackoverflow.com/questions/286141/remove-blank-attributes-from-an-object-in-javascript const payload = { search_term: searchTerm, field_value_pairs: { ...searchData } diff --git a/dbrepo-ui/components/search/AdvancedSearch.vue b/dbrepo-ui/components/search/AdvancedSearch.vue new file mode 100644 index 0000000000000000000000000000000000000000..ff71d33cd36e25837d2cd5bc286039d4e52ec478 --- /dev/null +++ b/dbrepo-ui/components/search/AdvancedSearch.vue @@ -0,0 +1,237 @@ +<template> + <div> + <v-card flat tile> + <v-card-text class="pt-0 pl-4 pb-6 pr-4"> + <v-row dense> + <v-col cols="3"> + <v-select + v-model="advancedSearchData.type" + :items="fieldItems" + item-text="name" + item-value="value" + solo + label="Type" /> + </v-col> + </v-row> + <v-row dense> + <v-col cols="3"> + <v-text-field + v-model="advancedSearchData.id" + clearable + label="ID" /> + </v-col> + <v-col cols="3"> + <v-text-field + v-if="!hideFields.hideNameField" + v-model="advancedSearchData.name" + clearable + label="Name" /> + </v-col> + <v-col cols="3"> + <v-text-field + v-if="!hideFields.hideInternalNameField" + v-model="advancedSearchData.internal_name" + clearable + label="Internal Name" /> + </v-col> + </v-row> + <v-row v-if="loadingFields" dense> + <v-progress-circular color="primary" indeterminate /> + </v-row> + <v-row v-if="!loadingFields && renderedFields" dense> + <v-col v-for="field in renderedFields" :key="`f-${field.attribute_name}`" cols="3"> + <v-select + v-if="field.type === 'boolean'" + v-model="advancedSearchData[generateDynamicVModelKey(field)]" + clearable + :items="booleanItems" + item-text="name" + item-value="value" + :label="generateFriendlyName(field)" /> + <v-text-field + v-if="(field.type === 'keyword' && field.attribute_name !== 'column_type') || field.type === 'text' || field.type === 'date'" + v-model="advancedSearchData[generateDynamicVModelKey(field)]" + type="text" + :label="generateFriendlyName(field)" + clearable /> + <v-select + v-if="field.type === 'keyword' && field.attribute_name === 'column_type'" + v-model="advancedSearchData[generateDynamicVModelKey(field)]" + :items="columnTypes" + item-value="value" + clearable + :label="generateFriendlyName(field)" /> + <v-text-field + v-if="field.type === 'integer'" + v-model="advancedSearchData[generateDynamicVModelKey(field)]" + type="number" + :label="generateFriendlyName(field)" + clearable /> + </v-col> + </v-row> + <v-row dense> + <v-btn class="mr-2" color="primary" :loading="loading" small @click="advancedSearch"> + Search + </v-btn> + </v-row> + </v-card-text> + </v-card> + </div> +</template> +<script> +import SearchService from '@/api/search.service' +import QueryMapper from '@/api/query.mapper' + +export default { + data () { + return { + loading: false, + loadingFields: false, + showAdvancedSearch: false, + columnTypes: QueryMapper.mySql8DataTypes().map((datatype) => { + datatype.value = datatype.value.toUpperCase() + return datatype + }), + fieldItems: [ + { name: 'Database', value: 'database' }, + { name: 'Table', value: 'table' }, + { name: 'Column', value: 'column' }, + { name: 'User', value: 'user' }, + { name: 'Identifier', value: 'identifier' }, + { name: 'Concept', value: 'concept' }, + { name: 'Unit', value: 'unit' }, + { name: 'View', value: 'view' } + ], + booleanItems: [ + { name: 'True', value: true }, + { name: 'False', value: false } + ], + fieldsResponse: null, + renderedFields: [], + advancedSearchData: { + name: null, + internal_name: null, + id: null, + type: 'database' + } + } + }, + computed: { + hideFields () { + const selectedOption = this.advancedSearchData.type + return { + hideNameField: selectedOption === 'identifier', + hideInternalNameField: ['identifier', 'user', 'concept', 'unit'].includes(selectedOption) + } + } + }, + watch: { + 'advancedSearchData.type': { + handler (newType, oldType) { + if (!newType) { + return + } + console.debug('switched advanced search type to', newType) + this.resetAdvancedSearchFields() + this.loadingFields = true + SearchService.getFields(newType) + .then((response) => { + this.loadingFields = false + const { fields } = response + this.renderedFields = fields.filter(field => this.shouldRenderItem(field)) + }) + .finally(() => { + this.loadingFields = false + }) + }, + immediate: true + } + }, + mounted () { + this.advancedSearch() + }, + methods: { + /* Removes all advanced search fields when switching the type */ + resetAdvancedSearchFields () { + Object.keys(this.advancedSearchData) + .filter(k => !['name', 'internal_name', 'id', 'type'].includes(k)) + .forEach(k => delete this.advancedSearchData[k]) + }, + advancedSearch () { + console.debug('performing advanced search') + if (this.search) { + this.advancedSearchData.search_term = this.search + } else { + delete this.advancedSearchData.search_term + } + this.loading = true + SearchService.search(this.advancedSearchData) + .then((response) => { + this.$emit('search-result', response.map(h => h._source)) + }) + .finally(() => { + this.loading = false + }) + }, + isAdvancedSearchEmpty () { + return !( + this.advancedSearchData.type || + this.advancedSearchData.id || + this.advancedSearchData.name || + this.advancedSearchData.internal_name + ) + }, + dynamicFieldsMap () { + // Defines a mapping to narrow down the fields rendered for the advanced search + return { + database: ['created', 'description', 'is_public'], + table: ['created', 'description', 'is_public'], + column: ['column_type', 'is_primary_key', 'is_null_allowed'], + user: ['firstname', 'lastname', 'username'], + identifier: [ + 'creators.properties.creator_name', 'creators.properties.name_identifier', + 'descriptions.properties.description', 'doi', 'funders.properties.funder_identifier', + 'licenses', 'publication_year', 'titles.properties.title', 'visibility' + ], + view: ['is_public', 'query'], + concept: ['uri'], + unit: ['uri'] + } + }, + getLastFlattenedItem (str) { + // Returns substring after the last dot otherwise the string itself if no dots are contained + if (!str) { return '' } + + // Check if string is a flattened nested object + return str.includes('.') ? str.split('.').slice(-1)[0] : str + }, + generateFriendlyName (item) { + // Generates a proper name to be displayed with the dynamic component + if (!item) { return '' } + + const specialAbbreviations = { + doi: 'DOI', + uri: 'URI' + // Add more abbreviations here, if needed + } + const str = this.getLastFlattenedItem(item.attribute_name) + + return str.split('_').map((word) => { + const lowerWord = word.toLowerCase() + return specialAbbreviations[lowerWord] || (word.charAt(0).toUpperCase() + word.slice(1)) + }).join(' ') + }, + generateDynamicVModelKey (item) { + // Generates a dynamic v-model; It will be attached to the advancedSearchData object + if (!item) { return '' } + + return `${this.advancedSearchData.type}.${item.attribute_name}` + }, + shouldRenderItem (item) { + // Checks if item's attribute_name matches any wanted field + // The expected response is of a flattened format, so this method must be modified accordingly if the response is changed + return this.dynamicFieldsMap()[this.advancedSearchData.type].includes(item.attribute_name) + } + } +} +</script> diff --git a/dbrepo-ui/layouts/default.vue b/dbrepo-ui/layouts/default.vue index 4c4a717e3edebc9b0b0ab1d038bcc0a1224f997e..496f427136f5434d73c9b472d2c021c6bf004f0c 100644 --- a/dbrepo-ui/layouts/default.vue +++ b/dbrepo-ui/layouts/default.vue @@ -98,9 +98,6 @@ append-icon="mdi-magnify" :placeholder="$t('search.fuzzy.placeholder', { name: 'vue-i18n' })" @click:append="retrieve" /> - <v-btn class="ml-2" plain type="submit" name="search-advanced" @click="toggleAdvancedSearch"> - Advanced - </v-btn> <v-spacer /> <v-btn v-if="!user" @@ -133,14 +130,6 @@ </v-btn> </template> <v-list> - <!-- - <v-list-item - v-for="locale in []" - :key="locale.code" - :to="switchLocalePath(locale.code)"> - <v-list-item-title>{{ locale.name }}</v-list-item-title> - </v-list-item> - --> <v-list-item v-if="user" @click="logout"> @@ -151,87 +140,6 @@ </v-app-bar> </v-form> <v-main> - <!-- Advanced Search card --> - <v-card v-if="showAdvancedSearch" id="advanced_search" flat tile> - <v-card-text> - <v-container fluid> - <v-row> - <v-col cols="auto"> - <v-select - v-model="advancedSearchData.type" - :items="fieldItems" - item-text="name" - item-value="value" - label="Type" /> - </v-col> - <v-col cols="auto"> - <v-text-field v-model="advancedSearchData.id" clearable label="ID" variant="underlined" /> - </v-col> - <v-col cols="auto"> - <v-text-field - v-if="!hideFields.hideNameField" - v-model="advancedSearchData.name" - clearable - label="Name" - variant="underlined" /> - </v-col> - <v-col cols="auto"> - <v-text-field - v-if="!hideFields.hideInternalNameField" - v-model="advancedSearchData.internal_name" - clearable - label="Internal Name" - variant="underlined" /> - </v-col> - </v-row> - <v-row v-if="fieldsResponse"> - <!-- Loop through fields of Response --> - <span v-for="field in fieldsResponse.fields" :key="`${field.attribute_name}`"> - <!-- Loop through "fields" list --> - <template v-if="shouldRenderItem(field)"> - <v-col cols="auto"> - <v-select - v-if="field.type === 'boolean'" - v-model="advancedSearchData[generateDynamicVModelKey(field)]" - clearable - :items="booleanItems" - item-text="name" - item-value="value" - :label="generateFriendlyName(field)" /> - <v-text-field - v-if="(field.type === 'keyword' && field.attribute_name !== 'column_type') || field.type === 'text' || field.type === 'date'" - v-model="advancedSearchData[generateDynamicVModelKey(field)]" - type="text" - :label="generateFriendlyName(field)" - clearable /> - <v-select - v-if="field.type === 'keyword' && field.attribute_name === 'column_type'" - v-model="advancedSearchData[generateDynamicVModelKey(field)]" - :items="columnTypes" - item-value="value" - clearable - :label="generateFriendlyName(field)" /> - <v-text-field - v-if="field.type === 'integer'" - v-model="advancedSearchData[generateDynamicVModelKey(field)]" - type="number" - :label="generateFriendlyName(field)" - clearable /> - </v-col> - </template> - </span> - </v-row> - </v-container> - </v-card-text> - <v-card-text> - <v-btn class="mr-2" color="primary" small @click="advancedSearch"> - Search - </v-btn> - <v-btn small @click="toggleAdvancedSearch"> - Cancel - </v-btn> - </v-card-text> - </v-card> <v-container> <nuxt /> </v-container> @@ -240,12 +148,9 @@ </template> <script> -import SearchService from '@/api/search.service' import AuthenticationService from '@/api/authentication.service' import DatabaseService from '@/api/database.service' -import EventBus from '@/api/eventBus' import TableService from '@/api/table.service' -import QueryMapper from '@/api/query.mapper' export default { data () { @@ -259,33 +164,7 @@ export default { loadingUser: true, loadingSearch: false, loadingDatabases: false, - search: null, - showAdvancedSearch: false, - columnTypes: QueryMapper.mySql8DataTypes().map((datatype) => { - datatype.value = datatype.value.toUpperCase() - return datatype - }), - fieldItems: [ - { name: 'Database', value: 'database' }, - { name: 'Table', value: 'table' }, - { name: 'Column', value: 'column' }, - { name: 'User', value: 'user' }, - { name: 'Identifier', value: 'identifier' }, - { name: 'Concept', value: 'concept' }, - { name: 'Unit', value: 'unit' }, - { name: 'View', value: 'view' } - ], - booleanItems: [ - { name: 'True', value: true }, - { name: 'False', value: false } - ], - fieldsResponse: null, - advancedSearchData: { - name: null, - internal_name: null, - id: null, - type: null - } + search: null } }, computed: { @@ -333,13 +212,6 @@ export default { }, databaseCount () { return this.$store.state.databaseCount - }, - hideFields () { - const selectedOption = this.advancedSearchData.type - return { - hideNameField: selectedOption === 'identifier', - hideInternalNameField: ['identifier', 'user', 'concept', 'unit'].includes(selectedOption) - } } }, watch: { @@ -374,20 +246,6 @@ export default { }, deep: true, immediate: true - }, - 'advancedSearchData.type': { - handler (newType, oldType) { - if (!newType) { - return - } - console.debug('switched advanced search type to', newType) - this.resetAdvancedSearchFields() - SearchService.getFields(newType) - .then((response) => { - this.fieldsResponse = response - }) - }, - immediate: true } }, mounted () { @@ -410,12 +268,6 @@ export default { submit () { this.$refs.form.validate() }, - /* Removes all advanced search fields when switching the type */ - resetAdvancedSearchFields () { - Object.keys(this.advancedSearchData) - .filter(k => !['name', 'internal_name', 'id', 'type'].includes(k)) - .forEach(k => delete this.advancedSearchData[k]) - }, login () { const redirect = ![undefined, '/', '/login'].includes(this.$router.currentRoute.path) this.$router.push({ path: '/login', query: redirect ? { redirect: this.$router.currentRoute.path } : {} }) @@ -487,89 +339,12 @@ export default { this.$store.commit('SET_SEARCH_PASSWORD', this.$config.searchPassword) this.$store.commit('SET_DOI_URL', this.$config.doiUrl) console.debug('runtime config', this.$config) - }, - advancedSearch () { - console.debug('performing advanced search') - if (this.search) { - this.advancedSearchData.search_term = this.search - } else { - delete this.advancedSearchData.search_term - } - EventBus.$emit('advancedSearchButtonClicked') - this.$router.push({ path: '/search' }) - }, - toggleAdvancedSearch () { - this.showAdvancedSearch = !this.showAdvancedSearch - }, - isAdvancedSearchEmpty () { - return !( - this.advancedSearchData.type || - this.advancedSearchData.id || - this.advancedSearchData.name || - this.advancedSearchData.internal_name - ) - }, - dynamicFieldsMap () { - // Defines a mapping to narrow down the fields rendered for the advanced search - return { - database: ['created', 'description', 'is_public'], - table: ['created', 'description', 'is_public'], - column: ['column_type', 'is_primary_key', 'is_null_allowed'], - user: ['firstname', 'lastname', 'username'], - identifier: [ - 'creators.properties.creator_name', 'creators.properties.name_identifier', - 'descriptions.properties.description', 'doi', 'funders.properties.funder_identifier', - 'licenses', 'publication_year', 'titles.properties.title', 'visibility' - ], - view: ['is_public', 'query'], - concept: ['uri'], - unit: ['uri'] - } - }, - getLastFlattenedItem (str) { - // Returns substring after the last dot otherwise the string itself if no dots are contained - if (!str) { return '' } - - // Check if string is a flattened nested object - return str.includes('.') ? str.split('.').slice(-1)[0] : str - }, - generateFriendlyName (item) { - // Generates a proper name to be displayed with the dynamic component - if (!item) { return '' } - - const specialAbbreviations = { - doi: 'DOI', - uri: 'URI' - // Add more abbreviations here, if needed - } - const str = this.getLastFlattenedItem(item.attribute_name) - - return str.split('_').map((word) => { - const lowerWord = word.toLowerCase() - return specialAbbreviations[lowerWord] || (word.charAt(0).toUpperCase() + word.slice(1)) - }).join(' ') - }, - generateDynamicVModelKey (item) { - // Generates a dynamic v-model; It will be attached to the advancedSearchData object - if (!item) { return '' } - - return `${this.advancedSearchData.type}.${item.attribute_name}` - }, - shouldRenderItem (item) { - // Checks if item's attribute_name matches any wanted field - // The expected response is of a flattened format, so this method must be modified accordingly if the response is changed - return this.dynamicFieldsMap()[this.advancedSearchData.type].includes(item.attribute_name) } }, head () { return { title: this.$config.title } - }, - provide () { - return { - advancedSearchData: this.advancedSearchData - } } } </script> diff --git a/dbrepo-ui/pages/index.vue b/dbrepo-ui/pages/index.vue index 673999993f6411ad4b392807d74e382ed83c7e66..91424a243ed4ffc849655f6123a01669b56f1967 100644 --- a/dbrepo-ui/pages/index.vue +++ b/dbrepo-ui/pages/index.vue @@ -1,6 +1,10 @@ <template> <div> + <v-toolbar flat> + <v-toolbar-title v-text="$t('databases.recent', { name: 'vue-i18n' })" /> + </v-toolbar> <v-card flat tile> + <v-divider class="mx-4" /> <v-card-text v-if="infoLinks && infoLinks.length > 0"> <div class="mb-2">Important Links</div> <div class="text--primary"> @@ -13,11 +17,7 @@ </ul> </div> </v-card-text> - <v-divider class="mx-4" /> </v-card> - <v-toolbar flat> - <v-toolbar-title v-text="$t('databases.recent', { name: 'vue-i18n' })" /> - </v-toolbar> <DatabaseList ref="databases" /> </div> </template> diff --git a/dbrepo-ui/pages/search/index.vue b/dbrepo-ui/pages/search/index.vue index 3d6635c7d18c907a33b13c26eb0a108ba5875052..027db242e8620c277830db89251ac2cafa5525b7 100644 --- a/dbrepo-ui/pages/search/index.vue +++ b/dbrepo-ui/pages/search/index.vue @@ -1,6 +1,6 @@ <template> <div> - <v-toolbar flat> + <v-toolbar flat tile> <v-toolbar-title v-text="header" /> <v-spacer /> <v-toolbar-title> @@ -9,7 +9,9 @@ </v-btn> </v-toolbar-title> </v-toolbar> - <v-progress-linear v-if="loading" color="primary" /> + <v-card flat tile> + <AdvancedSearch ref="adv" @search-result="onSearchResult" /> + </v-card> <v-card v-for="(result, idx) in results" :key="idx" @@ -45,15 +47,15 @@ </template> <script> -import EventBus from '@/api/eventBus' import SearchService from '@/api/search.service' import CreateDB from '@/components/dialogs/CreateDB' +import AdvancedSearch from '@/components/search/AdvancedSearch' export default { components: { - CreateDB + CreateDB, + AdvancedSearch }, - inject: ['advancedSearchData'], data () { return { results: [], @@ -92,39 +94,14 @@ export default { }, watch: { '$route.query.q': { - handler (query) { - if (this.advancedSearchData) { - return - } + handler () { this.retrieve() - }, - deep: true, - immediate: true - }, - '$route.query.t': { - handler (type) { - if (this.advancedSearchData) { - return - } - this.retrieve() - }, - deep: true, - immediate: true + } } }, - created () { - EventBus.$on('advancedSearchButtonClicked', () => { - this.doAdvancedSearch(this.advancedSearchData) - }) - }, - beforeDestroy () { - EventBus.$off('advancedSearchButtonClicked') - }, mounted () { - if (Object.keys(this.advancedSearchData).some(key => key !== 'search_term')) { - this.doAdvancedSearch(this.advancedSearchData) - } else if (this.query) { - this.retrieve(this.query) + if (this.query) { + this.retrieve() } }, methods: { @@ -133,7 +110,7 @@ export default { return } this.loading = true - SearchService.search(this.query) + SearchService.search({ search_term: this.query }) .then((hits) => { this.results = hits.map(h => h._source) }) @@ -141,16 +118,6 @@ export default { this.loading = false }) }, - doAdvancedSearch (advancedSearchData) { - console.debug('advanced search data:', advancedSearchData) - SearchService.search(advancedSearchData) - .then((response) => { - this.results = response.map(h => h._source) - }) - .finally(() => { - this.loading = false - }) - }, isDatabase (item) { if (!item) { return false @@ -302,6 +269,9 @@ export default { if (event.success) { this.$router.push('/database?f=my') } + }, + onSearchResult (results) { + this.results = results } } }