mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Add O-Counter for Performers and Sort/Filter Performers by O-Counter (#3588)
* initial commit of sort performer by o-count * work on o_counter filter * filter working * sorting, filtering using combined scene+image count * linting * fix performer list view --------- Co-authored-by: jpnsfw <none@none.com>
This commit is contained in:
@@ -25,6 +25,7 @@ fragment PerformerData on Performer {
|
|||||||
image_count
|
image_count
|
||||||
gallery_count
|
gallery_count
|
||||||
movie_count
|
movie_count
|
||||||
|
o_counter
|
||||||
|
|
||||||
tags {
|
tags {
|
||||||
...SlimTagData
|
...SlimTagData
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ input PerformerFilterType {
|
|||||||
image_count: IntCriterionInput
|
image_count: IntCriterionInput
|
||||||
"""Filter by gallery count"""
|
"""Filter by gallery count"""
|
||||||
gallery_count: IntCriterionInput
|
gallery_count: IntCriterionInput
|
||||||
|
"""Filter by o count"""
|
||||||
|
o_counter: IntCriterionInput
|
||||||
"""Filter by StashID"""
|
"""Filter by StashID"""
|
||||||
stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
|
stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
|
||||||
"""Filter by StashID"""
|
"""Filter by StashID"""
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type Performer {
|
|||||||
scene_count: Int # Resolver
|
scene_count: Int # Resolver
|
||||||
image_count: Int # Resolver
|
image_count: Int # Resolver
|
||||||
gallery_count: Int # Resolver
|
gallery_count: Int # Resolver
|
||||||
|
o_counter: Int # Resolver
|
||||||
scenes: [Scene!]!
|
scenes: [Scene!]!
|
||||||
stash_ids: [StashID!]!
|
stash_ids: [StashID!]!
|
||||||
# rating expressed as 1-5
|
# rating expressed as 1-5
|
||||||
|
|||||||
@@ -127,6 +127,24 @@ func (r *performerResolver) GalleryCount(ctx context.Context, obj *models.Perfor
|
|||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *performerResolver) OCounter(ctx context.Context, obj *models.Performer) (ret *int, err error) {
|
||||||
|
var res_scene int
|
||||||
|
var res_image int
|
||||||
|
var res int
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
res_scene, err = r.repository.Scene.OCountByPerformerID(ctx, obj.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res_image, err = r.repository.Image.OCountByPerformerID(ctx, obj.ID)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = res_scene + res_image
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) (ret []*models.Scene, err error) {
|
func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) (ret []*models.Scene, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Scene.FindByPerformerID(ctx, obj.ID)
|
ret, err = r.repository.Scene.FindByPerformerID(ctx, obj.ID)
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ type ImageReader interface {
|
|||||||
FindByChecksum(ctx context.Context, checksum string) ([]*Image, error)
|
FindByChecksum(ctx context.Context, checksum string) ([]*Image, error)
|
||||||
FindByGalleryID(ctx context.Context, galleryID int) ([]*Image, error)
|
FindByGalleryID(ctx context.Context, galleryID int) ([]*Image, error)
|
||||||
CountByGalleryID(ctx context.Context, galleryID int) (int, error)
|
CountByGalleryID(ctx context.Context, galleryID int) (int, error)
|
||||||
|
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
|
||||||
Count(ctx context.Context) (int, error)
|
Count(ctx context.Context) (int, error)
|
||||||
Size(ctx context.Context) (float64, error)
|
Size(ctx context.Context) (float64, error)
|
||||||
All(ctx context.Context) ([]*Image, error)
|
All(ctx context.Context) ([]*Image, error)
|
||||||
|
|||||||
@@ -79,6 +79,27 @@ func (_m *ImageReaderWriter) CountByGalleryID(ctx context.Context, galleryID int
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OCountByPerformerID provides a mock function with given fields: ctx, performerID
|
||||||
|
func (_m *ImageReaderWriter) OCountByPerformerID(ctx context.Context, performerID int) (int, error) {
|
||||||
|
ret := _m.Called(ctx, performerID)
|
||||||
|
|
||||||
|
var r0 int
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, int) int); ok {
|
||||||
|
r0 = rf(ctx, performerID)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
||||||
|
r1 = rf(ctx, performerID)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// Create provides a mock function with given fields: ctx, newImage
|
// Create provides a mock function with given fields: ctx, newImage
|
||||||
func (_m *ImageReaderWriter) Create(ctx context.Context, newImage *models.ImageCreateInput) error {
|
func (_m *ImageReaderWriter) Create(ctx context.Context, newImage *models.ImageCreateInput) error {
|
||||||
ret := _m.Called(ctx, newImage)
|
ret := _m.Called(ctx, newImage)
|
||||||
|
|||||||
@@ -102,6 +102,27 @@ func (_m *SceneReaderWriter) CountByPerformerID(ctx context.Context, performerID
|
|||||||
return r0, r1
|
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)
|
||||||
|
|
||||||
|
var r0 int
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, int) int); ok {
|
||||||
|
r0 = rf(ctx, performerID)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
||||||
|
r1 = rf(ctx, performerID)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// CountByStudioID provides a mock function with given fields: ctx, studioID
|
// CountByStudioID provides a mock function with given fields: ctx, studioID
|
||||||
func (_m *SceneReaderWriter) CountByStudioID(ctx context.Context, studioID int) (int, error) {
|
func (_m *SceneReaderWriter) CountByStudioID(ctx context.Context, studioID int) (int, error) {
|
||||||
ret := _m.Called(ctx, studioID)
|
ret := _m.Called(ctx, studioID)
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ type PerformerFilterType struct {
|
|||||||
ImageCount *IntCriterionInput `json:"image_count"`
|
ImageCount *IntCriterionInput `json:"image_count"`
|
||||||
// Filter by gallery count
|
// Filter by gallery count
|
||||||
GalleryCount *IntCriterionInput `json:"gallery_count"`
|
GalleryCount *IntCriterionInput `json:"gallery_count"`
|
||||||
|
// Filter by O count
|
||||||
|
OCounter *IntCriterionInput `json:"o_counter"`
|
||||||
// Filter by StashID
|
// Filter by StashID
|
||||||
StashID *StringCriterionInput `json:"stash_id"`
|
StashID *StringCriterionInput `json:"stash_id"`
|
||||||
// Filter by StashID Endpoint
|
// Filter by StashID Endpoint
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ type SceneReader interface {
|
|||||||
VideoFileLoader
|
VideoFileLoader
|
||||||
|
|
||||||
CountByPerformerID(ctx context.Context, performerID int) (int, error)
|
CountByPerformerID(ctx context.Context, performerID int) (int, error)
|
||||||
|
OCountByPerformerID(ctx context.Context, performerID int) (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)
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ type Performer struct {
|
|||||||
Deleted bool `json:"deleted"`
|
Deleted bool `json:"deleted"`
|
||||||
Edits []*Edit `json:"edits,omitempty"`
|
Edits []*Edit `json:"edits,omitempty"`
|
||||||
SceneCount int `json:"scene_count"`
|
SceneCount int `json:"scene_count"`
|
||||||
|
OCounter int `json:"o_counter"`
|
||||||
MergedIds []string `json:"merged_ids,omitempty"`
|
MergedIds []string `json:"merged_ids,omitempty"`
|
||||||
Studios []*PerformerStudio `json:"studios,omitempty"`
|
Studios []*PerformerStudio `json:"studios,omitempty"`
|
||||||
IsFavorite bool `json:"is_favorite"`
|
IsFavorite bool `json:"is_favorite"`
|
||||||
@@ -1771,6 +1772,7 @@ const (
|
|||||||
PerformerSortEnumName PerformerSortEnum = "NAME"
|
PerformerSortEnumName PerformerSortEnum = "NAME"
|
||||||
PerformerSortEnumBirthdate PerformerSortEnum = "BIRTHDATE"
|
PerformerSortEnumBirthdate PerformerSortEnum = "BIRTHDATE"
|
||||||
PerformerSortEnumSceneCount PerformerSortEnum = "SCENE_COUNT"
|
PerformerSortEnumSceneCount PerformerSortEnum = "SCENE_COUNT"
|
||||||
|
PerformerSortEnumOCounter PerformerSortEnum = "O_COUNTER"
|
||||||
PerformerSortEnumCareerStartYear PerformerSortEnum = "CAREER_START_YEAR"
|
PerformerSortEnumCareerStartYear PerformerSortEnum = "CAREER_START_YEAR"
|
||||||
PerformerSortEnumDebut PerformerSortEnum = "DEBUT"
|
PerformerSortEnumDebut PerformerSortEnum = "DEBUT"
|
||||||
PerformerSortEnumCreatedAt PerformerSortEnum = "CREATED_AT"
|
PerformerSortEnumCreatedAt PerformerSortEnum = "CREATED_AT"
|
||||||
@@ -1781,6 +1783,7 @@ var AllPerformerSortEnum = []PerformerSortEnum{
|
|||||||
PerformerSortEnumName,
|
PerformerSortEnumName,
|
||||||
PerformerSortEnumBirthdate,
|
PerformerSortEnumBirthdate,
|
||||||
PerformerSortEnumSceneCount,
|
PerformerSortEnumSceneCount,
|
||||||
|
PerformerSortEnumOCounter,
|
||||||
PerformerSortEnumCareerStartYear,
|
PerformerSortEnumCareerStartYear,
|
||||||
PerformerSortEnumDebut,
|
PerformerSortEnumDebut,
|
||||||
PerformerSortEnumCreatedAt,
|
PerformerSortEnumCreatedAt,
|
||||||
@@ -1789,7 +1792,7 @@ var AllPerformerSortEnum = []PerformerSortEnum{
|
|||||||
|
|
||||||
func (e PerformerSortEnum) IsValid() bool {
|
func (e PerformerSortEnum) IsValid() bool {
|
||||||
switch e {
|
switch e {
|
||||||
case PerformerSortEnumName, PerformerSortEnumBirthdate, PerformerSortEnumSceneCount, PerformerSortEnumCareerStartYear, PerformerSortEnumDebut, PerformerSortEnumCreatedAt, PerformerSortEnumUpdatedAt:
|
case PerformerSortEnumName, PerformerSortEnumBirthdate, PerformerSortEnumSceneCount, PerformerSortEnumOCounter, PerformerSortEnumCareerStartYear, PerformerSortEnumDebut, PerformerSortEnumCreatedAt, PerformerSortEnumUpdatedAt:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -722,6 +722,28 @@ func (m *countCriterionHandlerBuilder) handler(criterion *models.IntCriterionInp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type joinedMultiSumCriterionHandlerBuilder struct {
|
||||||
|
primaryTable string
|
||||||
|
foreignTable1 string
|
||||||
|
joinTable1 string
|
||||||
|
foreignTable2 string
|
||||||
|
joinTable2 string
|
||||||
|
primaryFK string
|
||||||
|
foreignFK1 string
|
||||||
|
foreignFK2 string
|
||||||
|
sum string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *joinedMultiSumCriterionHandlerBuilder) handler(criterion *models.IntCriterionInput) criterionHandlerFunc {
|
||||||
|
return func(ctx context.Context, f *filterBuilder) {
|
||||||
|
if criterion != nil {
|
||||||
|
clause, args := getJoinedMultiSumCriterionClause(m.primaryTable, m.foreignTable1, m.joinTable1, m.foreignTable2, m.joinTable2, m.primaryFK, m.foreignFK1, m.foreignFK2, m.sum, *criterion)
|
||||||
|
|
||||||
|
f.addWhere(clause, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handler for StringCriterion for string list fields
|
// handler for StringCriterion for string list fields
|
||||||
type stringListCriterionHandlerBuilder struct {
|
type stringListCriterionHandlerBuilder struct {
|
||||||
// table joining primary and foreign objects
|
// table joining primary and foreign objects
|
||||||
|
|||||||
@@ -513,6 +513,19 @@ func (qb *ImageStore) CountByGalleryID(ctx context.Context, galleryID int) (int,
|
|||||||
return count(ctx, q)
|
return count(ctx, q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (qb *ImageStore) OCountByPerformerID(ctx context.Context, performerID int) (int, error) {
|
||||||
|
table := qb.table()
|
||||||
|
joinTable := performersImagesJoinTable
|
||||||
|
q := dialect.Select(goqu.COALESCE(goqu.SUM("o_counter"), 0)).From(table).InnerJoin(joinTable, goqu.On(table.Col(idColumn).Eq(joinTable.Col(imageIDColumn)))).Where(joinTable.Col(performerIDColumn).Eq(performerID))
|
||||||
|
|
||||||
|
var ret int
|
||||||
|
if err := querySimple(ctx, q, &ret); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (qb *ImageStore) FindByFolderID(ctx context.Context, folderID file.FolderID) ([]*models.Image, error) {
|
func (qb *ImageStore) FindByFolderID(ctx context.Context, folderID file.FolderID) ([]*models.Image, error) {
|
||||||
table := qb.table()
|
table := qb.table()
|
||||||
fileTable := goqu.T(fileTable)
|
fileTable := goqu.T(fileTable)
|
||||||
|
|||||||
@@ -629,6 +629,7 @@ func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.Perform
|
|||||||
query.handleCriterion(ctx, performerSceneCountCriterionHandler(qb, filter.SceneCount))
|
query.handleCriterion(ctx, performerSceneCountCriterionHandler(qb, filter.SceneCount))
|
||||||
query.handleCriterion(ctx, performerImageCountCriterionHandler(qb, filter.ImageCount))
|
query.handleCriterion(ctx, performerImageCountCriterionHandler(qb, filter.ImageCount))
|
||||||
query.handleCriterion(ctx, performerGalleryCountCriterionHandler(qb, filter.GalleryCount))
|
query.handleCriterion(ctx, performerGalleryCountCriterionHandler(qb, filter.GalleryCount))
|
||||||
|
query.handleCriterion(ctx, performerOCounterCriterionHandler(qb, filter.OCounter))
|
||||||
query.handleCriterion(ctx, dateCriterionHandler(filter.Birthdate, tableName+".birthdate"))
|
query.handleCriterion(ctx, dateCriterionHandler(filter.Birthdate, tableName+".birthdate"))
|
||||||
query.handleCriterion(ctx, dateCriterionHandler(filter.DeathDate, tableName+".death_date"))
|
query.handleCriterion(ctx, dateCriterionHandler(filter.DeathDate, tableName+".death_date"))
|
||||||
query.handleCriterion(ctx, timestampCriterionHandler(filter.CreatedAt, tableName+".created_at"))
|
query.handleCriterion(ctx, timestampCriterionHandler(filter.CreatedAt, tableName+".created_at"))
|
||||||
@@ -805,6 +806,22 @@ func performerGalleryCountCriterionHandler(qb *PerformerStore, count *models.Int
|
|||||||
return h.handler(count)
|
return h.handler(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func performerOCounterCriterionHandler(qb *PerformerStore, count *models.IntCriterionInput) criterionHandlerFunc {
|
||||||
|
h := joinedMultiSumCriterionHandlerBuilder{
|
||||||
|
primaryTable: performerTable,
|
||||||
|
foreignTable1: sceneTable,
|
||||||
|
joinTable1: performersScenesTable,
|
||||||
|
foreignTable2: imageTable,
|
||||||
|
joinTable2: performersImagesTable,
|
||||||
|
primaryFK: performerIDColumn,
|
||||||
|
foreignFK1: sceneIDColumn,
|
||||||
|
foreignFK2: imageIDColumn,
|
||||||
|
sum: "o_counter",
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.handler(count)
|
||||||
|
}
|
||||||
|
|
||||||
func performerStudiosCriterionHandler(qb *PerformerStore, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
func performerStudiosCriterionHandler(qb *PerformerStore, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||||
return func(ctx context.Context, f *filterBuilder) {
|
return func(ctx context.Context, f *filterBuilder) {
|
||||||
if studios != nil {
|
if studios != nil {
|
||||||
@@ -906,6 +923,9 @@ func (qb *PerformerStore) getPerformerSort(findFilter *models.FindFilterType) st
|
|||||||
default:
|
default:
|
||||||
sortQuery += getSort(sort, direction, "performers")
|
sortQuery += getSort(sort, direction, "performers")
|
||||||
}
|
}
|
||||||
|
if sort == "o_counter" {
|
||||||
|
return getMultiSumSort("o_counter", performerTable, sceneTable, performersScenesTable, imageTable, performersImagesTable, performerIDColumn, sceneIDColumn, imageIDColumn, direction)
|
||||||
|
}
|
||||||
|
|
||||||
// Whatever the sorting, always use name/id as a final sort
|
// Whatever the sorting, always use name/id as a final sort
|
||||||
sortQuery += ", COALESCE(performers.name, performers.id) COLLATE NATURAL_CI ASC"
|
sortQuery += ", COALESCE(performers.name, performers.id) COLLATE NATURAL_CI ASC"
|
||||||
|
|||||||
@@ -680,6 +680,19 @@ func (qb *SceneStore) CountByPerformerID(ctx context.Context, performerID int) (
|
|||||||
return count(ctx, q)
|
return count(ctx, q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (qb *SceneStore) OCountByPerformerID(ctx context.Context, performerID int) (int, error) {
|
||||||
|
table := qb.table()
|
||||||
|
joinTable := scenesPerformersJoinTable
|
||||||
|
|
||||||
|
q := dialect.Select(goqu.COALESCE(goqu.SUM("o_counter"), 0)).From(table).InnerJoin(joinTable, goqu.On(table.Col(idColumn).Eq(joinTable.Col(sceneIDColumn)))).Where(joinTable.Col(performerIDColumn).Eq(performerID))
|
||||||
|
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),
|
||||||
|
|||||||
@@ -103,6 +103,27 @@ func getCountSort(primaryTable, joinTable, primaryFK, direction string) string {
|
|||||||
return fmt.Sprintf(" ORDER BY (SELECT COUNT(*) FROM %s WHERE %s = %s.id) %s", joinTable, primaryFK, primaryTable, getSortDirection(direction))
|
return fmt.Sprintf(" ORDER BY (SELECT COUNT(*) FROM %s WHERE %s = %s.id) %s", joinTable, primaryFK, primaryTable, getSortDirection(direction))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMultiSumSort(sum string, primaryTable, foreignTable1, joinTable1, foreignTable2, joinTable2, primaryFK, foreignFK1, foreignFK2, direction string) string {
|
||||||
|
return fmt.Sprintf(" ORDER BY (SELECT SUM(%s) "+
|
||||||
|
"FROM ("+
|
||||||
|
"SELECT SUM(%s) as %s from %s s "+
|
||||||
|
"LEFT JOIN %s ON %s.id = s.%s "+
|
||||||
|
"WHERE s.%s = %s.id "+
|
||||||
|
"UNION ALL "+
|
||||||
|
"SELECT SUM(%s) as %s from %s s "+
|
||||||
|
"LEFT JOIN %s ON %s.id = s.%s "+
|
||||||
|
"WHERE s.%s = %s.id "+
|
||||||
|
")) %s",
|
||||||
|
sum,
|
||||||
|
sum, sum, joinTable1,
|
||||||
|
foreignTable1, foreignTable1, foreignFK1,
|
||||||
|
primaryFK, primaryTable,
|
||||||
|
sum, sum, joinTable2,
|
||||||
|
foreignTable2, foreignTable2, foreignFK2,
|
||||||
|
primaryFK, primaryTable,
|
||||||
|
getSortDirection(direction))
|
||||||
|
}
|
||||||
|
|
||||||
func getStringSearchClause(columns []string, q string, not bool) sqlClause {
|
func getStringSearchClause(columns []string, q string, not bool) sqlClause {
|
||||||
var likeClauses []string
|
var likeClauses []string
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
@@ -287,6 +308,28 @@ func getCountCriterionClause(primaryTable, joinTable, primaryFK string, criterio
|
|||||||
return getIntCriterionWhereClause(lhs, criterion)
|
return getIntCriterionWhereClause(lhs, criterion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getJoinedMultiSumCriterionClause(primaryTable, foreignTable1, joinTable1, foreignTable2, joinTable2, primaryFK string, foreignFK1 string, foreignFK2 string, sum string, criterion models.IntCriterionInput) (string, []interface{}) {
|
||||||
|
lhs := fmt.Sprintf("(SELECT SUM(%s) "+
|
||||||
|
"FROM ("+
|
||||||
|
"SELECT SUM(%s) as %s from %s s "+
|
||||||
|
"LEFT JOIN %s ON %s.id = s.%s "+
|
||||||
|
"WHERE s.%s = %s.id "+
|
||||||
|
"UNION ALL "+
|
||||||
|
"SELECT SUM(%s) as %s from %s s "+
|
||||||
|
"LEFT JOIN %s ON %s.id = s.%s "+
|
||||||
|
"WHERE s.%s = %s.id "+
|
||||||
|
"))",
|
||||||
|
sum,
|
||||||
|
sum, sum, joinTable1,
|
||||||
|
foreignTable1, foreignTable1, foreignFK1,
|
||||||
|
primaryFK, primaryTable,
|
||||||
|
sum, sum, joinTable2,
|
||||||
|
foreignTable2, foreignTable2, foreignFK2,
|
||||||
|
primaryFK, primaryTable,
|
||||||
|
)
|
||||||
|
return getIntCriterionWhereClause(lhs, criterion)
|
||||||
|
}
|
||||||
|
|
||||||
func coalesce(column string) string {
|
func coalesce(column string) string {
|
||||||
return fmt.Sprintf("COALESCE(%s, '')", column)
|
return fmt.Sprintf("COALESCE(%s, '')", column)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import NavUtils from "src/utils/navigation";
|
|||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { GridCard } from "../Shared/GridCard";
|
import { GridCard } from "../Shared/GridCard";
|
||||||
import { CountryFlag } from "../Shared/CountryFlag";
|
import { CountryFlag } from "../Shared/CountryFlag";
|
||||||
|
import { SweatDrops } from "../Shared/SweatDrops";
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { TagLink } from "../Shared/TagLink";
|
import { TagLink } from "../Shared/TagLink";
|
||||||
@@ -137,6 +138,21 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function maybeRenderOCounter() {
|
||||||
|
if (!performer.o_counter) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="o-counter">
|
||||||
|
<Button className="minimal">
|
||||||
|
<span className="fa-icon">
|
||||||
|
<SweatDrops />
|
||||||
|
</span>
|
||||||
|
<span>{performer.o_counter}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function maybeRenderTagPopoverButton() {
|
function maybeRenderTagPopoverButton() {
|
||||||
if (performer.tags.length <= 0) return;
|
if (performer.tags.length <= 0) return;
|
||||||
|
|
||||||
@@ -173,6 +189,7 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||||||
performer.image_count ||
|
performer.image_count ||
|
||||||
performer.gallery_count ||
|
performer.gallery_count ||
|
||||||
performer.tags.length > 0 ||
|
performer.tags.length > 0 ||
|
||||||
|
performer.o_counter ||
|
||||||
performer.movie_count
|
performer.movie_count
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
@@ -184,6 +201,7 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||||||
{maybeRenderImagesPopoverButton()}
|
{maybeRenderImagesPopoverButton()}
|
||||||
{maybeRenderGalleriesPopoverButton()}
|
{maybeRenderGalleriesPopoverButton()}
|
||||||
{maybeRenderTagPopoverButton()}
|
{maybeRenderTagPopoverButton()}
|
||||||
|
{maybeRenderOCounter()}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ export const PerformerListTable: React.FC<IPerformerListTableProps> = (
|
|||||||
<h6>{performer.gallery_count}</h6>
|
<h6>{performer.gallery_count}</h6>
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<h6>{performer.o_counter}</h6>
|
||||||
|
</td>
|
||||||
<td>{performer.birthdate}</td>
|
<td>{performer.birthdate}</td>
|
||||||
<td>{!!performer.height_cm && formatHeight(performer.height_cm)}</td>
|
<td>{!!performer.height_cm && formatHeight(performer.height_cm)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -114,6 +117,7 @@ export const PerformerListTable: React.FC<IPerformerListTableProps> = (
|
|||||||
<th>{intl.formatMessage({ id: "scene_count" })}</th>
|
<th>{intl.formatMessage({ id: "scene_count" })}</th>
|
||||||
<th>{intl.formatMessage({ id: "image_count" })}</th>
|
<th>{intl.formatMessage({ id: "image_count" })}</th>
|
||||||
<th>{intl.formatMessage({ id: "gallery_count" })}</th>
|
<th>{intl.formatMessage({ id: "gallery_count" })}</th>
|
||||||
|
<th>{intl.formatMessage({ id: "o_counter" })}</th>
|
||||||
<th>{intl.formatMessage({ id: "birthdate" })}</th>
|
<th>{intl.formatMessage({ id: "birthdate" })}</th>
|
||||||
<th>{intl.formatMessage({ id: "height" })}</th>
|
<th>{intl.formatMessage({ id: "height" })}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ const sortByOptions = [
|
|||||||
messageID: "gallery_count",
|
messageID: "gallery_count",
|
||||||
value: "galleries_count",
|
value: "galleries_count",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
messageID: "o_counter",
|
||||||
|
value: "o_counter",
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const displayModeOptions = [
|
const displayModeOptions = [
|
||||||
@@ -84,6 +88,7 @@ const criterionOptions = [
|
|||||||
createMandatoryNumberCriterionOption("scene_count"),
|
createMandatoryNumberCriterionOption("scene_count"),
|
||||||
createMandatoryNumberCriterionOption("image_count"),
|
createMandatoryNumberCriterionOption("image_count"),
|
||||||
createMandatoryNumberCriterionOption("gallery_count"),
|
createMandatoryNumberCriterionOption("gallery_count"),
|
||||||
|
createMandatoryNumberCriterionOption("o_counter"),
|
||||||
createBooleanCriterionOption("ignore_auto_tag"),
|
createBooleanCriterionOption("ignore_auto_tag"),
|
||||||
new NumberCriterionOption("height", "height_cm", "height_cm"),
|
new NumberCriterionOption("height", "height_cm", "height_cm"),
|
||||||
...numberCriteria.map((c) => createNumberCriterionOption(c)),
|
...numberCriteria.map((c) => createNumberCriterionOption(c)),
|
||||||
|
|||||||
Reference in New Issue
Block a user