Skip to content

Commit

Permalink
Add images to frontend (#245)
Browse files Browse the repository at this point in the history
* Plumbing work to add images

* Add image properties to artefact card

* Set default '' for some artefact fields

* Add image related columns to artefact list view

* Add image fields to artefact page

* Make the height of artefact cards dynamic

* Add a factory for artefact filters

* Add image specific filters

* Add family specific filters

* Use null pattern for artefacts with no assignee

* Extract common filters across families

* Add pocket filter
  • Loading branch information
omar-selo authored Jan 20, 2025
1 parent b930f02 commit 4d6016b
Show file tree
Hide file tree
Showing 21 changed files with 324 additions and 78 deletions.
15 changes: 10 additions & 5 deletions frontend/lib/models/artefact.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@ class Artefact with _$Artefact {
required int id,
required String name,
required String version,
required String track,
required String store,
required String series,
required String repo,
@Default('') String track,
@Default('') String store,
@Default('') String series,
@Default('') String repo,
@Default('') String os,
@Default('') String release,
@Default('') String owner,
@Default('') String sha256,
@Default('') @JsonKey(name: 'image_url') String imageUrl,
required ArtefactStatus status,
required StageName stage,
@JsonKey(name: 'all_environment_reviews_count')
required int allEnvironmentReviewsCount,
@JsonKey(name: 'completed_environment_reviews_count')
required int completedEnvironmentReviewsCount,
User? assignee,
@Default(emptyUser) User assignee,
@JsonKey(name: 'bug_link') required String bugLink,
@JsonKey(name: 'due_date') DateTime? dueDate,
}) = _Artefact;
Expand Down
2 changes: 1 addition & 1 deletion frontend/lib/models/family_name.dart
Original file line number Diff line number Diff line change
@@ -1 +1 @@
enum FamilyName { snap, deb, charm }
enum FamilyName { snap, deb, charm, image }
127 changes: 104 additions & 23 deletions frontend/lib/models/filters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'artefact.dart';
import 'artefact_environment.dart';
import 'environment_review.dart';
import 'family_name.dart';
import 'filter.dart';

part 'filters.freezed.dart';
Expand Down Expand Up @@ -82,30 +83,55 @@ class Filters<T> with _$Filters<T> {
}
}

final emptyArtefactFilters = Filters<Artefact>(
Filters<Artefact> createEmptyArtefactFilters(FamilyName family) {
switch (family) {
case FamilyName.image:
return emptyImageFilters;
case FamilyName.snap:
return emptySnapFilters;
case FamilyName.deb:
return emptyDebFilters;
case FamilyName.charm:
return emptyCharmFilters;
}
}

final emptyCharmFilters = Filters<Artefact>(
filters: [
Filter<Artefact>(
name: 'Assignee',
extractOption: (artefact) => artefact.assignee?.name,
),
Filter<Artefact>(
name: 'Status',
extractOption: (artefact) => artefact.status.name,
),
Filter<Artefact>(
name: 'Due date',
extractOption: (artefact) {
final now = DateTime.now();
final dueDate = artefact.dueDate;

if (dueDate == null) return 'No due date';
if (dueDate.isBefore(now)) return 'Overdue';

final daysDueIn = now.difference(dueDate).inDays;
if (daysDueIn >= 7) return 'More than a week';
return 'Within a week';
},
),
_artefactAssigneeFilter,
_artefactStatusFilter,
_artefactDueDateFilter,
_artefactRiskFilter,
],
);

final emptyDebFilters = Filters<Artefact>(
filters: [
_artefactAssigneeFilter,
_artefactStatusFilter,
_artefactDueDateFilter,
_artefactSeriesFilter,
_artefactPocketFilter,
],
);

final emptySnapFilters = Filters<Artefact>(
filters: [
_artefactAssigneeFilter,
_artefactStatusFilter,
_artefactDueDateFilter,
_artefactRiskFilter,
],
);

final emptyImageFilters = Filters<Artefact>(
filters: [
_artefactOSFilter,
_artefactReleaseFilter,
_artefactOwnerFilter,
_artefactAssigneeFilter,
_artefactStatusFilter,
_artefactDueDateFilter,
],
);

Expand All @@ -127,3 +153,58 @@ final emptyArtefactEnvironmentsFilters = Filters<ArtefactEnvironment>(
),
],
);

