mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 18:58:59 -04:00
added content kind badge and filter to searches
This commit is contained in:
1 parent
e934932b0f
commit
e293aa4d2a
5 files changed
+61
-4
No files matched your search
@@ -2,7 +2,7 @@ import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import type { SearchStatsSummary, SearchEvent } from '@core/models/search-stats.models';
|
||||
import { SeekerSearchType, SeekerSearchReason, SearchCommandStatus } from '@core/models/search-stats.models';
|
||||
import { SeekerSearchType, SeekerSearchReason, SeekerContentKind, SearchCommandStatus } from '@core/models/search-stats.models';
|
||||
import type { PaginatedResult } from '@core/models/pagination.model';
|
||||
|
||||
export enum SortDirection {
|
||||
@@ -28,6 +28,7 @@ export interface SearchEventsQuery {
|
||||
searchStatus?: SearchCommandStatus[];
|
||||
searchType?: SeekerSearchType;
|
||||
searchReason?: SeekerSearchReason;
|
||||
contentKind?: SeekerContentKind;
|
||||
grabbed?: boolean;
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ export class SearchStatsApi {
|
||||
if (query.sortDirection) params = params.set('sortDirection', query.sortDirection);
|
||||
if (query.searchType) params = params.set('searchType', query.searchType);
|
||||
if (query.searchReason) params = params.set('searchReason', query.searchReason);
|
||||
if (query.contentKind) params = params.set('contentKind', query.contentKind);
|
||||
if (query.grabbed !== undefined) params = params.set('grabbed', String(query.grabbed));
|
||||
|
||||
if (query.searchStatus?.length) {
|
||||
|
||||
@@ -38,6 +38,13 @@ export enum SeekerSearchReason {
|
||||
Replacement = 'Replacement',
|
||||
}
|
||||
|
||||
export enum SeekerContentKind {
|
||||
Standard = 'Standard',
|
||||
Daily = 'Daily',
|
||||
Anime = 'Anime',
|
||||
Special = 'Special',
|
||||
}
|
||||
|
||||
export enum SearchCommandStatus {
|
||||
Pending = 'Pending',
|
||||
Started = 'Started',
|
||||
@@ -59,4 +66,5 @@ export interface SearchEvent {
|
||||
grabbedItems: string[] | null;
|
||||
cycleId: string | null;
|
||||
isDryRun: boolean;
|
||||
contentKind: SeekerContentKind | null;
|
||||
}
|
||||
@@ -164,6 +164,9 @@
|
||||
{{ formatSearchReason(event.searchReason) }}
|
||||
</app-badge>
|
||||
}
|
||||
@if (event.contentKind) {
|
||||
<app-badge severity="default" size="sm">{{ event.contentKind }}</app-badge>
|
||||
}
|
||||
@if (event.searchStatus) {
|
||||
<app-badge [severity]="searchStatusSeverity(event.searchStatus)" size="sm">
|
||||
{{ event.searchStatus }}
|
||||
@@ -269,6 +272,15 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<span class="filter-group__label">Content kind</span>
|
||||
<app-select
|
||||
[value]="draft().contentKind"
|
||||
[options]="contentKindOptions"
|
||||
(valueChange)="updateDraft('contentKind', $any($event))"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<span class="filter-group__label">Grabbed</span>
|
||||
<app-select
|
||||
|
||||
+26
-2
@@ -4,7 +4,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { SearchStatsApi, SearchEventsSortBy, SortDirection } from '@core/api/search-stats.api';
|
||||
import type { SearchEventsQuery } from '@core/api/search-stats.api';
|
||||
import { SeekerSearchType, SeekerSearchReason, SearchCommandStatus } from '@core/models/search-stats.models';
|
||||
import { SeekerSearchType, SeekerSearchReason, SeekerContentKind, SearchCommandStatus } from '@core/models/search-stats.models';
|
||||
import type { InstanceSearchStat, SearchEvent, SearchStatsSummary } from '@core/models/search-stats.models';
|
||||
import type { PaginatedResult } from '@core/models/pagination.model';
|
||||
import { AppHubService } from '@core/realtime/app-hub.service';
|
||||
@@ -80,6 +80,7 @@ const EVENTS: PaginatedResult<SearchEvent> = {
|
||||
grabbedItems: ['Release.One', 'Release.Two'],
|
||||
cycleId: 'cycle-aaaaaaaa-1111',
|
||||
isDryRun: false,
|
||||
contentKind: SeekerContentKind.Special,
|
||||
},
|
||||
{
|
||||
id: 'event-2',
|
||||
@@ -94,6 +95,7 @@ const EVENTS: PaginatedResult<SearchEvent> = {
|
||||
grabbedItems: null,
|
||||
cycleId: null,
|
||||
isDryRun: true,
|
||||
contentKind: null,
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
@@ -198,6 +200,7 @@ describe('SearchesTabComponent', () => {
|
||||
searchStatus: undefined,
|
||||
searchType: undefined,
|
||||
searchReason: undefined,
|
||||
contentKind: undefined,
|
||||
grabbed: undefined,
|
||||
});
|
||||
});
|
||||
@@ -213,24 +216,37 @@ describe('SearchesTabComponent', () => {
|
||||
component.toggleStatus(SearchCommandStatus.Completed);
|
||||
component.updateDraft('searchType', SeekerSearchType.Replacement);
|
||||
component.updateDraft('searchReason', SeekerSearchReason.Missing);
|
||||
component.updateDraft('contentKind', SeekerContentKind.Anime);
|
||||
component.updateDraft('grabbed', 'false');
|
||||
component.applyFilters();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.drawerOpen()).toBe(false);
|
||||
expect(component.selectedInstanceId()).toBe('radarr-zulu');
|
||||
expect(component.activeFilterCount()).toBe(5);
|
||||
expect(component.activeFilterCount()).toBe(6);
|
||||
expect(lastQuery()).toMatchObject({
|
||||
instanceId: 'radarr-zulu',
|
||||
search: 'show',
|
||||
searchStatus: [SearchCommandStatus.Failed],
|
||||
searchType: SeekerSearchType.Replacement,
|
||||
searchReason: SeekerSearchReason.Missing,
|
||||
contentKind: SeekerContentKind.Anime,
|
||||
grabbed: false,
|
||||
cycleId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('sends the chosen content kind in the events query', () => {
|
||||
const { fixture, component, lastQuery } = setup();
|
||||
|
||||
component.openFilters();
|
||||
component.updateDraft('contentKind', SeekerContentKind.Special);
|
||||
component.applyFilters();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(lastQuery().contentKind).toBe(SeekerContentKind.Special);
|
||||
});
|
||||
|
||||
it('resolves the current cycle id only for the selected instance and drops it when the instance is cleared', () => {
|
||||
const { fixture, component, lastQuery } = setup();
|
||||
|
||||
@@ -333,6 +349,14 @@ describe('SearchesTabComponent', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('shows a content kind badge only for an event that has one', () => {
|
||||
const { fixture } = setup();
|
||||
|
||||
const rows = fixture.nativeElement.querySelectorAll('.list-row');
|
||||
expect((rows[0] as HTMLElement).textContent).toContain('Special');
|
||||
expect((rows[1] as HTMLElement).textContent).not.toContain('Special');
|
||||
});
|
||||
|
||||
it('shows how many items remain in the cycle progress row', () => {
|
||||
const { fixture } = setup();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { SearchStatsApi, SearchEventsSortBy, SortDirection } from '@core/api/sea
|
||||
import type { SearchEventsQuery } from '@core/api/search-stats.api';
|
||||
import type { SearchStatsSummary, SearchEvent, InstanceSearchStat } from '@core/models/search-stats.models';
|
||||
import type { PaginatedResult } from '@core/models/pagination.model';
|
||||
import { SeekerSearchType, SeekerSearchReason, SearchCommandStatus } from '@core/models/search-stats.models';
|
||||
import { SeekerSearchType, SeekerSearchReason, SeekerContentKind, SearchCommandStatus } from '@core/models/search-stats.models';
|
||||
import { AppHubService } from '@core/realtime/app-hub.service';
|
||||
import { ToastService } from '@core/services/toast.service';
|
||||
import { PaginationService, PAGE_SIZE_STORAGE_KEYS } from '@core/services/pagination.service';
|
||||
@@ -46,6 +46,7 @@ interface AdvancedFilters extends Record<string, unknown> {
|
||||
statuses: SearchCommandStatus[];
|
||||
searchType: SeekerSearchType | '';
|
||||
searchReason: SeekerSearchReason | '';
|
||||
contentKind: SeekerContentKind | '';
|
||||
grabbed: TriState;
|
||||
}
|
||||
|
||||
@@ -55,6 +56,7 @@ const EMPTY_FILTERS: AdvancedFilters = {
|
||||
statuses: [],
|
||||
searchType: '',
|
||||
searchReason: '',
|
||||
contentKind: '',
|
||||
grabbed: 'any',
|
||||
};
|
||||
|
||||
@@ -163,6 +165,7 @@ export class SearchesTabComponent {
|
||||
searchStatus: a.statuses.length ? a.statuses : undefined,
|
||||
searchType: a.searchType || undefined,
|
||||
searchReason: a.searchReason || undefined,
|
||||
contentKind: a.contentKind || undefined,
|
||||
grabbed: triToBool(a.grabbed),
|
||||
};
|
||||
});
|
||||
@@ -207,6 +210,14 @@ export class SearchesTabComponent {
|
||||
{ label: 'Replacement', value: SeekerSearchReason.Replacement },
|
||||
];
|
||||
|
||||
readonly contentKindOptions: SelectOption[] = [
|
||||
{ label: 'Any', value: '' },
|
||||
{ label: 'Standard', value: SeekerContentKind.Standard },
|
||||
{ label: 'Daily', value: SeekerContentKind.Daily },
|
||||
{ label: 'Anime', value: SeekerContentKind.Anime },
|
||||
{ label: 'Special', value: SeekerContentKind.Special },
|
||||
];
|
||||
|
||||
readonly triStateOptions: SelectOption[] = [
|
||||
{ label: 'Any', value: 'any' },
|
||||
{ label: 'Yes', value: 'true' },
|
||||
|
||||
Reference in new issue
Block a user