Added filtering on status to the requests page

pull/3539/head
Jamie Rees 4 years ago
parent 1ea6a6dc48
commit 4a8c1cd25a

1
.gitignore vendored

@ -248,3 +248,4 @@ _Pvt_Extensions
*.vscode *.vscode
/src/Ombi/database.json /src/Ombi/database.json
/src/Ombi/healthchecksdb /src/Ombi/healthchecksdb
/src/Ombi/ClientApp/package-lock.json

@ -267,10 +267,10 @@ namespace Ombi.Core.Engine
allRequests = allRequests.Where(x => x.Approved && !x.Available && (!x.Denied.HasValue || !x.Denied.Value)); allRequests = allRequests.Where(x => x.Approved && !x.Available && (!x.Denied.HasValue || !x.Denied.Value));
break; break;
case RequestStatus.Available: case RequestStatus.Available:
allRequests = allRequests.Where(x => x.Available && (!x.Denied.HasValue || !x.Denied.Value)); allRequests = allRequests.Where(x => x.Available);
break; break;
case RequestStatus.Denied: case RequestStatus.Denied:
allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value); allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available);
break; break;
default: default:
break; break;
@ -332,12 +332,11 @@ namespace Ombi.Core.Engine
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true); //var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
} }
allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase) var requests = (sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
? allRequests.OrderBy(x => prop.GetValue(x)) ? allRequests.ToList().OrderBy(x => prop.GetValue(x))
: allRequests.OrderByDescending(x => prop.GetValue(x)); : allRequests.ToList().OrderByDescending(x => prop.GetValue(x))).ToList();
var total = await allRequests.CountAsync(); var total = requests.Count();
var requests = await allRequests.Skip(position).Take(count) requests = requests.Skip(position).Take(count).ToList();
.ToListAsync();
await CheckForSubscription(shouldHide, requests); await CheckForSubscription(shouldHide, requests);
return new RequestsViewModel<MovieRequests> return new RequestsViewModel<MovieRequests>

@ -93,7 +93,7 @@ namespace Ombi.Store.Context
} }
needToSave = true; needToSave = true;
NotificationTemplates notificationToAdd; NotificationTemplates notificationToAdd = null;
switch (notificationType) switch (notificationType)
{ {
case NotificationType.NewRequest: case NotificationType.NewRequest:
@ -207,7 +207,10 @@ namespace Ombi.Store.Context
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
NotificationTemplates.Add(notificationToAdd); if (notificationToAdd != null)
{
NotificationTemplates.Add(notificationToAdd);
}
} }
} }

