Skip to content

Commit

Permalink
DWPF-784 Typeahead strong match & no results redirect (#509)
Browse files Browse the repository at this point in the history
- Added logic around zero results tabs, so that when no results are
present in the tab, redirect.
- Added Logic & UI for autocompletion search functionality via
search-form.

---------

Co-authored-by: Ryan Williams <[email protected]>
  • Loading branch information
0RWilliams and Ryan Williams authored Nov 23, 2023
1 parent 22d978e commit 5786ed3
Show file tree
Hide file tree
Showing 13 changed files with 251 additions and 20 deletions.
72 changes: 72 additions & 0 deletions assets/stylesheets/components/_search_field.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ $input-size: 40px;
}

.search-form {
position: relative;
display: flex;
flex: 0 $input-size;
margin: 7px 0;
Expand Down Expand Up @@ -110,3 +111,74 @@ $input-size: 40px;
}
}
}


.autocomplete-overlay {
position: absolute;
z-index: 100;
top: 100%;
left: 0;
width: 100%;
background-color: #ffffff;
box-shadow: rgba(11, 12, 12, 0.256863) 0 2px 6px;
}

.profile-image{
vertical-align:middle;
padding-top: 3px;
padding-left: 3px;
width: 40px;
height: 40px;
}

.autocomplete-choice{
width: 100%;
display: block;
position: relative;
padding-bottom: 5px;
padding-top: 5px;
border-bottom: solid #b1b4b6;
border-width: 1px 0;
list-style-type: none;
}

.autocomplete-choice a{
text-decoration: none;
}

.autocomplete-text {
@include govuk-font($size: 16);
margin-left: 5px;
padding-left: 5px;
margin: 0;
display:block
}

.people .autocomplete-text {
@include govuk-font($size: 16);
margin-left: 5px;
padding-left: 5px;
margin: 0;
display:inline
}

.autocomplete-title {
@include govuk-font($size: 16, $weight: bold);
border-top: 2px solid #1d70b8;
width: 100%;
padding-top: 5px;
padding-left: 5px;
margin: 0;
}

.autocomplete-title a:hover{
text-decoration: underline;
}

.autocomplete-choice:hover {
background-color: govuk-colour("light-grey");
}

.autocomplete-choice:hover p{
text-decoration: underline;
}
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"cropperjs": "^1.5.13",
"css-loader": "^5.2.7",
"govuk-frontend": "^4.5.0",
"htmx.org": "^1.8.6",
"htmx.org": "^1.9.8",
"mini-css-extract-plugin": "^1.6.2",
"sass": "^1.59.3",
"sass-loader": "^12.6.0",
Expand Down
21 changes: 15 additions & 6 deletions src/search/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,20 @@


class SearchVector:
def __init__(self, request, annotate_score=False):
def __init__(self, request):
self.request = request
self.annotate_score = annotate_score

def _wagtail_search(self, queryset, query, *args, **kwargs):
"""
Allows e.g. score annotation without polluting overridden search method
"""
return_method = queryset.search(query, *args, **kwargs)

if self.annotate_score:
return_method = return_method.annotate_score("_score")
return_method = queryset.search(query, *args, **kwargs).annotate_score("_score")

return return_method

def _wagtail_autocomplete(self, queryset, query, *args, **kwargs):
return queryset.autocomplete(query, *args, **kwargs)

def get_queryset(self):
raise NotImplementedError

Expand Down Expand Up @@ -52,6 +51,10 @@ def get_query(self, query_str):
def pinned(self, query):
return self.get_queryset().pinned(query)

def autocomplete(self, query, *args, **kwargs):
queryset = self.get_queryset().not_pinned(query)
return self._wagtail_autocomplete(queryset, query, *args, **kwargs)

def search(self, query, *args, **kwargs):
queryset = self.get_queryset().not_pinned(query)
query = self.get_query(query)
Expand Down Expand Up @@ -107,6 +110,9 @@ def search(self, query, *args, **kwargs):
)
return self._wagtail_search(queryset, query, *args, **kwargs)

def autocomplete(self, query, *args, **kwargs):
return self._wagtail_autocomplete(self.get_queryset(), query, *args, **kwargs)


class TeamsSearchVector(SearchVector):
def get_queryset(self):
Expand All @@ -116,3 +122,6 @@ def search(self, query, *args, **kwargs):
queryset = self.get_queryset()
query = get_search_query(TeamIndexManager, query, Team, *args, **kwargs)
return self._wagtail_search(queryset, query, *args, **kwargs)

