Add studio *_count filters and sort options (#1307)

This commit is contained in:
peolic
2021-04-20 09:48:36 +03:00
committed by GitHub
parent 1759a99f65
commit 9200f167bf
6 changed files with 237 additions and 22 deletions

View File

@@ -149,6 +149,12 @@ input StudioFilterType {
stash_id: String stash_id: String
"""Filter to only include studios missing this property""" """Filter to only include studios missing this property"""
is_missing: String is_missing: String
"""Filter by scene count"""
scene_count: IntCriterionInput
"""Filter by image count"""
image_count: IntCriterionInput
"""Filter by gallery count"""
gallery_count: IntCriterionInput
"""Filter by url""" """Filter by url"""
url: StringCriterionInput url: StringCriterionInput
} }

View File

@@ -126,7 +126,7 @@ func TestImageQueryPath(t *testing.T) {
verifyImagePath(t, pathCriterion, totalImages-1) verifyImagePath(t, pathCriterion, totalImages-1)
pathCriterion.Modifier = models.CriterionModifierMatchesRegex pathCriterion.Modifier = models.CriterionModifierMatchesRegex
pathCriterion.Value = "image_.*1_Path" pathCriterion.Value = "image_.*01_Path"
verifyImagePath(t, pathCriterion, 1) // TODO - 2 if zip path is included verifyImagePath(t, pathCriterion, 1) // TODO - 2 if zip path is included
pathCriterion.Modifier = models.CriterionModifierNotMatchesRegex pathCriterion.Modifier = models.CriterionModifierNotMatchesRegex

View File

@@ -34,6 +34,8 @@ const (
sceneIdxWithTag sceneIdxWithTag
sceneIdxWithTwoTags sceneIdxWithTwoTags
sceneIdxWithStudio sceneIdxWithStudio
sceneIdx1WithStudio
sceneIdx2WithStudio
sceneIdxWithMarker sceneIdxWithMarker
sceneIdxWithPerformerTag sceneIdxWithPerformerTag
sceneIdxWithPerformerTwoTags sceneIdxWithPerformerTwoTags
@@ -53,6 +55,8 @@ const (
imageIdxWithTag imageIdxWithTag
imageIdxWithTwoTags imageIdxWithTwoTags
imageIdxWithStudio imageIdxWithStudio
imageIdx1WithStudio
imageIdx2WithStudio
imageIdxInZip // TODO - not implemented imageIdxInZip // TODO - not implemented
imageIdxWithPerformerTag imageIdxWithPerformerTag
imageIdxWithPerformerTwoTags imageIdxWithPerformerTwoTags
@@ -105,6 +109,8 @@ const (
galleryIdxWithTag galleryIdxWithTag
galleryIdxWithTwoTags galleryIdxWithTwoTags
galleryIdxWithStudio galleryIdxWithStudio
galleryIdx1WithStudio
galleryIdx2WithStudio
galleryIdxWithPerformerTag galleryIdxWithPerformerTag
galleryIdxWithPerformerTwoTags galleryIdxWithPerformerTwoTags
// new indexes above // new indexes above
@@ -140,11 +146,14 @@ const (
const ( const (
studioIdxWithScene = iota studioIdxWithScene = iota
studioIdxWithTwoScenes
studioIdxWithMovie studioIdxWithMovie
studioIdxWithChildStudio studioIdxWithChildStudio
studioIdxWithParentStudio studioIdxWithParentStudio
studioIdxWithImage studioIdxWithImage
studioIdxWithTwoImages
studioIdxWithGallery studioIdxWithGallery
studioIdxWithTwoGalleries
// new indexes above // new indexes above
// studios with dup names start from the end // studios with dup names start from the end
studioIdxWithDupName studioIdxWithDupName
@@ -213,6 +222,8 @@ var (
sceneStudioLinks = [][2]int{ sceneStudioLinks = [][2]int{
{sceneIdxWithStudio, studioIdxWithScene}, {sceneIdxWithStudio, studioIdxWithScene},
{sceneIdx1WithStudio, studioIdxWithTwoScenes},
{sceneIdx2WithStudio, studioIdxWithTwoScenes},
} }
) )
@@ -222,6 +233,8 @@ var (
} }
imageStudioLinks = [][2]int{ imageStudioLinks = [][2]int{
{imageIdxWithStudio, studioIdxWithImage}, {imageIdxWithStudio, studioIdxWithImage},
{imageIdx1WithStudio, studioIdxWithTwoImages},
{imageIdx2WithStudio, studioIdxWithTwoImages},
} }
imageTagLinks = [][2]int{ imageTagLinks = [][2]int{
{imageIdxWithTag, tagIdxWithImage}, {imageIdxWithTag, tagIdxWithImage},
@@ -250,6 +263,12 @@ var (
{galleryIdx2WithPerformer, performerIdxWithTwoGalleries}, {galleryIdx2WithPerformer, performerIdxWithTwoGalleries},
} }
galleryStudioLinks = [][2]int{
{galleryIdxWithStudio, studioIdxWithGallery},
{galleryIdx1WithStudio, studioIdxWithTwoGalleries},
{galleryIdx2WithStudio, studioIdxWithTwoGalleries},
}
galleryTagLinks = [][2]int{ galleryTagLinks = [][2]int{
{galleryIdxWithTag, tagIdxWithGallery}, {galleryIdxWithTag, tagIdxWithGallery},
{galleryIdxWithTwoTags, tagIdx1WithGallery}, {galleryIdxWithTwoTags, tagIdx1WithGallery},
@@ -413,8 +432,8 @@ func populateDB() error {
return fmt.Errorf("error linking gallery tags: %s", err.Error()) return fmt.Errorf("error linking gallery tags: %s", err.Error())
} }
if err := linkGalleryStudio(r.Gallery(), galleryIdxWithStudio, studioIdxWithGallery); err != nil { if err := linkGalleryStudios(r.Gallery()); err != nil {
return fmt.Errorf("error linking gallery studio: %s", err.Error()) return fmt.Errorf("error linking gallery studios: %s", err.Error())
} }
if err := createMarker(r.SceneMarker(), sceneIdxWithMarker, tagIdxWithPrimaryMarker, []int{tagIdxWithMarker}); err != nil { if err := createMarker(r.SceneMarker(), sceneIdxWithMarker, tagIdxWithPrimaryMarker, []int{tagIdxWithMarker}); err != nil {
@@ -1017,7 +1036,7 @@ func linkImagePerformers(qb models.ImageReaderWriter) error {
func linkGalleryPerformers(qb models.GalleryReaderWriter) error { func linkGalleryPerformers(qb models.GalleryReaderWriter) error {
return doLinks(galleryPerformerLinks, func(galleryIndex, performerIndex int) error { return doLinks(galleryPerformerLinks, func(galleryIndex, performerIndex int) error {
galleryID := imageIDs[galleryIndex] galleryID := galleryIDs[galleryIndex]
performers, err := qb.GetPerformerIDs(galleryID) performers, err := qb.GetPerformerIDs(galleryID)
if err != nil { if err != nil {
return err return err
@@ -1029,17 +1048,29 @@ func linkGalleryPerformers(qb models.GalleryReaderWriter) error {
}) })
} }
func linkGalleryTags(iqb models.GalleryReaderWriter) error { func linkGalleryStudios(qb models.GalleryReaderWriter) error {
return doLinks(galleryStudioLinks, func(galleryIndex, studioIndex int) error {
gallery := models.GalleryPartial{
ID: galleryIDs[galleryIndex],
StudioID: &sql.NullInt64{Int64: int64(studioIDs[studioIndex]), Valid: true},
}
_, err := qb.UpdatePartial(gallery)
return err
})
}
func linkGalleryTags(qb models.GalleryReaderWriter) error {
return doLinks(galleryTagLinks, func(galleryIndex, tagIndex int) error { return doLinks(galleryTagLinks, func(galleryIndex, tagIndex int) error {
galleryID := imageIDs[galleryIndex] galleryID := galleryIDs[galleryIndex]
tags, err := iqb.GetTagIDs(galleryID) tags, err := qb.GetTagIDs(galleryID)
if err != nil { if err != nil {
return err return err
} }
tags = append(tags, tagIDs[tagIndex]) tags = append(tags, tagIDs[tagIndex])
return iqb.UpdateTags(galleryID, tags) return qb.UpdateTags(galleryID, tags)
}) })
} }
@@ -1070,13 +1101,3 @@ func linkStudiosParent(qb models.StudioWriter) error {
func addTagImage(qb models.TagWriter, tagIndex int) error { func addTagImage(qb models.TagWriter, tagIndex int) error {
return qb.UpdateImage(tagIDs[tagIndex], models.DefaultTagImage) return qb.UpdateImage(tagIDs[tagIndex], models.DefaultTagImage)
} }
func linkGalleryStudio(qb models.GalleryWriter, galleryIndex, studioIndex int) error {
gallery := models.GalleryPartial{
ID: galleryIDs[galleryIndex],
StudioID: &sql.NullInt64{Int64: int64(studioIDs[studioIndex]), Valid: true},
}
_, err := qb.UpdatePartial(gallery)
return err
}

View File

@@ -165,6 +165,10 @@ func (qb *studioQueryBuilder) Query(studioFilter *models.StudioFilterType, findF
query.addArg(stashIDFilter) query.addArg(stashIDFilter)
} }
query.handleCountCriterion(studioFilter.SceneCount, studioTable, sceneTable, studioIDColumn)
query.handleCountCriterion(studioFilter.ImageCount, studioTable, imageTable, studioIDColumn)
query.handleCountCriterion(studioFilter.GalleryCount, studioTable, galleryTable, studioIDColumn)
query.handleStringCriterionInput(studioFilter.URL, "studios.url") query.handleStringCriterionInput(studioFilter.URL, "studios.url")
if isMissingFilter := studioFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" { if isMissingFilter := studioFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
@@ -209,8 +213,16 @@ func (qb *studioQueryBuilder) getStudioSort(findFilter *models.FindFilterType) s
sort = findFilter.GetSort("name") sort = findFilter.GetSort("name")
direction = findFilter.GetDirection() direction = findFilter.GetDirection()
} }
switch sort {
case "images_count":
return getCountSort(studioTable, imageTable, studioIDColumn, direction)
case "galleries_count":
return getCountSort(studioTable, galleryTable, studioIDColumn, direction)
default:
return getSort(sort, direction, "studios") return getSort(sort, direction, "studios")
} }
}
func (qb *studioQueryBuilder) queryStudio(query string, args []interface{}) (*models.Studio, error) { func (qb *studioQueryBuilder) queryStudio(query string, args []interface{}) (*models.Studio, error) {
results, err := qb.queryStudios(query, args) results, err := qb.queryStudios(query, args)

View File

@@ -265,6 +265,147 @@ func TestStudioDestroyStudioImage(t *testing.T) {
} }
} }
func TestStudioQuerySceneCount(t *testing.T) {
const sceneCount = 1
sceneCountCriterion := models.IntCriterionInput{
Value: sceneCount,
Modifier: models.CriterionModifierEquals,
}
verifyStudiosSceneCount(t, sceneCountCriterion)
sceneCountCriterion.Modifier = models.CriterionModifierNotEquals
verifyStudiosSceneCount(t, sceneCountCriterion)
sceneCountCriterion.Modifier = models.CriterionModifierGreaterThan
verifyStudiosSceneCount(t, sceneCountCriterion)
sceneCountCriterion.Modifier = models.CriterionModifierLessThan
verifyStudiosSceneCount(t, sceneCountCriterion)
}
func verifyStudiosSceneCount(t *testing.T, sceneCountCriterion models.IntCriterionInput) {
withTxn(func(r models.Repository) error {
sqb := r.Studio()
studioFilter := models.StudioFilterType{
SceneCount: &sceneCountCriterion,
}
studios := queryStudio(t, sqb, &studioFilter, nil)
assert.Greater(t, len(studios), 0)
for _, studio := range studios {
sceneCount, err := r.Scene().CountByStudioID(studio.ID)
if err != nil {
return err
}
verifyInt(t, sceneCount, sceneCountCriterion)
}
return nil
})
}
func TestStudioQueryImageCount(t *testing.T) {
const imageCount = 1
imageCountCriterion := models.IntCriterionInput{
Value: imageCount,
Modifier: models.CriterionModifierEquals,
}
verifyStudiosImageCount(t, imageCountCriterion)
imageCountCriterion.Modifier = models.CriterionModifierNotEquals
verifyStudiosImageCount(t, imageCountCriterion)
imageCountCriterion.Modifier = models.CriterionModifierGreaterThan
verifyStudiosImageCount(t, imageCountCriterion)
imageCountCriterion.Modifier = models.CriterionModifierLessThan
verifyStudiosImageCount(t, imageCountCriterion)
}
func verifyStudiosImageCount(t *testing.T, imageCountCriterion models.IntCriterionInput) {
withTxn(func(r models.Repository) error {
sqb := r.Studio()
studioFilter := models.StudioFilterType{
ImageCount: &imageCountCriterion,
}
studios := queryStudio(t, sqb, &studioFilter, nil)
assert.Greater(t, len(studios), 0)
for _, studio := range studios {
pp := 0
_, count, err := r.Image().Query(&models.ImageFilterType{
Studios: &models.MultiCriterionInput{
Value: []string{strconv.Itoa(studio.ID)},
Modifier: models.CriterionModifierIncludes,
},
}, &models.FindFilterType{
PerPage: &pp,
})
if err != nil {
return err
}
verifyInt(t, count, imageCountCriterion)
}
return nil
})
}
func TestStudioQueryGalleryCount(t *testing.T) {
const galleryCount = 1
galleryCountCriterion := models.IntCriterionInput{
Value: galleryCount,
Modifier: models.CriterionModifierEquals,
}
verifyStudiosGalleryCount(t, galleryCountCriterion)
galleryCountCriterion.Modifier = models.CriterionModifierNotEquals
verifyStudiosGalleryCount(t, galleryCountCriterion)
galleryCountCriterion.Modifier = models.CriterionModifierGreaterThan
verifyStudiosGalleryCount(t, galleryCountCriterion)
galleryCountCriterion.Modifier = models.CriterionModifierLessThan
verifyStudiosGalleryCount(t, galleryCountCriterion)
}
func verifyStudiosGalleryCount(t *testing.T, galleryCountCriterion models.IntCriterionInput) {
withTxn(func(r models.Repository) error {
sqb := r.Studio()
studioFilter := models.StudioFilterType{
GalleryCount: &galleryCountCriterion,
}
studios := queryStudio(t, sqb, &studioFilter, nil)
assert.Greater(t, len(studios), 0)
for _, studio := range studios {
pp := 0
_, count, err := r.Gallery().Query(&models.GalleryFilterType{
Studios: &models.MultiCriterionInput{
Value: []string{strconv.Itoa(studio.ID)},
Modifier: models.CriterionModifierIncludes,
},
}, &models.FindFilterType{
PerPage: &pp,
})
if err != nil {
return err
}
verifyInt(t, count, galleryCountCriterion)
}
return nil
})
}
func TestStudioStashIDs(t *testing.T) { func TestStudioStashIDs(t *testing.T) {
if err := withTxn(func(r models.Repository) error { if err := withTxn(func(r models.Repository) error {
qb := r.Studio() qb := r.Studio()

View File

@@ -240,12 +240,21 @@ export class ListFilterModel {
} }
case FilterMode.Studios: case FilterMode.Studios:
this.sortBy = "name"; this.sortBy = "name";
this.sortByOptions = ["name", "scenes_count", "random"]; this.sortByOptions = [
"name",
"scenes_count",
"images_count",
"galleries_count",
"random",
];
this.displayModeOptions = [DisplayMode.Grid]; this.displayModeOptions = [DisplayMode.Grid];
this.criterionOptions = [ this.criterionOptions = [
new NoneCriterionOption(), new NoneCriterionOption(),
new ParentStudiosCriterionOption(), new ParentStudiosCriterionOption(),
new StudioIsMissingCriterionOption(), new StudioIsMissingCriterionOption(),
ListFilterModel.createCriterionOption("scene_count"),
ListFilterModel.createCriterionOption("image_count"),
ListFilterModel.createCriterionOption("gallery_count"),
ListFilterModel.createCriterionOption("url"), ListFilterModel.createCriterionOption("url"),
]; ];
break; break;
@@ -1034,8 +1043,34 @@ export class ListFilterModel {
}; };
break; break;
} }
case "studioIsMissing": case "studioIsMissing": {
result.is_missing = (criterion as IsMissingCriterion).value; result.is_missing = (criterion as IsMissingCriterion).value;
break;
}
case "scene_count": {
const countCrit = criterion as NumberCriterion;
result.scene_count = {
value: countCrit.value,
modifier: countCrit.modifier,
};
break;
}
case "image_count": {
const countCrit = criterion as NumberCriterion;
result.image_count = {
value: countCrit.value,
modifier: countCrit.modifier,
};
break;
}
case "gallery_count": {
const countCrit = criterion as NumberCriterion;
result.gallery_count = {
value: countCrit.value,
modifier: countCrit.modifier,
};
break;
}
// no default // no default
} }
}); });