@ -1,57 +1,73 @@
<div class="mat-elevation-z8"> <div class="mat-elevation-z8">
<grid-spinner [loading]="isLoadingResults"></grid-spinner> <grid-spinner [loading]="isLoadingResults"></grid-spinner>
<mat-form-field>
<mat-select placeholder="Requests to Display" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()"> <!-- <div class="row"> -->
<mat-option value="10">10</mat-option> <div class="row justify-content-md-center top-spacing">
<mat-option value="15">15</mat-option> <div class="btn-group" role="group">
<mat-option value="30">30</mat-option> <button type="button" (click)="switchFilter(RequestFilter.All)" [attr.color]="currentFilter === RequestFilter.All ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.All ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.AllRequests' | translate}}</button>
<mat-option value="100">100</mat-option> <button type="button" (click)="switchFilter(RequestFilter.Pending)" [attr.color]="currentFilter === RequestFilter.Pending ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Pending ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.PendingRequests' | translate}}</button>
</mat-select> <button type="button" (click)="switchFilter(RequestFilter.Processing)" [attr.color]="currentFilter === RequestFilter.Processing ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Processing ? 'mat-accent' : 'mat-primary'" mat-raised-button
</mat-form-field> class="btn grow">{{'Requests.ProcessingRequests' | translate}}</button>
<button type="button" (click)="switchFilter(RequestFilter.Available)" [attr.color]="currentFilter === RequestFilter.Available ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Available ? 'mat-accent' : 'mat-primary'" mat-raised-button
<table mat-table [dataSource]="dataSource" class="table" matSort [matSortActive]="defaultSort" class="btn grow">{{'Requests.AvailableRequests' | translate}}</button>
matSortDisableClear [matSortDirection]="defaultOrder"> <button type="button" (click)="switchFilter(RequestFilter.Denied)" [attr.color]="currentFilter === RequestFilter.Denied ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Denied ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.DeniedRequests' | translate}}</button>
</div>
</div>
<ng-container matColumnDef="requestedUser.requestedBy">
<th mat-header-cell *matHeaderCellDef > Requested By </th> <div class="row">
<td mat-cell *matCellDef="let element"> {{element.requestedUser?.userAlias}} </td> <div class="col-md-2 offset-md-10">
</ng-container> <mat-form-field>
<mat-select placeholder="Requests to Display" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
<ng-container matColumnDef="title"> <mat-option value="10">10</mat-option>
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Title </th> <mat-option value="15">15</mat-option>
<td mat-cell *matCellDef="let element"> {{element.title}} ({{element.releaseDate | amLocal | amDateFormat: <mat-option value="30">30</mat-option>
'YYYY'}}) </td> <mat-option value="100">100</mat-option>
</ng-container> </mat-select>
</mat-form-field>
<ng-container matColumnDef="requestedDate"> </div>
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Request Date </th> </div>
<td mat-cell *matCellDef="let element"> {{element.requestedDate | amLocal | amDateFormat: 'LL'}} </td> <!-- </div> -->
</ng-container> <table mat-table [dataSource]="dataSource" class="table" matSort [matSortActive]="defaultSort" matSortDisableClear [matSortDirection]="defaultOrder">
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Status </th> <ng-container matColumnDef="requestedUser.requestedBy">
<td mat-cell *matCellDef="let element"> {{element.status}} </td> <th mat-header-cell *matHeaderCellDef> Requested By </th>
</ng-container> <td mat-cell *matCellDef="let element"> {{element.requestedUser?.userAlias}} </td>
</ng-container>
<ng-container matColumnDef="requestStatus"> <ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Request Status </th> <th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Title </th>
<td mat-cell *matCellDef="let element"> {{element.requestStatus | translate}} </td> <td mat-cell *matCellDef="let element"> {{element.title}} ({{element.releaseDate | amLocal | amDateFormat: 'YYYY'}}) </td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions" > <ng-container matColumnDef="requestedDate">
<th mat-header-cell *matHeaderCellDef> </th> <th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Request Date </th>
<td mat-cell *matCellDef="let element" > <td mat-cell *matCellDef="let element"> {{element.requestedDate | amLocal | amDateFormat: 'LL'}} </td>
<button mat-raised-button color="accent" [routerLink]="'/details/movie/' + element.theMovieDbId">Details</button> </ng-container>
<button mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin">Options</button>
</td> <ng-container matColumnDef="status">
</ng-container> <th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Status </th>
<td mat-cell *matCellDef="let element"> {{element.status}} </td>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> </ng-container>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<ng-container matColumnDef="requestStatus">
<mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator> <th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Request Status </th>
</div> <td mat-cell *matCellDef="let element"> {{element.requestStatus | translate}} </td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> </th>
<td mat-cell *matCellDef="let element">
<button mat-raised-button color="accent" [routerLink]="'/details/movie/' + element.theMovieDbId">Details</button>
<button mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin">Options</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
</div>

