Skip to content
This repository has been archived by the owner on Feb 21, 2022. It is now read-only.

Commit

Permalink
feat: zoo search (#392)
Browse files Browse the repository at this point in the history
* feat: zoo search

* refactor: CR  fixes

* refactor: rename meilisearch to globalsearch app

Co-authored-by: Jurica Grgicevic <[email protected]>
  • Loading branch information
gjuro87 and gjuro87 authored May 25, 2021
1 parent 7c79ae1 commit 8eda7d1
Show file tree
Hide file tree
Showing 39 changed files with 640 additions and 238 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest

from zoo.analytics.models import Dependency, DependencyType
from zoo.meilisearch.indexer import Indexer as uut
from zoo.globalsearch.indexer import Indexer as uut
from zoo.services.models import Service

pytestmark = pytest.mark.django_db
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ def test_search_result():
"Service": [service],
"Schema": [],
}
with patch("zoo.meilisearch.views.MeiliSearchView") as mock_view:
with patch("zoo.globalsearch.views.GlobalSearchView") as mock_view:
mock_view.get_context_data.return_value = expected_result
assert expected_result == mock_view.get_context_data()
File renamed without changes.
87 changes: 87 additions & 0 deletions zoo/api/query.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
from math import ceil

import graphene
from django.apps import apps
from graphene import relay

from ..analytics.models import Dependency, DependencyType, DependencyUsage
from ..auditing.models import Issue
from ..globalsearch.indexer import IndexType
from ..globalsearch.meili_client import meili_client
from ..repos.models import Repository
from ..services.models import Service
from . import types
from .paginator import Paginator

DependencyTypeEnum = graphene.Enum.from_enum(DependencyType)
SearchTypeEnum = graphene.Enum.from_enum(IndexType)


class Query(graphene.ObjectType):
Expand Down Expand Up @@ -36,6 +42,13 @@ class Query(graphene.ObjectType):
description="List of dependency usages. Returns first 10 nodes if pagination is not specified.",
)

all_search_results = relay.ConnectionField(
types.SearchResultsConnection,
description="List of search results. Returns first 10 nodes if pagination is not specified.",
search_query=graphene.String(),
search_type=SearchTypeEnum(),
)

def resolve_all_issues(self, info, **kwargs):
paginator = Paginator(**kwargs)
total = Issue.objects.all().count()
Expand Down Expand Up @@ -142,3 +155,77 @@ def resolve_all_dependency_usages(self, info, **kwargs):
return types.DependencyUsageConnection(
page_info=page_info, edges=edges, total_count=total
)

def resolve_all_search_results(self, info, **kwargs):
paginator = Paginator(**kwargs)
query_param = kwargs.get("search_query", "")
search_type = kwargs.get("search_type", IndexType.Service.value)

indexes = meili_client.get_indexes()
# get all total counts for each meili index
search_index = None
total_count = {}
for index in indexes:
total_count[index["uid"]] = meili_client.get_index(index["uid"]).search(
query=query_param, opt_params={"offset": 0, "limit": 1}
)["nbHits"]

if index["uid"] == search_type:
search_index = index

# get all results for requested index
search_results = Query._get_all_for_index(
query_param, search_index, total_count.get(search_index["uid"])
)
edges = []
for i, search_result in enumerate(
search_results[paginator.slice_from : paginator.slice_to]
):
cursor = paginator.get_edge_cursor(i + 1)
edges.append(
types.SearchResultsConnection.Edge(node=search_result, cursor=cursor)
)

return types.SearchResultsConnection(
page_info=paginator.get_page_info(total_count[search_type]),
edges=edges,
total_services_count=total_count[IndexType.Service.value],
total_analytics_count=total_count[IndexType.Dependency.value],
)

@staticmethod
def _get_all_for_index(query_param, search_index, total_count):
limit, search_results = 100, []

if not any([total_count, search_index]):
return search_results

pages = ceil(total_count / limit)
for page in range(pages):
results = meili_client.get_index(search_index["uid"]).search(
query=query_param, opt_params={"offset": page * limit, "limit": limit}
)
search_results.extend(
Query._objects_from_result(results["hits"], search_index)
)

