mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-04-24 05:47:22 -04:00
New: Custom Filters for Stats
This commit is contained in:
parent
c873b3ffac
commit
b608e38454
12 changed files with 214 additions and 97 deletions
|
@ -1,9 +1,11 @@
|
|||
import { AppSectionItemState } from 'App/State/AppSectionState';
|
||||
import { Filter } from 'App/State/AppState';
|
||||
import { Filter, FilterBuilderProp } from 'App/State/AppState';
|
||||
import Indexer from 'Indexer/Indexer';
|
||||
import { IndexerStats } from 'typings/IndexerStats';
|
||||
|
||||
export interface IndexerStatsAppState
|
||||
extends AppSectionItemState<IndexerStats> {
|
||||
filterBuilderProps: FilterBuilderProp<Indexer>[];
|
||||
selectedFilterKey: string;
|
||||
filters: Filter[];
|
||||
}
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
.fullWidthChart {
|
||||
display: inline-block;
|
||||
padding: 15px 25px;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.halfWidthChart {
|
||||
display: inline-block;
|
||||
padding: 15px 25px;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.chartContainer {
|
||||
margin: 5px;
|
||||
padding: 15px 25px;
|
||||
height: 300px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--chartBackgroundColor);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.halfWidthChart {
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
padding: 15px 25px;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'chartContainer': string;
|
||||
'fullWidthChart': string;
|
||||
'halfWidthChart': string;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import BarChart from 'Components/Chart/BarChart';
|
|||
import DoughnutChart from 'Components/Chart/DoughnutChart';
|
||||
import StackedBarChart from 'Components/Chart/StackedBarChart';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
|
@ -17,6 +18,7 @@ import {
|
|||
fetchIndexerStats,
|
||||
setIndexerStatsFilter,
|
||||
} from 'Store/Actions/indexerStatsActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import {
|
||||
IndexerStatsHost,
|
||||
IndexerStatsIndexer,
|
||||
|
@ -24,7 +26,7 @@ import {
|
|||
} from 'typings/IndexerStats';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import IndexerStatsFilterMenu from './IndexerStatsFilterMenu';
|
||||
import IndexerStatsFilterModal from './IndexerStatsFilterModal';
|
||||
import styles from './IndexerStats.css';
|
||||
|
||||
function getAverageResponseTimeData(indexerStats: IndexerStatsIndexer[]) {
|
||||
|
@ -165,15 +167,26 @@ function getHostQueryData(indexerStats: IndexerStatsHost[]) {
|
|||
const indexerStatsSelector = () => {
|
||||
return createSelector(
|
||||
(state: AppState) => state.indexerStats,
|
||||
(indexerStats: IndexerStatsAppState) => {
|
||||
return indexerStats;
|
||||
createCustomFiltersSelector('indexerStats'),
|
||||
(indexerStats: IndexerStatsAppState, customFilters) => {
|
||||
return {
|
||||
...indexerStats,
|
||||
customFilters,
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function IndexerStats() {
|
||||
const { isFetching, isPopulated, item, error, filters, selectedFilterKey } =
|
||||
useSelector(indexerStatsSelector());
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
item,
|
||||
error,
|
||||
filters,
|
||||
customFilters,
|
||||
selectedFilterKey,
|
||||
} = useSelector(indexerStatsSelector());
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -193,10 +206,13 @@ function IndexerStats() {
|
|||
<PageContent>
|
||||
<PageToolbar>
|
||||
<PageToolbarSection alignContent={align.RIGHT} collapseButtons={false}>
|
||||
<IndexerStatsFilterMenu
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
onFilterSelect={onFilterSelect}
|
||||
filterModalConnectorComponent={IndexerStatsFilterModal}
|
||||
isDisabled={false}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
@ -213,57 +229,73 @@ function IndexerStats() {
|
|||
{isLoaded && (
|
||||
<div>
|
||||
<div className={styles.fullWidthChart}>
|
||||
<BarChart
|
||||
data={getAverageResponseTimeData(item.indexers)}
|
||||
title={translate('AverageResponseTimesMs')}
|
||||
/>
|
||||
<div className={styles.chartContainer}>
|
||||
<BarChart
|
||||
data={getAverageResponseTimeData(item.indexers)}
|
||||
title={translate('AverageResponseTimesMs')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.fullWidthChart}>
|
||||
<BarChart
|
||||
data={getFailureRateData(item.indexers)}
|
||||
title={translate('IndexerFailureRate')}
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
<div className={styles.chartContainer}>
|
||||
<BarChart
|
||||
data={getFailureRateData(item.indexers)}
|
||||
title={translate('IndexerFailureRate')}
|
||||
kind={kinds.WARNING}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<StackedBarChart
|
||||
data={getTotalRequestsData(item.indexers)}
|
||||
title={translate('TotalIndexerQueries')}
|
||||
/>
|
||||
<div className={styles.chartContainer}>
|
||||
<StackedBarChart
|
||||
data={getTotalRequestsData(item.indexers)}
|
||||
title={translate('TotalIndexerQueries')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<BarChart
|
||||
data={getNumberGrabsData(item.indexers)}
|
||||
title={translate('TotalIndexerSuccessfulGrabs')}
|
||||
/>
|
||||
<div className={styles.chartContainer}>
|
||||
<BarChart
|
||||
data={getNumberGrabsData(item.indexers)}
|
||||
title={translate('TotalIndexerSuccessfulGrabs')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<BarChart
|
||||
data={getUserAgentQueryData(item.userAgents)}
|
||||
title={translate('TotalUserAgentQueries')}
|
||||
horizontal={true}
|
||||
/>
|
||||
<div className={styles.chartContainer}>
|
||||
<BarChart
|
||||
data={getUserAgentQueryData(item.userAgents)}
|
||||
title={translate('TotalUserAgentQueries')}
|
||||
horizontal={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<BarChart
|
||||
data={getUserAgentGrabsData(item.userAgents)}
|
||||
title={translate('TotalUserAgentGrabs')}
|
||||
horizontal={true}
|
||||
/>
|
||||
<div className={styles.chartContainer}>
|
||||
<BarChart
|
||||
data={getUserAgentGrabsData(item.userAgents)}
|
||||
title={translate('TotalUserAgentGrabs')}
|
||||
horizontal={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<DoughnutChart
|
||||
data={getHostQueryData(item.hosts)}
|
||||
title={translate('TotalHostQueries')}
|
||||
horizontal={true}
|
||||
/>
|
||||
<div className={styles.chartContainer}>
|
||||
<DoughnutChart
|
||||
data={getHostQueryData(item.hosts)}
|
||||
title={translate('TotalHostQueries')}
|
||||
horizontal={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.halfWidthChart}>
|
||||
<DoughnutChart
|
||||
data={getHostGrabsData(item.hosts)}
|
||||
title={translate('TotalHostGrabs')}
|
||||
horizontal={true}
|
||||
/>
|
||||
<div className={styles.chartContainer}>
|
||||
<DoughnutChart
|
||||
data={getHostGrabsData(item.hosts)}
|
||||
title={translate('TotalHostGrabs')}
|
||||
horizontal={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import React from 'react';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import { align } from 'Helpers/Props';
|
||||
|
||||
interface IndexerStatsFilterMenuProps {
|
||||
selectedFilterKey: string | number;
|
||||
filters: object[];
|
||||
isDisabled: boolean;
|
||||
onFilterSelect(filterName: string): unknown;
|
||||
}
|
||||
|
||||
function IndexerStatsFilterMenu(props: IndexerStatsFilterMenuProps) {
|
||||
const { selectedFilterKey, filters, isDisabled, onFilterSelect } = props;
|
||||
|
||||
return (
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
isDisabled={isDisabled}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={[]}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default IndexerStatsFilterMenu;
|
56
frontend/src/Indexer/Stats/IndexerStatsFilterModal.tsx
Normal file
56
frontend/src/Indexer/Stats/IndexerStatsFilterModal.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import FilterModal from 'Components/Filter/FilterModal';
|
||||
import { setIndexerStatsFilter } from 'Store/Actions/indexerStatsActions';
|
||||
|
||||
function createIndexerStatsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.indexerStats.item,
|
||||
(indexerStats) => {
|
||||
return indexerStats;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createFilterBuilderPropsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.indexerStats.filterBuilderProps,
|
||||
(filterBuilderProps) => {
|
||||
return filterBuilderProps;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface IndexerStatsFilterModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default function IndexerStatsFilterModal(
|
||||
props: IndexerStatsFilterModalProps
|
||||
) {
|
||||
const sectionItems = [useSelector(createIndexerStatsSelector())];
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'indexerStats';
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatchSetFilter = useCallback(
|
||||
(payload: unknown) => {
|
||||
dispatch(setIndexerStatsFilter(payload));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FilterModal
|
||||
// TODO: Don't spread all the props
|
||||
{...props}
|
||||
sectionItems={sectionItems}
|
||||
filterBuilderProps={filterBuilderProps}
|
||||
customFilterType={customFilterType}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -3,6 +3,7 @@ import { batchActions } from 'redux-batched-actions';
|
|||
import { filterBuilderTypes, filterBuilderValueTypes } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { set, update } from './baseActions';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
|
@ -55,19 +56,20 @@ export const defaultState = {
|
|||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'startDate',
|
||||
label: 'Start Date',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
name: 'indexers',
|
||||
label: () => translate('Indexers'),
|
||||
type: filterBuilderTypes.CONTAINS,
|
||||
valueType: filterBuilderValueTypes.INDEXER
|
||||
},
|
||||
{
|
||||
name: 'endDate',
|
||||
label: 'End Date',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
name: 'tags',
|
||||
label: () => translate('Tags'),
|
||||
type: filterBuilderTypes.CONTAINS,
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
],
|
||||
selectedFilterKey: 'all'
|
||||
selectedFilterKey: 'all',
|
||||
customFilters: []
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
@ -81,6 +83,10 @@ export const persistState = [
|
|||
export const FETCH_INDEXER_STATS = 'indexerStats/fetchIndexerStats';
|
||||
export const SET_INDEXER_STATS_FILTER = 'indexerStats/setIndexerStatsFilter';
|
||||
|
||||
function getCustomFilters(state, type) {
|
||||
return state.customFilters.items.filter((customFilter) => customFilter.type === type);
|
||||
}
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
|
@ -94,23 +100,35 @@ export const actionHandlers = handleThunks({
|
|||
[FETCH_INDEXER_STATS]: function(getState, payload, dispatch) {
|
||||
const state = getState();
|
||||
const indexerStats = state.indexerStats;
|
||||
const customFilters = getCustomFilters(state, section);
|
||||
const selectedFilters = findSelectedFilters(indexerStats.selectedFilterKey, indexerStats.filters, customFilters);
|
||||
|
||||
const requestParams = {
|
||||
endDate: moment().toISOString()
|
||||
};
|
||||
|
||||
selectedFilters.forEach((selectedFilter) => {
|
||||
if (selectedFilter.key === 'indexers') {
|
||||
requestParams.indexers = selectedFilter.value.join(',');
|
||||
}
|
||||
|
||||
if (selectedFilter.key === 'tags') {
|
||||
requestParams.tags = selectedFilter.value.join(',');
|
||||
}
|
||||
});
|
||||
|
||||
if (indexerStats.selectedFilterKey !== 'all') {
|
||||
let dayCount = 7;
|
||||
if (indexerStats.selectedFilterKey === 'lastSeven') {
|
||||
requestParams.startDate = moment().add(-7, 'days').endOf('day').toISOString();
|
||||
}
|
||||
|
||||
if (indexerStats.selectedFilterKey === 'lastThirty') {
|
||||
dayCount = 30;
|
||||
requestParams.startDate = moment().add(-30, 'days').endOf('day').toISOString();
|
||||
}
|
||||
|
||||
if (indexerStats.selectedFilterKey === 'lastNinety') {
|
||||
dayCount = 90;
|
||||
requestParams.startDate = moment().add(-90, 'days').endOf('day').toISOString();
|
||||
}
|
||||
|
||||
requestParams.startDate = moment().add(-dayCount, 'days').endOf('day').toISOString();
|
||||
}
|
||||
|
||||
const basesAttrs = {
|
||||
|
|
|
@ -187,6 +187,7 @@ module.exports = {
|
|||
//
|
||||
// Charts
|
||||
|
||||
chartBackgroundColor: '#262626',
|
||||
failedColors: ['#ffbeb2', '#feb4a6', '#fdab9b', '#fca290', '#fb9984', '#fa8f79', '#f9856e', '#f77b66', '#f5715d', '#f36754', '#f05c4d', '#ec5049', '#e74545', '#e13b42', '#da323f', '#d3293d', '#ca223c', '#c11a3b', '#b8163a', '#ae123a'],
|
||||
chartColorsDiversified: ['#90caf9', '#f4d166', '#ff8a65', '#ce93d8', '#80cba9', '#ffab91', '#8097ea', '#bcaaa4', '#a57583', '#e4e498', '#9e96af', '#c6ab81', '#6972c6', '#619fc6', '#81ad81', '#f48fb1', '#82afca', '#b5b071', '#8b959b', '#7ec0b4'],
|
||||
chartColors: ['#f4d166', '#f6c760', '#f8bc58', '#f8b252', '#f7a84a', '#f69e41', '#f49538', '#f38b2f', '#f28026', '#f0751e', '#eb6c1c', '#e4641e', '#de5d1f', '#d75521', '#cf4f22', '#c64a22', '#bc4623', '#b24223', '#a83e24', '#9e3a26']
|
||||
|
|
|
@ -187,6 +187,7 @@ module.exports = {
|
|||
//
|
||||
// Charts
|
||||
|
||||
chartBackgroundColor: '#fff',
|
||||
failedColors: ['#ffbeb2', '#feb4a6', '#fdab9b', '#fca290', '#fb9984', '#fa8f79', '#f9856e', '#f77b66', '#f5715d', '#f36754', '#f05c4d', '#ec5049', '#e74545', '#e13b42', '#da323f', '#d3293d', '#ca223c', '#c11a3b', '#b8163a', '#ae123a'],
|
||||
chartColorsDiversified: ['#90caf9', '#f4d166', '#ff8a65', '#ce93d8', '#80cba9', '#ffab91', '#8097ea', '#bcaaa4', '#a57583', '#e4e498', '#9e96af', '#c6ab81', '#6972c6', '#619fc6', '#81ad81', '#f48fb1', '#82afca', '#b5b071', '#8b959b', '#7ec0b4'],
|
||||
chartColors: ['#f4d166', '#f6c760', '#f8bc58', '#f8b252', '#f7a84a', '#f69e41', '#f49538', '#f38b2f', '#f28026', '#f0751e', '#eb6c1c', '#e4641e', '#de5d1f', '#d75521', '#cf4f22', '#c64a22', '#bc4623', '#b24223', '#a83e24', '#9e3a26']
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace NzbDrone.Core.Test.IndexerStatsTests
|
|||
.Setup(o => o.Between(It.IsAny<DateTime>(), It.IsAny<DateTime>()))
|
||||
.Returns<DateTime, DateTime>((s, f) => history);
|
||||
|
||||
var statistics = Subject.IndexerStatistics(DateTime.UtcNow.AddMonths(-1), DateTime.UtcNow);
|
||||
var statistics = Subject.IndexerStatistics(DateTime.UtcNow.AddMonths(-1), DateTime.UtcNow, new List<int> { 5 });
|
||||
|
||||
statistics.IndexerStatistics.Count.Should().Be(1);
|
||||
statistics.IndexerStatistics.First().AverageResponseTime.Should().Be(0);
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace NzbDrone.Core.IndexerStats
|
|||
{
|
||||
public interface IIndexerStatisticsService
|
||||
{
|
||||
CombinedStatistics IndexerStatistics(DateTime start, DateTime end);
|
||||
CombinedStatistics IndexerStatistics(DateTime start, DateTime end, List<int> indexerIds);
|
||||
}
|
||||
|
||||
public class IndexerStatisticsService : IIndexerStatisticsService
|
||||
|
@ -22,13 +22,15 @@ namespace NzbDrone.Core.IndexerStats
|
|||
_indexerFactory = indexerFactory;
|
||||
}
|
||||
|
||||
public CombinedStatistics IndexerStatistics(DateTime start, DateTime end)
|
||||
public CombinedStatistics IndexerStatistics(DateTime start, DateTime end, List<int> indexerIds)
|
||||
{
|
||||
var history = _historyService.Between(start, end);
|
||||
|
||||
var groupedByIndexer = history.GroupBy(h => h.IndexerId);
|
||||
var groupedByUserAgent = history.GroupBy(h => h.Data.GetValueOrDefault("source") ?? "");
|
||||
var groupedByHost = history.GroupBy(h => h.Data.GetValueOrDefault("host") ?? "");
|
||||
var filteredHistory = history.Where(h => indexerIds.Contains(h.IndexerId));
|
||||
|
||||
var groupedByIndexer = filteredHistory.GroupBy(h => h.IndexerId);
|
||||
var groupedByUserAgent = filteredHistory.GroupBy(h => h.Data.GetValueOrDefault("source") ?? "");
|
||||
var groupedByHost = filteredHistory.GroupBy(h => h.Data.GetValueOrDefault("host") ?? "");
|
||||
|
||||
var indexerStatsList = new List<IndexerStatistics>();
|
||||
var userAgentStatsList = new List<UserAgentStatistics>();
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerStats;
|
||||
using NzbDrone.Core.Tags;
|
||||
using Prowlarr.Http;
|
||||
|
||||
namespace Prowlarr.Api.V1.Indexers
|
||||
|
@ -9,20 +14,41 @@ namespace Prowlarr.Api.V1.Indexers
|
|||
public class IndexerStatsController : Controller
|
||||
{
|
||||
private readonly IIndexerStatisticsService _indexerStatisticsService;
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly ITagService _tagService;
|
||||
|
||||
public IndexerStatsController(IIndexerStatisticsService indexerStatisticsService)
|
||||
public IndexerStatsController(IIndexerStatisticsService indexerStatisticsService, IIndexerFactory indexerFactory, ITagService tagService)
|
||||
{
|
||||
_indexerStatisticsService = indexerStatisticsService;
|
||||
_indexerFactory = indexerFactory;
|
||||
_tagService = tagService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Produces("application/json")]
|
||||
public IndexerStatsResource GetAll(DateTime? startDate, DateTime? endDate)
|
||||
public IndexerStatsResource GetAll(DateTime? startDate, DateTime? endDate, string indexers, string tags)
|
||||
{
|
||||
var statsStartDate = startDate ?? DateTime.MinValue;
|
||||
var statsEndDate = endDate ?? DateTime.Now;
|
||||
var parsedIndexers = new List<int>();
|
||||
var parsedTags = new List<int>();
|
||||
var indexerIds = _indexerFactory.All().Select(i => i.Id).ToList();
|
||||
|
||||
var indexerStats = _indexerStatisticsService.IndexerStatistics(statsStartDate, statsEndDate);
|
||||
if (tags.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parsedTags.AddRange(tags.Split(',').Select(_tagService.GetTag).Select(t => t.Id));
|
||||
|
||||
indexerIds = indexerIds.Intersect(parsedTags.SelectMany(t => _indexerFactory.AllForTag(t).Select(i => i.Id))).ToList();
|
||||
}
|
||||
|
||||
if (indexers.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parsedIndexers.AddRange(indexers.Split(',').Select(x => Convert.ToInt32(x)));
|
||||
|
||||
indexerIds = indexerIds.Intersect(parsedIndexers).ToList();
|
||||
}
|
||||
|
||||
var indexerStats = _indexerStatisticsService.IndexerStatistics(statsStartDate, statsEndDate, indexerIds);
|
||||
|
||||
var indexerResource = new IndexerStatsResource
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue