Skip to content

Commit

Permalink
WIP: Side-by-side comparison of components (#1620)
Browse files Browse the repository at this point in the history
* created checkbox and form for submitting components for comparison. created rough start for displaying differences between prime component and rest

* for now just implementing two comparison

* click to read full text after 50 chars

* styling and added Control part

* displaying comparisons for x number of component statements against the prime component. Styling and abstracted out the comparison block into an included template

* check for pid

* removing detail/summary not really necessary

* Condense comparison listings into rows of a single table

Co-authored-by: Greg Elin <Greg Elin>
  • Loading branch information
davidpofo authored Jun 10, 2021
1 parent 0458a41 commit 2a65a01
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 3 deletions.
1 change: 1 addition & 0 deletions controls/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@

# Component Library
url(r'^components$', views.component_library, name="component_library"),
url(r'^components/compare$', views.compare_components, name="compare_components"),
url(r'^components/new$', views.new_element, name="new_element"),
url(r'^components/(?P<element_id>.*)/_copy$', views.component_library_component_copy, name="component_library_component_copy"),
url(r'^components/(?P<element_id>.*)$', views.component_library_component, name="component_library_component"),
Expand Down
56 changes: 56 additions & 0 deletions controls/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,62 @@ def component_library(request):

return render(request, "components/component_library.html", context)

def diff_components_prettyHtml(smt1, smt2):
"""Generate a diff of two statements of type `control_implementation`"""
dmp = dmp_module.diff_match_patch()
val1 = ""
val2 = ""
if hasattr(smt1, 'body'):
val1 = smt1.body
if hasattr(smt2, 'body'):
val2 = smt2.body

diff = dmp.diff_main(val1, val2)
if len(diff) == 1:
return "Statement is identical."
return dmp.diff_prettyHtml(diff)

def compare_components(request):
"""
Compare submitted components
"""
# TODO: need to figure out how to accumulate all checked boxes not one in pageobj
compare_list = request.POST.getlist('componentcomparecheckbox')
if compare_list:
element_list = list(Element.objects.filter(pk__in=compare_list).exclude(element_type='system').distinct())
compare_prime, element_list = element_list[0], element_list[1:]# The first component selected will be compared against the rest
compare_prime_smts = compare_prime.statements(StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value)
elif len(compare_list) <= 1:
# add messages
messages.add_message(request, messages.WARNING, f"Not enough components were selected to compare!")
return HttpResponseRedirect("/controls/components")
difference_tuples = []
differences = []
for component in element_list:
differences = []
# compare each component's statements to prime
cmt_smts = component.statements(StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value)
if cmt_smts.exists():
# TODO: Need to create a tuple with smt id to return appropriate
for smt in cmt_smts:
smt_prime = compare_prime_smts.filter(sid=smt.sid).filter(pid=smt.pid).filter(sid_class=smt.sid_class).first()
# If it is not a statement found in both components then we just add styling
if smt_prime:
diff = diff_components_prettyHtml(smt_prime, smt)
else:
diff = f"<span><ins style='background:#e6ffe6;'>{smt.body}</ins><span>"
differences.append(diff)
difference_tuples.extend(zip([component.name] * len(cmt_smts), cmt_smts, differences))
if request.method == 'POST':
context = {
"element_list": element_list,
"compare_prime": compare_prime,
"prime_smts": compare_prime_smts,
"secondary_smts": cmt_smts,
"differences": difference_tuples
}
return render(request, "components/compare_components.html", context)

@login_required
def import_records(request):
"""Display the records of component imports"""
Expand Down
25 changes: 25 additions & 0 deletions templates/components/compare_block.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div class="col-xs-12 col-sm-12 col-lg-12 ">
<h2 class="sub-header">Compared components</h2>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead class="thead-dark">
<tr>
<th scope="col">Component</th>
<th scope="col">Control</th>
<th scope="col">Catalog</th>
<th scope="col">Statement</th>
</tr>
</thead>
<tbody>
{% for cmp_name, smt, diff in comp_differences %}
<tr>
<td scope="row" style="width: 110px;"><b>{{ cmp_name }}</b></td>
<td style="width: 90px;">{{ smt.sid }}{% if smt.pid %}.{{ smt.pid }}{% endif %}</td>
<td style="width: 190px;">{{ smt.sid_class }}</td>
<td><a href="#diff_{{ forloop.counter }}" class="" data-toggle="collapse" style="text-decoration: none;font-weight: normal; font-size: 9pt; color: black;">{{ diff|safe }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
72 changes: 72 additions & 0 deletions templates/components/compare_components.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{% extends "base.html" %}
{% load humanize %}
{% load static %}

{% block title %}
{{ portfolio.title }} Components Selected
{% endblock %}

{% block head %}
{% include "controls/_style-controls.html" %}
<style>
.float-container {
border: 3px solid #fff;
padding: 20px;
}

.float-child {
width: 100%;
float: left;
padding: 20px;
border: 2px solid slategray;
}

</style>

{% endblock %}

{% block contextbar %}{% endblock %}

{% block body %}

<div class="float-container">

<div class="float-child">
<h1>Component comparison</h1>
<div class="col-xs-12 col-sm-12 col-lg-12">
<h2 class="sub-header">{{ compare_prime.name }}</h2>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead class="thead-dark">
<tr>
<th scope="col">Component</th>
<th scope="col">Control</th>
<th scope="col">Catalog</th>
<th scope="col">Statement</th>
</tr>
</thead>
<tbody>
{% for prime_smt in prime_smts %}
<tr>
<td scope="row" style="width: 110px;"><b>{{ compare_prime.name }}</b></td>
<td scope="row" style="width: 90px;">{{ prime_smt.sid }}{% if prime_smt.pid %}.{{ prime_smt.pid }}{% endif %}</th>
<td style="width: 190px;">{{ prime_smt.sid_class }}</td>
<td>{{ prime_smt.body|safe }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>

<div class="float-child">
{% with comp_differences=differences %}
{% include "components/compare_block.html" %}
{% endwith %}
</div>

</div>

{% endblock %}

23 changes: 20 additions & 3 deletions templates/components/component_library.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,38 @@ <h2 class="">Component Library</h2>
{% else %}
<p>You do not have access to any components.</p>
{% endif %}

<form action="{% url 'compare_components' %}" name="compare_form" method="POST">
{% csrf_token %}
<div id="" class="row">
<button id="compare-components" href="{% url 'compare_components' %}" class="pull-left btn btn-info">Compare Components</button>
<div id="" class="col-xs-12"><span>{% include 'components/paginate_comp.html' %}</span></div>
</div>

<div id="tab-content" class="row rows-header">
<div id="" class="col-xs-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">Component</div>
<div id="" class="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6">Description</div>
<div id="" class="col-xs-5 col-sm-5 col-md-5 col-lg-5 col-xl-5">Description</div>
<div id="" class="col-xs-1 col-sm-1 col-md-1 col-lg-1 col-xl-1">Compare</div>
<div id="" class="col-xs-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><span class="pull-right">Statements</span></div>
</div>
{% for component in page_obj %}
<div id="tab-content" class="row row-control" style="">
<div id="" class="col-xs-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<div><a href={% url 'component_library_component' element_id=component.id %}>{{ component.name }}</a></div>
</div>
<div id="" class="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6">
<div id="" class="col-xs-5 col-sm-5 col-md-5 col-lg-5 col-xl-5">
{% if component.description %}{{ component.description }}{% else %}<span class="not-provided">No description provided.</span>{% endif %}
<div>{% for tag in component.tags.all %}<span class="component-tag"><a href="{% url 'component_library' %}?search={{ tag.label }}">{{ tag.label }}</a></span> {% endfor %}</div>
</div>
<div id="" class="col-xs-1 col-sm-1 col-md-1 col-lg-1 col-xl-1">
<label for="comp_comparison_{{ forloop.counter }}"></label><input type="checkbox" id="comp_comparison_{{ forloop.counter }}" name="componentcomparecheckbox" value="{{ component.id }}">
</div>
<div id="" class="pull-right col-xs-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
<span class="pull-right">{% if component.get_control_impl_smts_prototype_count > 0 %}{{ component.get_control_impl_smts_prototype_count }} control{{ component.get_control_impl_smts_prototype_count|pluralize }}</span>{% else %}
<span class="not-provided">No statements</span>{% endif %}
</div>
</div>
{% endfor %}
</form>

{% include "components/import-component-modal.html" %}
</div>
Expand All @@ -87,4 +94,14 @@ <h2 class="">Component Library</h2>
{% endblock %}

{% block scripts %}
<script>

$(document).ready(function() {

var $submit = $("#compare-components").hide(),
$cbs = $('input[name="componentcomparecheckbox"]').click(function() {
$submit.toggle( $cbs.is(":checked") );
});
});
</script>
{% endblock %}

0 comments on commit 2a65a01

Please sign in to comment.