Skip to content

Visual update of scores on analysis tab. #1419

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

Merged
merged 3 commits into from
Jul 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions app/lib/frontend/color.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

/// Represents an RGBA color code.
class Color {
static const black = const Color(0, 0, 0);
static const blue = const Color(0, 0, 255);
static const green = const Color(0, 255, 0);
static const peach = const Color(255, 229, 180);
static const red = const Color(255, 0, 0);
static const silver = const Color(192, 192, 192);
static const slateGray = const Color(112, 128, 144);
static const white = const Color(255, 255, 255);

final int r;
final int g;
final int b;
final double a;

const Color(this.r, this.g, this.b, [this.a = 1.0]);

Color change({int r, int g, int b, double a}) =>
new Color(r ?? this.r, g ?? this.g, b ?? this.b, a ?? this.a);

Color interpolateTo(Color to, double value) {
final red = ((to.r - r) * value).round() + r;
final green = ((to.g - g) * value).round() + g;
final blue = ((to.b - b) * value).round() + b;
final alpha = ((to.a - a) * value) + a;
return new Color(red, green, blue, alpha);
}

Color multipleValues(double value) {
return change(
r: (r * value).round(),
g: (g * value).round(),
b: (b * value).round(),
);
}

@override
String toString() => a == 1.0
? 'rgb($r, $g, $b)'
: 'rgba($r, $g, $b, ${a.toStringAsFixed(4)})';
}

/// Represents the selection of colors that will be used to draw a specific shape.
class Brush {
final Color background;
final Color color;
final Color shadow;

const Brush({this.background, this.color, this.shadow});

Brush interpolateTo(Brush to, double value) {
return new Brush(
background: background.interpolateTo(to.background, value),
color: color.interpolateTo(to.color, value),
shadow: shadow.interpolateTo(to.shadow, value),
);
}

Brush change({Color background, Color color, Color shadow}) {
return new Brush(
background: background ?? this.background,
color: color ?? this.color,
shadow: shadow ?? this.shadow);
}
}

/// A semi-transparent black for using as a non-intrusive shadow.
final _blackShadow = Color.black.change(a: 0.5);

/// Color to use when the analysis/score is missing (skipped or not done yet).
final _scoreBoxMissing = new Color(204, 204, 204);

/// Color to use when the analysis result was top of the range (70+).
final _scoreBoxSolid = new Color(1, 117, 194);

/// Color to use when the analysis result was in the middle of the range (40-70)
final _scoreBoxGood = new Color(0, 196, 179);

/// Color to use when the analysis result was in the lower range (0-40)
final _scoreBoxRotten = new Color(187, 36, 0);

/// The default set of colors to use.
final _defaultBrush = new Brush(
background: _scoreBoxMissing, color: Color.white, shadow: _blackShadow);

/// Get the [Brush] that will be used to render the overall score progress bar.
Brush overallScoreBrush(double score) {
if (score == null) {
return _defaultBrush;
}
if (score <= 0.4) {
return _defaultBrush.change(background: _scoreBoxRotten);
}
if (score <= 0.7) {
return _defaultBrush.change(background: _scoreBoxGood);
}
return _defaultBrush.change(background: _scoreBoxSolid);
}

/// Get the [Brush] that will be used to render the generic score progress bars
/// (e.g. popularity or health score).
Brush genericScoreBrush(double score) {
if (score == null) {
return _defaultBrush;
}
final bg = Color.slateGray.interpolateTo(Color.silver, score);
return _defaultBrush.change(background: bg);
}
40 changes: 34 additions & 6 deletions app/lib/frontend/templates.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import '../shared/search_service.dart' show SearchQuery, serializeSearchOrder;
import '../shared/urls.dart' as urls;
import '../shared/utils.dart';

import 'color.dart';
import 'model_properties.dart' show Author;
import 'models.dart';
import 'static_files.dart';
Expand Down Expand Up @@ -276,10 +277,7 @@ class TemplateService {
'has_dev': devDeps.isNotEmpty,
'dev': devDeps,
},
'health': _formatScore(extract?.health),
'maintenance': _formatScore(extract?.maintenance),
'popularity': _formatScore(extract?.popularity),
'score_box_html': _renderScoreBox(analysisStatus, extract?.overallScore),
'score_bars': _renderScoreBars(extract),
};

return _renderTemplate('pkg/analysis_tab', data);
Expand Down Expand Up @@ -420,6 +418,34 @@ class TemplateService {
return values;
}

Map<String, dynamic> _renderScoreBars(AnalysisExtract extract) {
String renderScoreBar(double score, Brush brush) {
return _renderTemplate('pkg/score_bar', {
'percent': _formatScore(score ?? 0.0),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A skipped package will have a 0 score rather than a N/A score?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The percent will be used only to render the width of the progress bar, while the score one line below will be used to render the actual text label. Btw. great catch, because I haven't used the score to do so.

'score': _formatScore(score),
'background': brush.background.toString(),
'color': brush.color.toString(),
'shadow': brush.shadow.toString(),
});
}

final analysisSkipped = _isAnalysisSkipped(extract?.analysisStatus);
final healthScore = extract?.health;
final maintenanceScore = extract?.maintenance;
final popularityScore = extract?.popularity;
final overallScore = analysisSkipped ? null : extract?.overallScore ?? 0.0;
return {
'health_html':
renderScoreBar(healthScore, genericScoreBrush(healthScore)),
'maintenance_html':
renderScoreBar(maintenanceScore, genericScoreBrush(maintenanceScore)),
'popularity_html':
renderScoreBar(popularityScore, genericScoreBrush(popularityScore)),
'overall_html':
renderScoreBar(overallScore, overallScoreBrush(overallScore)),
};
}