return search_results

@staticmethod
def _objects_from_result(search_results, index):
result_objects = []
try:
model = apps.get_model(index["uid"], index["name"])
for i, result in enumerate(search_results):
model_object = model.objects.get(pk=result["id"])
if index["name"] == IndexType.Dependency.name:
dependency = types.Dependency.from_db(model_object)
node = types.SearchResult(id=i, dependency=dependency)
if index["name"] == IndexType.Service.name:
service = types.Service.from_db(model_object)
node = types.SearchResult(id=i, service=service)
result_objects.append(node)
except LookupError:
pass

return result_objects
16 changes: 16 additions & 0 deletions zoo/api/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,19 @@ def resolve_title(self, info):

def resolve_description(self, info):
return self.kind.format_description(self.details)


class SearchResult(graphene.ObjectType):
dependency = graphene.Field(Dependency)
service = graphene.Field(Service)

class Meta:
interfaces = (relay.Node,)


class SearchResultsConnection(relay.Connection):
total_analytics_count = graphene.Int()
total_services_count = graphene.Int()

class Meta:
node = SearchResult
2 changes: 1 addition & 1 deletion zoo/base/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def ready(self):
from ..analytics import tasks as analytics_tasks
from ..auditing import tasks as auditing_tasks
from ..datacenters import tasks as datacenters_tasks
from ..meilisearch import tasks as meilisearch_tasks
from ..globalsearch import tasks as meilisearch_tasks
from ..objectives import tasks as objective_tasks
from ..repos import tasks as repos_tasks
from ..services import tasks as service_tasks
Expand Down
32 changes: 32 additions & 0 deletions zoo/base/assets/js/header_search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
$(document).ready(function() {
const URI = require('urijs')
const searchInput = $('.ui.search input')
const enterKeyCode = 13
const slashKeyCode = 47
const currentUrl = URI(window.location.href)
const currentQuery = URI.parseQuery(currentUrl.query()).q

$(document).keypress(function(event) {
if(event.which == slashKeyCode) {
event.preventDefault();
searchInput.focus()
searchInput.selectionStart = searchInput.selectionEnd = searchInput.value.length
}
});

searchInput.keypress((event) => {
if(event.which === enterKeyCode) {
let newUrl = currentUrl.pathname('search')
newUrl.search("")

if(searchInput.val())
newUrl.addSearch(URI.parseQuery('?q=' + searchInput.val()))
window.location.href = newUrl.toString()
}
})

if(currentQuery) {
searchInput.val(currentQuery)
}

});
21 changes: 0 additions & 21 deletions zoo/base/assets/js/project_list.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,4 @@
import '../style/project_list.less'
const URI = require('urijs')
const searchInput = $('.ui.search input')
const enterKeyCode = 13

const currentUrl = URI(window.location.href)
const currentQuery = URI.parseQuery(currentUrl.query()).q

searchInput.keypress((event) => {
if(event.which === enterKeyCode) {
let newUrl = URI(currentUrl.path())

if(searchInput.val())
newUrl.addSearch(URI.parseQuery('?q=' + searchInput.val()))

window.location.href = newUrl.toString()
}
})

if(currentQuery) {
searchInput.val(currentQuery)
}

