Skip to content

Commit

Permalink
Add artefacts sorting (#206)
Browse files Browse the repository at this point in the history
* Make tapping a header change url sort query parameters

* Slightly increase artefact list view width

* Show sort arrow on headers

* Fix bug in dashboard view

* Actually sort artefacts by column

* Use constant for search query parameter

* Use constants for duplicate hardcoded strings

* Refactor sort by query parameters values into

* Remove unused import

* Fix sort query parameters vanishing when changing filters

* Add trailing comma

* Some code style improvements
  • Loading branch information
omar-selo committed Aug 20, 2024
1 parent 2395b39 commit cfb4caf
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 33 deletions.
26 changes: 20 additions & 6 deletions frontend/lib/providers/filtered_family_artefacts.dart
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
import 'dart:collection';

import 'package:dartx/dartx.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../models/artefact.dart';
import '../models/filters.dart';
import '../routing.dart';
import '../utils/artefact_sorting.dart';
import 'family_artefacts.dart';

part 'filtered_family_artefacts.g.dart';

@riverpod
Map<int, Artefact> filteredFamilyArtefacts(
LinkedHashMap<int, Artefact> filteredFamilyArtefacts(
FilteredFamilyArtefactsRef ref,
Uri pageUri,
) {
final family = AppRoutes.familyFromUri(pageUri);
final artefacts = ref.watch(familyArtefactsProvider(family)).requireValue;
final filters =
emptyArtefactFilters.copyWithQueryParams(pageUri.queryParametersAll);
final searchValue = pageUri.queryParameters['q'] ?? '';
final searchValue =
pageUri.queryParameters[CommonQueryParameters.searchQuery] ?? '';

final filteredArtefacts = artefacts.values
.filter(
(artefact) =>
_artefactPassesSearch(artefact, searchValue) &&
filters.doesObjectPassFilters(artefact),
)
.toList();

sortArtefacts(pageUri.queryParameters, filteredArtefacts);

return artefacts.filterValues(
(artefact) =>
_artefactPassesSearch(artefact, searchValue) &&
filters.doesObjectPassFilters(artefact),
return LinkedHashMap.fromIterable(
filteredArtefacts,
key: (a) => a.id,
value: (a) => a,
);
}

Expand Down
3 changes: 2 additions & 1 deletion frontend/lib/providers/filtered_test_executions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Future<List<TestExecution>> filteredTestExecutions(
final artefactId = AppRoutes.artefactIdFromUri(pageUri);
final filters =
emptyTestExecutionFilters.copyWithQueryParams(pageUri.queryParametersAll);
final searchValue = pageUri.queryParameters['q'] ?? '';
final searchValue =
pageUri.queryParameters[CommonQueryParameters.searchQuery] ?? '';

final builds = await ref.watch(artefactBuildsProvider(artefactId).future);
final testExecutions = [
Expand Down
11 changes: 11 additions & 0 deletions frontend/lib/routing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ final appRouter = GoRouter(
],
);

enum SortDirection {
asc,
desc,
}

class CommonQueryParameters {
static const searchQuery = 'q';
static const sortBy = 'sortBy';
static const sortDirection = 'direction';
}

class AppRoutes {
static const snaps = '/snaps';
static const debs = '/debs';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
import '../../../../models/artefact.dart';
import '../../../../providers/filtered_family_artefacts.dart';
import '../../../../routing.dart';
import '../../../../utils/artefact_sorting.dart';

part 'row.dart';
part 'headers.dart';
Expand Down Expand Up @@ -42,9 +43,10 @@ class ArtefactsListView extends ConsumerWidget {
final artefacts =
ref.watch(filteredFamilyArtefactsProvider(pageUri)).values.toList();

return Center(
return Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 1200,
width: 1300,
child: ListView.separated(
itemCount: artefacts.length + 1,
itemBuilder: (_, i) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,117 @@ part of 'artefacts_list_view.dart';

typedef ColumnMetadata = ({
String name,
ArtefactSortingQuery queryParam,
int flex,
Widget Function(BuildContext, Artefact) cellBuilder
});

const _snapColumnsMetadata = <ColumnMetadata>[
(name: 'Name', flex: 2, cellBuilder: _buildNameCell),
(name: 'Version', flex: 2, cellBuilder: _buildVersionCell),
(name: 'Track', flex: 1, cellBuilder: _buildTrackCell),
(name: 'Risk', flex: 1, cellBuilder: _buildStageCell),
(name: 'Due date', flex: 1, cellBuilder: _buildDueDateCell),
(name: 'Reviews remaining', flex: 1, cellBuilder: _buildReviewsRemainingCell),
(name: 'Status', flex: 1, cellBuilder: _buildStatusCell),
(name: 'Assignee', flex: 1, cellBuilder: _buildAssigneeCell),
(
name: 'Name',
queryParam: ArtefactSortingQuery.name,
flex: 2,
cellBuilder: _buildNameCell,
),
(
name: 'Version',
queryParam: ArtefactSortingQuery.version,
flex: 2,
cellBuilder: _buildVersionCell,
),
(
name: 'Track',
queryParam: ArtefactSortingQuery.track,
flex: 1,
cellBuilder: _buildTrackCell,
),
(
name: 'Risk',
queryParam: ArtefactSortingQuery.risk,
flex: 1,
cellBuilder: _buildStageCell,
),
(
name: 'Due date',
queryParam: ArtefactSortingQuery.dueDate,
flex: 1,
cellBuilder: _buildDueDateCell,
),
(
name: 'Reviews remaining',
queryParam: ArtefactSortingQuery.reviewsRemaining,
flex: 1,
cellBuilder: _buildReviewsRemainingCell,
),
(
name: 'Status',
queryParam: ArtefactSortingQuery.status,
flex: 1,
cellBuilder: _buildStatusCell,
),
(
name: 'Assignee',
queryParam: ArtefactSortingQuery.assignee,
flex: 1,
cellBuilder: _buildAssigneeCell,
),
];

const _debColumnsMetadata = <ColumnMetadata>[
(name: 'Name', flex: 2, cellBuilder: _buildNameCell),
(name: 'Version', flex: 2, cellBuilder: _buildVersionCell),
(name: 'Series', flex: 1, cellBuilder: _buildSeriesCell),
(name: 'Repo', flex: 1, cellBuilder: _buildRepoCell),
(name: 'Pocket', flex: 1, cellBuilder: _buildStageCell),
(name: 'Due date', flex: 1, cellBuilder: _buildDueDateCell),
(name: 'Reviews remaining', flex: 1, cellBuilder: _buildReviewsRemainingCell),
(name: 'Status', flex: 1, cellBuilder: _buildStatusCell),
(name: 'Assignee', flex: 1, cellBuilder: _buildAssigneeCell),
(
name: 'Name',
queryParam: ArtefactSortingQuery.name,
flex: 2,
cellBuilder: _buildNameCell,
),
(
name: 'Version',
queryParam: ArtefactSortingQuery.version,
flex: 2,
cellBuilder: _buildVersionCell,
),
(
name: 'Series',
queryParam: ArtefactSortingQuery.series,
flex: 1,
cellBuilder: _buildSeriesCell,
),
(
name: 'Repo',
queryParam: ArtefactSortingQuery.repo,
flex: 1,
cellBuilder: _buildRepoCell,
),
(
name: 'Pocket',
queryParam: ArtefactSortingQuery.pocket,
flex: 1,
cellBuilder: _buildStageCell,
),
(
name: 'Due date',
queryParam: ArtefactSortingQuery.dueDate,
flex: 1,
cellBuilder: _buildDueDateCell,
),
(
name: 'Reviews remaining',
queryParam: ArtefactSortingQuery.reviewsRemaining,
flex: 1,
cellBuilder: _buildReviewsRemainingCell,
),
(
name: 'Status',
queryParam: ArtefactSortingQuery.status,
flex: 1,
cellBuilder: _buildStatusCell,
),
(
name: 'Assignee',
queryParam: ArtefactSortingQuery.assignee,
flex: 1,
cellBuilder: _buildAssigneeCell,
),
];

Widget _buildNameCell(BuildContext context, Artefact artefact) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,67 @@ class _Headers extends StatelessWidget {

@override
Widget build(BuildContext context) {
final uri = AppRoutes.uriFromContext(context);
final sortBy = uri.queryParameters[CommonQueryParameters.sortBy];
final sortDirection =
uri.queryParameters[CommonQueryParameters.sortDirection];

return SizedBox(
height: 56,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: columnsMetaData
.map(
(data) => Expanded(
flex: data.flex,
child: Text(
data.name,
style: Theme.of(context).textTheme.titleLarge,
child: InkWell(
onTap: () => _handleFilterTap(context, uri, data),
child: Row(
children: [
Flexible(
child: Text(
data.name,
style: Theme.of(context).textTheme.titleLarge,
),
),
if (sortBy == data.queryParam.name)
if (sortDirection == SortDirection.asc.name)
const Icon(Icons.arrow_upward)
else
const Icon(Icons.arrow_downward),
],
),
),
),
)
.toList(),
),
);
}

void _handleFilterTap(
BuildContext context,
Uri pageUri,
ColumnMetadata columnData,
) {
final queryParameters = pageUri.queryParameters;
final existingSortBy = queryParameters[CommonQueryParameters.sortBy];
final existingDirection =
queryParameters[CommonQueryParameters.sortDirection];

final newSortBy = columnData.queryParam.name;
final isSameSortBy = existingSortBy == newSortBy;
final wasAscending = existingDirection == SortDirection.asc.name;
final newDirection = isSameSortBy && wasAscending
? SortDirection.desc.name
: SortDirection.asc.name;

final newQueryParameters = {
...queryParameters,
CommonQueryParameters.sortBy: newSortBy,
CommonQueryParameters.sortDirection: newDirection,
};

context.go(pageUri.replace(queryParameters: newQueryParameters).toString());
}
}
12 changes: 10 additions & 2 deletions frontend/lib/ui/page_filters/page_filters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class PageFiltersView extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final pageUri = AppRoutes.uriFromContext(context);
final searchQuery = pageUri.queryParameters['q'];
final searchQuery =
pageUri.queryParameters[CommonQueryParameters.searchQuery];
final filters = ref.watch(pageFiltersProvider(pageUri));

return SizedBox(
Expand Down Expand Up @@ -69,10 +70,17 @@ class PageFiltersView extends ConsumerWidget {
Uri pageUri,
BuildContext context,
) {
final sortBy = pageUri.queryParameters[CommonQueryParameters.sortBy];
final sortDirection =
pageUri.queryParameters[CommonQueryParameters.sortDirection];
final searchValue = ref.read(searchValueProvider(searchQuery)).trim();
final queryParams = {
if (searchValue.isNotEmpty) 'q': searchValue,
if (searchValue.isNotEmpty)
CommonQueryParameters.searchQuery: searchValue,
...ref.read(pageFiltersProvider(pageUri)).toQueryParams(),
if (sortBy != null) CommonQueryParameters.sortBy: sortBy,
if (sortDirection != null)
CommonQueryParameters.sortDirection: sortDirection,
};
context.go(
pageUri.replace(queryParameters: queryParams).toString(),
Expand Down
5 changes: 4 additions & 1 deletion frontend/lib/ui/page_filters/page_search_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';

import '../../providers/search_value.dart';
import '../../routing.dart';
import '../vanilla/vanilla_search_bar.dart';

final pageSearchBarKey = GlobalKey<_PageSearchBarState>();
Expand Down Expand Up @@ -35,7 +36,9 @@ class _PageSearchBarState extends ConsumerState<PageSearchBar> {

@override
Widget build(BuildContext context) {
final searchQuery = GoRouterState.of(context).uri.queryParameters['q'];
final searchQuery = GoRouterState.of(context)
.uri
.queryParameters[CommonQueryParameters.searchQuery];

return _SearchNotifierListener(
searchQuery: searchQuery,
Expand Down
Loading

0 comments on commit cfb4caf

Please sign in to comment.