diff --git a/src/Ombi.Core/Engine/V2/IssuesEngine.cs b/src/Ombi.Core/Engine/V2/IssuesEngine.cs index f154d0eca..f938e8ec6 100644 --- a/src/Ombi.Core/Engine/V2/IssuesEngine.cs +++ b/src/Ombi.Core/Engine/V2/IssuesEngine.cs @@ -12,7 +12,7 @@ namespace Ombi.Core.Engine.V2 public interface IIssuesEngine { Task> GetIssues(int position, int take, IssueStatus status, CancellationToken token); - Task> GetIssuesByTitle(string title, CancellationToken token); + Task GetIssuesByProviderId(string providerId, CancellationToken token); } public class IssuesEngine : IIssuesEngine @@ -32,7 +32,7 @@ namespace Ombi.Core.Engine.V2 public async Task> 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(); @@ -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> GetIssuesByTitle(string title, CancellationToken token) + public async Task 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(); + 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 { get; set; } } } diff --git a/src/Ombi/ClientApp/src/app/app.module.ts b/src/Ombi/ClientApp/src/app/app.module.ts index cfaf783cf..4de2c9f9f 100644 --- a/src/Ombi/ClientApp/src/app/app.module.ts +++ b/src/Ombi/ClientApp/src/app/app.module.ts @@ -145,6 +145,7 @@ export function JwtTokenGetter() { MatCheckboxModule, MatProgressSpinnerModule, MDBBootstrapModule.forRoot(), + // NbThemeModule.forRoot({ name: 'dark'}), JwtModule.forRoot({ config: { tokenGetter: JwtTokenGetter, diff --git a/src/Ombi/ClientApp/src/app/interfaces/IIssues.ts b/src/Ombi/ClientApp/src/app/interfaces/IIssues.ts index b01749057..10de2a596 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IIssues.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IIssues.ts @@ -66,5 +66,6 @@ export interface IUpdateStatus { export interface IIssuesSummary { title: string; count: number; + providerId: string; issues: IIssues[]; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/issues/components/details/details.component.html b/src/Ombi/ClientApp/src/app/issues/components/details/details.component.html new file mode 100644 index 000000000..b8e8dc1f6 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/issues/components/details/details.component.html @@ -0,0 +1,38 @@ +
+ +

Issues for {{details.title}}

+
+ Has Request {{hasRequest}} +
+
+
+
+
{{issue.subject}}
+ {{issue.userReported?.userName}} on {{issue.createdDate | date:short}} +
+
+

+ {{issue.description}} +

+
+
+ + + + + + +
+ +
+
+
+
+
+ +
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/issues/components/details/details.component.scss b/src/Ombi/ClientApp/src/app/issues/components/details/details.component.scss new file mode 100644 index 000000000..d6dcd67de --- /dev/null +++ b/src/Ombi/ClientApp/src/app/issues/components/details/details.component.scss @@ -0,0 +1,9 @@ +@import "~styles/variables.scss"; + +::ng-deep .mat-card { + background: $ombi-background-primary-accent; +} + +.top-spacing { + margin-top:2%; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/issues/components/details/details.component.ts b/src/Ombi/ClientApp/src/app/issues/components/details/details.component.ts new file mode 100644 index 000000000..ed00bc041 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/issues/components/details/details.component.ts @@ -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; + } + } + +} diff --git a/src/Ombi/ClientApp/src/app/issues/components/index.ts b/src/Ombi/ClientApp/src/app/issues/components/index.ts index 7bf8e197a..86ceefe89 100644 --- a/src/Ombi/ClientApp/src/app/issues/components/index.ts +++ b/src/Ombi/ClientApp/src/app/issues/components/index.ts @@ -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] }, ]; \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/issues/components/issues-list/issues-list.component.html b/src/Ombi/ClientApp/src/app/issues/components/issues-list/issues-list.component.html deleted file mode 100644 index 61f154193..000000000 --- a/src/Ombi/ClientApp/src/app/issues/components/issues-list/issues-list.component.html +++ /dev/null @@ -1,21 +0,0 @@ - \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/issues/components/issues-list/issues-list.component.ts b/src/Ombi/ClientApp/src/app/issues/components/issues-list/issues-list.component.ts deleted file mode 100644 index 82883a4f2..000000000 --- a/src/Ombi/ClientApp/src/app/issues/components/issues-list/issues-list.component.ts +++ /dev/null @@ -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; - }); - } -} diff --git a/src/Ombi/ClientApp/src/app/issues/components/issues-list/issues-list.constants.ts b/src/Ombi/ClientApp/src/app/issues/components/issues-list/issues-list.constants.ts deleted file mode 100644 index f4a00e24b..000000000 --- a/src/Ombi/ClientApp/src/app/issues/components/issues-list/issues-list.constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const COLUMNS = [ - "title" -] \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/issues/issues.component.html b/src/Ombi/ClientApp/src/app/issues/issues.component.html index 4d9daeffc..72734ed85 100644 --- a/src/Ombi/ClientApp/src/app/issues/issues.component.html +++ b/src/Ombi/ClientApp/src/app/issues/issues.component.html @@ -1,19 +1,28 @@
-
+
-
-