String _renderLicenses(String baseUrl, List<LicenseFile> licenses) {
if (licenses == null || licenses.isEmpty) return null;
return licenses.map((license) {
Expand Down Expand Up @@ -835,10 +861,12 @@ String _getAuthorsHtml(List<String> authors) {
}).join('<br/>');
}

bool _isAnalysisSkipped(AnalysisStatus status) =>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe skipped is the wrong word here? I was thinking of packages that are brand new (no data yet), or there's a minor problem why the data isn't there right now; not so much that it's packages that are obsolete.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have an AnalysisStatus value for the pending ones (e.g. the one that is scheduled but not done yet), only for the ones that have been done. This way if a status is missing (==null), then it is by default pending (and should be scheduled), otherwise it is done.

We 'skip' two kinds of packages in the analysis: one that is older than two years, and one that is flagged as discontinued. We shall analyse everything else.

Ideally #1352 could also track the scheduled but not done yet status too.

status == AnalysisStatus.outdated || status == AnalysisStatus.discontinued;

String _renderScoreBox(AnalysisStatus status, double overallScore,
{bool isNewPackage, String package}) {
final skippedAnalysis = status == AnalysisStatus.outdated ||
status == AnalysisStatus.discontinued;
final skippedAnalysis = _isAnalysisSkipped(status);
final score = skippedAnalysis ? null : overallScore;
final String formattedScore = _formatScore(score);
final String scoreClass = _classifyScore(score);
Expand Down
87 changes: 61 additions & 26 deletions app/test/frontend/golden/analysis_tab_aborted.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,3 @@
<h2>Analysis</h2>

<p>
We analyzed this package on Dec 18, 2017, and provided a score, details, and suggestions below.
Analysis was completed with status <i>aborted</i> using:
</p>

<ul>
<li>Dart: </li>
<li>pana: 0.8.0</li>

</ul>

<hr />

<h4>Scores</h4>

<table id='scores-table'>
<tr>
<td class="score-name">
Expand All @@ -26,8 +9,19 @@ <h4>Scores</h4>
</div>
</div>
</td>
<td class="score-value">--</td>
<td class="score-range">/ 100</td>
<td class="score-value">

<div class="score-percent-row">
<div class="score-percent" style="left: 0%;">--</div>
</div>

<div class="score-progress-row">
<div class="score-progress-frame">
<div class="score-progress" style="width: 0%; background: rgb(204, 204, 204);"></div>
</div>
</div>

</td>
</tr>
<tr>
<td class="score-name">
Expand All @@ -39,8 +33,17 @@ <h4>Scores</h4>
</div>
</div>
</td>
<td class="score-value">--</td>
<td class="score-range">/ 100</td>
<td class="score-value">
<div class="score-percent-row">
<div class="score-percent" style="left: 0%;">--</div>
</div>

<div class="score-progress-row">
<div class="score-progress-frame">
<div class="score-progress" style="width: 0%; background: rgb(204, 204, 204);"></div>
</div>
</div>
</td>
</tr>
<tr>
<td class="score-name">
Expand All @@ -52,24 +55,56 @@ <h4>Scores</h4>
</div>
</div>
</td>
<td class="score-value">--</td>
<td class="score-range">/ 100</td>
<td class="score-value">
<div class="score-percent-row">
<div class="score-percent" style="left: 0%;">--</div>
</div>

<div class="score-progress-row">
<div class="score-progress-frame">
<div class="score-progress" style="width: 0%; background: rgb(204, 204, 204);"></div>
</div>
</div>
</td>
</tr>
<tr>
<td class="score-name">
<div class="tooltip-base hoverable">
<span class="tooltip-dotted"><b>Overall score:</b></span>
<span class="tooltip-dotted"><b>Overall:</b></span>
<div class="tooltip-content">
Weighted score of the above.
<a href="/help#overall-score">[more]</a>
</div>
</div>
</td>
<td><div class="score-box"><span class="number -missing" title="Awaiting analysis to complete.">--</span></div></td>
<td>
<div class="score-percent-row">
<div class="score-percent" style="left: 0%;">0</div>
</div>

<div class="score-progress-row">
<div class="score-progress-frame">
<div class="score-progress" style="width: 0%; background: rgb(187, 36, 0);"></div>
</div>
</div>
</td>
</tr>
</table>

Learn more about <a href="/help#scoring">scoring</a>.
<div style="text-align: right; font-size: 10pt;">Learn more about <a href="/help#scoring">scoring</a>.</div>

<hr />

<p>
We analyzed this package on Dec 18, 2017, and provided a score, details, and suggestions below.
Analysis was completed with status <i>aborted</i> using:
</p>

<ul>
<li>Dart: </li>
<li>pana: 0.8.0</li>

</ul>

<h4>Platforms</h4>

Expand Down
Loading