Skip to content

Commit

Permalink
Feature/responses operator (#4352)
Browse files Browse the repository at this point in the history
  • Loading branch information
damianpumar authored Nov 29, 2023
1 parent 26d8be1 commit 1ec6060
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 38 deletions.
5 changes: 5 additions & 0 deletions frontend/v1/domain/entities/common/Filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export interface RangeValue {
le?: number;
}

export type ValuesOption = {
values: string[];
operator?: "and" | "or";
};

export abstract class Filter {
abstract get name(): string;

Expand Down
30 changes: 30 additions & 0 deletions frontend/v1/domain/entities/response/ResponseCriteria.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,34 @@ describe("ResponseCriteria", () => {
},
]);
});

test("should be able to parse url params for options with operator", () => {
const criteria = new ResponseCriteria();
criteria.complete("multi-label.operator.or.values.label2.label3.label4");

expect(criteria.value).toEqual([
{
name: "multi-label",
value: {
operator: "or",
values: ["label2", "label3", "label4"],
},
},
]);
});

test("should be able to parse url params for options with operator", () => {
const criteria = new ResponseCriteria();
criteria.complete("multi-label.operator.and.values.label2.label3.label4");

expect(criteria.value).toEqual([
{
name: "multi-label",
value: {
operator: "and",
values: ["label2", "label3", "label4"],
},
},
]);
});
});
84 changes: 78 additions & 6 deletions frontend/v1/domain/entities/response/ResponseCriteria.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Criteria } from "../common/Criteria";
import { RangeValue } from "../common/Filter";
import { RangeValue, ValuesOption } from "../common/Filter";
import { ResponseSearch } from "./ResponseFilter";

export class ResponseCriteria extends Criteria {
Expand All @@ -9,7 +9,7 @@ export class ResponseCriteria extends Criteria {
if (!urlParams) return;

urlParams.split("~").forEach((response) => {
const [name] = response.split(".");
const [name, ...rest] = response.split(".");

const score = this.getRangeValue(response);

Expand All @@ -21,14 +21,31 @@ export class ResponseCriteria extends Criteria {
le: score.le,
},
});
} else {
const values = response.split(".").slice(1);

return;
}

if (rest[0] === "operator" && rest[2] === "values") {
const operator = rest[1];
const values = rest.slice(3);

this.value.push({
name,
value: values,
value: {
operator: operator as ValuesOption["operator"],
values,
},
});

return;
}

const values = response.split(".").slice(1);

this.value.push({
name,
value: values,
});
});
}

Expand Down Expand Up @@ -60,10 +77,65 @@ export class ResponseCriteria extends Criteria {
return `${response.name}.ge${rangeValue.ge}.le${rangeValue.le}`;
}

const valuesOption = response.value as ValuesOption;

if ("operator" in valuesOption && valuesOption) {
return `${response.name}.operator.${
valuesOption.operator
}.values.${valuesOption.values.join(".")}`;
}

const values = response.value as string[];

return `${response.name}.${values.map((v) => v).join(".")}`;
return `${response.name}.${values.join(".")}`;
})
.join("~");
}

get or() {
if (!this.isCompleted) return [];

const orResponses = this.value.filter((s) => {
const { operator } = s.value as ValuesOption;

return operator !== "and";
});

return orResponses.map((r) => {
const { operator, values } = r.value as ValuesOption;

if (operator) {
return {
name: r.name,
value: values,
};
}

return {
name: r.name,
value: r.value,
};
});
}

get and() {
if (!this.isCompleted) return [];

const andResponses = this.value.filter((s) => {
const { operator } = s.value as ValuesOption;

return operator === "and";
});

return andResponses.flatMap((s) => {
const value = s.value as ValuesOption;

return value.values.map((value) => {
return {
name: s.name,
value,
};
});
});
}
}
79 changes: 64 additions & 15 deletions frontend/v1/domain/entities/response/ResponseFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,80 @@ import {
FilterWithScore,
OptionForFilter,
RangeValue,
ValuesOption,
} from "../common/Filter";
import { Question } from "../question/Question";

export interface ResponseSearch {
name: string;
value: string[] | RangeValue;
value: string[] | RangeValue | ValuesOption;
}

