Skip to content

Commit

Permalink
Allow package list to be sortable (#300)
Browse files Browse the repository at this point in the history
* Allow package list to be sortable

* Fix CS
  • Loading branch information
giggsey authored Oct 15, 2020
1 parent c43aacd commit efe2fcf
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/Controller/OrganizationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public function overview(Organization $organization): Response
*/
public function packages(Organization $organization, Request $request): Response
{
$filter = PackageFilter::fromRequest($request);
$filter = PackageFilter::fromRequest($request, 'name');

$count = $this->packageQuery->count($organization->id(), $filter);

Expand Down
41 changes: 37 additions & 4 deletions src/Query/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ class Filter
{
private int $offset = 0;
private int $limit = 20;
private ?string $sortColumn = null;
private string $sortOrder;

public function __construct(int $offset = 0, int $limit = 20)
public function __construct(int $offset = 0, int $limit = 20, ?string $sort = null)
{
if ($offset >= 0) {
$this->offset = $offset;
Expand All @@ -24,6 +26,15 @@ public function __construct(int $offset = 0, int $limit = 20)
if ($this->limit > 100) {
$this->limit = 100;
}

// $sort = 'column:order'
$sortParts = explode(':', $sort ?? '');
if ($sortParts[0] !== '') {
$this->sortColumn = $sortParts[0];
}

$sortOrder = $sortParts[1] ?? 'asc';
$this->sortOrder = in_array($sortOrder, ['asc', 'desc'], true) ? $sortOrder : 'asc';
}

public function getOffset(): int
Expand All @@ -36,22 +47,44 @@ public function getLimit(): int
return $this->limit;
}

public function getSortColumn(): ?string
{
return $this->sortColumn;
}

public function getSortOrder(): string
{
return $this->sortOrder;
}

public function hasSort(): bool
{
return $this->sortColumn !== null;
}

/**
* @return array<string,string>
*/
public function getQueryStringParams(): array
{
return [
$return = [
'offset' => (string) $this->getOffset(),
'limit' => (string) $this->getLimit(),
];

if ($this->hasSort()) {
$return['sort'] = $this->sortColumn.':'.$this->sortOrder;
}

return $return;
}

public static function fromRequest(Request $request): self
public static function fromRequest(Request $request, ?string $defaultSortColumn = null): self
{
return new self(
(int) $request->get('offset', 0),
(int) $request->get('limit', 20)
(int) $request->get('limit', 20),
$request->get('sort', $defaultSortColumn),
);
}
}
14 changes: 13 additions & 1 deletion src/Query/User/PackageQuery/DbalPackageQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ public function findAll(string $organizationId, Filter $filter): array
$params[':term'] = '%'.$filter->getSearchTerm().'%';
}

$sortSQL = 'name ASC';

$sortColumnMappings = [
'name' => 'name',
'version' => 'latest_released_version',
'date' => 'latest_release_date',
];

if ($filter->hasSort() && isset($sortColumnMappings[$filter->getSortColumn()])) {
$sortSQL = $sortColumnMappings[$filter->getSortColumn()].' '.$filter->getSortOrder();
}

return array_map(
function (array $data): Package {
return $this->hydratePackage($data);
Expand All @@ -66,7 +78,7 @@ function (array $data): Package {
WHERE organization_id = :organization_id
'.$filterSQL.'
GROUP BY id
ORDER BY name ASC
ORDER BY '.$sortSQL.'
LIMIT :limit OFFSET :offset',
$params
)
Expand Down
9 changes: 5 additions & 4 deletions src/Query/User/PackageQuery/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ class Filter extends \Buddy\Repman\Query\Filter
{
private ?string $searchTerm;

public function __construct(int $offset = 0, int $limit = 20, ?string $searchTerm = null)
public function __construct(int $offset = 0, int $limit = 20, ?string $sort = null, ?string $searchTerm = null)
{
parent::__construct($offset, $limit);
parent::__construct($offset, $limit, $sort);

$this->searchTerm = $searchTerm;
}
Expand All @@ -38,12 +38,13 @@ public function getQueryStringParams(): array
return $params;
}

public static function fromRequest(Request $request): self
public static function fromRequest(Request $request, ?string $defaultSortColumn = null): self
{
return new self(
(int) $request->get('offset', 0),
(int) $request->get('limit', 20),
$request->get('search', null)
$request->get('sort', $defaultSortColumn),
$request->get('search', null),
);
}
}
22 changes: 22 additions & 0 deletions templates/component/sort.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% if filter.getSortColumn() == column %}
{% if filter.getSortOrder() == 'asc' %}
{% set newSortOrder = 'desc' %}
{% set sortTitle = 'Descending' %}
{% else %}
{% set newSortOrder = 'asc' %}
{% set sortTitle = 'Ascending' %}
{% endif %}
<a href="{{ path(app.request.get('_route'), app.request.get('_route_params') | merge(app.request.query.all) | merge({sort: column ~ ':' ~ newSortOrder, offset: 0})) }}"
title="Sort {{ sortTitle }}">
{% if filter.getSortOrder() == 'asc' %}
{% include 'svg/sort-asc.svg' %}
{% else %}
{% include 'svg/sort-desc.svg' %}
{% endif %}
</a>
{% else %}
<a href="{{ path(app.request.get('_route'), app.request.get('_route_params') | merge(app.request.query.all) | merge({sort: column ~ ':asc', offset: 0})) }}"
title="Sort ascending" class="text-muted">
{% include 'svg/sort-asc.svg' %}
</a>
{% endif %}
6 changes: 3 additions & 3 deletions templates/organization/packages.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
</td>
</tr>
<tr>
<th>Name</th>
<th>Latest version</th>
<th>Released</th>
<th>Name {% include 'component/sort.html.twig' with {column: 'name'} %}</th>
<th>Latest version {% include 'component/sort.html.twig' with {column: 'version'} %}</th>
<th>Released {% include 'component/sort.html.twig' with {column: 'date'} %}</th>
<th>Description</th>
{% if is_granted('ROLE_ORGANIZATION_MEMBER', organization) %}
<th>Webhook</th>
Expand Down
18 changes: 18 additions & 0 deletions templates/svg/sort-asc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions templates/svg/sort-desc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions tests/Functional/Controller/OrganizationControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,67 @@ public function testPagination(): void
self::assertStringContainsString('offset=0&amp;limit=20', $content);
}

public function testSorting(): void
{
$buddyId = $this->fixtures->createOrganization('buddy', $this->userId);

for ($i = 1; $i < 6; ++$i) {
$submissionTime = (new \DateTimeImmutable())->add(new \DateInterval("P{$i}D"));

$packageId = $this->fixtures->addPackage($buddyId, 'https://buddy.com');
$this->fixtures->syncPackageWithData($packageId, 'buddy-works/package-'.$i, 'Test', "1.{$i}", $submissionTime);
}

$this->client->request('GET', $this->urlTo('organization_packages', ['organization' => 'buddy', 'limit' => 1]));

self::assertTrue($this->client->getResponse()->isOk());
$content = (string) $this->client->getResponse()->getContent();
self::assertStringContainsString('buddy-works/package-1', $content);
self::assertStringContainsString('sort=name:desc', $content);
self::assertStringContainsString('sort=version:asc', $content);
self::assertStringContainsString('sort=date:asc', $content);

// Sort by name desc
$this->client->request('GET', $this->urlTo('organization_packages', ['organization' => 'buddy', 'limit' => 1, 'sort' => 'name:desc']));

self::assertTrue($this->client->getResponse()->isOk());
$content = (string) $this->client->getResponse()->getContent();
self::assertStringContainsString('buddy-works/package-5', $content);
self::assertStringContainsString('sort=name:asc', $content);
self::assertStringContainsString('sort=version:asc', $content);
self::assertStringContainsString('sort=date:asc', $content);

// Sort by version desc
$this->client->request('GET', $this->urlTo('organization_packages', ['organization' => 'buddy', 'limit' => 1, 'sort' => 'version:desc']));

self::assertTrue($this->client->getResponse()->isOk());
$content = (string) $this->client->getResponse()->getContent();
self::assertStringContainsString('buddy-works/package-5', $content);
self::assertStringContainsString('sort=name:asc', $content);
self::assertStringContainsString('sort=version:desc', $content);
self::assertStringContainsString('sort=date:asc', $content);

// Sort by released date asc
$this->client->request('GET', $this->urlTo('organization_packages', ['organization' => 'buddy', 'limit' => 1, 'sort' => 'date:asc']));

self::assertTrue($this->client->getResponse()->isOk());
$content = (string) $this->client->getResponse()->getContent();
self::assertStringContainsString('buddy-works/package-1', $content);
self::assertStringContainsString('sort=name:asc', $content);
self::assertStringContainsString('sort=version:asc', $content);
self::assertStringContainsString('sort=date:desc', $content);

// Sort by invalid column
$this->client->request('GET', $this->urlTo('organization_packages', ['organization' => 'buddy', 'limit' => 1, 'sort' => 'invalid-column:asc']));

self::assertTrue($this->client->getResponse()->isOk());
$content = (string) $this->client->getResponse()->getContent();
self::assertStringContainsString('buddy-works/package-1', $content);
self::assertStringContainsString('sort=name:asc', $content);
self::assertStringContainsString('sort=version:asc', $content);
self::assertStringContainsString('sort=date:asc', $content);
}

public function testRemovePackage(): void
{
$buddyId = $this->fixtures->createOrganization('buddy', $this->userId);
Expand Down

0 comments on commit efe2fcf

Please sign in to comment.