Filter<Artefact> _artefactAssigneeFilter = Filter<Artefact>(
name: 'Assignee',
extractOption: (artefact) => artefact.assignee.name,
);

Filter<Artefact> _artefactStatusFilter = Filter<Artefact>(
name: 'Status',
extractOption: (artefact) => artefact.status.name,
);

Filter<Artefact> _artefactDueDateFilter = Filter<Artefact>(
name: 'Due date',
extractOption: (artefact) {
final now = DateTime.now();
final dueDate = artefact.dueDate;

if (dueDate == null) return 'No due date';
if (dueDate.isBefore(now)) return 'Overdue';

final daysDueIn = now.difference(dueDate).inDays;
if (daysDueIn >= 7) return 'More than a week';
return 'Within a week';
},
);

Filter<Artefact> _artefactRiskFilter = Filter<Artefact>(
name: 'Risk',
extractOption: (artefact) => artefact.stage.name,
);

Filter<Artefact> _artefactSeriesFilter = Filter<Artefact>(
name: 'Series',
extractOption: (artefact) => artefact.series,
);

Filter<Artefact> _artefactOSFilter = Filter<Artefact>(
name: 'OS type',
extractOption: (artefact) => artefact.os,
);

Filter<Artefact> _artefactReleaseFilter = Filter<Artefact>(
name: 'Release',
extractOption: (artefact) => artefact.release,
);

Filter<Artefact> _artefactOwnerFilter = Filter<Artefact>(
name: 'Owner',
extractOption: (artefact) => artefact.owner,
);

Filter<Artefact> _artefactPocketFilter = Filter<Artefact>(
name: 'Pocket',
extractOption: (artefact) => artefact.stage.name,
);
16 changes: 15 additions & 1 deletion frontend/lib/models/stage_name.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import 'family_name.dart';

enum StageName { edge, beta, candidate, stable, proposed, updates }
enum StageName {
edge,
beta,
candidate,
stable,
proposed,
updates,
pending,
current
}

List<StageName> familyStages(FamilyName family) {
switch (family) {
Expand All @@ -23,5 +32,10 @@ List<StageName> familyStages(FamilyName family) {
StageName.candidate,
StageName.stable,
];
case FamilyName.image:
return [
StageName.pending,
StageName.current,
];
}
}
13 changes: 12 additions & 1 deletion frontend/lib/models/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,31 @@ class User with _$User {
required String name,
@JsonKey(name: 'launchpad_email') required String launchpadEmail,
@JsonKey(name: 'launchpad_handle') required String launchpadHandle,
@Default(false) bool isEmpty,
}) = _User;

factory User.fromJson(Map<String, Object?> json) => _$UserFromJson(json);

String get initials {
if (isEmpty) return 'N/A';

final names = name.split(' ');
final numOfNames = names.length;
switch (numOfNames) {
case 0:
return 'N/A';
throw Exception('User is missing a name');
case 1:
return names[0][0].capitalize();
default:
return names[0][0].capitalize() + names[numOfNames - 1][0].capitalize();
}
}
}

