Skip to content

Commit

Permalink
Improve Package Details UX (#298)
Browse files Browse the repository at this point in the history
* Show Package Actions dropdown on every package page

* Move Package List icon to the left of the header bar

Link back to the package details from each Action page

* Show Installs & Security on Package Details

Format install counts

* Fix tests

Switch to extracting the text via Symfony, as that deals with HTML tags & whitespace
  • Loading branch information
giggsey authored Oct 15, 2020
1 parent f4de6b5 commit c43aacd
Show file tree
Hide file tree
Showing 17 changed files with 192 additions and 84 deletions.
1 change: 1 addition & 0 deletions src/Controller/OrganizationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ public function packageDetails(Organization $organization, Package $package, Req
'filter' => $filter,
'count' => $this->packageQuery->versionCount($package->id()),
'versions' => $this->packageQuery->getVersions($package->id(), $filter),
'installs' => $this->packageQuery->getInstalls($package->id(), 0),
]);
}

Expand Down
17 changes: 16 additions & 1 deletion src/Query/User/PackageQuery/DbalPackageQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,22 @@ public function count(string $organizationId, Filter $filter): int
public function getById(string $id): Option
{
$data = $this->connection->fetchAssoc(
'SELECT id, organization_id, type, repository_url, name, latest_released_version, latest_release_date, description, last_sync_at, last_sync_error, webhook_created_at, keep_last_releases
'SELECT
id,
organization_id,
type,
repository_url,
name,
latest_released_version,
latest_release_date,
description,
last_sync_at,
last_sync_error,
webhook_created_at,
last_scan_date,
last_scan_status,
last_scan_result,
keep_last_releases
FROM "organization_package"
WHERE id = :id', [
':id' => $id,
Expand Down
2 changes: 1 addition & 1 deletion templates/admin/proxy/dist.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
{% for package in packages %}
<tr>
<td>{{ package }}</td>
<td>{{ downloads[package].downloads ?? 0 }}</td>
<td class="number-format">{{ downloads[package].downloads ?? 0 }}</td>
<td>
{% if downloads[package] is defined %}
<span data-toggle="tooltip" title="{{ downloads[package].lastDownload|date_time }} ({{ gmt_offset() }})">
Expand Down
2 changes: 1 addition & 1 deletion templates/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
<h3 class="card-title flex-grow-1">
{% block header %}{% endblock %}
</h3>
<div class="">
<div class="d-flex">
{% block header_btn %}{% endblock %}
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions templates/component/js/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@
});

$('[data-toggle="tooltip"]').tooltip();

