Skip to content

Commit 6bd93d0

Browse files
committed
SPARQL/SHACL summary reports services
1 parent a7ad704 commit 6bd93d0

11 files changed

Lines changed: 839 additions & 46 deletions

ted_sws/core/model/validate.py

Whitespace-only changes.

ted_sws/core/model/validation_report.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from pathlib import Path
2-
from typing import Optional
2+
from typing import Optional, List
33

44
from ted_sws.core.model import PropertyBaseModel
5+
from ted_sws.core.model.manifestation import SPARQLQueryResult, ValidationManifestation, Manifestation, SPARQLQuery
56
from ted_sws.core.model.notice import Notice
7+
from ted_sws.core.model.validation_report_data import ReportPackageNoticeData, ReportNoticeData
68

79

810
class ReportNoticeMetadata(PropertyBaseModel):
@@ -17,3 +19,66 @@ class ReportNotice(PropertyBaseModel):
1719
metadata: Optional[ReportNoticeMetadata] = ReportNoticeMetadata()
1820

1921

22+
class QueryValidationSummaryCountReportStatus(PropertyBaseModel):
23+
count: Optional[int] = 0
24+
notices: Optional[List[ReportPackageNoticeData]] = []
25+
26+
27+
class SPARQLValidationSummaryCountReport(PropertyBaseModel):
28+
valid: Optional[QueryValidationSummaryCountReportStatus] = QueryValidationSummaryCountReportStatus()
29+
unverifiable: Optional[QueryValidationSummaryCountReportStatus] = QueryValidationSummaryCountReportStatus()
30+
warning: Optional[QueryValidationSummaryCountReportStatus] = QueryValidationSummaryCountReportStatus()
31+
invalid: Optional[QueryValidationSummaryCountReportStatus] = QueryValidationSummaryCountReportStatus()
32+
error: Optional[QueryValidationSummaryCountReportStatus] = QueryValidationSummaryCountReportStatus()
33+
34+
35+
class SPARQLSummaryQuery(PropertyBaseModel):
36+
"""
37+
Stores SPARQL query details
38+
"""
39+
title: Optional[str]
40+
query: str
41+
42+
43+
class SPARQLValidationSummaryQueryResult(PropertyBaseModel):
44+
"""
45+
46+
"""
47+
query: SPARQLSummaryQuery
48+
identifier: Optional[str]
49+
aggregate: Optional[SPARQLValidationSummaryCountReport] = SPARQLValidationSummaryCountReport()
50+
test_suite_identifier: Optional[str]
51+
52+
53+
class SPARQLValidationSummaryReport(ValidationManifestation):
54+
notices: Optional[List[ReportNoticeData]] = []
55+
mapping_suite_ids: Optional[List[str]] = []
56+
test_suite_ids: Optional[List[str]] = []
57+
validation_results: Optional[List[SPARQLValidationSummaryQueryResult]] = []
58+
59+
60+
class SHACLSummaryQuery(PropertyBaseModel):
61+
"""
62+
Stores SPARQL query details
63+
"""
64+
result_path: Optional[str]
65+
66+
67+
class SHACLValidationSummarySeverityCountResult(PropertyBaseModel):
68+
info: Optional[QueryValidationSummaryCountReportStatus] = QueryValidationSummaryCountReportStatus()
69+
warning: Optional[QueryValidationSummaryCountReportStatus] = QueryValidationSummaryCountReportStatus()
70+
violation: Optional[QueryValidationSummaryCountReportStatus] = QueryValidationSummaryCountReportStatus()
71+
72+
73+
class SHACLValidationSummaryResult(PropertyBaseModel):
74+
query: Optional[SHACLSummaryQuery] = SHACLSummaryQuery()
75+
result_severity: Optional[SHACLValidationSummarySeverityCountResult] = SHACLValidationSummarySeverityCountResult()
76+
conforms: Optional[QueryValidationSummaryCountReportStatus] = QueryValidationSummaryCountReportStatus()
77+
test_suite_identifier: Optional[str]
78+
79+
80+
class SHACLValidationSummaryReport(ValidationManifestation):
81+
notices: Optional[List[ReportNoticeData]] = []
82+
mapping_suite_ids: Optional[List[str]] = []
83+
test_suite_ids: Optional[List[str]] = []
84+
validation_results: Optional[List[SHACLValidationSummaryResult]] = []

