diff --git a/backend/app.py b/backend/app.py index 0d7dc0f..6cd0d97 100644 --- a/backend/app.py +++ b/backend/app.py @@ -9,7 +9,6 @@ import click from .question import Question from .question_search import QuestionSearch - app = Flask(__name__) connections.create_connection(hosts=["localhost"]) @@ -39,6 +38,15 @@ def index(): "category": { "terms": {"field": "category"}, }, + "suggestions": { + "significant_terms": { + "field": "body", + "mutual_information": { + "include_negatives": True, + }, + "size": 40, + }, + } }, }) @@ -51,6 +59,9 @@ def index(): for bucket in response.aggregations.category.buckets ] + suggestions = [{"key": bucket.key, "count": bucket.doc_count} + for bucket in response.aggregations.suggestions.buckets] + date_facets = [] results = [] @@ -59,14 +70,17 @@ def index(): url = Question.url(hit) results.append({ - "id": hit.meta.id, "score": hit.meta.score, - "title": hit.title, "body": summary, - "category": hit.category, "date": hit.date, + "id": hit.meta.id, "score": hit.meta.score, "title": hit.title, + "body": summary, "category": hit.category, "date": hit.date, "url": url, }) return jsonify( - facets={"months": date_facets, "categories": category_facets}, + facets={ + "months": date_facets, + "categories": category_facets, + }, + suggestions=suggestions, results=results, hits=round_sigfig(response.hits.total, 4), took=response.took / 1000, diff --git a/backend/question.py b/backend/question.py index 762f3ca..2078350 100644 --- a/backend/question.py +++ b/backend/question.py @@ -3,7 +3,10 @@ from elasticsearch_dsl import Document, Date, Keyword, Text class Question(Document): title = Text(analyzer="snowball") - body = Text(analyzer="snowball") + body = Text( + analyzer="snowball", + fielddata=True, + ) category = Keyword() date = Date() diff --git a/frontend/src/components/ResultBody.vue b/frontend/src/components/ResultBody.vue index c24c301..a8735b2 100644 --- a/frontend/src/components/ResultBody.vue +++ b/frontend/src/components/ResultBody.vue @@ -1,5 +1,12 @@ @@ -40,19 +49,25 @@ import {Component, Prop, Watch} from "vue-property-decorator"; @Component export default class ResultBody extends Vue { - @Prop() query; + @Prop() value; + json = null; activeCategories = []; hits = 0; results = []; responseTime = 0; facets = {}; - @Watch("query") + @Watch("value") async onQueryChanged(value) { await this.search(); } + @Watch("$route") + async onRouteChanged() { + this.$emit("input", this.$route.query.q); + } + async toggleCategory(category) { const name = category.category; @@ -67,7 +82,7 @@ export default class ResultBody extends Vue { } async search() { - const query = encodeURIComponent(this.query); + const query = encodeURIComponent(this.value); let queryString = `?q=${query}`; if (this.activeCategories.length > 0) { @@ -79,12 +94,16 @@ export default class ResultBody extends Vue { const url = `/api/${queryString}`; let response = await fetch(url); - let json = await response.json(); + this.json = await response.json(); - this.results = json.results; - this.hits = json.hits; - this.responseTime = json.took; - this.facets = json.facets; + this.results = this.json.results; + this.hits = this.json.hits; + this.responseTime = this.json.took; + this.facets = this.json.facets; + } + + appendToQuery(value) { + this.$emit("input", `${this.value} AND ${value}`) } } @@ -171,4 +190,25 @@ export default class ResultBody extends Vue { .category__count { text-align: right; } + +.suggestions { + display: flex; + flex-flow: row wrap; + max-width: 1000px; + margin: 12px 0; + + height: 70px; + overflow-y: hidden; +} + +.suggestion { + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 12px; + + background-color: #f8f8f8; + padding: 7px 16px; + margin: 2px; + + cursor: pointer; +} diff --git a/frontend/src/components/Results.vue b/frontend/src/components/Results.vue index bb4ea3d..9e0db88 100644 --- a/frontend/src/components/Results.vue +++ b/frontend/src/components/Results.vue @@ -1,7 +1,7 @@ diff --git a/frontend/src/components/TopBar.vue b/frontend/src/components/TopBar.vue index 3d9f1d5..e018b7a 100644 --- a/frontend/src/components/TopBar.vue +++ b/frontend/src/components/TopBar.vue @@ -37,8 +37,10 @@ import {Component, Prop} from "vue-property-decorator"; export default class TopBar extends Vue { @Prop() value = ""; - search(event) { - this.$emit("input", event.target.value) + search (event) { + if (event.target.value.trim().length > 0) { + this.$emit("input", event.target.value) + } } } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index b00b543..9bf74b1 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1763,9 +1763,9 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= copy-webpack-plugin@^4.0.1: - version "4.5.3" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.5.3.tgz#14a224d205e46f7a79f7956028e1da6df2225ff2" - integrity sha512-VKCiNXQcc8zyznaepXfKpCH2cZD+/j3T3B+gsFY97P7qMlEsj34wr/sI9OCG7QPUUh7gAHVx3q8Q1rdQIDM4bA== + version "4.5.4" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.5.4.tgz#f2b2782b3cd5225535c3dc166a80067e7d940f27" + integrity sha512-0lstlEyj74OAtYMrDxlNZsU7cwFijAI3Ofz2fD6Mpo9r4xCv4yegfa3uHIKvZY1NSuOtE9nvG6TAhJ+uz9gDaQ== dependencies: cacache "^10.0.4" find-cache-dir "^1.0.0" @@ -4538,12 +4538,12 @@ node-libs-browser@^2.0.0: vm-browserify "0.0.4" node-notifier@^5.1.2: - version "5.2.1" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea" - integrity sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg== + version "5.3.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.3.0.tgz#c77a4a7b84038733d5fb351aafd8a268bfe19a01" + integrity sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q== dependencies: growly "^1.3.0" - semver "^5.4.1" + semver "^5.5.0" shellwords "^0.1.1" which "^1.3.0" @@ -5971,7 +5971,7 @@ selfsigned@^1.9.1: dependencies: node-forge "0.7.5" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==