const emptyUser = User(
id: -1,
name: 'N/A',
launchpadEmail: '',
launchpadHandle: '',
isEmpty: true,
);
4 changes: 2 additions & 2 deletions frontend/lib/providers/filtered_family_artefacts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ LinkedHashMap<int, Artefact> filteredFamilyArtefacts(
) {
final family = AppRoutes.familyFromUri(pageUri);
final artefacts = ref.watch(familyArtefactsProvider(family)).requireValue;
final filters =
emptyArtefactFilters.copyWithQueryParams(pageUri.queryParametersAll);
final filters = createEmptyArtefactFilters(family)
.copyWithQueryParams(pageUri.queryParametersAll);
final searchValue =
pageUri.queryParameters[CommonQueryParameters.searchQuery] ?? '';

Expand Down
2 changes: 1 addition & 1 deletion frontend/lib/providers/page_filters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class PageFilters extends _$PageFilters {
.values
.toList();

return emptyArtefactFilters
return createEmptyArtefactFilters(family)
.copyWithOptionsExtracted(artefacts)
.copyWithQueryParams(pageUri.queryParametersAll);
}
Expand Down
24 changes: 22 additions & 2 deletions frontend/lib/routing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ final appRouter = GoRouter(
),
),
),
GoRoute(
path: AppRoutes.images,
pageBuilder: (_, __) => const NoTransitionPage(
child: Dashboard(),
),
),
GoRoute(
path: '${AppRoutes.images}/:artefactId',
pageBuilder: (context, state) => NoTransitionPage(
child: ArtefactPage(
artefactId: int.parse(state.pathParameters['artefactId']!),
),
),
),
],
),
],
Expand All @@ -80,6 +94,7 @@ class AppRoutes {
static const snaps = '/snaps';
static const debs = '/debs';
static const charms = '/charms';
static const images = '/images';

static Uri uriFromContext(BuildContext context) =>
GoRouterState.of(context).uri;
Expand All @@ -90,6 +105,7 @@ class AppRoutes {
if (path.startsWith(snaps)) return FamilyName.snap;
if (path.startsWith(debs)) return FamilyName.deb;
if (path.startsWith(charms)) return FamilyName.charm;
if (path.startsWith(images)) return FamilyName.image;

throw Exception('Unknown route: $path');
}
Expand All @@ -101,12 +117,13 @@ class AppRoutes {
}

static bool isDashboardPage(Uri uri) =>
{snaps, debs, charms}.contains(uri.path);
{snaps, debs, charms, images}.contains(uri.path);

static bool isArtefactPage(Uri uri) =>
(uri.path.contains(AppRoutes.snaps) ||
uri.path.contains(AppRoutes.debs) ||
uri.path.contains(AppRoutes.charms)) &&
uri.path.contains(AppRoutes.charms) ||
uri.path.contains(AppRoutes.images)) &&
uri.pathSegments.length == 2;
}

Expand All @@ -125,6 +142,9 @@ void navigateToArtefactPage(BuildContext context, int artefactId) {
case FamilyName.charm:
path = AppRoutes.charms + path;
break;
case FamilyName.image:
path = AppRoutes.images + path;
break;
}

context.go(path);
Expand Down
13 changes: 6 additions & 7 deletions frontend/lib/ui/artefact_page/artefact_page_header.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ class ArtefactPageHeader extends StatelessWidget {
const SizedBox(width: Spacing.level4),
ArtefactSignoffButton(artefact: artefact),
const SizedBox(width: Spacing.level4),
if (assignee != null)
UserAvatar(
user: assignee,
allEnvironmentReviewsCount: artefact.allEnvironmentReviewsCount,
completedEnvironmentReviewsCount:
artefact.completedEnvironmentReviewsCount,
),
UserAvatar(
user: assignee,
allEnvironmentReviewsCount: artefact.allEnvironmentReviewsCount,
completedEnvironmentReviewsCount:
artefact.completedEnvironmentReviewsCount,
),
const SizedBox(width: Spacing.level4),
if (dueDate != null)
Text(
Expand Down
15 changes: 15 additions & 0 deletions frontend/lib/ui/artefact_page/artefact_page_info_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ class ArtefactPageInfoSection extends StatelessWidget {
Text('series: ${artefact.series}', style: fontStyle),
if (artefact.repo.isNotEmpty)
Text('repo: ${artefact.repo}', style: fontStyle),
if (artefact.os.isNotEmpty)
Text('os: ${artefact.os}', style: fontStyle),
if (artefact.release.isNotEmpty)
Text('release: ${artefact.release}', style: fontStyle),
if (artefact.owner.isNotEmpty)
Text('owner: ${artefact.owner}', style: fontStyle),
if (artefact.sha256.isNotEmpty)
Text('sha256: ${artefact.sha256}', style: fontStyle),
if (artefact.imageUrl.isNotEmpty)
InlineUrlText(
leadingText: 'image link: ',
url: artefact.imageUrl,
urlText: artefact.imageUrl,
fontStyle: fontStyle,
),
if (bugLink.isNotBlank)
InlineUrlText(
leadingText: 'bug link: ',
Expand Down
Loading

0 comments on commit 4d6016b

Please sign in to comment.