$('.project-filter .ui.basic.label').popup({
transition: 'fade down',
Expand Down
2 changes: 1 addition & 1 deletion zoo/base/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"zoo.datacenters.apps.DatacentersConfig",
"zoo.instance.apps.InstanceConfig",
"zoo.libraries.apps.LibrariesConfig",
"zoo.meilisearch.apps.MeiliSearchConfig",
"zoo.globalsearch.apps.GlobalSearchConfig",
"zoo.objectives.apps.ObjectivesConfig",
"zoo.pagerduty.apps.PagerdutyConfig",
"zoo.repos.apps.ReposConfig",
Expand Down
23 changes: 23 additions & 0 deletions zoo/base/templates/base.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% extends 'skeleton.html' %}
{% load static %}

{% block body %}
<div class="ui small brown inverted top fixed pointing main menu">
Expand Down Expand Up @@ -26,6 +27,24 @@
<i class="chart bar icon"></i>
Objectives
</a>
{% if selected == 'search_overview' %}
<a href="{% url 'search_overview' %}" class="active item">
<i class="search icon"></i>
Search Results
</a>
{% endif %}
{% block search_form %}
<div class="ui item">
<div class="ui right aligned category fluid search project-filter" style="width: 250px;">
<div class="ui left labeled icon fluid input">
<input id="search-bar" name="q" class="prompt" type="text" placeholder="Search ..." tabindex="1">
<i class="search icon"></i>
</div>
<div class="results"></div>
</div>
</div>
</div>
{% endblock %}
<div class="ui link item user-actions">
<i class="user icon"></i>
<span class="text">{{ request.user }}</span>
Expand Down Expand Up @@ -73,3 +92,7 @@ <h2 class="ui header">
{% block content %}{% endblock %}
</div>
{% endblock %}

{% block scripts %}
<script src="{% static 'header_search.js' %}"></script>
{% endblock %}
98 changes: 98 additions & 0 deletions zoo/base/templates/shared/base_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{% load utils %}

{% for object in object_list %}
<div class="ui project segment">
<h3 class="ui header">
<span>
<a href="{{ object.get_absolute_url }}">{{ object.name }}</a>
<small>
·
owned by
{% if object.owner_url %}
<a href="{{ object.owner_url }}">{{ object.owner }}</a>
{% else %}
{{ object.owner }}
{% endif %}
</small>
</span>

{% spaceless %}
<div class="flex-horizontal tags-wrapper">
{% if object.status %}
<a class="ui {% label_color 'status' object.status %} circular label">status:{{ object.status }}</a>
{% endif %}
{% if object.impact %}
<a class="ui {% label_color 'impact' object.impact %} circular label">impact:{{ object.impact }}</a>
{% endif %}
</div>
{% endspaceless %}

</h3>
<div class="flex-horizontal actions-wrapper">
{% if 'Support' in project_links %}
{% project_link 'Support' object.slack_url icon='comment alternate' %}
{% endif %}
{% if 'Repository' in project_links %}
{% project_link 'Repository' object.gitlab_url icon='code' %}
{% endif %}
{% if 'Dashboard' in project_links %}
{% project_link 'Dashboard' object.dashboard_url icon='chart pie' %}
{% endif %}
{% if 'Alerts' in project_links %}
{% project_link 'Alerts' object.pagerduty_url icon='warning' %}
{% endif %}
{% if 'Documentation' in project_links %}
{% project_link 'Documentation' object.docs_url icon='graduation cap' %}
{% endif %}
</div>
</div>
{% empty %}
<div class="ui basic segment">
<div class="ui very padded placeholder segment">
<div class="ui icon header">
<i class="huge grin beam sweat outline icon"></i>
<br>
<div class="content">
Oops! We couldn't find your thing...
<div class="sub header">
Make sure the query is correct or add the project if needed
</div>
</div>
</div>
<br>
<a href="{% url project_create_view %}" class="ui yellow black-text button">Add project</a>
</div>
</div>
{% endfor %}
{% if is_paginated %}
<div class="ui basic center aligned segment">
<div class="ui pagination menu">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}" class="item">
<i class="chevron left icon"></i>
</a>
{% else %}
<div class="active item">
<i class="chevron left icon"></i>
</div>
{% endif %}
<div class="item">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</div>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}" class="item">
<i class="chevron right icon"></i>
</a>
{% else %}
<div class="active item">
<i class="chevron right icon"></i>
</div>
{% endif %}
</div>
</div>
{% endif %}
{% if project_create_view %}
<a href="{% url project_create_view %}">
<button class="ui huge yellow corner circular icon button">
<i class="ui plus icon"></i>
</button>
</a>
{% endif %}
Loading

0 comments on commit 8eda7d1

Please sign in to comment.