$('.number-format').each(function() {
$(this).text(parseInt($(this).text()).toLocaleString());
});
})();
7 changes: 7 additions & 0 deletions templates/component/js/stats.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
xaxis: {
type: 'datetime',
},
yaxis: {
labels: {
formatter: function (value) {
return value.toLocaleString();
},
},
},
labels: [{{ installs.days | map(day => "'#{day.date}'") | join(',') | raw }}],
colors: ["#206bc4"],
legend: {
Expand Down
7 changes: 7 additions & 0 deletions templates/component/js/statsVersions.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
xaxis: {
type: 'datetime',
},
yaxis: {
labels: {
formatter: function (value) {
return value.toLocaleString();
},
},
},
colors: ["#206bc4"],
noData: {
text: 'Loading...'
Expand Down
56 changes: 56 additions & 0 deletions templates/component/packageActions.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{% set currentPath = app.request.get('_route') %}
<span class="dropdown ml-1">
<button class="btn btn-secondary dropdown-toggle" data-toggle="dropdown">Actions</button>
<div class="dropdown-menu">
{% if package.name %}
{% if currentPath != 'organization_package_details' %}
<a href="{{ path('organization_package_details', {organization: organization.alias, package: package.id}) }}" class="dropdown-item">
{% include 'svg/package.svg' %} Details
</a>
{% endif %}
{% if currentPath != 'organization_package_stats' %}
<a href="{{ path('organization_package_stats', {organization: organization.alias, package: package.id}) }}" class="dropdown-item">
{% include 'svg/bar-chart.svg' %} Statistics
</a>
{% endif %}
{% if currentPath != 'organization_package_webhook' %}
<a href="{{ path('organization_package_webhook', {organization: organization.alias, package: package.id}) }}" class="dropdown-item">
{% include 'svg/link.svg' %} Webhook
</a>
{% endif %}
{% endif %}
{% if package.isSynchronizedSuccessfully %}
<a
class="dropdown-item"
data-target="confirmation"
data-action="{{ path('organization_package_scan', {organization: organization.alias, package: package.id }) }}"
data-method="POST"
href="#"
>
{% include 'svg/shield.svg' %} Scan
</a>
{% endif %}
{% if package.name or package.lastSyncError %}
<a
class="dropdown-item"
data-target="confirmation"
data-action="{{ path('organization_package_update', {organization: organization.alias, package: package.id }) }}"
data-method="POST"
href="#"
>
{% include 'svg/refresh.svg' %} Update
</a>
{% if is_granted('ROLE_ORGANIZATION_OWNER', organization) %}
<a
class="dropdown-item"
data-target="confirmation"
data-action="{{ path('organization_package_remove', {organization: organization.alias, package: package.id }) }}"
data-method="DELETE"
href="#"
>
{% include 'svg/trash.svg' %} Remove
</a>
{% endif %}
{% endif %}
</div>
</span>
51 changes: 44 additions & 7 deletions templates/organization/package/details.html.twig
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
{% extends 'base.html.twig' %}

{% block header %}{{ package.name }} details{% endblock %}
{% block header_btn %}
<a href="{{ path('organization_packages', {"organization":organization.alias}) }}" class="btn btn-primary">
{% include 'svg/package.svg' %} Package list
{% block header %}
<a href="{{ path('organization_packages', {"organization":organization.alias}) }}"
class="btn btn-secondary px-1 mr-1" title="Package List">
<span class="text-muted mr-1">&laquo;</span>
{% include 'svg/package.svg' %}
</a>

{{ package.name }} details
{% endblock %}
{% block header_btn %}
{% include 'component/packageActions.html.twig' %}
{% endblock %}

{% block content %}
Expand All @@ -21,15 +27,46 @@
</div>
</div>

<div class="row">
<div class="col-6">
<h4>Total Installs</h4>
<p>
<a href="{{ path('organization_package_stats', {organization: organization.alias, package: package.id}) }}"
class="number-format">
{{ installs.total }}
</a>
</p>
</div>
<div class="col-6">
<h4>Security</h4>
<p>
<a
href="{{ path('organization_package_scan_results', {organization: organization.alias, package: package.id}) }}"
data-content="{% apply escape %}{% include 'organization/package/scanResultContent.html.twig' with {'result': package.lastScanResultContent, 'simple': true} %}{% endapply %}"
data-data-toggle="tooltip" title="{{ package.scanResultDate|date_time|time_diff }}"
data-toggle="scan-result-popover"
>
{% if package.isScanResultOk %}
<span class="badge badge-success">{{ package.scanResultStatus }}</span>
{% elseif package.isScanResultPending or package.isScanResultNotAvailable %}
<span class="badge badge-warning">{{ package.scanResultStatus }}</span>
{% else %}
<span class="badge badge-danger">{{ package.scanResultStatus }}</span>
{% endif %}
</a>
</p>
</div>
</div>

{% if package.latestReleaseDate %}
<div class="row">
<div class="col-6">
<h4>Latest version</h4>
<p>
{{ package.latestReleasedVersion }} (released:
<span data-toggle="tooltip" title="{{ package.latestReleaseDate|date_time_utc }} ({{ gmt_offset() }})">
{{ package.latestReleaseDate|date_time_utc|time_diff }}
</span>)
<span data-toggle="tooltip"
title="{{ package.latestReleaseDate|date_time_utc }} ({{ gmt_offset() }})">
{{ package.latestReleaseDate|date_time_utc|time_diff }}</span>)
</p>
</div>
<div class="col-6">
Expand Down
16 changes: 12 additions & 4 deletions templates/organization/package/scanResults.html.twig
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
{% extends 'base.html.twig' %}

{% block header %}Package: {{ package.name }} security scan results{% endblock %}
{% block header %}
<a href="{{ path('organization_packages', {"organization":organization.alias}) }}"
class="btn btn-secondary px-1 mr-1" title="Package List">
<span class="text-muted mr-1">&laquo;</span>
{% include 'svg/package.svg' %}
</a>

<a href="{{ path('organization_package_details', {organization: organization.alias, package: package.id}) }}">{{ package.name }}</a>
security scan results
{% endblock %}

{% block header_btn %}
<a href="{{ path('organization_packages', {"organization":organization.alias}) }}" class="btn btn-primary">
{% include 'svg/package.svg' %} Package list
</a>
{% include 'component/packageActions.html.twig' %}
{% endblock %}

{% block content %}
Expand Down
22 changes: 18 additions & 4 deletions templates/organization/package/stats.html.twig
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
{% extends 'organization/stats.html.twig' %}

{% block header %}{{ package.name }} installs (last {{ days }} days){% endblock %}
{% block header %}
<a href="{{ path('organization_packages', {"organization":organization.alias}) }}"
class="btn btn-secondary px-1 mr-1" title="Package List">
<span class="text-muted mr-1">&laquo;</span>
{% include 'svg/package.svg' %}
</a>

<a href="{{ path('organization_package_details', {organization: organization.alias, package: package.id}) }}">{{ package.name }}</a>
installs (last {{ days }} days)
{% endblock %}

{% block header_btn %}
{{ parent() }}
{% include 'component/packageActions.html.twig' %}
{% endblock %}

{% block content %}
<h3>Daily installs</h3>
Expand All @@ -10,9 +24,9 @@

<h3>Summary</h3>
<p>
<span class="mr-5">Today installs: {{ (installs.days | last).installs }}</span>
<span class="mr-5">Last {{ days ?? 30 }} days installs: {{ installs.daysTotal }}</span>
<span class="mr-5"><strong>Total installs: {{ installs.total }}</strong></span>
<span class="mr-5">Today installs: <span class="number-format">{{ (installs.days | last).installs }}</span></span>
<span class="mr-5">Last {{ days ?? 30 }} days installs: <span class="number-format">{{ installs.daysTotal }}</span></span>
<span class="mr-5"><strong>Total installs: <span class="number-format">{{ installs.total }}</span></strong></span>
</p>

<hr />
Expand Down
15 changes: 11 additions & 4 deletions templates/organization/package/webhook.html.twig
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
{% extends 'base.html.twig' %}

{% block header %}Package: {{ package.name }} webhook{% endblock %}
{% block header_btn %}
<a href="{{ path('organization_packages', {"organization":organization.alias}) }}" class="btn btn-primary">
{% include 'svg/package.svg' %} Package list
{% block header %}
<a href="{{ path('organization_packages', {"organization":organization.alias}) }}"
class="btn btn-secondary px-1 mr-1" title="Package List">
<span class="text-muted mr-1">&laquo;</span>
{% include 'svg/package.svg' %}
</a>

<a href="{{ path('organization_package_details', {organization: organization.alias, package: package.id}) }}">{{ package.name }}</a>
webhook
{% endblock %}
{% block header_btn %}
{% include 'component/packageActions.html.twig' %}
{% endblock %}

{% block content %}
Expand Down
50 changes: 1 addition & 49 deletions templates/organization/packages.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -111,55 +111,7 @@
{% endif %}
</td>
<td>
<span class="dropdown ml-1">
<button class="btn btn-secondary dropdown-toggle align-text-top" data-toggle="dropdown">Actions</button>
<div class="dropdown-menu">
{% if package.name %}
<a href="{{ path('organization_package_details', {organization: organization.alias, package: package.id}) }}" class="dropdown-item">
{% include 'svg/package.svg' %} Details
</a>
<a href="{{ path('organization_package_stats', {organization: organization.alias, package: package.id}) }}" class="dropdown-item">
{% include 'svg/bar-chart.svg' %} Statistics
</a>
<a href="{{ path('organization_package_webhook', {organization: organization.alias, package: package.id}) }}" class="dropdown-item">
{% include 'svg/link.svg' %} Webhook
</a>
{% endif %}
{% if package.isSynchronizedSuccessfully %}
<a
class="dropdown-item"
data-target="confirmation"
data-action="{{ path('organization_package_scan', {organization: organization.alias, package: package.id }) }}"
data-method="POST"
href="#"
>
{% include 'svg/shield.svg' %} Scan
</a>
{% endif %}
{% if package.name or package.lastSyncError %}
<a
class="dropdown-item"
data-target="confirmation"
data-action="{{ path('organization_package_update', {organization: organization.alias, package: package.id }) }}"
data-method="POST"
href="#"
>
{% include 'svg/refresh.svg' %} Update
</a>
{% if is_granted('ROLE_ORGANIZATION_OWNER', organization) %}
<a
class="dropdown-item"
data-target="confirmation"
data-action="{{ path('organization_package_remove', {organization: organization.alias, package: package.id }) }}"
data-method="DELETE"
href="#"
>
{% include 'svg/trash.svg' %} Remove
</a>
{% endif %}
{% endif %}
</div>
</span>
{% include 'component/packageActions.html.twig' %}
</td>
{% endif %}
</tr>
Expand Down
6 changes: 3 additions & 3 deletions templates/organization/stats.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@

<h3>Summary:</h3>
<p>
Today installs: {{ (installs.days | last).installs }}<br />
Last {{ days ?? 30 }} days installs: {{ installs.daysTotal }}<br />
<strong>Total installs: {{ installs.total }}</strong>
Today installs: <span class="number-format">{{ (installs.days | last).installs }}</span><br />
Last {{ days ?? 30 }} days installs: <span class="number-format">{{ installs.daysTotal }}</span><br />
<strong>Total installs: <span class="number-format">{{ installs.total }}</span></strong>
</p>
{% endblock %}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ public function testStats(): void
$orgId = $this->fixtures->createOrganization('buddy', $this->userId);
$packageId = $this->fixtures->addPackage($orgId, 'https://some.url');
$this->fixtures->addPackageDownload(1, $packageId);
$this->client->request('GET', $this->urlTo('admin_stats'));
$crawler = $this->client->request('GET', $this->urlTo('admin_stats'));

self::assertTrue($this->client->getResponse()->isOk());
self::assertStringContainsString('Total installs: 1', $this->lastResponseBody());
self::assertStringContainsString('Total installs: 1', $crawler->text(null, true));
}
}
4 changes: 2 additions & 2 deletions tests/Functional/Controller/Admin/ProxyControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ public function testStats(): void
[new Package('buddy-works/repman', '1.0.0.0')],
new \DateTimeImmutable('2020-04-27 19:34:00')
);
$this->client->request('GET', $this->urlTo('admin_proxy_stats'));
$crawler = $this->client->request('GET', $this->urlTo('admin_proxy_stats'));

self::assertTrue($this->client->getResponse()->isOk());
self::assertStringContainsString('Total installs: 1', $this->lastResponseBody());
self::assertStringContainsString('Total installs: 1', $crawler->text(null, true));
}

public function testRemoveDistPackage(): void
Expand Down
Loading

0 comments on commit c43aacd

Please sign in to comment.