Skip to content
Snippets Groups Projects
Commit 6044fa63 authored by Antoine Lambert's avatar Antoine Lambert
Browse files

browse: Add software origins search interface

Closes T848
parent a75cd1ec
No related branches found
No related tags found
No related merge requests found
......@@ -113,7 +113,7 @@ Origin
.. parsed-literal::
$ curl -i :swh_web_api:`origin/search/python/`?limit=2
$ curl -i :swh_web_api:`origin/search/python/?limit=2`
**Response:**
......
......@@ -4,7 +4,11 @@
# See top-level LICENSE file for more information
import dateutil
import json
from distutils.util import strtobool
from django.http import HttpResponse
from django.shortcuts import render
from django.utils.safestring import mark_safe
from django.template.defaultfilters import filesizeformat
......@@ -618,3 +622,25 @@ def origin_log_browse(request, origin_id, visit_id=None, timestamp=None):
'top_right_link_text': None,
'include_top_navigation': True,
'no_origin_context': False})
@browse_route(r'origin/search/(?P<url_pattern>.+)/',
view_name='browse-origin-search')
def origin_search(request, url_pattern):
"""Search for origins whose urls contain a provided string pattern
or match a provided regular expression.
The search is performed in a case insensitive way.
"""
offset = int(request.GET.get('offset', '0'))
limit = int(request.GET.get('limit', '50'))
regexp = request.GET.get('regexp', 'false')
results = service.search_origin(url_pattern, offset, limit,
bool(strtobool(regexp)))
results = json.dumps(list(results), sort_keys=True, indent=4,
separators=(',', ': '))
return HttpResponse(results, content_type='application/json')
......@@ -446,4 +446,19 @@ fieldset[disabled] .btn-swh.active {
.swh-counter {
font-size: 150%;
}
\ No newline at end of file
}
.swh-loading {
display : none;
}
.swh-loading.show {
display:inline-block;
position: absolute;
background: white;
border: 1px solid black;
top: 50%;
left: 50%;
transform: translate(0, -50%);
margin: -50px 0px 0px -50px;
text-align: center;
}
swh/web/static/img/swh-spinner.gif

61.7 KiB

