Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

Commit fdebc9a

Browse files
authored
refactor: improve tooltips in HTML report (#431)
* refactor: introduce `icon` html component * refactor: introduce `report_details_tooltip` html component * refactor: improve complexity tooltips * refactor: introduce issue details tooltip
1 parent 7e0fee6 commit fdebc9a

File tree

10 files changed

+288
-132
lines changed

10 files changed

+288
-132
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'package:html/dom.dart';
2+
3+
enum IconType {
4+
complexity,
5+
issue,
6+
}
7+
8+
Element renderIcon(IconType type) {
9+
switch (type) {
10+
case IconType.complexity:
11+
return _renderComplexityIcon();
12+
case IconType.issue:
13+
return _renderIssueIcon();
14+
}
15+
}
16+
17+
Element _renderComplexityIcon() => Element.tag('svg')
18+
..attributes['xmlns'] = 'http://www.w3.org/2000/svg'
19+
..attributes['viewBox'] = '0 0 32 32'
20+
..append(
21+
Element.tag('path')
22+
..attributes['d'] =
23+
'M16 3C8.832 3 3 8.832 3 16s5.832 13 13 13 13-5.832 13-13S23.168 3 16 3zm0 2c6.086 0 11 4.914 11 11s-4.914 11-11 11S5 22.086 5 16 9.914 5 16 5zm-1 5v2h2v-2zm0 4v8h2v-8z',
24+
);
25+
26+
Element _renderIssueIcon() => Element.tag('svg')
27+
..attributes['xmlns'] = 'http://www.w3.org/2000/svg'
28+
..attributes['viewBox'] = '0 0 24 24'
29+
..append(
30+
Element.tag('path')
31+
..attributes['d'] =
32+
'M12 1.016c-.393 0-.786.143-1.072.43l-9.483 9.482a1.517 1.517 0 000 2.144l9.483 9.485c.286.286.667.443 1.072.443s.785-.157 1.072-.443l9.485-9.485a1.517 1.517 0 000-2.144l-9.485-9.483A1.513 1.513 0 0012 1.015zm0 2.183L20.8 12 12 20.8 3.2 12 12 3.2zM11 7v6h2V7h-2zm0 8v2h2v-2h-2z',
33+
);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'package:html/dom.dart';
2+
3+
import '../../../../../../utils/string_extension.dart';
4+
import '../../../../models/issue.dart';
5+
import '../../../../models/severity.dart';
6+
7+
Element renderIssueDetailsTooltip(Issue issue) {
8+
final title = issue.severity != Severity.none
9+
? '${issue.severity.toString().capitalize()}: ${issue.ruleId}'
10+
: issue.ruleId;
11+
12+
final tooltip = Element.tag('div')
13+
..classes.add('metrics-source-code__tooltip')
14+
..append(Element.tag('div')
15+
..classes.add('metrics-source-code__tooltip-title')
16+
..text = title)
17+
..append(Element.tag('p')
18+
..classes.add('metrics-source-code__tooltip-section')
19+
..text = issue.message);
20+
21+
if (issue.verboseMessage != null) {
22+
tooltip.append(Element.tag('p')
23+
..classes.add('metrics-source-code__tooltip-section')
24+
..text = issue.verboseMessage);
25+
}
26+
27+
tooltip.append(Element.tag('p')
28+
..classes.add('metrics-source-code__tooltip-section')
29+
..append(Element.tag('a')
30+
..classes.add('metrics-source-code__tooltip-link')
31+
..attributes['href'] = issue.documentation.toString()
32+
..attributes['target'] = '_blank'
33+
..attributes['rel'] = 'noopener noreferrer'
34+
..attributes['title'] = 'Open documentation'
35+
..text = 'Open documentation'));
36+
37+
return tooltip;
38+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'package:html/dom.dart';
2+
3+
import '../../../../metrics/metric_utils.dart';
4+
import '../../../../metrics/models/metric_value.dart';
5+
import '../../../../models/report.dart';
6+
7+
Element renderDetailsTooltip(Report entityReport, String entityType) {
8+
final tooltip = Element.tag('div')
9+
..classes.add('metrics-source-code__tooltip')
10+
..append(Element.tag('div')
11+
..classes.add('metrics-source-code__tooltip-title')
12+
..text = '$entityType stats:');
13+
14+
final metrics = entityReport.metrics.toList()
15+
..sort((a, b) => a.documentation.name.compareTo(b.documentation.name));
16+
17+
for (final metric in metrics) {
18+
tooltip.append(Element.tag('p')
19+
..classes.add('metrics-source-code__tooltip-text')
20+
..append(renderDetailsTooltipMetric(metric)));
21+
}
22+
23+
return tooltip;
24+
}
25+
26+
Element renderDetailsTooltipMetric(MetricValue<num> metric) {
27+
final metricName = metric.documentation.name.toLowerCase();
28+
final violationLevel = metric.level.toString();
29+
30+
return Element.tag('div')
31+
..classes.add('metrics-source-code__tooltip-section')
32+
..append(Element.tag('p')
33+
..classes.add('metrics-source-code__tooltip-text')
34+
..append(Element.tag('a')
35+
..classes.add('metrics-source-code__tooltip-link')
36+
..attributes['href'] = documentation(metric.metricsId).toString()
37+
..attributes['target'] = '_blank'
38+
..attributes['rel'] = 'noopener noreferrer'
39+
..attributes['title'] = metricName
40+
..text = '$metricName:&nbsp;')
41+
..append(Element.tag('span')..text = metric.value.round().toString()))
42+
..append(Element.tag('p')
43+
..classes.add('metrics-source-code__tooltip-text')
44+
..append(Element.tag('span')
45+
..classes.add('metrics-source-code__tooltip-label')
46+
..text = '$metricName violation level:&nbsp;')
47+
..append(Element.tag('span')
48+
..classes.addAll([
49+
'metrics-source-code__tooltip-level',
50+
'metrics-source-code__tooltip-level--$violationLevel',
51+
])
52+
..text = violationLevel));
53+
}

lib/src/analyzers/lint_analyzer/reporters/reporters_list/html/lint_html_reporter.dart

Lines changed: 26 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import '../../../metrics/metrics_list/cyclomatic_complexity/cyclomatic_complexit
1111
import '../../../metrics/models/metric_value_level.dart';
1212
import '../../../models/lint_file_report.dart';
1313
import '../../utility_selector.dart';
14+
import 'components/icon.dart';
15+
import 'components/issue_details_tooltip.dart';
1416
import 'models/file_metrics_report.dart';
1517
import 'utility_functions.dart';
1618

@@ -331,42 +333,35 @@ class LintHtmlReporter extends HtmlReporter<LintFileReport> {
331333
final cyclomaticValues = Element.tag('td')
332334
..classes.add('metrics-source-code__complexity');
333335
for (var i = 1; i <= sourceFileLines.length; ++i) {
334-
final functionReport = record.functions.values.firstWhereOrNull(
335-
(functionReport) =>
336-
functionReport.location.start.line <= i &&
337-
functionReport.location.end.line >= i,
338-
);
339-
340336
final complexityValueElement = Element.tag('div')
341337
..classes.add('metrics-source-code__text');
342338

339+
final classReport = record.classes.entries.firstWhereOrNull((report) =>
340+
report.value.location.start.line <= i &&
341+
report.value.location.end.line >= i);
342+
if (classReport != null && classReport.value.location.start.line == i) {
343+
complexityValueElement
344+
..classes.add('metrics-source-code__text--with-icon')
345+
..append(renderComplexityIcon(classReport.value, classReport.key));
346+
}
347+
348+
final functionReport = record.functions.entries.firstWhereOrNull(
349+
(report) =>
350+
report.value.location.start.line <= i &&
351+
report.value.location.end.line >= i,
352+
);
353+
343354
var line = ' ';
344355
if (functionReport != null) {
345-
if (functionReport.location.start.line == i) {
346-
final complexityTooltip =
347-
renderFunctionDetailsTooltip(functionReport);
348-
349-
final complexityIcon = Element.tag('div')
350-
..classes.addAll([
351-
'metrics-source-code__icon',
352-
'metrics-source-code__icon--complexity',
353-
])
354-
..append(Element.tag('svg')
355-
..attributes['xmlns'] = 'http://www.w3.org/2000/svg'
356-
..attributes['viewBox'] = '0 0 32 32'
357-
..append(Element.tag('path')
358-
..attributes['d'] =
359-
'M16 3C8.832 3 3 8.832 3 16s5.832 13 13 13 13-5.832 13-13S23.168 3 16 3zm0 2c6.086 0 11 4.914 11 11s-4.914 11-11 11S5 22.086 5 16 9.914 5 16 5zm-1 5v2h2v-2zm0 4v8h2v-8z'))
360-
..append(complexityTooltip);
361-
356+
if (functionReport.value.location.start.line == i) {
362357
complexityValueElement
363-
..attributes['class'] =
364-
'${complexityValueElement.attributes['class']} metrics-source-code__text--with-icon'
365-
.trim()
366-
..append(complexityIcon);
358+
..classes.add('metrics-source-code__text--with-icon')
359+
..append(
360+
renderComplexityIcon(functionReport.value, functionReport.key),
361+
);
367362
}
368363

369-
final lineWithComplexityIncrement = functionReport
364+
final lineWithComplexityIncrement = functionReport.value
370365
.metric(CyclomaticComplexityMetric.metricId)
371366
?.context
372367
.where((element) => element.location.start.line == i)
@@ -385,7 +380,7 @@ class LintHtmlReporter extends HtmlReporter<LintFileReport> {
385380
}
386381
*/
387382

388-
final functionViolationLevel = functionReport.metricsLevel;
383+
final functionViolationLevel = functionReport.value.metricsLevel;
389384

390385
final lineViolationStyle = lineWithComplexityIncrement > 0
391386
? _violationLevelLineStyle[functionViolationLevel]
@@ -398,36 +393,12 @@ class LintHtmlReporter extends HtmlReporter<LintFileReport> {
398393
.firstWhereOrNull((element) => element.location.start.line == i);
399394

400395
if (architecturalIssues != null) {
401-
final issueTooltip = Element.tag('div')
402-
..classes.add('metrics-source-code__tooltip')
403-
..append(Element.tag('div')
404-
..classes.add('metrics-source-code__tooltip-title')
405-
..text = architecturalIssues.ruleId)
406-
..append(Element.tag('p')
407-
..classes.add('metrics-source-code__tooltip-section')
408-
..text = architecturalIssues.message)
409-
..append(Element.tag('p')
410-
..classes.add('metrics-source-code__tooltip-section')
411-
..text = architecturalIssues.verboseMessage)
412-
..append(Element.tag('a')
413-
..classes.add('metrics-source-code__tooltip-link')
414-
..attributes['href'] = architecturalIssues.documentation.toString()
415-
..attributes['target'] = '_blank'
416-
..attributes['rel'] = 'noopener noreferrer'
417-
..attributes['title'] = 'Open documentation'
418-
..text = 'Open documentation');
419-
420396
final issueIcon = Element.tag('div')
421397
..classes.addAll(
422398
['metrics-source-code__icon', 'metrics-source-code__icon--issue'],
423399
)
424-
..append(Element.tag('svg')
425-
..attributes['xmlns'] = 'http://www.w3.org/2000/svg'
426-
..attributes['viewBox'] = '0 0 24 24'
427-
..append(Element.tag('path')
428-
..attributes['d'] =
429-
'M12 1.016c-.393 0-.786.143-1.072.43l-9.483 9.482a1.517 1.517 0 000 2.144l9.483 9.485c.286.286.667.443 1.072.443s.785-.157 1.072-.443l9.485-9.485a1.517 1.517 0 000-2.144l-9.485-9.483A1.513 1.513 0 0012 1.015zm0 2.183L20.8 12 12 20.8 3.2 12 12 3.2zM11 7v6h2V7h-2zm0 8v2h2v-2h-2z'))
430-
..append(issueTooltip);
400+
..append(renderIcon(IconType.issue))
401+
..append(renderIssueDetailsTooltip(architecturalIssues));
431402

432403
complexityValueElement.append(issueIcon);
433404
}

lib/src/analyzers/lint_analyzer/reporters/reporters_list/html/utility_functions.dart

Lines changed: 11 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
import 'package:html/dom.dart';
22

3-
import '../../../metrics/models/metric_value.dart';
43
import '../../../models/report.dart';
4+
import 'components/icon.dart';
5+
import 'components/report_details_tooltip.dart';
56
import 'lint_html_reporter.dart';
67

78
const _violations = 'violations';
89

10+
Element renderComplexityIcon(Report entityReport, String entityType) =>
11+
Element.tag('div')
12+
..classes.addAll([
13+
'metrics-source-code__icon',
14+
'metrics-source-code__icon--complexity',
15+
])
16+
..append(renderIcon(IconType.complexity))
17+
..append(renderDetailsTooltip(entityReport, entityType));
18+
919
Element renderSummaryMetric(
1020
String name,
1121
int metricValue, {
@@ -29,50 +39,6 @@ Element renderSummaryMetric(
2939
..text = value);
3040
}
3141

32-
Element renderFunctionDetailsTooltip(Report report) {
33-
final tooltip = Element.tag('div')
34-
..classes.add('metrics-source-code__tooltip')
35-
..append(Element.tag('div')
36-
..classes.add('metrics-source-code__tooltip-title')
37-
..text = 'Function stats:');
38-
39-
final metrics = report.metrics.toList()
40-
..sort((a, b) => a.documentation.name.compareTo(b.documentation.name));
41-
42-
for (final metric in metrics) {
43-
tooltip.append(Element.tag('p')
44-
..classes.add('metrics-source-code__tooltip-text')
45-
..append(renderFunctionDetailsTooltipMetric(metric)));
46-
}
47-
48-
return tooltip;
49-
}
50-
51-
Element renderFunctionDetailsTooltipMetric(MetricValue<num> metric) {
52-
final metricName = metric.documentation.name.toLowerCase();
53-
final violationLevel = metric.level.toString();
54-
55-
return Element.tag('div')
56-
..classes.add('metrics-source-code__tooltip-section')
57-
..append(Element.tag('p')
58-
..classes.add('metrics-source-code__tooltip-text')
59-
..append(Element.tag('span')
60-
..classes.add('metrics-source-code__tooltip-label')
61-
..text = '$metricName:&nbsp;')
62-
..append(Element.tag('span')..text = metric.value.round().toString()))
63-
..append(Element.tag('p')
64-
..classes.add('metrics-source-code__tooltip-text')
65-
..append(Element.tag('span')
66-
..classes.add('metrics-source-code__tooltip-label')
67-
..text = '$metricName violation level:&nbsp;')
68-
..append(Element.tag('span')
69-
..classes.addAll([
70-
'metrics-source-code__tooltip-level',
71-
'metrics-source-code__tooltip-level--$violationLevel',
72-
])
73-
..text = violationLevel));
74-
}
75-
7642
Element renderTableRecord(ReportTableRecord record) {
7743
final recordHaveCyclomaticComplexityViolations =
7844
record.report.cyclomaticComplexityViolations > 0;

lib/src/reporters/resources/main.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ pre.prettyprint.prettyprint {
177177
position: absolute; /* containing block: .metrics-source-code__icon */
178178
left: 0;
179179
bottom: var(--dart-code-metrics-source-code-tooltip-bottom);
180-
width: var(--dart-code-metrics-source-code-tooltip-width);
180+
min-width: var(--dart-code-metrics-source-code-tooltip-width);
181181
padding:
182182
calc(var(--dart-code-metrics-source-code-tooltip-padding) / 2)
183183
var(--dart-code-metrics-source-code-tooltip-padding);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@TestOn('vm')
2+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/reporters/reporters_list/html/components/icon.dart';
3+
import 'package:test/test.dart';
4+
5+
void main() {
6+
group('renderIcon', () {
7+
test('returns complexity icon', () {
8+
expect(
9+
renderIcon(IconType.complexity).outerHtml,
10+
equals(
11+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M16 3C8.832 3 3 8.832 3 16s5.832 13 13 13 13-5.832 13-13S23.168 3 16 3zm0 2c6.086 0 11 4.914 11 11s-4.914 11-11 11S5 22.086 5 16 9.914 5 16 5zm-1 5v2h2v-2zm0 4v8h2v-8z"></path></svg>',
12+
),
13+
);
14+
});
15+
16+
test('returns issue icon', () {
17+
expect(
18+
renderIcon(IconType.issue).outerHtml,
19+
equals(
20+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 1.016c-.393 0-.786.143-1.072.43l-9.483 9.482a1.517 1.517 0 000 2.144l9.483 9.485c.286.286.667.443 1.072.443s.785-.157 1.072-.443l9.485-9.485a1.517 1.517 0 000-2.144l-9.485-9.483A1.513 1.513 0 0012 1.015zm0 2.183L20.8 12 12 20.8 3.2 12 12 3.2zM11 7v6h2V7h-2zm0 8v2h2v-2h-2z"></path></svg>',
21+
),
22+
);
23+
});
24+
});
25+
}

0 commit comments

Comments
 (0)