mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Add additional stats to the Stats page (#3812)
* Add o_counter, play_duration, play_count, unique_play_count stats
This commit is contained in:
parent
4f0e0e1d99
commit
ff22577ce0
9 changed files with 209 additions and 21 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -63,4 +63,4 @@ node_modules
|
||||||
/stash
|
/stash
|
||||||
dist
|
dist
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/.local
|
/.local*
|
||||||
|
|
@ -40,16 +40,20 @@ query AllTagsForFilter {
|
||||||
|
|
||||||
query Stats {
|
query Stats {
|
||||||
stats {
|
stats {
|
||||||
scene_count,
|
scene_count
|
||||||
scenes_size,
|
scenes_size
|
||||||
scenes_duration,
|
scenes_duration
|
||||||
image_count,
|
image_count
|
||||||
images_size,
|
images_size
|
||||||
gallery_count,
|
gallery_count
|
||||||
performer_count,
|
performer_count
|
||||||
studio_count,
|
studio_count
|
||||||
movie_count,
|
movie_count
|
||||||
tag_count
|
tag_count
|
||||||
|
total_o_count
|
||||||
|
total_play_duration
|
||||||
|
total_play_count
|
||||||
|
scenes_played
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,8 @@ type StatsResultType {
|
||||||
studio_count: Int!
|
studio_count: Int!
|
||||||
movie_count: Int!
|
movie_count: Int!
|
||||||
tag_count: Int!
|
tag_count: Int!
|
||||||
|
total_o_count: Int!
|
||||||
|
total_play_duration: Float!
|
||||||
|
total_play_count: Int!
|
||||||
|
scenes_played: Int!
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,18 +157,26 @@ func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) {
|
||||||
studiosCount, _ := studiosQB.Count(ctx)
|
studiosCount, _ := studiosQB.Count(ctx)
|
||||||
moviesCount, _ := moviesQB.Count(ctx)
|
moviesCount, _ := moviesQB.Count(ctx)
|
||||||
tagsCount, _ := tagsQB.Count(ctx)
|
tagsCount, _ := tagsQB.Count(ctx)
|
||||||
|
totalOCount, _ := scenesQB.OCount(ctx)
|
||||||
|
totalPlayDuration, _ := scenesQB.PlayDuration(ctx)
|
||||||
|
totalPlayCount, _ := scenesQB.PlayCount(ctx)
|
||||||
|
uniqueScenePlayCount, _ := scenesQB.UniqueScenePlayCount(ctx)
|
||||||
|
|
||||||
ret = StatsResultType{
|
ret = StatsResultType{
|
||||||
SceneCount: scenesCount,
|
SceneCount: scenesCount,
|
||||||
ScenesSize: scenesSize,
|
ScenesSize: scenesSize,
|
||||||
ScenesDuration: scenesDuration,
|
ScenesDuration: scenesDuration,
|
||||||
ImageCount: imageCount,
|
ImageCount: imageCount,
|
||||||
ImagesSize: imageSize,
|
ImagesSize: imageSize,
|
||||||
GalleryCount: galleryCount,
|
GalleryCount: galleryCount,
|
||||||
PerformerCount: performersCount,
|
PerformerCount: performersCount,
|
||||||
StudioCount: studiosCount,
|
StudioCount: studiosCount,
|
||||||
MovieCount: moviesCount,
|
MovieCount: moviesCount,
|
||||||
TagCount: tagsCount,
|
TagCount: tagsCount,
|
||||||
|
TotalOCount: totalOCount,
|
||||||
|
TotalPlayDuration: totalPlayDuration,
|
||||||
|
TotalPlayCount: totalPlayCount,
|
||||||
|
ScenesPlayed: uniqueScenePlayCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -687,6 +687,27 @@ func (_m *SceneReaderWriter) IncrementWatchCount(ctx context.Context, id int) (i
|
||||||
return r0, r1
|
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
|
// OCountByPerformerID provides a mock function with given fields: ctx, performerID
|
||||||
func (_m *SceneReaderWriter) OCountByPerformerID(ctx context.Context, performerID int) (int, error) {
|
func (_m *SceneReaderWriter) OCountByPerformerID(ctx context.Context, performerID int) (int, error) {
|
||||||
ret := _m.Called(ctx, performerID)
|
ret := _m.Called(ctx, performerID)
|
||||||
|
|
@ -708,6 +729,48 @@ func (_m *SceneReaderWriter) OCountByPerformerID(ctx context.Context, performerI
|
||||||
return r0, r1
|
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
|
// Query provides a mock function with given fields: ctx, options
|
||||||
func (_m *SceneReaderWriter) Query(ctx context.Context, options models.SceneQueryOptions) (*models.SceneQueryResult, error) {
|
func (_m *SceneReaderWriter) Query(ctx context.Context, options models.SceneQueryOptions) (*models.SceneQueryResult, error) {
|
||||||
ret := _m.Called(ctx, options)
|
ret := _m.Called(ctx, options)
|
||||||
|
|
@ -815,6 +878,27 @@ func (_m *SceneReaderWriter) Size(ctx context.Context) (float64, error) {
|
||||||
return r0, r1
|
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
|
// Update provides a mock function with given fields: ctx, updatedScene
|
||||||
func (_m *SceneReaderWriter) Update(ctx context.Context, updatedScene *models.Scene) error {
|
func (_m *SceneReaderWriter) Update(ctx context.Context, updatedScene *models.Scene) error {
|
||||||
ret := _m.Called(ctx, updatedScene)
|
ret := _m.Called(ctx, updatedScene)
|
||||||
|
|
|
||||||
|
|
@ -168,12 +168,16 @@ type SceneReader interface {
|
||||||
|
|
||||||
CountByPerformerID(ctx context.Context, performerID int) (int, error)
|
CountByPerformerID(ctx context.Context, performerID int) (int, error)
|
||||||
OCountByPerformerID(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)
|
// FindByStudioID(studioID int) ([]*Scene, error)
|
||||||
FindByMovieID(ctx context.Context, movieID int) ([]*Scene, error)
|
FindByMovieID(ctx context.Context, movieID int) ([]*Scene, error)
|
||||||
CountByMovieID(ctx context.Context, movieID int) (int, error)
|
CountByMovieID(ctx context.Context, movieID int) (int, error)
|
||||||
Count(ctx context.Context) (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)
|
Size(ctx context.Context) (float64, error)
|
||||||
Duration(ctx context.Context) (float64, error)
|
Duration(ctx context.Context) (float64, error)
|
||||||
|
PlayDuration(ctx context.Context) (float64, error)
|
||||||
// SizeCount() (string, error)
|
// SizeCount() (string, error)
|
||||||
CountByStudioID(ctx context.Context, studioID int) (int, error)
|
CountByStudioID(ctx context.Context, studioID int) (int, error)
|
||||||
CountByTagID(ctx context.Context, tagID int) (int, error)
|
CountByTagID(ctx context.Context, tagID int) (int, error)
|
||||||
|
|
|
||||||
|
|
@ -705,6 +705,18 @@ func (qb *SceneStore) OCountByPerformerID(ctx context.Context, performerID int)
|
||||||
return ret, nil
|
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) {
|
func (qb *SceneStore) FindByMovieID(ctx context.Context, movieID int) ([]*models.Scene, error) {
|
||||||
sq := dialect.From(scenesMoviesJoinTable).Select(scenesMoviesJoinTable.Col(sceneIDColumn)).Where(
|
sq := dialect.From(scenesMoviesJoinTable).Select(scenesMoviesJoinTable.Col(sceneIDColumn)).Where(
|
||||||
scenesMoviesJoinTable.Col(movieIDColumn).Eq(movieID),
|
scenesMoviesJoinTable.Col(movieIDColumn).Eq(movieID),
|
||||||
|
|
@ -730,6 +742,24 @@ func (qb *SceneStore) Count(ctx context.Context) (int, error) {
|
||||||
return count(ctx, q)
|
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) {
|
func (qb *SceneStore) Size(ctx context.Context) (float64, error) {
|
||||||
table := qb.table()
|
table := qb.table()
|
||||||
fileTable := fileTableMgr.table
|
fileTable := fileTableMgr.table
|
||||||
|
|
@ -771,6 +801,19 @@ func (qb *SceneStore) Duration(ctx context.Context) (float64, error) {
|
||||||
return ret, nil
|
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) {
|
func (qb *SceneStore) CountByStudioID(ctx context.Context, studioID int) (int, error) {
|
||||||
table := qb.table()
|
table := qb.table()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ export const Stats: React.FC = () => {
|
||||||
3
|
3
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const totalPlayDuration = TextUtils.secondsAsTimeString(
|
||||||
|
data.stats.total_play_duration,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<div className="col col-sm-8 m-sm-auto row stats">
|
<div className="col col-sm-8 m-sm-auto row stats">
|
||||||
|
|
@ -114,6 +119,38 @@ export const Stats: React.FC = () => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col col-sm-8 m-sm-auto row stats">
|
||||||
|
<div className="stats-element">
|
||||||
|
<p className="title">
|
||||||
|
<FormattedNumber value={data.stats.total_o_count} />
|
||||||
|
</p>
|
||||||
|
<p className="heading">
|
||||||
|
<FormattedMessage id="stats.total_o_count" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="stats-element">
|
||||||
|
<p className="title">
|
||||||
|
<FormattedNumber value={data.stats.total_play_count} />
|
||||||
|
</p>
|
||||||
|
<p className="heading">
|
||||||
|
<FormattedMessage id="stats.total_play_count" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="stats-element">
|
||||||
|
<p className="title">
|
||||||
|
<FormattedNumber value={data.stats.scenes_played} />
|
||||||
|
</p>
|
||||||
|
<p className="heading">
|
||||||
|
<FormattedMessage id="stats.scenes_played" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="stats-element">
|
||||||
|
<p className="title">{totalPlayDuration || "-"}</p>
|
||||||
|
<p className="heading">
|
||||||
|
<FormattedMessage id="stats.total_play_duration" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1218,7 +1218,11 @@
|
||||||
"stats": {
|
"stats": {
|
||||||
"image_size": "Images size",
|
"image_size": "Images size",
|
||||||
"scenes_duration": "Scenes duration",
|
"scenes_duration": "Scenes duration",
|
||||||
"scenes_size": "Scenes size"
|
"scenes_size": "Scenes size",
|
||||||
|
"scenes_played": "Scenes Played",
|
||||||
|
"total_play_duration": "Total Play Duration",
|
||||||
|
"total_play_count": "Total Play Count",
|
||||||
|
"total_o_count": "Total O-Count"
|
||||||
},
|
},
|
||||||
"status": "Status: {statusText}",
|
"status": "Status: {statusText}",
|
||||||
"studio": "Studio",
|
"studio": "Studio",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue