Path filter for scenes and galleries (#834)

This commit is contained in:
WithoutPants
2020-10-12 08:19:51 +11:00
committed by GitHub
parent 08276ac616
commit ade109d9e4
11 changed files with 201 additions and 50 deletions

View File

@@ -64,6 +64,8 @@ input SceneMarkerFilterType {
} }
input SceneFilterType { input SceneFilterType {
"""Filter by path"""
path: StringCriterionInput
"""Filter by rating""" """Filter by rating"""
rating: IntCriterionInput rating: IntCriterionInput
"""Filter by o-counter""" """Filter by o-counter"""
@@ -101,6 +103,8 @@ input StudioFilterType {
} }
input GalleryFilterType { input GalleryFilterType {
"""Filter by path"""
path: StringCriterionInput
"""Filter to only include galleries missing this property""" """Filter to only include galleries missing this property"""
is_missing: String is_missing: String
} }

View File

@@ -154,6 +154,8 @@ func (qb *GalleryQueryBuilder) Query(galleryFilter *GalleryFilterType, findFilte
query.addArg(thisArgs...) query.addArg(thisArgs...)
} }
query.handleStringCriterionInput(galleryFilter.Path, "galleries.path")
if isMissingFilter := galleryFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" { if isMissingFilter := galleryFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
switch *isMissingFilter { switch *isMissingFilter {
case "scene": case "scene":

View File

@@ -125,6 +125,34 @@ func galleryQueryQ(t *testing.T, qb models.GalleryQueryBuilder, q string, expect
assert.Len(t, galleries, totalGalleries) assert.Len(t, galleries, totalGalleries)
} }
func TestGalleryQueryPath(t *testing.T) {
const galleryIdx = 1
galleryPath := getGalleryStringValue(galleryIdx, "Path")
pathCriterion := models.StringCriterionInput{
Value: galleryPath,
Modifier: models.CriterionModifierEquals,
}
verifyGalleriesPath(t, pathCriterion)
pathCriterion.Modifier = models.CriterionModifierNotEquals
verifyGalleriesPath(t, pathCriterion)
}
func verifyGalleriesPath(t *testing.T, pathCriterion models.StringCriterionInput) {
sqb := models.NewGalleryQueryBuilder()
galleryFilter := models.GalleryFilterType{
Path: &pathCriterion,
}
galleries, _ := sqb.Query(&galleryFilter, nil)
for _, gallery := range galleries {
verifyString(t, gallery.Path, pathCriterion)
}
}
func TestGalleryQueryIsMissingScene(t *testing.T) { func TestGalleryQueryIsMissingScene(t *testing.T) {
qb := models.NewGalleryQueryBuilder() qb := models.NewGalleryQueryBuilder()
isMissing := "scene" isMissing := "scene"

View File

@@ -206,18 +206,18 @@ func (qb *PerformerQueryBuilder) Query(performerFilter *PerformerFilterType, fin
} }
} }
handleStringCriterion(tableName+".ethnicity", performerFilter.Ethnicity, &query) query.handleStringCriterionInput(performerFilter.Ethnicity, tableName+".ethnicity")
handleStringCriterion(tableName+".country", performerFilter.Country, &query) query.handleStringCriterionInput(performerFilter.Country, tableName+".country")
handleStringCriterion(tableName+".eye_color", performerFilter.EyeColor, &query) query.handleStringCriterionInput(performerFilter.EyeColor, tableName+".eye_color")
handleStringCriterion(tableName+".height", performerFilter.Height, &query) query.handleStringCriterionInput(performerFilter.Height, tableName+".height")
handleStringCriterion(tableName+".measurements", performerFilter.Measurements, &query) query.handleStringCriterionInput(performerFilter.Measurements, tableName+".measurements")
handleStringCriterion(tableName+".fake_tits", performerFilter.FakeTits, &query) query.handleStringCriterionInput(performerFilter.FakeTits, tableName+".fake_tits")
handleStringCriterion(tableName+".career_length", performerFilter.CareerLength, &query) query.handleStringCriterionInput(performerFilter.CareerLength, tableName+".career_length")
handleStringCriterion(tableName+".tattoos", performerFilter.Tattoos, &query) query.handleStringCriterionInput(performerFilter.Tattoos, tableName+".tattoos")
handleStringCriterion(tableName+".piercings", performerFilter.Piercings, &query) query.handleStringCriterionInput(performerFilter.Piercings, tableName+".piercings")
// TODO - need better handling of aliases // TODO - need better handling of aliases
handleStringCriterion(tableName+".aliases", performerFilter.Aliases, &query) query.handleStringCriterionInput(performerFilter.Aliases, tableName+".aliases")
query.sortAndPagination = qb.getPerformerSort(findFilter) + getPagination(findFilter) query.sortAndPagination = qb.getPerformerSort(findFilter) + getPagination(findFilter)
idsResult, countResult := query.executeFind() idsResult, countResult := query.executeFind()
@@ -231,27 +231,6 @@ func (qb *PerformerQueryBuilder) Query(performerFilter *PerformerFilterType, fin
return performers, countResult return performers, countResult
} }
func handleStringCriterion(column string, value *StringCriterionInput, query *queryBuilder) {
if value != nil {
if modifier := value.Modifier.String(); value.Modifier.IsValid() {
switch modifier {
case "EQUALS":
clause, thisArgs := getSearchBinding([]string{column}, value.Value, false)
query.addWhere(clause)
query.addArg(thisArgs...)
case "NOT_EQUALS":
clause, thisArgs := getSearchBinding([]string{column}, value.Value, true)
query.addWhere(clause)
query.addArg(thisArgs...)
case "IS_NULL":
query.addWhere(column + " IS NULL")
case "NOT_NULL":
query.addWhere(column + " IS NOT NULL")
}
}
}
}
func getBirthYearFilterClause(criterionModifier CriterionModifier, value int) ([]string, []interface{}) { func getBirthYearFilterClause(criterionModifier CriterionModifier, value int) ([]string, []interface{}) {
var clauses []string var clauses []string
var args []interface{} var args []interface{}

View File

@@ -312,21 +312,9 @@ func (qb *SceneQueryBuilder) Query(sceneFilter *SceneFilterType, findFilter *Fin
query.addArg(thisArgs...) query.addArg(thisArgs...)
} }
if rating := sceneFilter.Rating; rating != nil { query.handleStringCriterionInput(sceneFilter.Path, "scenes.path")
clause, count := getIntCriterionWhereClause("scenes.rating", *sceneFilter.Rating) query.handleIntCriterionInput(sceneFilter.Rating, "scenes.rating")
query.addWhere(clause) query.handleIntCriterionInput(sceneFilter.OCounter, "scenes.o_counter")
if count == 1 {
query.addArg(sceneFilter.Rating.Value)
}
}
if oCounter := sceneFilter.OCounter; oCounter != nil {
clause, count := getIntCriterionWhereClause("scenes.o_counter", *sceneFilter.OCounter)
query.addWhere(clause)
if count == 1 {
query.addArg(sceneFilter.OCounter.Value)
}
}
if durationFilter := sceneFilter.Duration; durationFilter != nil { if durationFilter := sceneFilter.Duration; durationFilter != nil {
clause, thisArgs := getDurationWhereClause(*durationFilter) clause, thisArgs := getDurationWhereClause(*durationFilter)

View File

@@ -135,6 +135,62 @@ func sceneQueryQ(t *testing.T, sqb models.SceneQueryBuilder, q string, expectedS
assert.Len(t, scenes, totalScenes) assert.Len(t, scenes, totalScenes)
} }
func TestSceneQueryPath(t *testing.T) {
const sceneIdx = 1
scenePath := getSceneStringValue(sceneIdx, "Path")
pathCriterion := models.StringCriterionInput{
Value: scenePath,
Modifier: models.CriterionModifierEquals,
}
verifyScenesPath(t, pathCriterion)
pathCriterion.Modifier = models.CriterionModifierNotEquals
verifyScenesPath(t, pathCriterion)
}
func verifyScenesPath(t *testing.T, pathCriterion models.StringCriterionInput) {
sqb := models.NewSceneQueryBuilder()
sceneFilter := models.SceneFilterType{
Path: &pathCriterion,
}
scenes, _ := sqb.Query(&sceneFilter, nil)
for _, scene := range scenes {
verifyString(t, scene.Path, pathCriterion)
}
}
func verifyNullString(t *testing.T, value sql.NullString, criterion models.StringCriterionInput) {
t.Helper()
assert := assert.New(t)
if criterion.Modifier == models.CriterionModifierIsNull {
assert.False(value.Valid, "expect is null values to be null")
}
if criterion.Modifier == models.CriterionModifierNotNull {
assert.True(value.Valid, "expect is null values to be null")
}
if criterion.Modifier == models.CriterionModifierEquals {
assert.Equal(criterion.Value, value.String)
}
if criterion.Modifier == models.CriterionModifierNotEquals {
assert.NotEqual(criterion.Value, value.String)
}
}
func verifyString(t *testing.T, value string, criterion models.StringCriterionInput) {
t.Helper()
assert := assert.New(t)
if criterion.Modifier == models.CriterionModifierEquals {
assert.Equal(criterion.Value, value)
}
if criterion.Modifier == models.CriterionModifierNotEquals {
assert.NotEqual(criterion.Value, value)
}
}
func TestSceneQueryRating(t *testing.T) { func TestSceneQueryRating(t *testing.T) {
const rating = 3 const rating = 3
ratingCriterion := models.IntCriterionInput{ ratingCriterion := models.IntCriterionInput{

View File

@@ -48,6 +48,37 @@ func (qb *queryBuilder) addArg(args ...interface{}) {
qb.args = append(qb.args, args...) qb.args = append(qb.args, args...)
} }
func (qb *queryBuilder) handleIntCriterionInput(c *IntCriterionInput, column string) {
if c != nil {
clause, count := getIntCriterionWhereClause(column, *c)
qb.addWhere(clause)
if count == 1 {
qb.addArg(c.Value)
}
}
}
func (qb *queryBuilder) handleStringCriterionInput(c *StringCriterionInput, column string) {
if c != nil {
if modifier := c.Modifier.String(); c.Modifier.IsValid() {
switch modifier {
case "EQUALS":
clause, thisArgs := getSearchBinding([]string{column}, c.Value, false)
qb.addWhere(clause)
qb.addArg(thisArgs...)
case "NOT_EQUALS":
clause, thisArgs := getSearchBinding([]string{column}, c.Value, true)
qb.addWhere(clause)
qb.addArg(thisArgs...)
case "IS_NULL":
qb.addWhere(column + " IS NULL")
case "NOT_NULL":
qb.addWhere(column + " IS NOT NULL")
}
}
}
}
var randomSortFloat = rand.Float64() var randomSortFloat = rand.Float64()
func selectAll(tableName string) string { func selectAll(tableName string) string {

View File

@@ -3,6 +3,7 @@
* Add selective scene export. * Add selective scene export.
### 🎨 Improvements ### 🎨 Improvements
* Add path filter to scene and gallery query.
* Add button to hide left panel on scene page. * Add button to hide left panel on scene page.
* Add link to parent studio in studio page. * Add link to parent studio in studio page.
* Add missing scenes movie filter. * Add missing scenes movie filter.

View File

@@ -6,6 +6,7 @@ import { ILabeledId, ILabeledValue, IOptionType } from "../types";
export type CriterionType = export type CriterionType =
| "none" | "none"
| "path"
| "rating" | "rating"
| "o_counter" | "o_counter"
| "resolution" | "resolution"
@@ -48,6 +49,8 @@ export abstract class Criterion {
switch (type) { switch (type) {
case "none": case "none":
return "None"; return "None";
case "path":
return "Path";
case "rating": case "rating":
return "Rating"; return "Rating";
case "o_counter": case "o_counter":
@@ -237,10 +240,10 @@ export class StringCriterion extends Criterion {
public parameterName: string; public parameterName: string;
public modifier = CriterionModifier.Equals; public modifier = CriterionModifier.Equals;
public modifierOptions = [ public modifierOptions = [
Criterion.getModifierOption(CriterionModifier.Equals), StringCriterion.getModifierOption(CriterionModifier.Equals),
Criterion.getModifierOption(CriterionModifier.NotEquals), StringCriterion.getModifierOption(CriterionModifier.NotEquals),
Criterion.getModifierOption(CriterionModifier.IsNull), StringCriterion.getModifierOption(CriterionModifier.IsNull),
Criterion.getModifierOption(CriterionModifier.NotNull), StringCriterion.getModifierOption(CriterionModifier.NotNull),
]; ];
public options: string[] | undefined; public options: string[] | undefined;
public value: string = ""; public value: string = "";
@@ -262,6 +265,43 @@ export class StringCriterion extends Criterion {
this.parameterName = type; this.parameterName = type;
} }
} }
public static getModifierOption(
modifier: CriterionModifier = CriterionModifier.Equals
): ILabeledValue {
switch (modifier) {
case CriterionModifier.Equals:
return { value: CriterionModifier.Equals, label: "Includes" };
case CriterionModifier.NotEquals:
return { value: CriterionModifier.NotEquals, label: "Excludes" };
default:
return super.getModifierOption(modifier);
}
}
public getLabel(): string {
let modifierString: string;
switch (this.modifier) {
case CriterionModifier.Equals:
modifierString = "includes";
break;
case CriterionModifier.NotEquals:
modifierString = "excludes";
break;
default:
return this.getLabel();
}
const valueString = this.getLabelValue();
return `${Criterion.getLabel(this.type)} ${modifierString} ${valueString}`;
}
}
export class MandatoryStringCriterion extends StringCriterion {
public modifierOptions = [
StringCriterion.getModifierOption(CriterionModifier.Equals),
StringCriterion.getModifierOption(CriterionModifier.NotEquals),
];
} }
export class NumberCriterion extends Criterion { export class NumberCriterion extends Criterion {

View File

@@ -6,6 +6,7 @@ import {
StringCriterion, StringCriterion,
NumberCriterion, NumberCriterion,
DurationCriterion, DurationCriterion,
MandatoryStringCriterion,
} from "./criterion"; } from "./criterion";
import { FavoriteCriterion } from "./favorite"; import { FavoriteCriterion } from "./favorite";
import { HasMarkersCriterion } from "./has-markers"; import { HasMarkersCriterion } from "./has-markers";
@@ -30,6 +31,8 @@ export function makeCriteria(type: CriterionType = "none") {
switch (type) { switch (type) {
case "none": case "none":
return new NoneCriterion(); return new NoneCriterion();
case "path":
return new MandatoryStringCriterion(type, type);
case "rating": case "rating":
return new RatingCriterion(); return new RatingCriterion();
case "o_counter": case "o_counter":

View File

@@ -20,6 +20,7 @@ import {
NumberCriterion, NumberCriterion,
StringCriterion, StringCriterion,
DurationCriterion, DurationCriterion,
MandatoryStringCriterion,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { import {
FavoriteCriterion, FavoriteCriterion,
@@ -124,6 +125,7 @@ export class ListFilterModel {
]; ];
this.criterionOptions = [ this.criterionOptions = [
new NoneCriterionOption(), new NoneCriterionOption(),
ListFilterModel.createCriterionOption("path"),
new RatingCriterionOption(), new RatingCriterionOption(),
ListFilterModel.createCriterionOption("o_counter"), ListFilterModel.createCriterionOption("o_counter"),
new ResolutionCriterionOption(), new ResolutionCriterionOption(),
@@ -199,6 +201,7 @@ export class ListFilterModel {
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List]; this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
this.criterionOptions = [ this.criterionOptions = [
new NoneCriterionOption(), new NoneCriterionOption(),
ListFilterModel.createCriterionOption("path"),
new GalleryIsMissingCriterionOption(), new GalleryIsMissingCriterionOption(),
]; ];
break; break;
@@ -380,6 +383,14 @@ export class ListFilterModel {
const result: SceneFilterType = {}; const result: SceneFilterType = {};
this.criteria.forEach((criterion) => { this.criteria.forEach((criterion) => {
switch (criterion.type) { switch (criterion.type) {
case "path": {
const pathCrit = criterion as MandatoryStringCriterion;
result.path = {
value: pathCrit.value,
modifier: pathCrit.modifier,
};
break;
}
case "rating": { case "rating": {
const ratingCrit = criterion as RatingCriterion; const ratingCrit = criterion as RatingCriterion;
result.rating = { result.rating = {
@@ -647,6 +658,14 @@ export class ListFilterModel {
const result: GalleryFilterType = {}; const result: GalleryFilterType = {};
this.criteria.forEach((criterion) => { this.criteria.forEach((criterion) => {
switch (criterion.type) { switch (criterion.type) {
case "path": {
const pathCrit = criterion as MandatoryStringCriterion;
result.path = {
value: pathCrit.value,
modifier: pathCrit.modifier,
};
break;
}
case "galleryIsMissing": case "galleryIsMissing":
result.is_missing = (criterion as IsMissingCriterion).value; result.is_missing = (criterion as IsMissingCriterion).value;
break; break;