Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Side-by-side comparison of components #1620

Merged
merged 8 commits into from
Jun 10, 2021
Merged
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 %}