Fixed the issue with the tv search not working #1463

This commit is contained in:
Jamie.Rees 2017-09-18 14:40:22 +01:00
parent cd8a44cab8
commit 9f164e4098
12 changed files with 131 additions and 87 deletions

View file

@ -7,15 +7,12 @@ namespace Ombi.Core.Engine.Interfaces
public interface ITvSearchEngine public interface ITvSearchEngine
{ {
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm); Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm);
Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid);
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid); Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Popular();
Task<IEnumerable<SearchTvShowViewModel>> Popular(); Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Anticipated();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatches();
Task<IEnumerable<SearchTvShowViewModel>> Anticipated(); Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Trending();
Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
Task<IEnumerable<SearchTvShowViewModel>> Trending();
} }
} }

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Ombi.Core.Engine
{
public class TreeNode<T>
{
public string Label { get; set; }
public T Data { get; set; }
public List<TreeNode<T>> Children { get; set; }
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
}

View file

@ -54,6 +54,11 @@ namespace Ombi.Core.Engine
return null; return null;
} }
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm)
{
var result = await Search(searchTerm);
return result.Select(ParseIntoTreeNode).ToList();
}
public async Task<SearchTvShowViewModel> GetShowInformation(int tvdbid) public async Task<SearchTvShowViewModel> GetShowInformation(int tvdbid)
{ {
var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid); var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid);
@ -100,29 +105,55 @@ namespace Ombi.Core.Engine
return await ProcessResult(mapped, existingRequests, plexSettings, embySettings); return await ProcessResult(mapped, existingRequests, plexSettings, embySettings);
} }
public async Task<IEnumerable<SearchTvShowViewModel>> Popular() public async Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid)
{
var result = await GetShowInformation(tvdbid);
return ParseIntoTreeNode(result);
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Popular()
{ {
var result = await TraktApi.GetPopularShows(); var result = await TraktApi.GetPopularShows();
return await ProcessResults(result); var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
} }
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated() public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Anticipated()
{ {
var result = await TraktApi.GetAnticipatedShows(); var result = await TraktApi.GetAnticipatedShows();
return await ProcessResults(result); var processed= await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
} }
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches() public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatches()
{ {
var result = await TraktApi.GetMostWatchesShows(); var result = await TraktApi.GetMostWatchesShows();
return await ProcessResults(result); var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
} }
public async Task<IEnumerable<SearchTvShowViewModel>> Trending() public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Trending()
{ {
var result = await TraktApi.GetTrendingShows(); var result = await TraktApi.GetTrendingShows();
return await ProcessResults(result); var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
} }
private static TreeNode<SearchTvShowViewModel> ParseIntoTreeNode(SearchTvShowViewModel result)
{
return new TreeNode<SearchTvShowViewModel>
{
Data = result,
Children = new List<TreeNode<SearchTvShowViewModel>>
{
new TreeNode<SearchTvShowViewModel>
{
Data = result, Leaf = true
}
},
Leaf = false
};
}
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items) private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items)
{ {
@ -180,13 +211,13 @@ namespace Ombi.Core.Engine
// Find the existing request season // Find the existing request season
var existingSeason = var existingSeason =
existingRequestChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber); existingRequestChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber);
if(existingSeason == null) continue; if (existingSeason == null) continue;
foreach (var ep in existingSeason.Episodes) foreach (var ep in existingSeason.Episodes)
{ {
// Find the episode from what we are searching // Find the episode from what we are searching
var episodeSearching = season.Episodes.FirstOrDefault(x => x.EpisodeNumber == ep.EpisodeNumber); var episodeSearching = season.Episodes.FirstOrDefault(x => x.EpisodeNumber == ep.EpisodeNumber);
if(episodeSearching == null) if (episodeSearching == null)
{ {
continue; continue;
} }

View file

@ -51,17 +51,12 @@
<a *ngIf="result.trailer" href="{{result.trailer}}" target="_blank"><span class="label label-info">Trailer</span></a> <a *ngIf="result.trailer" href="{{result.trailer}}" target="_blank"><span class="label label-info">Trailer</span></a>
<span *ngIf="result.available" class="label label-success">Available</span> <span *ngIf="result.available" class="label label-success">Available</span>
<span *ngIf="result.quality" class="label label-success">{{result.quality}}p</span> <span *ngIf="result.quality" class="label label-success">{{result.quality}}p</span>
<span *ngIf="result.approved && !result.available" class="label label-info">Processing Request</span>
<span *ngIf="!result.approved">
<span *ngIf="result.requested && !result.available; then requested else notRequested"></span>
<ng-template #requested>
<span *ngIf="!result.available" class="label label-warning">Pending Approval</span>
</ng-template>
<ng-template #notRequested> <ng-template [ngIf]="result.available"><span class="label label-success">Available</span></ng-template>
<span *ngIf="!result.available" class="label label-danger">Not Yet Requested</span> <ng-template [ngIf]="result.approved && !result.available"><span class="label label-info">Processing Request</span></ng-template>
</ng-template> <ng-template [ngIf]="result.requested && !result.approved && !result.available"><span class="label label-warning">Pending Approval</span></ng-template>
</span> <ng-template [ngIf]="!result.requested && !result.available"><span class="label label-danger">Not Requested</span></ng-template>

View file

@ -54,16 +54,10 @@
{{ep.airDate | date: 'dd/MM/yyyy' }} {{ep.airDate | date: 'dd/MM/yyyy' }}
</td> </td>
<td> <td>
<span *ngIf="ep.available" class="label label-success">Available</span> <ng-template [ngIf]="ep.available"><span class="label label-success">Available</span></ng-template>
<span *ngIf="ep.approved && !ep.available" class="label label-info">Processing Request</span> <ng-template [ngIf]="ep.approved && !ep.available"><span class="label label-info">Processing Request</span></ng-template>
<div *ngIf="ep.requested && !ep.available; then requested else notRequested"></div> <ng-template [ngIf]="ep.requested && !ep.approved && !ep.available"><span class="label label-warning">Pending Approval</span></ng-template>
<ng-template #requested> <ng-template [ngIf]="!ep.requested && !ep.available"><span class="label label-danger">Not Requested</span></ng-template>
<span *ngIf="!ep.available" class="label label-warning">Pending Approval</span>
</ng-template>
<ng-template #notRequested>
<span *ngIf="!ep.available" class="label label-danger">Not Yet Requested</span>
</ng-template>
</td> </td>
<td> <td>

View file

@ -39,7 +39,6 @@ export class SeriesInformationComponent implements OnInit, OnDestroy {
}); });
} }
public submitRequests() { public submitRequests() {
this.series.requested = true; this.series.requested = true;

View file

@ -43,7 +43,7 @@
<div class="row"> <div class="row">
<div class="col-sm-2"> <div class="col-sm-2">
<img *ngIf="node.data.banner" class="img-responsive poster" width="150" [src]="node.data.banner" alt="poster"> <img *ngIf="node?.data?.banner" class="img-responsive poster" width="150" [src]="node.data.banner" alt="poster">
</div> </div>
<div class="col-sm-8"> <div class="col-sm-8">
@ -56,10 +56,7 @@
<span *ngIf="node.data.status" class="label label-primary" target="_blank">{{node.data.status}}</span> <span *ngIf="node.data.status" class="label label-primary" target="_blank">{{node.data.status}}</span>
<span *ngIf="node.data.firstAired" class="label label-info" target="_blank">Air Date: {{node.data.firstAired}}</span> <span *ngIf="node.data.firstAired" class="label label-info" target="_blank">Air Date: {{node.data.firstAired | date: 'dd/MM/yyyy'}}</span>
<span *ngIf="node.data.releaseDate" class="label label-info" target="_blank">Release Date: {{node.data.releaseDate | date: 'dd/MM/yyyy'}}</span>
<span *ngIf="node.data.available" class="label label-success">Available</span> <span *ngIf="node.data.available" class="label label-success">Available</span>
<span *ngIf="node.data.approved && !node.data.available" class="label label-info">Processing Request</span> <span *ngIf="node.data.approved && !node.data.available" class="label label-info">Processing Request</span>

View file

@ -9,7 +9,7 @@ import { NotificationService } from '../services/notification.service';
import { ISearchTvResult } from '../interfaces/ISearchTvResult'; import { ISearchTvResult } from '../interfaces/ISearchTvResult';
import { IRequestEngineResult } from '../interfaces/IRequestEngineResult'; import { IRequestEngineResult } from '../interfaces/IRequestEngineResult';
import { TreeNode } from "primeng/primeng"; import { TreeNode } from 'primeng/primeng';
@Component({ @Component({
selector: 'tv-search', selector: 'tv-search',
@ -21,7 +21,7 @@ export class TvSearchComponent implements OnInit, OnDestroy {
private subscriptions = new Subject<void>(); private subscriptions = new Subject<void>();
searchText: string; searchText: string;
searchChanged = new Subject<string>(); searchChanged = new Subject<string>();
tvResults: ISearchTvResult[]; tvResults: TreeNode[];
result: IRequestEngineResult; result: IRequestEngineResult;
searchApplied = false; searchApplied = false;
@ -38,16 +38,16 @@ export class TvSearchComponent implements OnInit, OnDestroy {
this.clearResults(); this.clearResults();
return; return;
} }
this.searchService.searchTv(this.searchText) this.searchService.searchTvTreeNode(this.searchText)
.takeUntil(this.subscriptions) .takeUntil(this.subscriptions)
.subscribe(x => { .subscribe(x => {
this.tvResults = this.transformData(x); this.tvResults = x;
this.searchApplied = true; this.searchApplied = true;
}); });
}); });
} }
openClosestTab(el: any): void { openClosestTab(el: any): void {
var rowclass = "undefined"; let rowclass = "undefined";
el = el.toElement; el = el.toElement;
while (el.className != rowclass) { while (el.className != rowclass) {
// Increment the loop to the parent node until we find the row we need // Increment the loop to the parent node until we find the row we need
@ -59,9 +59,9 @@ export class TvSearchComponent implements OnInit, OnDestroy {
// the class you specified // the class you specified
// Then we loop through the children to find the caret which we want to click // Then we loop through the children to find the caret which we want to click
var caretright = "ui-treetable-toggler fa fa-fw ui-clickable fa-caret-right"; let caretright = "ui-treetable-toggler fa fa-fw ui-clickable fa-caret-right";
var caretdown = "ui-treetable-toggler fa fa-fw ui-clickable fa-caret-down"; let caretdown = "ui-treetable-toggler fa fa-fw ui-clickable fa-caret-down";
for (var value of el.children) { for (let value of el.children) {
// the caret from the ui has 2 class selectors depending on if expanded or not // the caret from the ui has 2 class selectors depending on if expanded or not
// we search for both since we want to still toggle the clicking // we search for both since we want to still toggle the clicking
if (value.className === caretright || value.className === caretdown) { if (value.className === caretright || value.className === caretdown) {
@ -72,19 +72,6 @@ export class TvSearchComponent implements OnInit, OnDestroy {
} }
}; };
} }
transformData(datain: ISearchTvResult[]): any {
var temp: TreeNode[] = [];
datain.forEach(function (value) {
temp.push({
"data": value,
"children": [{
"data": value, leaf: true
}],
leaf: false
});
}, this)
return <TreeNode[]>temp;
}
ngOnInit(): void { ngOnInit(): void {
this.searchText = ""; this.searchText = "";
@ -143,12 +130,11 @@ export class TvSearchComponent implements OnInit, OnDestroy {
getExtraInfo() { getExtraInfo() {
this.tvResults.forEach((val, index) => { this.tvResults.forEach((val, index) => {
this.searchService.getShowInformation(val.id) this.searchService.getShowInformationTreeNode(val.data.id)
.takeUntil(this.subscriptions) .takeUntil(this.subscriptions)
.subscribe(x => { .subscribe(x => {
this.updateItem(val, x); this.updateItem(val.data, x);
}); });
}); });
} }
@ -161,7 +147,6 @@ export class TvSearchComponent implements OnInit, OnDestroy {
.takeUntil(this.subscriptions) .takeUntil(this.subscriptions)
.subscribe(x => { .subscribe(x => {
this.result = x; this.result = x;
if (this.result.requestAdded) { if (this.result.requestAdded) {
this.notificationService.success("Request Added", this.notificationService.success("Request Added",
`Request for ${searchResult.title} has been added successfully`); `Request for ${searchResult.title} has been added successfully`);
@ -171,7 +156,6 @@ export class TvSearchComponent implements OnInit, OnDestroy {
}); });
} }
allSeasons(searchResult: ISearchTvResult) { allSeasons(searchResult: ISearchTvResult) {
searchResult.requestAll = true; searchResult.requestAll = true;
this.request(searchResult); this.request(searchResult);
@ -191,11 +175,11 @@ export class TvSearchComponent implements OnInit, OnDestroy {
this.route.navigate(['/search/show', searchResult.id]); this.route.navigate(['/search/show', searchResult.id]);
} }
private updateItem(key: ISearchTvResult, updated: ISearchTvResult) { private updateItem(key: TreeNode, updated: TreeNode) {
var index = this.tvResults.indexOf(key, 0); let item = this.tvResults.filter((val) => {
if (index > -1) { return val.data == key;
this.tvResults[index] = updated; });
} item[0].data = updated.data;
} }
private clearResults() { private clearResults() {

View file

@ -5,6 +5,7 @@ import { Observable } from 'rxjs/Rx';
import { ServiceAuthHelpers } from './service.helpers'; import { ServiceAuthHelpers } from './service.helpers';
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult'; import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
import { ISearchTvResult } from '../interfaces/ISearchTvResult'; import { ISearchTvResult } from '../interfaces/ISearchTvResult';
import { TreeNode } from "primeng/primeng";
@Injectable() @Injectable()
export class SearchService extends ServiceAuthHelpers { export class SearchService extends ServiceAuthHelpers {
@ -37,21 +38,28 @@ export class SearchService extends ServiceAuthHelpers {
searchTv(searchTerm: string): Observable<ISearchTvResult[]> { searchTv(searchTerm: string): Observable<ISearchTvResult[]> {
return this.http.get(`${this.url}/Tv/` + searchTerm).map(this.extractData); return this.http.get(`${this.url}/Tv/` + searchTerm).map(this.extractData);
} }
searchTvTreeNode(searchTerm: string): Observable<TreeNode[]> {
return this.http.get(`${this.url}/Tv/${searchTerm}/tree`).map(this.extractData);
}
getShowInformationTreeNode(theTvDbId: number): Observable<TreeNode> {
return this.http.get(`${this.url}/Tv/info/${theTvDbId}/Tree`).map(this.extractData);
}
getShowInformation(theTvDbId: number): Observable<ISearchTvResult> { getShowInformation(theTvDbId: number): Observable<ISearchTvResult> {
return this.http.get(`${this.url}/Tv/info/${theTvDbId}`).map(this.extractData); return this.http.get(`${this.url}/Tv/info/${theTvDbId}`).map(this.extractData);
} }
popularTv(): Observable<ISearchTvResult[]> { popularTv(): Observable<TreeNode[]> {
return this.http.get(`${this.url}/Tv/popular`).map(this.extractData); return this.http.get(`${this.url}/Tv/popular`).map(this.extractData);
} }
mostWatchedTv(): Observable<ISearchTvResult[]> { mostWatchedTv(): Observable<TreeNode[]> {
return this.http.get(`${this.url}/Tv/mostwatched`).map(this.extractData); return this.http.get(`${this.url}/Tv/mostwatched`).map(this.extractData);
} }
anticipatedTv(): Observable<ISearchTvResult[]> { anticipatedTv(): Observable<TreeNode[]> {
return this.http.get(`${this.url}/Tv/anticipated`).map(this.extractData); return this.http.get(`${this.url}/Tv/anticipated`).map(this.extractData);
} }
trendingTv(): Observable<ISearchTvResult[]> { trendingTv(): Observable<TreeNode[]> {
return this.http.get(`${this.url}/Tv/trending`).map(this.extractData); return this.http.get(`${this.url}/Tv/trending`).map(this.extractData);
} }
} }

View file

@ -113,6 +113,30 @@ namespace Ombi.Controllers
return await TvEngine.Search(searchTerm); return await TvEngine.Search(searchTerm);
} }
/// <summary>
/// Searches for a Tv Show.
/// </summary>
/// <param name="searchTerm">The search term.</param>
/// <remarks>We use TvMaze as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/{searchTerm}/tree")]
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTvTreeNode(string searchTerm)
{
return await TvEngine.SearchTreeNode(searchTerm);
}
/// <summary>
/// Gets extra show information.
/// </summary>
/// <param name="tvdbId">The TVDB identifier.</param>
/// <remarks>We use TvMaze as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/info/{tvdbId}/tree")]
public async Task<TreeNode<SearchTvShowViewModel>> GetShowInfoTreeNode(int tvdbId)
{
return await TvEngine.GetShowInformationTreeNode(tvdbId);
}
/// <summary> /// <summary>
/// Gets extra show information. /// Gets extra show information.
/// </summary> /// </summary>
@ -131,7 +155,7 @@ namespace Ombi.Controllers
/// <remarks>We use Trakt.tv as the Provider</remarks> /// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns> /// <returns></returns>
[HttpGet("tv/popular")] [HttpGet("tv/popular")]
public async Task<IEnumerable<SearchTvShowViewModel>> PopularTv() public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTv()
{ {
return await TvEngine.Popular(); return await TvEngine.Popular();
} }
@ -141,7 +165,7 @@ namespace Ombi.Controllers
/// <remarks>We use Trakt.tv as the Provider</remarks> /// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns> /// <returns></returns>
[HttpGet("tv/anticipated")] [HttpGet("tv/anticipated")]
public async Task<IEnumerable<SearchTvShowViewModel>> AnticiplatedTv() public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticiplatedTv()
{ {
return await TvEngine.Anticipated(); return await TvEngine.Anticipated();
} }
@ -151,7 +175,7 @@ namespace Ombi.Controllers
/// <remarks>We use Trakt.tv as the Provider</remarks> /// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns> /// <returns></returns>
[HttpGet("tv/mostwatched")] [HttpGet("tv/mostwatched")]
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatched() public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatched()
{ {
return await TvEngine.MostWatches(); return await TvEngine.MostWatches();
} }
@ -161,7 +185,7 @@ namespace Ombi.Controllers
/// <remarks>We use Trakt.tv as the Provider</remarks> /// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns> /// <returns></returns>
[HttpGet("tv/trending")] [HttpGet("tv/trending")]
public async Task<IEnumerable<SearchTvShowViewModel>> Trending() public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Trending()
{ {
return await TvEngine.Trending(); return await TvEngine.Trending();
} }

View file

@ -4,7 +4,7 @@
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<RuntimeIdentifiers>win10-x64;osx.10.12-x64;ubuntu.16.04-x64;debian.8-x64;centos.7-x64;</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;osx.10.12-x64;ubuntu.16.04-x64;debian.8-x64;centos.7-x64;</RuntimeIdentifiers>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<TypeScriptToolsVersion>2.5</TypeScriptToolsVersion> <TypeScriptToolsVersion>2.3</TypeScriptToolsVersion>
<AssemblyVersion>$(SemVer)</AssemblyVersion> <AssemblyVersion>$(SemVer)</AssemblyVersion>
<FileVersion>$(SemVer)</FileVersion> <FileVersion>$(SemVer)</FileVersion>
<Version>$(FullVer)</Version> <Version>$(FullVer)</Version>

View file

@ -9,6 +9,7 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background-color: #1f1f1f;
} }
.app-loading { .app-loading {