ted_sws/core/model/validation_report_data.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,11 @@ class ReportNoticeData(PropertyBaseModel):
1010
"""
1111
notice_id: str
1212
path: Optional[str]
13+
14+
15+
class ReportPackageNoticeData(ReportNoticeData):
16+
"""
17+
Used for storing
18+
"""
19+
mapping_suite_versioned_id: Optional[str]
20+
mapping_suite_identifier: Optional[str]

ted_sws/notice_validator/resources/templates/_ms_meta_title_pre.jinja2

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
{% set notice_in_title = notices[0].notice_id if notices|length == 1 else "" %}
77
{% endif %}
88

9-
{% set ms_parts = mapping_suite_identifier.split("_") %}
10-
{% set ms_acronym = ms_parts[1] if ms_parts and ms_parts|length >= 2 else ms_parts[0] %}
9+
{% set ms_acronym = "" %}
10+
{% if mapping_suite_ids and mapping_suite_ids|length == 1 %}
11+
{% set mapping_suite_identifier = mapping_suite_ids[0] %}
12+
{% endif %}
13+
{% if mapping_suite_identifier %}
14+
{% set ms_parts = mapping_suite_identifier.split("_") %}
15+
{% set ms_acronym = ms_parts[1] if ms_parts and ms_parts|length >= 2 else ms_parts[0] %}
16+
{% endif %}
1117
{% set report_meta_title = ms_acronym + ("/" + notice_in_title if notice_in_title else "") %}
1218
{% if report_meta_title %}{{ report_meta_title }} {% endif %}
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
{% set result_values = ['info', 'warning', 'violation'] %}
2+
3+
<!DOCTYPE html>
4+
<html lang="en">
5+
<head>
6+
<meta charset="UTF-8">
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
9+
<title>{% include '_ms_meta_title_pre.jinja2' %}SHACL Validation Summary Report</title>
10+
<link href="https://cdn.datatables.net/1.10.22/css/jquery.dataTables.min.css" rel="stylesheet" type="text/css">
11+
<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
12+
<style type="text/css">
13+
body {
14+
font-family: Arial, sans-serif;
15+
margin: 25px auto;
16+
width: 80%;
17+
}
18+
table th, table td {
19+
vertical-align: top;
20+
}
21+
.center {
22+
text-align: center;
23+
}
24+
table thead th, table tr th {
25+
text-align: left;
26+
}
27+
.success {
28+
color: #3c763d;
29+
}
30+
.info {
31+
color: #31708f;
32+
}
33+
.warning {
34+
color: #8a6d3b;
35+
}
36+
.violation, .error {
37+
color: #a94442;
38+
}
39+
.strong {
40+
font-weight: bold;
41+
}
42+
.dialog h4 {
43+
margin: 0 0 10px;
44+
}
45+
.dialog ul, .dialog ol {
46+
margin-top: 0;
47+
}
48+
.dialog {
49+
display: none;
50+
}
51+
.dialog li {
52+
margin-bottom: 8px;
53+
}
54+
55+
[data-role=collapsible].collapsible-wrapper {
56+
padding: 10px 0;
57+
}
58+
.nowrap, table.report-details>tbody>tr>th {
59+
white-space: nowrap;
60+
}
61+
table.report-details>tbody>tr>td {
62+
width: 100%;
63+
}
64+
table.report-details>thead>tr{
65+
height: 0;
66+
}
67+
[data-role=collapsible] h4 {
68+
margin: 0;
69+
}
70+
[data-role=collapsible] h4 a {
71+
padding: 3px 10px;
72+
background: #ccc;
73+
width: 100%;
74+
box-sizing: border-box;
75+
display: block;
76+
color: #111;
77+
text-decoration: none;
78+
margin: -3px 0;
79+
}
80+
81+
[data-role=collapsible][data-state=collapsed] h4 a:before {
82+
content: '+ expand';
83+
}
84+
[data-role=collapsible] h4 a:before {
85+
content: '- collapse';
86+
}
87+
[data-role=collapsible][data-state=collapsed] .ui-collapsible-content {
88+
visibility: hidden;
89+
height: 0;
90+
padding: 0 10px;;
91+
}
92+
93+
[data-role=collapsible] .ui-collapsible-content {
94+
padding: 10px;
95+
}
96+
</style>
97+
</head>
98+
<h1>SPARQL Validation Summary Report</h1>
99+
<hr>
100+
<h2>Report details: </h2>
101+
<table class="dataTable report-details">
102+
<tbody>
103+
<tr>
104+
<th>Date created</th>
105+
<td>{{ created }}</td>
106+
</tr>
107+
{% if mapping_suite_ids %}
108+
<tr>
109+
<th>Mapping Suites</th>
110+
<td>{{ mapping_suite_ids|join("<br>") }}</td>
111+
</tr>
112+
{% endif %}
113+
{% if test_suite_ids %}
114+
<tr>
115+
<th>Test Suites</th>
116+
<td>{{ test_suite_ids|join("<br>") }}</td>
117+
</tr>
118+
{% endif %}
119+
{% if notices %}
120+
<tr>
121+
{% set notice_ids = notices|map(attribute='notice_id')|unique|list %}
122+
{% set nr_notices = notice_ids|length %}
123+
{% if nr_notices > 1 %}
124+
<th>Notice identifiers ({{ nr_notices }})</th>
125+
<td><div data-role="collapsible" data-state="collapsed"
126+
class="collapsible-wrapper">
127+
<h4><a href="#"></a></h4>
128+
<div class="ui-collapsible-content">
129+
{% for notice_id in notice_ids %}
130+
{{ notice_id }}<br>
131+
{% endfor %}
132+
</div>
133+
</div></td>
134+
{% elif nr_notices == 1 %}
135+
<th>Notice identifier</th>
136+
<td>{{ notice_ids[0] }}</td>
137+
{% endif %}
138+
</tr>
139+
{% endif %}
140+
</tbody>
141+
</table>
142+
143+
<hr>
144+
145+
<h2>Results</h2>
146+
<table class="display" data-order='[[0, "asc"]]'>
147+
<thead class="center aligned">
148+
<tr>
149+
<th>Test Suite</th>
150+
<th class="center">Conforms</th>
151+
<th>Result Path</th>
152+
{% for result_value in result_values %}
153+
<th class="center"><span class="strong {{ result_value }}">{{ result_value }}</span></th>
154+
{% endfor %}
155+
</tr>
156+
</thead>
157+
<tbody>
158+
{% for result in validation_results %}
159+
<tr>
160+
<td>{{ result.test_suite_identifier }}</td>
161+
<td class="center">
162+
<div><span class="strong{% if result.conforms.count > 0 %} success{% endif %}">{{ result.conforms.count }}</span></div>
163+
{% if result.conforms.count > 0 %}
164+
<br>
165+
<button class="open-dialog">Details</button>
166+
<div class="dialog" title="{{ result.test_suite_identifier }}">
167+
{% if result.conforms.notices|length > 0 %}
168+
<h4>Notices:</h4>
169+
<ol>
170+
{% for notice in result.conforms.notices %}
171+
<li>
172+
<a href="file://{{ template_metadata.output_path }}/mappings/{{ notice.mapping_suite_identifier }}/output/{{ notice.path }}/test_suite_report" target="_blank">{{ notice.notice_id }}</a>
173+
in <a href="file://{{ template_metadata.output_path }}/mappings/{{ notice.mapping_suite_identifier }}/output" target="_blank"><b>{{ notice.mapping_suite_identifier }}</b></a>
174+
at <a href="file://{{ template_metadata.output_path }}/mappings/{{ notice.mapping_suite_identifier }}/output/{{ notice.path }}" target="_blank"><b>{{ notice.path }}</b></a>
175+
</li>
176+
{% endfor %}
177+
</ol>
178+
{% endif %}
179+
</div>
180+
{% endif %}
181+
</td>
182+
<td>{{ result.query.result_path }}</td>
183+
{% for result_value in result_values %}
184+
<td class="center">
185+
{% set _value = result.result_severity[result_value] %}
186+
<div><span class="strong{% if _value.count > 0 %} {{ result_value }}{% endif %}">{{ _value.count }}</span></div>
187+
{% if _value.count > 0 %}
188+
<br>
189+
<button class="open-dialog">Details</button>
190+
<div class="dialog" title="[{{ result_value|upper }}]">
191+
{% if _value.notices|length > 0 %}
192+
<h4>Notices:</h4>
193+
<ol>
194+
{% for notice in _value.notices %}
195+
<li>
196+
<a href="file://{{ template_metadata.output_path }}/mappings/{{ notice.mapping_suite_identifier }}/output/{{ notice.path }}/test_suite_report" target="_blank">{{ notice.notice_id }}</a>
197+
in <a href="file://{{ template_metadata.output_path }}/mappings/{{ notice.mapping_suite_identifier }}/output" target="_blank"><b>{{ notice.mapping_suite_identifier }}</b></a>
198+
at <a href="file://{{ template_metadata.output_path }}/mappings/{{ notice.mapping_suite_identifier }}/output/{{ notice.path }}" target="_blank"><b>{{ notice.path }}</b></a>
199+
</li>
200+
{% endfor %}
201+
</ol>
202+
{% endif %}
203+
</div>
204+
{% endif %}
205+
</td>
206+
{% endfor %}
207+
</tr>
208+
{% endfor %}
209+
</tbody>
210+
</table>
211+
<hr>
212+
</body>
213+
</html>
214+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
215+
<script type="text/javascript" src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
216+
<script type="text/javascript" src="https://cdn.datatables.net/1.10.22/js/jquery.dataTables.min.js"></script>
217+
<script type="text/javascript" src="https://cdn.datatables.net/buttons/1.6.4/js/dataTables.buttons.min.js"></script>
218+
<script type="text/javascript" src="https://cdn.datatables.net/buttons/1.6.4/js/buttons.print.min.js"></script>
219+
<script type="text/javascript">
220+
$(document).ready(function () {
221+
$("[data-role=collapsible] h4 a").on("click", function(e) {
222+
$p = $(this).closest("[data-role=collapsible]");
223+
$p.attr("data-state", $p.attr("data-state") == "collapsed" ? "expanded" : "collapsed");
224+
$c = $p.find('.ui-collapsible-content');
225+
$c.attr("data-state", $c.attr("data-state") == "collapsed" ? "expanded" : "collapsed");
226+
return false;
227+
});
228+
$("table.display").DataTable({
229+
dom: 'B<"clear">lfiprtip',
230+
buttons: [],
231+
"lengthMenu": [[5, 15, 30, -1], [5, 15, 30, "All"]],
232+
"pageLength": 15,
233+
responsive: {
234+
details: true
235+
}
236+
});
237+
238+
});
239+
240+
$(document).on('click', '.open-dialog', function () {
241+
$dialog = $(this).next('.dialog');
242+
$dialog.dialog({
243+
width: 800,
244+
modal: true,
245+
buttons: {
246+
"Close": function(event, ui) {
247+
$dialog.dialog('destroy');
248+
}
249+
},
250+
close: function(event, ui) {
251+
$dialog.dialog('destroy');
252+
}
253+
})
254+
$dialog.dialog('open');
255+
return false;
256+
});
257+
258+
</script>

0 commit comments

Comments
 (0)