Skip to content

Commit 315f2db

Browse files
committed
Merge remote-tracking branch 'upstream/master' into stable
2 parents 84a55e1 + 4649d81 commit 315f2db

File tree

7 files changed

+222
-65
lines changed

7 files changed

+222
-65
lines changed

app/app/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,7 @@ def callback(request):
897897

898898

899899
ELASTIC_SEARCH_URL = env('ELASTIC_SEARCH_URL', default='')
900+
ACTIVE_ELASTIC_INDEX = env('ACTIVE_ELASTIC_INDEX', default='search-index-1')
900901

901902
account_sid = env('TWILIO_ACCOUNT_SID', default='')
902903
auth_token = env('TWILIO_AUTH_TOKEN', default='')

app/assets/v2/js/search.js

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@ if (document.getElementById('gc-search')) {
44
el: '#gc-search',
55
data: {
66
term: '',
7-
results: {},
7+
results: [],
88
page: 0,
99
total: 0,
1010
perPage: 100,
1111
searchTerm: '',
1212
isLoading: false,
13+
isTypeSearchLoading: false,
14+
tabPageCount: {
15+
Profile: 0,
16+
Bounty: 0,
17+
Grant: 0,
18+
Kudos: 0,
19+
Quest: 0,
20+
Page: 0
21+
},
1322
isDirty: false,
1423
currentTab: 0,
1524
source_types: [
@@ -39,8 +48,19 @@ if (document.getElementById('gc-search')) {
3948
this.search();
4049
},
4150
computed: {
51+
sourceType() {
52+
return this.source_types[this.currentTab];
53+
},
54+
currentTotal() {
55+
return this.totals[this.sourceType];
56+
},
57+
currentPage() {
58+
const page = this.currentTab > 0 ? this.tabPageCount[this.sourceType] : this.page;
59+
60+
return page;
61+
},
4262
hasMoreResults() {
43-
return this.page !== false && this.total && this.page * this.perPage < this.total;
63+
return this.currentPage !== false && this.totals[this.sourceType] && this.page * this.perPage < this.totals[this.sourceType];
4464
}
4565
},
4666
methods: {
@@ -49,8 +69,15 @@ if (document.getElementById('gc-search')) {
4969
$('.has-search input[type=text]').focus();
5070
}, 100);
5171
},
72+
loadMoreResults: function() {
73+
if (this.sourceType === 'All') {
74+
this.search();
75+
return;
76+
}
77+
this.search_type(this.sourceType);
78+
},
5279
clear: function() {
53-
this.results = {};
80+
this.results = [];
5481
this.page = 0;
5582
this.total = 0;
5683
},
@@ -67,6 +94,58 @@ if (document.getElementById('gc-search')) {
6794
this.fetchController = new AbortController();
6895
}
6996
},
97+
change_tab: function(index, source_type) {
98+
const vm = this;
99+
100+
vm.currentTab = index;
101+
102+
if (index > 0) {
103+
vm.tabPageCount[source_type] = 0;
104+
vm.search_type(source_type);
105+
} else {
106+
vm.page = 0;
107+
vm.search();
108+
}
109+
},
110+
search_type: async function(type) {
111+
const vm = this;
112+
// use signal to kill fetch req
113+
const { signal } = vm.fetchController;
114+
115+
116+
if (vm.isTypeSearchLoading) {
117+
return;
118+
}
119+
120+
if (vm.term.length >= 3) {
121+
vm.isTypeSearchLoading = true;
122+
fetch(
123+
`/api/v0.1/search/?term=${vm.term}&page=${vm.tabPageCount[type]}&type=${type}`,
124+
{
125+
method: 'GET',
126+
signal: signal
127+
}
128+
).then((res) => {
129+
res.json().then((response) => {
130+
if (vm.tabPageCount[type] === 0) {
131+
vm.results = response.results;
132+
} else {
133+
vm.results.push(...response.results);
134+
}
135+
vm.isTypeSearchLoading = false;
136+
vm.tabPageCount[type] = response.page;
137+
});
138+
}).catch(() => {
139+
if (document.current_search == thisDate) {
140+
// clear the results
141+
vm.clear();
142+
// clear loading states
143+
vm.isTypeSearchLoading = false;
144+
}
145+
});
146+
}
147+
148+
},
70149
search: async function(e) {
71150
const vm = this;
72151
// use Date to enforce
@@ -98,9 +177,13 @@ if (document.getElementById('gc-search')) {
98177
).then((res) => {
99178
if (document.current_search == thisDate) {
100179
res.json().then((response) => {
101-
vm.results = groupBySource(response.results, vm.term !== vm.searchTerm ? {} : vm.results);
180+
if (vm.page === 0) {
181+
vm.results = response.results;
182+
} else {
183+
vm.results.push(...response.results);
184+
}
102185
vm.searchTerm = vm.term;
103-
vm.total = response.total;
186+
vm.totals = response.totals;
104187
vm.page = response.page;
105188
vm.perPage = response.perPage;
106189
// clear loading states
@@ -127,18 +210,6 @@ if (document.getElementById('gc-search')) {
127210
}
128211
document.current_search = new Date();
129212

130-
const groupBySource = (results, prev_results) => {
131-
let grouped_result = prev_results || {};
132-
133-
results.map(result => {
134-
const source_type = result.source_type;
135-
136-
grouped_result['All'] ? grouped_result['All'].push(result) : grouped_result['All'] = [result];
137-
grouped_result[source_type] ? grouped_result[source_type].push(result) : grouped_result[source_type] = [result];
138-
});
139-
return grouped_result;
140-
};
141-
142213
$(document).on('click', '.gc-search .dropdown-menu', function(event) {
143214
event.stopPropagation();
144215
});

app/assets/v2/scss/search.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
}
2323

2424
.gc-search-box.dropdown-menu {
25-
width: 520px;
25+
width: 540px;
2626
background-color: #ffffff;
2727
border-radius: 4px;
2828
top: 2.3rem;

app/dashboard/templates/shared/search.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,20 @@
4040
Click 'Go' to Search.
4141
</p>
4242
</template>
43-
<template v-else-if="Object.keys(results).length">
43+
<template v-else-if="results.length">
4444
<ul class="nav nav-tabs mt-1 gc-search-nav d-flex" role="tablist">
4545
<li class="nav-item flex-grow-1 text-center" v-for='(source_type, index) in source_types' :key="index">
46-
<a class="nav-link nav-line gc-search-tab__title px-1" @click="currentTab = index" :class="{active: currentTab === index}" v-bind:href="'#' + source_type" data-toggle="tab">[[ labels[source_type] ]]
47-
<span v-if="typeof results[source_type] !== 'undefined'">([[ results[source_type].length ]])</span>
46+
<a class="nav-link nav-line gc-search-tab__title px-1" @click="change_tab(index, source_type)" :class="{active: currentTab === index}" v-bind:href="'#' + source_type" data-toggle="tab">[[ labels[source_type] ]]
47+
<span v-if="typeof totals[source_type] !== 'undefined'">([[ totals[source_type] ]])</span>
4848
<span v-else>(0)</span>
4949
</a>
5050
</li>
5151
</ul>
5252
<div class="tab-content clearfix">
5353
<div class="tab-pane" :class="{active: currentTab === index}" v-bind:id="source_type" v-for='(source_type, index) in source_types' :key="index">
5454
<ul class="gc-search-result mb-0">
55-
<template v-if="results && results[source_type]">
56-
<li v-for="result in results[source_type]" class="gc-search-result__content p-3" :key="result.id">
55+
<template v-if="results">
56+
<li v-for="result in results" class="gc-search-result__content p-3" :key="result.id">
5757
<a :href="[[ result.url ]]" class="d-flex">
5858
<img class="gc-search__avatar my-auto mr-3" :src="[[ result.img_url ]]" :alt="result.title" />
5959
<div>
@@ -79,20 +79,20 @@
7979
</div>
8080
<template v-if="hasMoreResults">
8181
<div class="gc-search-more-results bg-white border-grey border-top-1 bottom-0 font-smaller-2 pb-3 position-sticky pt-3 text-center">
82-
<template v-if="isLoading">
82+
<template v-if="isLoading || isTypeSearchLoading">
8383
<a class="text-muted cursor-none disable-hover-underline" @click.prevent="false">
8484
<i class="font-smaller-1 fas fa-spinner fa-pulse mr-1"></i>
8585
Fetching result
8686
</a>
8787
</template>
8888
<template v-else>
89-
<a class="cursor-pointer" @click="search">Load more results (showing [[ page * perPage ]] of [[ total ]])...</a>
89+
<a class="cursor-pointer" @click="loadMoreResults">Load more results (showing [[ currentPage * perPage ]] of [[ currentTotal ]])...</a>
9090
</template>
9191
</div>
9292
</template>
9393
</template>
9494

95-
<template v-else-if="isLoading">
95+
<template v-else-if="isLoading || isTypeSearchLoading">
9696
<p class="text-center font-smaller-2 gc-search-result__description">
9797
<i class="font-smaller-1 fas fa-spinner fa-pulse mr-1"></i>
9898
Fetching result

app/search/management/commands/create_search_index_with_active_grants.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,4 @@ def handle(self, *args, **options):
6060
sr.put_on_elasticsearch(index_name)
6161
except Exception as e:
6262
print('failed:', e)
63-
6463
print('indexing complete')

app/search/models.py

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ class SearchResult(SuperModel):
1919
description = models.TextField(default='', blank=True)
2020
url = models.CharField(max_length=500, default='')
2121
img_url = models.CharField(max_length=600, default='', null=True)
22-
visible_to = models.ForeignKey('dashboard.Profile', related_name='search_results_visible', on_delete=models.CASCADE, db_index=True, null=True)
22+
visible_to = models.ForeignKey('dashboard.Profile', related_name='search_results_visible',
23+
on_delete=models.CASCADE, db_index=True, null=True)
2324

2425
def __str__(self):
2526
return f"{self.source_type}; {self.url}"
2627

2728
def check_for_active_grant(self):
2829
grant = Grant.objects.get(pk=self.source_id)
2930
return grant.active and not grant.hidden
30-
3131
def put_on_elasticsearch(self, index='search-index'):
3232
if self.visible_to:
3333
return None
@@ -37,8 +37,8 @@ def put_on_elasticsearch(self, index='search-index'):
3737
if not active_grant:
3838
return None
3939

40-
es = Elasticsearch([settings.ELASTIC_SEARCH_URL])
41-
source_type = str(str(self.source_type).replace('token', 'kudos')).title()
40+
es = Elasticsearch(settings.ELASTIC_SEARCH_URL)
41+
source_type = str(str(self.source_type).replace('token', 'kudos')).title()
4242
full_search = f"{self.title} {self.description} {source_type}"
4343
doc = {
4444
'title': self.title,
@@ -49,39 +49,77 @@ def put_on_elasticsearch(self, index='search-index'):
4949
'img_url': self.img_url,
5050
'timestamp': timezone.now(),
5151
'source_type': source_type,
52+
# in order to aggregate totals by type an int needs to be indexed as opposed to a string
53+
'source_type_id': self.source_type_id
5254
}
5355
res = es.index(index=index, id=self.pk, body=doc)
5456

57+
def search_by_type(query, content_type, page=0, num_results=500):
58+
if not settings.ELASTIC_SEARCH_URL and not settings.ACTIVE_ELASTIC_INDEX:
59+
return {}
60+
61+
es = Elasticsearch(settings.ELASTIC_SEARCH_URL)
62+
res = es.search(index=settings.ACTIVE_ELASTIC_INDEX, body={
63+
"from": page, "size": num_results,
64+
"query": {
65+
"bool": {
66+
"must": {
67+
"match": {
68+
"source_type_id": {
69+
"query": content_type
70+
}
71+
}
72+
},
73+
"should": [
74+
{
75+
"query_string": {
76+
"query": f"*{query}*",
77+
"fields": ["title^10", "description", "source_type"],
78+
}
79+
}
80+
],
81+
"minimum_should_match": "1"
82+
}
83+
},
84+
"aggs": {
85+
"search-totals": {
86+
"terms": {
87+
"field": "source_type_id"
88+
}
89+
}
90+
}
91+
})
92+
return res
93+
5594

5695
def search(query, page=0, num_results=500):
57-
if not settings.ELASTIC_SEARCH_URL:
96+
if not settings.ELASTIC_SEARCH_URL and not settings.ACTIVE_ELASTIC_INDEX:
5897
return {}
59-
es = Elasticsearch([settings.ELASTIC_SEARCH_URL])
98+
es = Elasticsearch(settings.ELASTIC_SEARCH_URL)
6099
# queries for wildcarded paginated results using boosts to lift by title and source_type=grant
61100
# index name will need updated once index is ready to be searched
62-
res = es.search(index="search-index", body={
63-
"from" : page, "size" : num_results,
64-
"query": {
65-
"bool": {
66-
"should": [
67-
{
68-
"query_string": {
69-
"query": f"*{query}*",
70-
"fields": ["title^10", "description", "source_type"],
71-
}
72-
},
73-
{
74-
"match": {
75-
"source_type": {
76-
"query": "grant",
77-
"boost": "2"
101+
res = es.search(index=settings.ACTIVE_ELASTIC_INDEX, body={
102+
"from": page, "size": num_results,
103+
"query": {
104+
"bool": {
105+
"should": [
106+
{
107+
"query_string": {
108+
"query": f"*{query}*",
109+
"fields": ["title^10", "description", "source_type"],
110+
}
111+
}
112+
],
113+
"minimum_should_match": "1"
114+
}
115+
},
116+
"aggs": {
117+
"search-totals": {
118+
"terms": {
119+
"field": "source_type_id"
78120
}
79-
}
80121
}
81-
],
82-
"minimum_should_match": "1"
83122
}
84-
}
85123
})
86124
return res
87125

@@ -94,7 +132,6 @@ def __str__(self):
94132
return f"{self.val}"
95133

96134

97-
98135
class Page(SuperModel):
99136
"""Records ProgrammingLanguage - the list for programming langauges"""
100137
title = models.CharField(max_length=255, default='')

0 commit comments

Comments
 (0)