class FilterWithOptionAndOperator {
public operator: "and" | "or" = "and";
public readonly options: FilterWithOption;

constructor(private readonly question: Question) {
this.options = new FilterWithOption(
question.name,
question.settings.options.map(({ value, text }) => {
return {
selected: false,
value: value.toString(),
text,
} as OptionForFilter;
})
);
}

get value(): string[] | ValuesOption {
if (this.question.isMultiLabelType)
return {
operator: this.operator,
values: this.options.value,
};

return this.options.value;
}

filterByText(text: string) {
return this.options.filterByText(text);
}

get selectedOptions(): OptionForFilter[] {
return this.options.selectedOptions;
}

complete(value: string[] | ValuesOption) {
if ("operator" in value) {
this.operator = value.operator;
this.options.complete(value.values);
} else {
this.options.complete(value as string[]);
}
}

clear() {
this.options.clear();
}

get hasOperator() {
return this.question.isMultiLabelType;
}

get isAnswered(): boolean {
return this.options.isAnswered;
}
}

class ResponseFilter extends Filter {
public readonly rangeValue: FilterWithScore;
public readonly options: FilterWithOption;
public readonly options: FilterWithOptionAndOperator;
constructor(private readonly question: Question) {
super();

if (this.isTerms) {
this.options = new FilterWithOption(
question.name,
question.settings.options.map(({ value, text }) => {
return {
selected: false,
value: value.toString(),
text,
} as OptionForFilter;
})
);
this.options = new FilterWithOptionAndOperator(question);
} else {
this.rangeValue = new FilterWithScore(
question.name,
Expand All @@ -51,17 +100,17 @@ class ResponseFilter extends Filter {
return this.question.name;
}

get value(): unknown {
get value(): string[] | RangeValue | ValuesOption {
if (this.isTerms) {
return this.options.value;
}

return this.rangeValue.value;
}

complete(value: unknown): void {
complete(value: string[] | RangeValue | ValuesOption): void {
if (this.isTerms) {
this.options.complete(value as string[]);
this.options.complete(value as string[] | ValuesOption);
} else {
this.rangeValue.complete(value as RangeValue);
}
Expand Down
7 changes: 1 addition & 6 deletions frontend/v1/domain/entities/suggestion/SuggestionCriteria.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { Criteria } from "../common/Criteria";
import { RangeValue } from "../common/Filter";

export type ValuesOption = {
values: string[];
operator?: "and" | "or";
};
import { RangeValue, ValuesOption } from "../common/Filter";

export interface ConfigurationSearch {
name: "score" | "value" | "agent";
Expand Down
7 changes: 2 additions & 5 deletions frontend/v1/domain/entities/suggestion/SuggestionFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@ import {
FilterWithScore,
OptionForFilter,
RangeValue,
ValuesOption,
} from "../common/Filter";
import { Question } from "../question/Question";
import { Agent } from "./Agent";
import {
ConfigurationSearch,
SuggestionSearch,
ValuesOption,
} from "./SuggestionCriteria";
import { ConfigurationSearch, SuggestionSearch } from "./SuggestionCriteria";

class ConfigurationValues extends Filter {
public readonly rangeValue: FilterWithScore;
Expand Down
19 changes: 13 additions & 6 deletions frontend/v1/infrastructure/repositories/RecordRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import { Question } from "@/v1/domain/entities/question/Question";
import { RecordCriteria } from "@/v1/domain/entities/record/RecordCriteria";
import { Pagination } from "@/v1/domain/entities/Pagination";
import { SimilarityOrder } from "@/v1/domain/entities/similarity/SimilarityCriteria";
import { RangeValue } from "~/v1/domain/entities/common/Filter";
import { ValuesOption } from "~/v1/domain/entities/suggestion/SuggestionCriteria";
import { RangeValue, ValuesOption } from "~/v1/domain/entities/common/Filter";

const RECORD_API_ERRORS = {
ERROR_FETCHING_RECORDS: "ERROR_FETCHING_RECORDS",
Expand Down Expand Up @@ -251,9 +250,8 @@ export class RecordRepository {
}

if (isFilteringByResponse) {
response.value.forEach((r) => {
response.or.forEach((r) => {
const value = r.value as RangeValue;

if ("ge" in value && "le" in value) {
body.filters.and.push({
type: "range",
Expand All @@ -264,10 +262,8 @@ export class RecordRepository {
ge: value.ge,
le: value.le,
});

return;
}

body.filters.and.push({
type: "terms",
scope: {
Expand All @@ -277,6 +273,17 @@ export class RecordRepository {
values: r.value as string[],
});
});

response.and.forEach((r) => {
body.filters.and.push({
type: "terms",
scope: {
entity: "response",
question: r.name,
},
values: [r.value],
});
});
}

if (isFilteringBySuggestion) {
Expand Down

0 comments on commit 1ec6060

Please sign in to comment.