......@@ -5,52 +5,74 @@
{% block content %}
<div class="panel-group" id="accordion">
{% if top_panel_visible %}
<div class="panel panel-default" style="overflow-x: auto;">
<div class="panel-heading">
{% if top_panel_collapsible %}
<a data-toggle="collapse" data-parent="#accordion" href="#swh-browse-top-collapse" style="outline:none;">
{% endif %}
<div class="panel-title pull-left">
<h4>{{ top_panel_text_left }}</h4>
<ul class="nav nav-tabs">
<li class="active">
<a href="#tab_browse" data-toggle="tab" style="outline:none;">Browse</a>
</li>
<li>
<a href="#tab_search" data-toggle="tab" style="outline:none;">Search</a>
</li>
</ul>
<div class="tab-content" style="margin-top: 5px;">
<div class="tab-pane active" id="tab_browse">
<div class="panel-group" id="accordion">
{% if top_panel_visible %}
<div class="panel panel-default" style="overflow-x: auto;">
<div class="panel-heading">
{% if top_panel_collapsible %}
<a data-toggle="collapse" data-parent="#accordion" href="#swh-browse-top-collapse" style="outline:none;">
{% endif %}
<div class="panel-title pull-left">
<h4>{{ top_panel_text_left }}</h4>
</div>
<div class="panel-title pull-right">
<h4>{{ top_panel_text_right }}</h4>
</div>
<div class="clearfix"></div>
{% if top_panel_collapsible %}
</a>
{% endif %}
</div>
<div class="panel-title pull-right">
<h4>{{ top_panel_text_right }}</h4>
{% if top_panel_collapsible %}
<div id="swh-browse-top-collapse" class="panel-collapse collapse">
{% endif %}
<table class="table">
<tbody>
{% for key, val in swh_object_metadata.items|dictsort:"0.lower" %}
<tr>
<th class="swh-metadata-table-row">{{ key }}</th>
<td class="swh-metadata-table-row">
<pre>{{ val | safe | urlize_links_and_mails | safe }}</pre>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if top_panel_collapsible %}
</div>
<div class="clearfix"></div>
{% if top_panel_collapsible %}
</a>
{% endif %}
{% endif %}
</div>
{% if top_panel_collapsible %}
<div id="swh-browse-top-collapse" class="panel-collapse collapse">
{% endif %}
<table class="table">
<tbody>
{% for key, val in swh_object_metadata.items|dictsort:"0.lower" %}
<tr>
<th class="swh-metadata-table-row">{{ key }}</th>
<td class="swh-metadata-table-row">
<pre>{{ val | safe | urlize_links_and_mails | safe }}</pre>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if top_panel_collapsible %}
{% if main_panel_visible %}
<div class="panel panel-default" style="overflow-x: auto;">
{% block swh-browse-main-panel-content %}{% endblock %}
</div>
{% endif %}
</div>
{% block swh-browse-after-panels %}{% endblock %}
</div>
{% endif %}
{% if main_panel_visible %}
<div class="panel panel-default" style="overflow-x: auto;">
{% block swh-browse-main-panel-content %}{% endblock %}
<div class="tab-pane" id="tab_search">
{% include "includes/origins-search.html" %}
</div>
{% endif %}
</div>
{% block swh-browse-after-panels %}{% endblock %}
{% endblock %}
{% load static %}
<div class="panel panel-default" style="overflow-x: auto;">
<div class="panel-heading">
<h4>Search Software Heritage origins to browse:</h4>
</div>
<div class="panel-body">
<form class="form-horizontal" id="search_origins">
<div class="input-group add-on">
<input class="form-control" placeholder="Enter string pattern(s) to search for in origin urls" type="text" id="origins-url-patterns"/>
<div class="input-group-btn">
<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
</div>
</div>
</form>
<div class="swh-loading">
<img src="{% static 'img/swh-spinner.gif' %}"></img>
<p>Searching origins ...</p>
</div>
<table class="table" id="origin-search-results">
<thead>
<tr>
<th>Origin id</th>
<th>Origin type</th>
<th>Origin url</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
<ul class="pager">
<li class="disabled" id="origins-prev-results-button"><a href="#">Previous</a></li>
<li class="disabled" id="origins-next-results-button"><a href="#">Next</a></li>
</ul>
</div>
</div>
<script>
var origin_patterns;
var per_page = 15;
var offset = 0;
var origins_search_url = "{% url 'browse-origin-search' 'url_regexp' %}?limit=limit_val&offset=offset_val&regexp=true";
var origin_browse_url = "{% url 'browse-origin' '0' %}";
var search_request = null;
function populateOriginSearchResultsTable(data, limit, offset) {
$("#origin-search-results tbody tr").remove();
var table = $("#origin-search-results tbody");
$.each(data, function(idx, elem){
var tableRow = '<tr>';
tableRow += '<td>' + elem.id + '</td>';
tableRow += '<td>' + elem.type + '</td>';
tableRow += '<td><a href="' + elem.url + '">' + elem.url + '</a></td>';
var browse_url = origin_browse_url.replace('0', elem.id);
tableRow += '<td><a href="' + browse_url + '">Browse</a></td>';
tableRow += '</tr>';
table.append(tableRow);
});
if (data.length == per_page) {
$('#origins-next-results-button').removeClass('disabled');
} else {
$('#origins-next-results-button').addClass('disabled');
}
if (offset > 0) {
$('#origins-prev-results-button').removeClass('disabled');
} else {
$('#origins-prev-results-button').addClass('disabled');
}
}
$(document).ready(function() {
if (typeof(Storage) !== "undefined") {
origin_patterns = sessionStorage.getItem("last-swh-origin-url-patterns");
var data = sessionStorage.getItem("last-swh-origin-search-results");
offset = sessionStorage.getItem("last-swh-origin-search-offset");
if (data) {
$("#origins-url-patterns").val(origin_patterns);
offset = parseInt(offset);
populateOriginSearchResultsTable(JSON.parse(data), per_page, offset);
}
}
});
// http://dsernst.com/2014/12/14/heaps-permutation-algorithm-in-javascript/
function swap(array, pos1, pos2) {
var temp = array[pos1];
array[pos1] = array[pos2];
array[pos2] = temp;
}
function heapsPermute(array, output, n) {
n = n || array.length; // set n default to array.length
if (n === 1) {
output(array);
} else {
for (var i = 1; i <= n; i += 1) {
heapsPermute(array, output, n - 1);
if (n % 2) {
var j = 1;
} else {
var j = i;
}
swap(array, j - 1, n - 1); // -1 to account for javascript zero-indexing
}
}
};
function searchOrigins(patterns, limit, offset) {
origin_patterns = patterns;
var patterns_array = patterns.trim().replace(/\s+/g,' ').split(' ');
var patterns_permut = []
heapsPermute(patterns_array, (p) => {patterns_permut.push(p.join('.*'));})
var regex = patterns_permut.join('|');
var search_url = origins_search_url.replace('url_regexp', regex)
.replace('limit_val', limit)
.replace('offset_val', offset);
if (search_request) {
search_request.abort();
}
$(".swh-loading").addClass('show');
search_request = $.ajax({
url: search_url,
dataType: 'json',
error: function() {
search_request = null;
$(".swh-loading").removeClass('show');
},
success: function (data) {
search_request = null;
if (typeof(Storage) !== "undefined") {
sessionStorage.setItem("last-swh-origin-url-patterns", patterns);
sessionStorage.setItem("last-swh-origin-search-results", JSON.stringify(data));
sessionStorage.setItem("last-swh-origin-search-offset", offset);
}
populateOriginSearchResultsTable(data, per_page, offset);
$(".swh-loading").removeClass('show');
}
});
}
$("#search_origins").submit(function (event) {
var patterns = $("#origins-url-patterns").val();
offset = 0;
searchOrigins(patterns, per_page, offset);
event.preventDefault();
});
$("#origins-next-results-button").click(function (event) {
offset += per_page;
searchOrigins(origin_patterns, per_page, offset);
event.preventDefault();
});
$("#origins-prev-results-button").click(function (event) {
offset -= per_page;
searchOrigins(origin_patterns, per_page, offset);
event.preventDefault();
});
</script>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment