more wip on the issues

pull/4083/head
tidusjar 3 years ago
parent 1289b840a0
commit 6683f8070c

@ -12,7 +12,7 @@ namespace Ombi.Core.Engine.V2
public interface IIssuesEngine
{
Task<IEnumerable<IssuesSummaryModel>> GetIssues(int position, int take, IssueStatus status, CancellationToken token);
Task<IEnumerable<IssuesSummaryModel>> GetIssuesByTitle(string title, CancellationToken token);
Task<IssuesSummaryModel> GetIssuesByProviderId(string providerId, CancellationToken token);
}
public class IssuesEngine : IIssuesEngine
@ -32,7 +32,7 @@ namespace Ombi.Core.Engine.V2
public async Task<IEnumerable<IssuesSummaryModel>> GetIssues(int position, int take, IssueStatus status, CancellationToken token)
{
var issues = await _issues.GetAll().Include(x => x.UserReported).Include(x => x.IssueCategory).Where(x => x.Status == status).Skip(position).Take(take).OrderBy(x => x.Title).ToListAsync(token);
var issues = await _issues.GetAll().Where(x => x.Status == status).Skip(position).Take(take).OrderBy(x => x.Title).ToListAsync(token);
var grouped = issues.GroupBy(x => x.Title, (key, g) => new { Title = key, Issues = g });
var model = new List<IssuesSummaryModel>();
@ -43,32 +43,30 @@ namespace Ombi.Core.Engine.V2
{
Count = group.Issues.Count(),
Title = group.Title,
Issues = group.Issues
ProviderId = group.Issues.FirstOrDefault()?.ProviderId
});
}
return model;
}
public async Task<IEnumerable<IssuesSummaryModel>> GetIssuesByTitle(string title, CancellationToken token)
public async Task<IssuesSummaryModel> GetIssuesByProviderId(string providerId, CancellationToken token)
{
var lowerTitle = title.ToLowerInvariant();
var issues = await _issues.GetAll().Include(x => x.UserReported).Include(x => x.IssueCategory).Where(x => x.Title.ToLowerInvariant() == lowerTitle).ToListAsync(token);
var grouped = issues.GroupBy(x => x.Title, (key, g) => new { Title = key, Issues = g });
var model = new List<IssuesSummaryModel>();
var issues = await _issues.GetAll().Include(x => x.Comments).ThenInclude(x => x.User).Include(x => x.UserReported).Include(x => x.IssueCategory).Where(x => x.ProviderId == providerId).ToListAsync(token);
var grouped = issues.GroupBy(x => x.Title, (key, g) => new { Title = key, Issues = g }).FirstOrDefault();
foreach (var group in grouped)
if (grouped == null)
{
model.Add(new IssuesSummaryModel
{
Count = group.Issues.Count(),
Title = group.Title,
Issues = group.Issues
});
return null;
}
return model;
return new IssuesSummaryModel
{
Count = grouped.Issues.Count(),
Title = grouped.Title,
ProviderId = grouped.Issues.FirstOrDefault()?.ProviderId,
Issues = grouped.Issues
};
}
}
@ -77,6 +75,7 @@ namespace Ombi.Core.Engine.V2
{
public string Title { get; set; }
public int Count { get; set; }
public string ProviderId { get; set; }
public IEnumerable<Issues> Issues { get; set; }
}
}