{{'Issues.PendingTitle' | translate}}

- -
- -
-

{{'Issues.InProgressTitle' | translate}}

- -
- -
-

{{'Issues.ResolvedTitle' | translate}}

- -
+ + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/issues/issues.module.ts b/src/Ombi/ClientApp/src/app/issues/issues.module.ts index 70b8a4bb1..08896a086 100644 --- a/src/Ombi/ClientApp/src/app/issues/issues.module.ts +++ b/src/Ombi/ClientApp/src/app/issues/issues.module.ts @@ -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, diff --git a/src/Ombi/ClientApp/src/app/issues/issuestable.component.html b/src/Ombi/ClientApp/src/app/issues/issuestable.component.html index 8244536ad..8e22d3e51 100644 --- a/src/Ombi/ClientApp/src/app/issues/issuestable.component.html +++ b/src/Ombi/ClientApp/src/app/issues/issuestable.component.html @@ -14,7 +14,7 @@ - + diff --git a/src/Ombi/ClientApp/src/app/issues/issuestable.component.ts b/src/Ombi/ClientApp/src/app/issues/issuestable.component.ts index 7ed826fc5..0fd81a534 100644 --- a/src/Ombi/ClientApp/src/app/issues/issuestable.component.ts +++ b/src/Ombi/ClientApp/src/app/issues/issuestable.component.ts @@ -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: { issues: summary.issues, title: summary.title }, - }); - } - } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts index 988859245..912f8a32c 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts @@ -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 } }); } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/issues-panel/issues-panel.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/shared/issues-panel/issues-panel.component.ts index dda7bcb89..206be2141 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/shared/issues-panel/issues-panel.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/issues-panel/issues-panel.component.ts @@ -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; diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/new-issue/new-issue.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/shared/new-issue/new-issue.component.ts index c42c14fe2..94972c70d 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/shared/new-issue/new-issue.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/new-issue/new-issue.component.ts @@ -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 { 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")); diff --git a/src/Ombi/ClientApp/src/app/services/issuesv2.service.ts b/src/Ombi/ClientApp/src/app/services/issuesv2.service.ts index 69b5c01c4..d792702a0 100644 --- a/src/Ombi/ClientApp/src/app/services/issuesv2.service.ts +++ b/src/Ombi/ClientApp/src/app/services/issuesv2.service.ts @@ -16,4 +16,8 @@ export class IssuesV2Service extends ServiceHelpers { public getIssues(position: number, take: number, status: IssueStatus): Observable { return this.http.get(`${this.url}${position}/${take}/${status}`, {headers: this.headers}); } + + public getIssuesByProviderId(providerId: string): Observable { + return this.http.get(`${this.url}details/${providerId}`, {headers: this.headers}); + } } diff --git a/src/Ombi/Controllers/V2/IssuesController.cs b/src/Ombi/Controllers/V2/IssuesController.cs index 214482b54..84eafa3ad 100644 --- a/src/Ombi/Controllers/V2/IssuesController.cs +++ b/src/Ombi/Controllers/V2/IssuesController.cs @@ -20,5 +20,12 @@ namespace Ombi.Controllers.V2 { return _engine.GetIssues(position, take, status, HttpContext.RequestAborted); } + + + [HttpGet("details/{providerId}")] + public Task GetIssueDetails(string providerId) + { + return _engine.GetIssuesByProviderId(providerId, HttpContext.RequestAborted); + } } }