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
|
||||
dist
|
||||
.DS_Store
|
||||
/.local
|
||||
/.local*
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,10 @@ 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,
|
||||
|
|
@ -169,6 +173,10 @@ func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) {
|
|||
StudioCount: studiosCount,
|
||||
MovieCount: moviesCount,
|
||||
TagCount: tagsCount,
|
||||
TotalOCount: totalOCount,
|
||||
TotalPlayDuration: totalPlayDuration,
|
||||
TotalPlayCount: totalPlayCount,
|
||||
ScenesPlayed: uniqueScenePlayCount,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ export const Stats: React.FC = () => {
|
|||
3
|
||||
);
|
||||
|
||||
const totalPlayDuration = TextUtils.secondsAsTimeString(
|
||||
data.stats.total_play_duration,
|
||||
3
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="col col-sm-8 m-sm-auto row stats">
|
||||
|
|
@ -114,6 +119,38 @@ export const Stats: React.FC = () => {
|
|||
</p>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1218,7 +1218,11 @@
|
|||
"stats": {
|
||||
"image_size": "Images size",
|
||||
"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}",
|
||||
"studio": "Studio",
|
||||
|
|
|
|||
Loading…
Reference in a new issue