From ff22577ce09feecb16c0fb94f506cdeb9780eae6 Mon Sep 17 00:00:00 2001 From: hontheinternet <121332499+hontheinternet@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:32:42 +0900 Subject: [PATCH] Add additional stats to the Stats page (#3812) * Add o_counter, play_duration, play_count, unique_play_count stats --- .gitignore | 2 +- graphql/documents/queries/misc.graphql | 22 ++++--- graphql/schema/types/stats.graphql | 4 ++ internal/api/resolver.go | 28 ++++++--- pkg/models/mocks/SceneReaderWriter.go | 84 ++++++++++++++++++++++++++ pkg/models/scene.go | 4 ++ pkg/sqlite/scene.go | 43 +++++++++++++ ui/v2.5/src/components/Stats.tsx | 37 ++++++++++++ ui/v2.5/src/locales/en-GB.json | 6 +- 9 files changed, 209 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 197fd7302..7b70f7306 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,4 @@ node_modules /stash dist .DS_Store -/.local \ No newline at end of file +/.local* \ No newline at end of file diff --git a/graphql/documents/queries/misc.graphql b/graphql/documents/queries/misc.graphql index e653635dc..791392fb0 100644 --- a/graphql/documents/queries/misc.graphql +++ b/graphql/documents/queries/misc.graphql @@ -40,16 +40,20 @@ query AllTagsForFilter { query Stats { stats { - scene_count, - scenes_size, - scenes_duration, - image_count, - images_size, - gallery_count, - performer_count, - studio_count, - movie_count, + scene_count + scenes_size + scenes_duration + image_count + images_size + gallery_count + performer_count + studio_count + movie_count tag_count + total_o_count + total_play_duration + total_play_count + scenes_played } } diff --git a/graphql/schema/types/stats.graphql b/graphql/schema/types/stats.graphql index fcadd54a7..3675c2a6b 100644 --- a/graphql/schema/types/stats.graphql +++ b/graphql/schema/types/stats.graphql @@ -9,4 +9,8 @@ type StatsResultType { studio_count: Int! movie_count: Int! tag_count: Int! + total_o_count: Int! + total_play_duration: Float! + total_play_count: Int! + scenes_played: Int! } diff --git a/internal/api/resolver.go b/internal/api/resolver.go index af26bef4d..0e1d33bbe 100644 --- a/internal/api/resolver.go +++ b/internal/api/resolver.go @@ -157,18 +157,26 @@ func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) { studiosCount, _ := studiosQB.Count(ctx) moviesCount, _ := moviesQB.Count(ctx) tagsCount, _ := tagsQB.Count(ctx) + totalOCount, _ := scenesQB.OCount(ctx) + totalPlayDuration, _ := scenesQB.PlayDuration(ctx) + totalPlayCount, _ := scenesQB.PlayCount(ctx) + uniqueScenePlayCount, _ := scenesQB.UniqueScenePlayCount(ctx) ret = StatsResultType{ - SceneCount: scenesCount, - ScenesSize: scenesSize, - ScenesDuration: scenesDuration, - ImageCount: imageCount, - ImagesSize: imageSize, - GalleryCount: galleryCount, - PerformerCount: performersCount, - StudioCount: studiosCount, - MovieCount: moviesCount, - TagCount: tagsCount, + SceneCount: scenesCount, + ScenesSize: scenesSize, + ScenesDuration: scenesDuration, + ImageCount: imageCount, + ImagesSize: imageSize, + GalleryCount: galleryCount, + PerformerCount: performersCount, + StudioCount: studiosCount, + MovieCount: moviesCount, + TagCount: tagsCount, + TotalOCount: totalOCount, + TotalPlayDuration: totalPlayDuration, + TotalPlayCount: totalPlayCount, + ScenesPlayed: uniqueScenePlayCount, } return nil diff --git a/pkg/models/mocks/SceneReaderWriter.go b/pkg/models/mocks/SceneReaderWriter.go index ee0c12496..b1e98d91f 100644 --- a/pkg/models/mocks/SceneReaderWriter.go +++ b/pkg/models/mocks/SceneReaderWriter.go @@ -687,6 +687,27 @@ func (_m *SceneReaderWriter) IncrementWatchCount(ctx context.Context, id int) (i return r0, r1 } +// OCount provides a mock function with given fields: ctx +func (_m *SceneReaderWriter) OCount(ctx context.Context) (int, error) { + ret := _m.Called(ctx) + + var r0 int + if rf, ok := ret.Get(0).(func(context.Context) int); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(int) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // OCountByPerformerID provides a mock function with given fields: ctx, performerID func (_m *SceneReaderWriter) OCountByPerformerID(ctx context.Context, performerID int) (int, error) { ret := _m.Called(ctx, performerID) @@ -708,6 +729,48 @@ func (_m *SceneReaderWriter) OCountByPerformerID(ctx context.Context, performerI return r0, r1 } +// PlayCount provides a mock function with given fields: ctx +func (_m *SceneReaderWriter) PlayCount(ctx context.Context) (int, error) { + ret := _m.Called(ctx) + + var r0 int + if rf, ok := ret.Get(0).(func(context.Context) int); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(int) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PlayDuration provides a mock function with given fields: ctx +func (_m *SceneReaderWriter) PlayDuration(ctx context.Context) (float64, error) { + ret := _m.Called(ctx) + + var r0 float64 + if rf, ok := ret.Get(0).(func(context.Context) float64); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(float64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Query provides a mock function with given fields: ctx, options func (_m *SceneReaderWriter) Query(ctx context.Context, options models.SceneQueryOptions) (*models.SceneQueryResult, error) { ret := _m.Called(ctx, options) @@ -815,6 +878,27 @@ func (_m *SceneReaderWriter) Size(ctx context.Context) (float64, error) { return r0, r1 } +// UniqueScenePlayCount provides a mock function with given fields: ctx +func (_m *SceneReaderWriter) UniqueScenePlayCount(ctx context.Context) (int, error) { + ret := _m.Called(ctx) + + var r0 int + if rf, ok := ret.Get(0).(func(context.Context) int); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(int) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Update provides a mock function with given fields: ctx, updatedScene func (_m *SceneReaderWriter) Update(ctx context.Context, updatedScene *models.Scene) error { ret := _m.Called(ctx, updatedScene) diff --git a/pkg/models/scene.go b/pkg/models/scene.go index 15cecb38b..92a28a206 100644 --- a/pkg/models/scene.go +++ b/pkg/models/scene.go @@ -168,12 +168,16 @@ type SceneReader interface { CountByPerformerID(ctx context.Context, performerID int) (int, error) OCountByPerformerID(ctx context.Context, performerID int) (int, error) + OCount(ctx context.Context) (int, error) // FindByStudioID(studioID int) ([]*Scene, error) FindByMovieID(ctx context.Context, movieID int) ([]*Scene, error) CountByMovieID(ctx context.Context, movieID int) (int, error) Count(ctx context.Context) (int, error) + PlayCount(ctx context.Context) (int, error) + UniqueScenePlayCount(ctx context.Context) (int, error) Size(ctx context.Context) (float64, error) Duration(ctx context.Context) (float64, error) + PlayDuration(ctx context.Context) (float64, error) // SizeCount() (string, error) CountByStudioID(ctx context.Context, studioID int) (int, error) CountByTagID(ctx context.Context, tagID int) (int, error) diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index 3bbcea41c..99250254b 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -705,6 +705,18 @@ func (qb *SceneStore) OCountByPerformerID(ctx context.Context, performerID int) return ret, nil } +func (qb *SceneStore) OCount(ctx context.Context) (int, error) { + table := qb.table() + + q := dialect.Select(goqu.COALESCE(goqu.SUM("o_counter"), 0)).From(table) + var ret int + if err := querySimple(ctx, q, &ret); err != nil { + return 0, err + } + + return ret, nil +} + func (qb *SceneStore) FindByMovieID(ctx context.Context, movieID int) ([]*models.Scene, error) { sq := dialect.From(scenesMoviesJoinTable).Select(scenesMoviesJoinTable.Col(sceneIDColumn)).Where( scenesMoviesJoinTable.Col(movieIDColumn).Eq(movieID), @@ -730,6 +742,24 @@ func (qb *SceneStore) Count(ctx context.Context) (int, error) { return count(ctx, q) } +func (qb *SceneStore) PlayCount(ctx context.Context) (int, error) { + q := dialect.Select(goqu.COALESCE(goqu.SUM("play_count"), 0)).From(qb.table()) + + var ret int + if err := querySimple(ctx, q, &ret); err != nil { + return 0, err + } + + return ret, nil +} + +func (qb *SceneStore) UniqueScenePlayCount(ctx context.Context) (int, error) { + table := qb.table() + q := dialect.Select(goqu.COUNT("*")).From(table).Where(table.Col("play_count").Gt(0)) + + return count(ctx, q) +} + func (qb *SceneStore) Size(ctx context.Context) (float64, error) { table := qb.table() fileTable := fileTableMgr.table @@ -771,6 +801,19 @@ func (qb *SceneStore) Duration(ctx context.Context) (float64, error) { return ret, nil } +func (qb *SceneStore) PlayDuration(ctx context.Context) (float64, error) { + table := qb.table() + + q := dialect.Select(goqu.COALESCE(goqu.SUM("play_duration"), 0)).From(table) + + var ret float64 + if err := querySimple(ctx, q, &ret); err != nil { + return 0, err + } + + return ret, nil +} + func (qb *SceneStore) CountByStudioID(ctx context.Context, studioID int) (int, error) { table := qb.table() diff --git a/ui/v2.5/src/components/Stats.tsx b/ui/v2.5/src/components/Stats.tsx index 79e206282..f177aa461 100644 --- a/ui/v2.5/src/components/Stats.tsx +++ b/ui/v2.5/src/components/Stats.tsx @@ -18,6 +18,11 @@ export const Stats: React.FC = () => { 3 ); + const totalPlayDuration = TextUtils.secondsAsTimeString( + data.stats.total_play_duration, + 3 + ); + return (
+
+
+
+
+
+
{totalPlayDuration || "-"}
+
+