def autocomplete(self, query, *args, **kwargs):
return self._wagtail_autocomplete(self.get_queryset(), query, *args, **kwargs)
73 changes: 73 additions & 0 deletions src/search/templates/search/partials/result/autocomplete_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{% load webpack_static from webpack_loader %}
{% load wagtailcore_tags %}

{% if search_query %}
<div style="display:none"
hx-on:htmx:load="dataLayer.push({ 'event': 'autocomplete-search', 'autocomplete-search_query': '{{ search_query }}', 'autocomplete_page_count':{{ pages_count }},'autocomplete_people_count':{{ people_count }},'autocomplete_teams_count':{{ teams_count }}});">
</div>
{% endif %}

{% if people %}
<div class="govuk-heading-s autocomplete-title">
<a class="govuk-divnk"
href="{% url 'search:category' 'people' %}?query={{ search_query }}"
onclick="dataLayer.push({ 'event': 'autocomplete-result-heading', 'autocomplete-result_selected_url': '{% url 'search:category' 'people' %}?query={{ search_query }}', 'autocomplete_result_selected_type': 'person'})">
People results</a>
</div>
{% endif %}
{% for person in people %}
<div class= "autocomplete-choice">
<a class="govuk-divnk"
href="{% url 'profile-view' person.slug %}"
onclick="dataLayer.push({ 'event': 'autocomplete-result', 'autocomplete-result_selected_url': '{% url 'profile-view' person.slug %}', 'autocomplete_result_selected_type': 'person'})">
<div class="people">
{% if person.photo %}
<img src="{{ person.photo_small.url }}"
alt="Profile image of {{ person.get_full_name }}"
class="profile-image">
{% else %}
<img src="{% webpack_static 'no-photo.png' %}"
alt="No photo"
class="profile-image">
{% endif %}
<p class="govuk-body autocomplete-text">{{ person.first_name }} {{ person.last_name }}</p>
</div>
</a>
</div>
{% endfor %}
{% if pages %}
<div class="govuk-heading-s autocomplete-title">
<a class="govuk-divnk"
href="{% url 'search:category' 'all' %}?query={{ search_query }}"
onclick="dataLayer.push({ 'event': 'autocomplete-result-heading', 'autocomplete-result_selected_url': '{% url 'search:category' 'all' %}?query={{ search_query }}', 'autocomplete_result_selected_type': 'page'})">Content results</a>
</div>
{% endif %}
{% for page in pages %}
<div class="autocomplete-choice">
<a class="govuk-divnk"
href="{% pageurl page %}"
onclick="dataLayer.push({ 'event': 'autocomplete-result', 'autocomplete-result_selected_url': '{% pageurl page %}', 'autocomplete_result_selected_type': 'all'})">
<div>
<p class="govuk-body autocomplete-text">{{ page.title }}</p>
</div>
</a>
</div>
{% endfor %}
{% if teams %}
<div class="govuk-heading-s autocomplete-title">
<a class="govuk-divnk"
href="{% url 'search:category' 'teams' %}?query={{ search_query }}"
onclick="dataLayer.push({ 'event': 'autocomplete-result-heading', 'autocomplete-result_selected_url': '{% url 'search:category' 'teams' %}?query={{ search_query }}', 'autocomplete_result_selected_type': 'teams'})">Teams results</a>
</div>
{% endif %}
{% for team in teams %}
<div class="autocomplete-choice">
<a class="govuk-divnk"
href="{% url 'team-view' team.slug %}"
onclick="dataLayer.push({ 'event': 'autocomplete-result', 'autocomplete-result_selected_url': '{% url 'team-view' team.slug %}', 'autocomplete_result_selected_type': 'teams'})">
<div>
<p class="govuk-body autocomplete-text">{{ team.name }}</p>
</div>
</a>
</div>
{% endfor %}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% load search %}
{% search_count category=search_count_category as hits %}
<a href="{% url 'search:category' name %}?query={{ search_query }}"
<a href="{% url 'search:category' name %}?query={{ search_query }}&tab_override=True"
class="govuk-!-font-size-19 govuk-link govuk-link--no-underline govuk-link--no-visited-state"
{% if search_category == name %}data-selected{% endif %}
data-search-category-name="{{ name }}"
Expand Down
7 changes: 6 additions & 1 deletion src/search/templates/search/partials/search_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
method="get">
<label for="search-site" class="govuk-visually-hidden">Search Digital Workspace</label>
<input id="search-site"
autocomplete="off"
class="govuk-input"
name="query"
placeholder="{{ placeholder|default:'Search site' }}"
type="search"
value="{{ search_query|default:'' }}" />
value="{{ search_query|default:'' }}"
hx-trigger="keyup changed delay:250ms"
hx-get="{% url 'search:autocomplete' %}"
hx-target="#autocomplete-results">
<button type="submit">Search</button>
<div id="autocomplete-results" class="autocomplete-overlay"></div>
</form>
6 changes: 3 additions & 3 deletions src/search/templates/search/partials/search_results_all.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

<div class="search-results__all">
<div class="left-col">
<div class="pages">{% search_category category='all_pages' show_heading=True %}</div>
<div class="pages">{% search_category category='all_pages' tab_name='all' show_heading=True %}</div>
</div>
<div class="right-col">
<div class="people">{% search_category category='people' limit=3 show_heading=True %}</div>
<div class="teams">{% search_category category='teams' limit=3 show_heading=True %}</div>
<div class="people">{% search_category category='people' tab_name='all' limit=3 show_heading=True %}</div>
<div class="teams">{% search_category category='teams' tab_name='all' limit=3 show_heading=True %}</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ <h3 class="govuk-heading-s">Featured {{ result_type_display }}</h3>
{% endfor %}

{% else %}
{% if tab_name != 'all' and not tab_override %}
<script lang="javascript">
url = '{% url 'search:category' 'all' %}?query={{search_query}}'
document.onload = window.location = url;
</script>
{% endif %}
<p class="govuk-body">There are no matching {{ result_type_display }}.</p>

{% if search_category == 'all_pages' %}
Expand Down
32 changes: 31 additions & 1 deletion src/search/templatetags/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
@register.inclusion_tag(
"search/partials/search_results_category.html", takes_context=True
)
def search_category(context, *, category, limit=None, show_heading=False):
def search_category(
context, *, category, tab_name=None, limit=None, show_heading=False
):
request = context["request"]
query = context["search_query"]
page = context["page"]
Expand Down Expand Up @@ -70,6 +72,8 @@ def search_category(context, *, category, limit=None, show_heading=False):
"pinned_results": pinned_results,
"num_pinned_results": f"{len(pinned_results)}",
"search_results": search_results,
"tab_name": tab_name,
"tab_override": context["tab_override"],
"search_query": query,
"count": count,
"show_heading": show_heading,
Expand All @@ -78,6 +82,32 @@ def search_category(context, *, category, limit=None, show_heading=False):
}


# Method for querying using wagtails default autocomplete functionality
#
def autocomplete(request, query):
limit = 3
search_results = {}

search_results.update(
{"tools": list(SEARCH_VECTORS["tools"](request).autocomplete(query)[:limit])}
)
search_results.update(
{
"pages": list(
SEARCH_VECTORS["all_pages"](request).autocomplete(query)[:limit]
)
}
)
search_results.update(
{"people": list(SEARCH_VECTORS["people"](request).autocomplete(query)[:limit])}
)
search_results.update(
{"teams": list(SEARCH_VECTORS["teams"](request).autocomplete(query)[:limit])}
)

return search_results


@register.simple_tag(takes_context=True)
def search_count(context, *, category):
request = context["request"]
Expand Down
2 changes: 1 addition & 1 deletion src/search/templatetags/search_explore.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def search_category(context, *, category, limit=None, show_heading=False):
query = context["search_query"]
page = context["page"]

search_vector = SEARCH_VECTORS[category](request, annotate_score=True)
search_vector = SEARCH_VECTORS[category](request)
search_results = list(search_vector.search(query))
count = len(search_results)

Expand Down
3 changes: 2 additions & 1 deletion src/search/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.urls import path
from django.views.generic import RedirectView

from .views import explore, search
from .views import explore, search, autocomplete


app_name = "search"
Expand All @@ -20,6 +20,7 @@
),
),
path("explore/", explore, name="explore"),
path("autocomplete/", autocomplete, name="autocomplete"),
path("<str:category>/", search, name="category"),
path("", search, name="home"),
]
Loading

0 comments on commit 5786ed3

Please sign in to comment.