@ -7,6 +7,7 @@ import { catchError, map, startWith, switchMap } from 'rxjs/operators';
import { RequestServiceV2 } from "../../../services/requestV2.service"; import { RequestServiceV2 } from "../../../services/requestV2.service";
import { AuthService } from "../../../auth/auth.service"; import { AuthService } from "../../../auth/auth.service";
import { StorageService } from "../../../shared/storage/storage-service"; import { StorageService } from "../../../shared/storage/storage-service";
import { RequestFilterType } from "../../models/RequestFilterType";
@Component({ @Component({
templateUrl: "./movies-grid.component.html", templateUrl: "./movies-grid.component.html",
@ -19,14 +20,19 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
public isLoadingResults = true; public isLoadingResults = true;
public displayedColumns: string[] = ['requestedUser.requestedBy', 'title', 'requestedDate', 'status', 'requestStatus', 'actions']; public displayedColumns: string[] = ['requestedUser.requestedBy', 'title', 'requestedDate', 'status', 'requestStatus', 'actions'];
public gridCount: string = "15"; public gridCount: string = "15";
public showUnavailableRequests: boolean;
public isAdmin: boolean; public isAdmin: boolean;
public defaultSort: string = "requestedDate"; public defaultSort: string = "requestedDate";
public defaultOrder: string = "desc"; public defaultOrder: string = "desc";
public RequestFilter = RequestFilterType;
private currentFilter: RequestFilterType = RequestFilterType.All;
private storageKey = "Movie_DefaultRequestListSort"; private storageKey = "Movie_DefaultRequestListSort";
private storageKeyOrder = "Movie_DefaultRequestListSortOrder"; private storageKeyOrder = "Movie_DefaultRequestListSortOrder";
private storageKeyGridCount = "Movie_DefaultGridCount"; private storageKeyGridCount = "Movie_DefaultGridCount";
private storageKeyCurrentFilter = "Movie_DefaultFilter";
private $data: Observable<any>;
@Output() public onOpenOptions = new EventEmitter<{ request: any, filter: any, onChange: any }>(); @Output() public onOpenOptions = new EventEmitter<{ request: any, filter: any, onChange: any }>();
@ -38,19 +44,25 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
} }
public ngOnInit() { public ngOnInit() {
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
const defaultCount = this.storageService.get(this.storageKeyGridCount); const defaultCount = this.storageService.get(this.storageKeyGridCount);
const defaultSort = this.storageService.get(this.storageKey); const defaultSort = this.storageService.get(this.storageKey);
const defaultOrder = this.storageService.get(this.storageKeyOrder); const defaultOrder = this.storageService.get(this.storageKeyOrder);
const defaultFilter = +this.storageService.get(this.storageKeyCurrentFilter);
if (defaultSort) { if (defaultSort) {
this.defaultSort = defaultSort; this.defaultSort = defaultSort;
} }
if (defaultOrder) { if (defaultOrder) {
this.defaultOrder = defaultOrder; this.defaultOrder = defaultOrder;
} }
if(defaultCount) { if (defaultCount) {
this.gridCount = defaultCount; this.gridCount = defaultCount;
} }
if (defaultFilter) {
this.currentFilter = defaultFilter;
}
} }
public async ngAfterViewInit() { public async ngAfterViewInit() {
@ -60,13 +72,12 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
// this.resultsLength = results.total; // this.resultsLength = results.total;
this.storageService.save(this.storageKeyGridCount, this.gridCount); this.storageService.save(this.storageKeyGridCount, this.gridCount);
this.storageService.save(this.storageKeyCurrentFilter, (+this.currentFilter).toString());
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
// If the user changes the sort order, reset back to the first page. // If the user changes the sort order, reset back to the first page.
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
merge(this.sort.sortChange, this.paginator.page) merge(this.sort.sortChange, this.paginator.page, this.currentFilter)
.pipe( .pipe(
startWith({}), startWith({}),
switchMap((value: any) => { switchMap((value: any) => {
@ -92,11 +103,19 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
} }
public loadData(): Observable<IRequestsViewModel<IMovieRequests>> { public loadData(): Observable<IRequestsViewModel<IMovieRequests>> {
if (this.showUnavailableRequests) { switch(RequestFilterType[RequestFilterType[this.currentFilter]]) {
return this.requestService.getMovieUnavailableRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction); case RequestFilterType.All:
} else { return this.requestService.getMovieRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
return this.requestService.getMovieRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction); case RequestFilterType.Pending:
return this.requestService.getMoviePendingRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
case RequestFilterType.Available:
return this.requestService.getMovieAvailableRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
case RequestFilterType.Processing:
return this.requestService.getMovieProcessingRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
case RequestFilterType.Denied:
return this.requestService.getMovieDeniedRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
} }
} }
public openOptions(request: IMovieRequests) { public openOptions(request: IMovieRequests) {
@ -112,4 +131,9 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
this.onOpenOptions.emit({ request: request, filter: filter, onChange: onChange }); this.onOpenOptions.emit({ request: request, filter: filter, onChange: onChange });
} }
public switchFilter(type: RequestFilterType) {
this.currentFilter = type;
this.ngAfterViewInit();
}
} }

@ -0,0 +1,7 @@
export enum RequestFilterType {
All,
Pending,
Processing,
Available,
Denied
}

@ -17,6 +17,22 @@ export class RequestServiceV2 extends ServiceHelpers {
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers}); return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
} }
public getMovieAvailableRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/available/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
}
public getMovieProcessingRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/processing/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
}
public getMoviePendingRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/pending/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
}
public getMovieDeniedRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/denied/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
}
public getTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> { public getTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers}); return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
} }

