From 2c1a7929e2818b167c8c0cb4a79af4565484d46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Pumar?= Date: Thu, 2 May 2024 09:24:48 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=A9=20feat=20order=20by=20suggestion?= =?UTF-8?q?=20score=20(#4731)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - [x] Order by suggestion score. ![Screenshot 2024-04-23 at 09 09 57](https://github.com/argilla-io/argilla/assets/7398909/bcdf446d-2ec1-449e-91bc-420982ac0a81) ![Screenshot 2024-04-23 at 09 09 53](https://github.com/argilla-io/argilla/assets/7398909/0b6212f5-69c7-495a-b736-064b814c7b1b) --- .../form/multi-label/MultiLabel.component.vue | 1 + .../LabelSelection.component.vue | 41 +++++++++- .../settings/SettingsQuestions.vue | 9 +- frontend/translation/de.js | 1 + frontend/translation/en.js | 1 + .../v1/domain/entities/question/Question.ts | 19 ++--- .../entities/question/QuestionAnswer.ts | 2 +- .../entities/question/QuestionSetting.test.ts | 82 +++++++++++++++++++ .../entities/question/QuestionSetting.ts | 51 ++++++++++++ frontend/v1/infrastructure/types/question.ts | 20 ++--- 10 files changed, 199 insertions(+), 28 deletions(-) create mode 100644 frontend/v1/domain/entities/question/QuestionSetting.test.ts create mode 100644 frontend/v1/domain/entities/question/QuestionSetting.ts diff --git a/frontend/components/feedback-task/container/questions/form/multi-label/MultiLabel.component.vue b/frontend/components/feedback-task/container/questions/form/multi-label/MultiLabel.component.vue index 929ffc9ea9..3ba4457ac5 100644 --- a/frontend/components/feedback-task/container/questions/form/multi-label/MultiLabel.component.vue +++ b/frontend/components/feedback-task/container/questions/form/multi-label/MultiLabel.component.vue @@ -6,6 +6,7 @@ :componentId="question.id" :suggestion="question.suggestion" :maxOptionsToShowBeforeCollapse="question.settings.visible_options" + :suggestionFirst="question.settings.suggestionFirst" :multiple="true" :isFocused="isFocused" v-model="question.answer.values" diff --git a/frontend/components/feedback-task/container/questions/form/shared-components/label-selection/LabelSelection.component.vue b/frontend/components/feedback-task/container/questions/form/shared-components/label-selection/LabelSelection.component.vue index 9d2d20ce42..89566ea2e0 100644 --- a/frontend/components/feedback-task/container/questions/form/shared-components/label-selection/LabelSelection.component.vue +++ b/frontend/components/feedback-task/container/questions/form/shared-components/label-selection/LabelSelection.component.vue @@ -121,6 +121,10 @@ export default { type: Boolean, default: () => false, }, + suggestionFirst: { + type: Boolean, + default: () => false, + }, isFocused: { type: Boolean, default: () => false, @@ -183,11 +187,40 @@ export default { .filter((option) => option.isSelected); }, visibleOptions() { - if (this.isExpanded) return this.filteredOptions; + if (!this.suggestionFirst) { + if (this.isExpanded) return this.filteredOptions; - return this.filteredOptions - .slice(0, this.maxOptionsToShowBeforeCollapse) - .concat(this.remainingVisibleOptions); + return this.filteredOptions + .slice(0, this.maxOptionsToShowBeforeCollapse) + .concat(this.remainingVisibleOptions); + } + + const suggestedOptions = this.filteredOptions + .filter((v) => this.suggestion && this.suggestion.isSuggested(v.value)) + .sort((a, b) => { + const isASuggested = this.suggestion.getSuggestion(a.value); + const isBSuggested = this.suggestion.getSuggestion(b.value); + + return isASuggested?.score > isBSuggested?.score ? -1 : 1; + }); + + const noSuggestedOptions = this.filteredOptions.filter( + (v) => !this.suggestion || !this.suggestion.isSuggested(v.value) + ); + + if (this.isExpanded) { + return [...suggestedOptions, ...noSuggestedOptions]; + } + + const options = [ + ...suggestedOptions.filter((o) => o.isSelected), + ...noSuggestedOptions.filter((o) => o.isSelected), + ]; + + return options.slice( + 0, + Math.max(options.length, this.maxOptionsToShowBeforeCollapse) + ); }, numberToShowInTheCollapseButton() { return this.filteredOptions.length - this.visibleOptions.length; diff --git a/frontend/components/feedback-task/settings/SettingsQuestions.vue b/frontend/components/feedback-task/settings/SettingsQuestions.vue index 30765b4044..a57caaaf48 100644 --- a/frontend/components/feedback-task/settings/SettingsQuestions.vue +++ b/frontend/components/feedback-task/settings/SettingsQuestions.vue @@ -78,8 +78,15 @@ >{{ $t("useMarkdown") }} + {{ $t("suggestionFirst") }} + option), - }, + }), }; } private restoreOriginal() { const { options, ...rest } = this.original.settings; - this.settings = { + this.settings = new QuestionSetting({ ...rest, options: options?.map((option: string) => option), - }; + }); } } diff --git a/frontend/v1/domain/entities/question/QuestionAnswer.ts b/frontend/v1/domain/entities/question/QuestionAnswer.ts index 49dce3e95e..5faa3be8cd 100644 --- a/frontend/v1/domain/entities/question/QuestionAnswer.ts +++ b/frontend/v1/domain/entities/question/QuestionAnswer.ts @@ -64,7 +64,7 @@ export class SpanQuestionAnswer extends QuestionAnswer { constructor( public readonly type: QuestionType, - private questionName: string, + questionName: string, options: Omit[] ) { super(type); diff --git a/frontend/v1/domain/entities/question/QuestionSetting.test.ts b/frontend/v1/domain/entities/question/QuestionSetting.test.ts new file mode 100644 index 0000000000..72448cc031 --- /dev/null +++ b/frontend/v1/domain/entities/question/QuestionSetting.test.ts @@ -0,0 +1,82 @@ +import { QuestionSetting } from "./QuestionSetting"; + +describe("QuestionSetting", () => { + describe("suggestionFirst", () => { + test("return true when options_order is suggestion", () => { + const setting = new QuestionSetting({ options_order: "suggestion" }); + expect(setting.suggestionFirst).toBeTruthy(); + }); + test("return false when options_order is natural", () => { + const setting = new QuestionSetting({ options_order: "natural" }); + expect(setting.suggestionFirst).toBeFalsy(); + }); + }); + + describe("isEqual", () => { + test("return false when options_order is different", () => { + const setting = new QuestionSetting({ options_order: "suggestion" }); + const setting2 = new QuestionSetting({ options_order: "natural" }); + expect(setting.isEqual(setting2)).toBeFalsy(); + }); + + test("return false when use_markdown is different", () => { + const setting = new QuestionSetting({ use_markdown: true }); + const setting2 = new QuestionSetting({ use_markdown: false }); + expect(setting.isEqual(setting2)).toBeFalsy(); + }); + + test("return false when visible_options is different", () => { + const setting = new QuestionSetting({ visible_options: 1 }); + const setting2 = new QuestionSetting({ visible_options: 2 }); + expect(setting.isEqual(setting2)).toBeFalsy(); + }); + + test("return false when options are different", () => { + const setting = new QuestionSetting({ options: ["a"] }); + const setting2 = new QuestionSetting({ options: ["b"] }); + expect(setting.isEqual(setting2)).toBeFalsy(); + }); + + test("return false if options are in different positions", () => { + const setting = new QuestionSetting({ options: ["a", "b"] }); + const setting2 = new QuestionSetting({ options: ["b", "a"] }); + expect(setting.isEqual(setting2)).toBeFalsy(); + }); + + test("return true when all properties are the same", () => { + const setting = new QuestionSetting({ + options_order: "suggestion", + use_markdown: true, + visible_options: 1, + options: ["a"], + }); + const setting2 = new QuestionSetting({ + options_order: "suggestion", + use_markdown: true, + visible_options: 1, + options: ["a"], + }); + expect(setting.isEqual(setting2)).toBeTruthy(); + }); + }); + + describe("shouldShowVisibleOptions", () => { + test("return false when options are less than 3", () => { + const setting = new QuestionSetting({ options: ["a", "b"] }); + expect(setting.shouldShowVisibleOptions).toBeFalsy(); + }); + + test("return false when visible_options is not present", () => { + const setting = new QuestionSetting({ options: ["a", "b", "c"] }); + expect(setting.shouldShowVisibleOptions).toBeFalsy(); + }); + + test("return true when options are more than 3 and visible_options is present", () => { + const setting = new QuestionSetting({ + options: ["a", "b", "c", "d"], + visible_options: 3, + }); + expect(setting.shouldShowVisibleOptions).toBeTruthy(); + }); + }); +}); diff --git a/frontend/v1/domain/entities/question/QuestionSetting.ts b/frontend/v1/domain/entities/question/QuestionSetting.ts new file mode 100644 index 0000000000..55d6d071c8 --- /dev/null +++ b/frontend/v1/domain/entities/question/QuestionSetting.ts @@ -0,0 +1,51 @@ +export class QuestionSetting { + type: + | "text" + | "ranking" + | "multi_label_selection" + | "label_selection" + | "span"; + + use_markdown: boolean; + visible_options: number; + allow_overlapping: boolean; + allow_character_annotation: boolean; + field: string; + options: any; + options_order: "natural" | "suggestion"; + + constructor(private readonly settings: any) { + this.type = settings.type; + + this.use_markdown = settings.use_markdown; + this.visible_options = settings.visible_options; + this.options = settings.options; + this.options_order = settings.options_order; + this.allow_overlapping = settings.allow_overlapping; + this.allow_character_annotation = settings.allow_character_annotation; + this.field = settings.field; + } + + get suggestionFirst() { + if (!this.options_order) return undefined; + + return this.options_order === "suggestion"; + } + + set suggestionFirst(value: boolean) { + this.options_order = value ? "suggestion" : "natural"; + } + + get shouldShowVisibleOptions() { + return this.options?.length > 3 && "visible_options" in this.settings; + } + + isEqual(setting: QuestionSetting) { + return ( + this.use_markdown === setting.use_markdown && + this.visible_options === setting.visible_options && + this.options_order === setting.options_order && + JSON.stringify(this.options) === JSON.stringify(setting.options) + ); + } +} diff --git a/frontend/v1/infrastructure/types/question.ts b/frontend/v1/infrastructure/types/question.ts index d8be2e89da..0dc7f4099d 100644 --- a/frontend/v1/infrastructure/types/question.ts +++ b/frontend/v1/infrastructure/types/question.ts @@ -34,20 +34,16 @@ interface SingleSelectionSetting { }[]; } -type Entity = { - id: string; - name: string; - color: string; -}; -type Span = { - from: string; - to: string; - entity: string; -}; interface SpanSetting { type: "span"; - entities: Entity[]; - values: Record; + options: { + description?: string; + text: string; + value: string; + }[]; + allow_overlapping: boolean; + allow_character_annotation: boolean; + field: string; } export interface BackendQuestion {