@ -145,6 +145,7 @@ export function JwtTokenGetter() {
MatCheckboxModule,
MatProgressSpinnerModule,
MDBBootstrapModule.forRoot(),
// NbThemeModule.forRoot({ name: 'dark'}),
JwtModule.forRoot({
config: {
tokenGetter: JwtTokenGetter,

@ -66,5 +66,6 @@ export interface IUpdateStatus {
export interface IIssuesSummary {
title: string;
count: number;
providerId: string;
issues: IIssues[];
}

@ -0,0 +1,38 @@
<div *ngIf="details">
<h1>Issues for {{details.title}}</h1>
<div>
<span>Has Request {{hasRequest}}</span>
<div class="row">
<div class="col-6 top-spacing" *ngFor="let issue of details.issues">
<div color="accent">
<div>
<div>{{issue.subject}}</div>
<span>{{issue.userReported?.userName}} on {{issue.createdDate | date:short}}</span>
</div>
<div>
<p>
{{issue.description}}
</p>
</div>
<div>
<button mat-raised-button color="accent">{{'Issues.Chat' | translate }}</button>
<span *ngIf="isAdmin && settings">
<button mat-raised-button color="accent"
*ngIf="issue.status === IssueStatus.Pending && settings.enableInProgress"
(click)="inProgress(issue)">{{'Issues.MarkInProgress' | translate }}</button>
<button mat-raised-button color="accent"
*ngIf="issue.status === IssueStatus.Pending && !settings.enableInProgress || issue.status == IssueStatus.InProgress"
(click)="resolve(issue)">{{'Issues.MarkResolved' | translate}}</button>
<button mat-raised-button color="warn" (click)="delete(issue)">{{'Issues.Delete' | translate}}</button>
</span>
</div>
</div>
</div>
</div>
</div>
<div>
<button mat-raised-button color="accent" (click)="navToMedia()">{{'Common.ViewDetails' | translate }}</button>
</div>
</div>

@ -0,0 +1,9 @@
@import "~styles/variables.scss";
::ng-deep .mat-card {
background: $ombi-background-primary-accent;
}
.top-spacing {
margin-top:2%;
}

@ -0,0 +1,88 @@
import { Component, Inject, OnInit, ViewEncapsulation } from "@angular/core";
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ActivatedRoute, ActivatedRouteSnapshot, Router } from "@angular/router";
import { TranslateService } from "@ngx-translate/core";
import { AuthService } from "../../../auth/auth.service";
import { IIssues, IIssueSettings, IIssuesSummary, IssueStatus, RequestType } from "../../../interfaces";
import { IssuesService, NotificationService, SettingsService } from "../../../services";
import { IssuesV2Service } from "../../../services/issuesv2.service";
export interface IssuesDetailsGroupData {
issues: IIssues[];
title: string;
}
@Component({
selector: "issues-details",
templateUrl: "details.component.html",
styleUrls: ["details.component.scss"],
encapsulation: ViewEncapsulation.None
})
export class IssuesDetailsComponent implements OnInit {
public details: IIssuesSummary;
public isAdmin: boolean;
public IssueStatus = IssueStatus;
public settings: IIssueSettings;
public get hasRequest(): boolean {
return this.details.issues.some(x => x.requestId);
}
private providerId: string;
constructor(private authService: AuthService, private settingsService: SettingsService,
private issueServiceV2: IssuesV2Service, private route: ActivatedRoute, private router: Router,
private issuesService: IssuesService, private translateService: TranslateService, private notificationService: NotificationService) {
this.route.params.subscribe(async (params: any) => {
if (typeof params.providerId === 'string' || params.providerId instanceof String) {
this.providerId = params.providerId;
}
});
}
public ngOnInit() {
this.isAdmin = this.authService.hasRole("Admin") || this.authService.hasRole("PowerUser");
this.settingsService.getIssueSettings().subscribe(x => this.settings = x);
this.issueServiceV2.getIssuesByProviderId(this.providerId).subscribe(x => this.details = x);
}
public resolve(issue: IIssues) {
this.issuesService.updateStatus({issueId: issue.id, status: IssueStatus.Resolved}).subscribe(x => {
this.notificationService.success(this.translateService.instant("Issues.MarkedAsResolved"));
issue.status = IssueStatus.Resolved;
});
}
public inProgress(issue: IIssues) {
this.issuesService.updateStatus({issueId: issue.id, status: IssueStatus.InProgress}).subscribe(x => {
this.notificationService.success(this.translateService.instant("Issues.MarkedAsInProgress"));
issue.status = IssueStatus.InProgress;
});
}
public async delete(issue: IIssues) {
await this.issuesService.deleteIssue(issue.id);
this.notificationService.success(this.translateService.instant("Issues.DeletedIssue"));
this.details.issues = this.details.issues.filter((el) => { return el.id !== issue.id; });
}
public navToMedia() {
const firstIssue = this.details.issues[0];
switch(firstIssue.requestType) {
case RequestType.movie:
this.router.navigate(['/details/movie/', firstIssue.providerId]);
return;
case RequestType.album:
this.router.navigate(['/details/artist/', firstIssue.providerId]);
return;
case RequestType.tvShow:
this.router.navigate(['/details/tv/', firstIssue.providerId]);
return;
}
}
}

@ -1,23 +1,19 @@
import { AuthGuard } from "../../auth/auth.guard";
import { IssuesListComponent } from "./issues-list/issues-list.component";
import { Routes } from "@angular/router";
import { IssuesV2Service } from "../../services/issuesv2.service";
import { IdentityService, SearchService } from "../../services";
import { DetailsGroupComponent } from "./details-group/details-group.component";
import { IssuesDetailsComponent } from "./details/details.component";
export const components: any[] = [
IssuesListComponent,
DetailsGroupComponent,
IssuesDetailsComponent,
];
export const providers: any[] = [
IssuesV2Service,
IdentityService,
SearchService,
];
export const routes: Routes = [
{ path: "", component: IssuesListComponent, canActivate: [AuthGuard] },
];

@ -1,21 +0,0 @@
<!-- <table mat-table [dataSource]="pendingIssues" multiTemplateDataRows class="mat-elevation-z8">
<ng-container matColumnDef="{{column}}" *ngFor="let column of columnsToDisplay">
<th mat-header-cell *matHeaderCellDef> {{column}} </th>
<td mat-cell *matCellDef="let element"> {{element[column]}} </td>
</ng-container>
<ng-container matColumnDef="expandedDetail">
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplay.length">
<div class="example-element-detail" [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
<div class="example-element-diagram">
<div class="example-element-position"> {{element.requestId}} </div>
</div>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let element; columns: columnsToDisplay;" class="example-element-row" [class.example-expanded-row]="expandedElement === element" (click)="expandedElement = expandedElement === element ? null : element">
</tr>
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
</table> -->

@ -1,68 +0,0 @@
import { Component, OnInit } from "@angular/core";
import { IssuesService } from "../../../services";
import { IIssueCount, IIssues, IPagenator, IssueStatus } from "../../../interfaces";
import { COLUMNS } from "./issues-list.constants";
@Component({
selector: "issues-list",
templateUrl: "issues-list.component.html",
})
export class IssuesListComponent implements OnInit {
public columnsToDisplay = COLUMNS
public pendingIssues: IIssues[];
public inProgressIssues: IIssues[];
public resolvedIssues: IIssues[];
public count: IIssueCount;
private takeAmount = 10;
private pendingSkip = 0;
private inProgressSkip = 0;
private resolvedSkip = 0;
constructor(private issueService: IssuesService) { }
public ngOnInit() {
this.getPending();
this.getInProg();
this.getResolved();
this.issueService.getIssuesCount().subscribe(x => this.count = x);
}
public changePagePending(event: IPagenator) {
this.pendingSkip = event.first;
this.getPending();
}
public changePageInProg(event: IPagenator) {
this.inProgressSkip = event.first;
this.getInProg();
}
public changePageResolved(event: IPagenator) {
this.resolvedSkip = event.first;
this.getResolved();
}
private getPending() {
this.issueService.getIssuesPage(this.takeAmount, this.pendingSkip, IssueStatus.Pending).subscribe(x => {
this.pendingIssues = x;
});
}
private getInProg() {
this.issueService.getIssuesPage(this.takeAmount, this.inProgressSkip, IssueStatus.InProgress).subscribe(x => {
this.inProgressIssues = x;
});
}
private getResolved() {
this.issueService.getIssuesPage(this.takeAmount, this.resolvedSkip, IssueStatus.Resolved).subscribe(x => {
this.resolvedIssues = x;
});
}
}

@ -1,19 +1,28 @@
<div class="small-middle-container">
<div class="row" *ngIf="count">
<div *ngIf="count">
<div *ngIf="pendingIssues.length > 0" class="col-4">
<h2>{{'Issues.PendingTitle' | translate}}</h2>
<issues-table [issues]="pendingIssues" (changePage)="changePagePending($event)" [totalRecords]="count.pending"></issues-table>
</div>
<div *ngIf="inProgressIssues.length > 0" class="col-4">
<h2>{{'Issues.InProgressTitle' | translate}}</h2>
<issues-table [issues]="inProgressIssues" (changePage)="changePageInProg($event)" [totalRecords]="count.inProgress"></issues-table>
</div>
<div *ngIf="resolvedIssues.length > 0" class="col-4">
<h2>{{'Issues.ResolvedTitle' | translate}}</h2>
<issues-table [issues]="resolvedIssues" (changePage)="changePageResolved($event)" [totalRecords]="count.resolved"></issues-table>
</div>
<mat-tab-group>
<mat-tab label="{{'Issues.PendingTitle' | translate}}">
<ng-template matTabContent>
<div *ngIf="pendingIssues.length > 0">
<issues-table [issues]="pendingIssues" (changePage)="changePagePending($event)" [totalRecords]="count.pending"></issues-table>
</div>
</ng-template>
</mat-tab>
<mat-tab *ngIf="inProgressIssues.length > 0" label="{{'Issues.InProgressTitle' | translate}}">
<ng-template matTabContent>
<div *ngIf="inProgressIssues">
<issues-table [issues]="inProgressIssues" (changePage)="changePageInProg($event)" [totalRecords]="count.inProgress"></issues-table>
</div>
</ng-template>
</mat-tab>
<mat-tab label="{{'Issues.ResolvedTitle' | translate}}">
<ng-template matTabContent>
<div *ngIf="resolvedIssues.length > 0">
<issues-table [issues]="resolvedIssues" (changePage)="changePageResolved($event)" [totalRecords]="count.resolved"></issues-table>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
</div>

@ -1,5 +1,6 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
// import { NbChatModule, NbThemeModule } from '@nebular/theme';
import { OrderModule } from "ngx-order-pipe";
@ -10,6 +11,7 @@ import { SharedModule as OmbiShared } from "../shared/shared.module";
import { IssueDetailsComponent } from "./issueDetails.component";
import { IssuesComponent } from "./issues.component";
import { IssuesTableComponent } from "./issuestable.component";
import { IssuesDetailsComponent } from "./components/details/details.component";
import { PipeModule } from "../pipes/pipe.module";
@ -17,7 +19,7 @@ import * as fromComponents from "./components";
const routes: Routes = [
{ path: "", component: IssuesComponent, canActivate: [AuthGuard] },
{ path: ":id", component: IssueDetailsComponent, canActivate: [AuthGuard] },
{ path: ":providerId", component: IssuesDetailsComponent, canActivate: [AuthGuard] },
];
@NgModule({
@ -25,7 +27,8 @@ const routes: Routes = [
RouterModule.forChild(routes),
OrderModule,
PipeModule,
OmbiShared
OmbiShared,
// NbChatModule,
],
declarations: [
IssuesComponent,

@ -14,7 +14,7 @@
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> </th>
<td mat-cell *matCellDef="let element">
<button mat-raised-button color="accent" (click)="openDetails(element)">{{ 'Issues.Details' | translate}}</button>
<button mat-raised-button color="accent" [routerLink]="[element.providerId]">{{ 'Issues.Details' | translate}}</button>
</td>
</ng-container>

@ -1,8 +1,7 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { MatDialog } from "@angular/material/dialog";
import { IIssues, IIssuesSummary, IPagenator, IssueStatus } from "../interfaces";
import { DetailsGroupComponent, IssuesDetailsGroupData } from "./components/details-group/details-group.component";
import { IIssuesSummary, IPagenator, IssueStatus } from "../interfaces";
@Component({
selector: "issues-table",
@ -52,12 +51,4 @@ export class IssuesTableComponent {
public paginate(event: IPagenator) {
this.changePage.emit(event);
}
public openDetails(summary: IIssuesSummary) {
const dialogRef = this.dialog.open(DetailsGroupComponent, {
width: "50vw",
data: <IssuesDetailsGroupData>{ issues: summary.issues, title: summary.title },
});
}
}

@ -113,9 +113,13 @@ export class MovieDetailsComponent {
}
public async issue() {
let provider = this.movie.id.toString();
if (this.movie.imdbId) {
provider = this.movie.imdbId;
}
const dialogRef = this.dialog.open(NewIssueComponent, {
width: '500px',
data: { requestId: this.movieRequest ? this.movieRequest.id : null, requestType: RequestType.movie, providerId: this.movie.imdbId ? this.movie.imdbId : this.movie.id, title: this.movie.title }
data: { requestId: this.movieRequest ? this.movieRequest.id : null, requestType: RequestType.movie, providerId: provider, title: this.movie.title }
});
}

@ -6,10 +6,11 @@ import { TranslateService } from "@ngx-translate/core";
@Component({
selector: "issues-panel",
templateUrl: "./issues-panel.component.html",
styleUrls: ["./issues-panel.component.scss"]
styleUrls: ["./issues-panel.component.scss"],
})
export class IssuesPanelComponent implements OnInit {
@Input() public providerId: string;
@Input() public isAdmin: boolean;
@ -22,7 +23,6 @@ export class IssuesPanelComponent implements OnInit {
constructor(private issuesService: IssuesService, private notificationService: NotificationService,
private translateService: TranslateService, private settingsService: SettingsService) {
}
public async ngOnInit() {
@ -54,7 +54,7 @@ export class IssuesPanelComponent implements OnInit {
this.issuesCount = this.issues.length;
this.calculateOutstanding();
}
private calculateOutstanding() {
this.isOutstanding = this.issues.some((i) => {
return i.status !== IssueStatus.Resolved;

@ -10,7 +10,7 @@ import { TranslateService } from "@ngx-translate/core";
templateUrl: "./new-issue.component.html",
})
export class NewIssueComponent implements OnInit {
public issue: IIssues;
public issueCategories: IIssueCategory[];
@ -40,9 +40,9 @@ export class NewIssueComponent implements OnInit {
public async ngOnInit(): Promise<void> {
this.issueCategories = await this.issueService.getCategories().toPromise();
}
}
public async createIssue() {
public async createIssue() {
const result = await this.issueService.createIssue(this.issue).toPromise();
if(result) {
this.messageService.send(this.translate.instant("Issues.IssueDialog.IssueCreated"));

@ -16,4 +16,8 @@ export class IssuesV2Service extends ServiceHelpers {
public getIssues(position: number, take: number, status: IssueStatus): Observable<IIssuesSummary[]> {
return this.http.get<IIssuesSummary[]>(`${this.url}${position}/${take}/${status}`, {headers: this.headers});
}
public getIssuesByProviderId(providerId: string): Observable<IIssuesSummary> {
return this.http.get<IIssuesSummary>(`${this.url}details/${providerId}`, {headers: this.headers});
}
}

@ -20,5 +20,12 @@ namespace Ombi.Controllers.V2
{
return _engine.GetIssues(position, take, status, HttpContext.RequestAborted);
}
[HttpGet("details/{providerId}")]
public Task<IssuesSummaryModel> GetIssueDetails(string providerId)
{
return _engine.GetIssuesByProviderId(providerId, HttpContext.RequestAborted);
}
}
}

Loading…
Cancel
Save