@ -40,7 +40,7 @@
</div> </div>
<div> <div>
<mat-checkbox formControlName="ignoreCertificateErrors" matTooltip="Enable if you are having connectivity problems over SSL"> <mat-checkbox formControlName="ignoreCertificateErrors" matTooltip="Enable if you are having connectivity problems over SSL">
Ignore any certificate errors Ignore any certificate errors (Please restart after changing)
</mat-checkbox> </mat-checkbox>
</div> </div>
<div> <div>
@ -71,4 +71,4 @@
</div> </div>
</div> </div>
</form> </form>
</fieldset> </fieldset>

@ -35,8 +35,9 @@ namespace Ombi.Controllers.V2
{ {
return await _movieRequestEngine.GetRequests(count, position, sort, sortOrder); return await _movieRequestEngine.GetRequests(count, position, sort, sortOrder);
} }
[HttpGet("movie/availble/{count:int}/{position:int}/{sort}/{sortOrder}")] [HttpGet("movie/availble/{count:int}/{position:int}/{sort}/{sortOrder}")]
[HttpGet("movie/available/{count:int}/{position:int}/{sort}/{sortOrder}")]
public async Task<RequestsViewModel<MovieRequests>> GetAvailableRequests(int count, int position, string sort, string sortOrder) public async Task<RequestsViewModel<MovieRequests>> GetAvailableRequests(int count, int position, string sort, string sortOrder)
{ {
return await _movieRequestEngine.GetRequestsByStatus(count, position, sort, sortOrder, RequestStatus.Available); return await _movieRequestEngine.GetRequestsByStatus(count, position, sort, sortOrder, RequestStatus.Available);

@ -153,7 +153,12 @@
"NextHours": "Another request will be added in {{time}} hours", "NextHours": "Another request will be added in {{time}} hours",
"NextMinutes": "Another request will be added in {{time}} minutes", "NextMinutes": "Another request will be added in {{time}} minutes",
"NextMinute": "Another request will be added in {{time}} minute" "NextMinute": "Another request will be added in {{time}} minute"
} },
"AllRequests": "All Requests",
"PendingRequests": "Pending Requests",
"ProcessingRequests": "Processing Requests",
"AvailableRequests": "Available Requests",
"DeniedRequests": "Denied Requests"
}, },
"Issues": { "Issues": {
"Title": "Issues", "Title": "Issues",

Loading…
Cancel
Save