Skip to content

Commit

Permalink
Add new favorite api
Browse files Browse the repository at this point in the history
Add favorite filter to search query

Fix `AssetInfoRepo.list`

Add favorites to search stats

Add favorties filter to asset view

Add favorites page

Use `favorites` as default filter

Reset zoom when search is reset

Make favorite items link to their asset

Show asset viewer on favourite page

Fix tests

fix error with empty buckets

Reset favorites filter when navigating to home

Fix hover color of active favorites

Cleanup code
  • Loading branch information
daniel-va committed Nov 7, 2024
1 parent 4590e79 commit b2df577
Show file tree
Hide file tree
Showing 79 changed files with 716 additions and 268 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,23 @@
asset-sg-menu-bar-item
icon="assets"
[link]="isActive ? null : [translateService.currentLang]"
[isActive]="isActive && ((isFiltersOpen$ | async) ?? false)"
[isActive]="isActive"
(click)="isActive ? toggleAssetDrawer() : null"
>
menuBar.filters
</li>
<li *ngIf="userExists$ | async" asset-sg-menu-bar-item icon="favourite" disabled>menuBar.favourites</li>
@if (userExists$ | async) {
<li
*rxLet="activeItem === 'favorites'; let isActive"
asset-sg-menu-bar-item
icon="favorite"
[link]="isActive ? null : [translateService.currentLang, 'favorites']"
[isActive]="isActive"
(click)="isActive ? toggleAssetDrawer() : null"
>
menuBar.favourites
</li>
}
<li
*canCreate="AssetEditPolicy"
asset-sg-menu-bar-item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ export class MenuBarComponent {

readonly activeItem$: Observable<MenuItem | null> = this.router.events.pipe(
filter((event) => event instanceof NavigationEnd),
startWith(() => undefined),
map((): MenuItem | null => {
const segments = this.router.parseUrl(this.router.url).root.children['primary'].segments;
if (segments.length === 1) {
const segments = (this.router.getCurrentNavigation() ?? this.router.lastSuccessfulNavigation)?.finalUrl?.root
.children['primary'].segments;
if (segments == null || segments.length === 1) {
return 'home';
}
const path = segments.slice(1).join('/');
Expand All @@ -43,6 +45,9 @@ export class MenuBarComponent {
if (isPath('asset-admin/new')) {
return 'create-asset';
}
if (isPath('favorites')) {
return 'favorites';
}
if (path == 'asset-admin' || isPath('admin')) {
return 'options';
}
Expand All @@ -58,4 +63,4 @@ export class MenuBarComponent {
protected readonly AssetEditPolicy = AssetEditPolicy;
}

type MenuItem = 'home' | 'create-asset' | 'options';
type MenuItem = 'home' | 'favorites' | 'create-asset' | 'options';
3 changes: 3 additions & 0 deletions apps/client-asset-sg/src/app/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export const deAppTranslations = {
nameTaken: "Der Name '{{name}}' wird bereits von einer anderen Arbeitsgruppe verwendet.",
},
},
favorites: {
title: 'Favoriten',
},
menuBar: {
filters: 'Filter',
admin: 'Verwaltung',
Expand Down
3 changes: 3 additions & 0 deletions apps/client-asset-sg/src/app/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const enAppTranslations: AppTranslations = {
nameTaken: "The name '{{name}}' is already taken by another workgroup.",
},
},
favorites: {
title: 'Favorites',
},
menuBar: {
filters: 'Filters',
admin: 'Administration',
Expand Down
3 changes: 3 additions & 0 deletions apps/client-asset-sg/src/app/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const frAppTranslations: AppTranslations = {
nameTaken: "Le nom '{{name}}' est déjà utilisé par un autre groupe de travail.",
},
},
favorites: {
title: 'Favorites',
},
menuBar: {
filters: 'Filtres',
admin: 'Administration',
Expand Down
3 changes: 3 additions & 0 deletions apps/client-asset-sg/src/app/i18n/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const itAppTranslations: AppTranslations = {
nameTaken: "IT Der Name '{{name}}' wird bereits von einer anderen Arbeitsgruppe verwendet.",
},
},
favorites: {
title: 'IT Favoriten',
},
menuBar: {
filters: 'IT Filter',
admin: 'IT Verwaltung',
Expand Down
3 changes: 3 additions & 0 deletions apps/client-asset-sg/src/app/i18n/rm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const rmAppTranslations: AppTranslations = {
nameTaken: "RM Name '{{name}}' wird bereits von einer anderen Arbeitsgruppe verwendet.",
},
},
favorites: {
title: 'RM Favoriten',
},
menuBar: {
filters: 'RM Filter',
admin: 'RM Verwaltung',
Expand Down
24 changes: 20 additions & 4 deletions apps/server-asset-sg/src/app.logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,30 @@ export class AppLogger implements LoggerService {
if (!(message instanceof Error)) {
output += level.color(`${message}`);
}
const suffix = [];
if (params.length !== 0 && !(params.length === 1 && params[0] === undefined)) {
output += ' ' + stringify(params, level);
let i = 0;
while (typeof params[i] === 'string') {
const line = params[i] as string;
suffix.push(line);
i += 1;
if (i >= params.length || line.endsWith('\n')) {
break;
}
}
params = params.slice(i);
if (params.length !== 0) {
output += ' ' + stringify(params, level);
}
}
const args: unknown[] = [`${prefix} ${output}`];
if (message instanceof Error) {
console.log(`${prefix} ${output}`, message);
} else {
console.log(`${prefix} ${output}`);
args.push(message);
}
if (suffix.length !== 0) {
args.push(level.color(`\n${suffix.join('\n')}`));
}
console.log(...args);
}
}

Expand Down
2 changes: 1 addition & 1 deletion apps/server-asset-sg/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ import { WorkgroupsController } from '@/features/workgroups/workgroups.controlle
@Module({
controllers: [
AppController,
FavoritesController,
AssetEditController,
AssetSearchController,
AssetSyncController,
AssetsController,
ContactsController,
FavoritesController,
FilesController,
StudiesController,
UsersController,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { fakeAssetFormatItemCode } from '../../../../../test/data/asset-format-i
// eslint-disable-next-line @nx/enforce-module-boundaries
import { fakeAssetKindItemCode } from '../../../../../test/data/asset-kind-item';
// eslint-disable-next-line @nx/enforce-module-boundaries
import { fakeContactKindItem } from '../../../../../test/data/contact-kind-item';
import { fakeContactKindItemCode } from '../../../../../test/data/contact-kind-item';

import { define } from '@/utils/define';

Expand All @@ -35,7 +35,7 @@ export const fakeUser = () => {

export const fakeContact = () =>
define<Omit<Contact, 'id'>>({
contactKindItemCode: fakeContactKindItem(),
contactKindItemCode: fakeContactKindItemCode(),
name: faker.company.name(),
street: faker.location.street(),
houseNumber: faker.location.buildingNumber(),
Expand Down
18 changes: 17 additions & 1 deletion apps/server-asset-sg/src/features/assets/asset-info.repo.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { AssetId, AssetInfo } from '@asset-sg/shared/v2';
import { AssetId, AssetInfo, UserId } from '@asset-sg/shared/v2';
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/core/prisma.service';
import { ReadRepo, RepoListOptions } from '@/core/repo';
import { assetInfoSelection, parseAssetInfoFromPrisma } from '@/features/assets/prisma-asset';

@Injectable()
export class AssetInfoRepo implements ReadRepo<AssetInfo, AssetId> {
constructor(private readonly prisma: PrismaService) {}

Expand All @@ -28,4 +30,18 @@ export class AssetInfoRepo implements ReadRepo<AssetInfo, AssetId> {
});
return entries.map(parseAssetInfoFromPrisma);
}

async listFavorites(userId: UserId): Promise<AssetInfo[]> {
const entries = await this.prisma.asset.findMany({
where: {
favorites: {
some: {
userId: userId,
},
},
},
select: assetInfoSelection,
});
return entries.map(parseAssetInfoFromPrisma);
}
}
4 changes: 2 additions & 2 deletions apps/server-asset-sg/src/features/assets/asset.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { handlePrismaMutationError } from '@/utils/prisma';
export class AssetRepo implements FindRepo<Asset, AssetId>, MutateRepo<Asset, AssetId, FullAssetData> {
constructor(private readonly prisma: PrismaService) {}

async count() {
return await this.prisma.asset.count();
async count(): Promise<number> {
return this.prisma.asset.count();
}

async find(id: AssetId): Promise<Asset | null> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class AssetSearchController {
limit = limit == null ? limit : Number(limit);
offset = offset == null ? offset : Number(offset);
restrictQueryForUser(query, user);
const result = await this.assetSearchService.search(query, { limit, offset, decode: false });
const result = await this.assetSearchService.search(query, user, { limit, offset, decode: false });
return plainToInstance(AssetSearchResultDTO, result);
}

Expand All @@ -48,7 +48,7 @@ export class AssetSearchController {
@CurrentUser() user: User
): Promise<AssetSearchStats> {
restrictQueryForUser(query, user);
const stats = await this.assetSearchService.aggregate(query);
const stats = await this.assetSearchService.aggregate(query, user);
return plainToInstance(AssetSearchStatsDTO, stats);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe(AssetSearchService, () => {
await create({ patch: fakeAssetPatch(), user });

// When
const result = await service.search({ text: `${text}` });
const result = await service.search({ text: `${text}` }, user);

// Then
assertSingleResult(result, asset);
Expand Down Expand Up @@ -194,11 +194,14 @@ describe(AssetSearchService, () => {
});

// When
const result = await service.search({
createDate: {
min: new Date(dateFromDateId(asset.createDate).getTime() - millisPerDay),
const result = await service.search(
{
createDate: {
min: new Date(dateFromDateId(asset.createDate).getTime() - millisPerDay),
},
},
});
user
);

// Then
assertSingleResult(result, asset);
Expand All @@ -217,11 +220,14 @@ describe(AssetSearchService, () => {
});

// When
const result = await service.search({
createDate: {
max: new Date(dateFromDateId(asset.createDate).getTime() + millisPerDay),
const result = await service.search(
{
createDate: {
max: new Date(dateFromDateId(asset.createDate).getTime() + millisPerDay),
},
},
});
user
);

// Then
assertSingleResult(result, asset);
Expand All @@ -244,14 +250,18 @@ describe(AssetSearchService, () => {
},
user: fakeUser(),
});
const user = fakeUser();

// When
const result = await service.search({
createDate: {
min: new Date(dateFromDateId(asset.createDate).getTime() - millisPerDay),
max: new Date(dateFromDateId(asset.createDate).getTime() + millisPerDay),
const result = await service.search(
{
createDate: {
min: new Date(dateFromDateId(asset.createDate).getTime() - millisPerDay),
max: new Date(dateFromDateId(asset.createDate).getTime() + millisPerDay),
},
},
});
user
);

// Then
assertSingleResult(result, asset);
Expand All @@ -274,9 +284,10 @@ describe(AssetSearchService, () => {
patch: { ...fakeAssetPatch(), assetLanguages: [{ languageItemCode: code3 }] },
user: fakeUser(),
});
const user = fakeUser();

// When
const result = await service.search({ languageItemCodes: [code1] });
const result = await service.search({ languageItemCodes: [code1] }, user);

// Then
assertSingleResult(result, asset);
Expand All @@ -293,7 +304,7 @@ describe(AssetSearchService, () => {
await create({ patch: { ...fakeAssetPatch(), assetKindItemCode: code3 }, user });

// When
const result = await service.search({ assetKindItemCodes: [code1] });
const result = await service.search({ assetKindItemCodes: [code1] }, user);

// Then
assertSingleResult(result, asset);
Expand All @@ -310,7 +321,7 @@ describe(AssetSearchService, () => {
await create({ patch: { ...fakeAssetPatch(), manCatLabelRefs: [code3] }, user });

// When
const result = await service.search({ manCatLabelItemCodes: [code1] });
const result = await service.search({ manCatLabelItemCodes: [code1] }, user);

// Then
assertSingleResult(result, asset);
Expand Down Expand Up @@ -346,7 +357,7 @@ describe(AssetSearchService, () => {
});

// When
const result = await service.search({ usageCodes: [usageCode] });
const result = await service.search({ usageCodes: [usageCode] }, user);

// Then
assertSingleResult(result, asset);
Expand All @@ -371,7 +382,7 @@ describe(AssetSearchService, () => {
});

// When
const result = await service.search({ authorId: contact1.contactId });
const result = await service.search({ authorId: contact1.contactId }, user);

// Then
assertSingleResult(result, asset);
Expand Down Expand Up @@ -417,8 +428,11 @@ describe(AssetSearchService, () => {
};

it('returns empty stats when no assets are present', async () => {
// Given
const user = fakeUser();

// When
const result = await service.aggregate({});
const result = await service.aggregate({}, user);

// Then
expect(result.total).toEqual(0);
Expand All @@ -436,7 +450,7 @@ describe(AssetSearchService, () => {
const asset = await create({ patch: fakeAssetPatch(), user });

// When
const result = await service.aggregate({});
const result = await service.aggregate({}, user);

// Then
assertSingleStats(result, asset);
Expand Down
Loading

0 comments on commit b2df577

Please sign in to comment.