+
{messages.map((message, i) => {
return
{message}
;
})}
diff --git a/packages/mephisto-task-addons/src/VideoAnnotator/AnnotationTrack.css b/packages/mephisto-task-addons/src/VideoAnnotator/AnnotationTrack.css
index 6131b136f..844e78c3c 100644
--- a/packages/mephisto-task-addons/src/VideoAnnotator/AnnotationTrack.css
+++ b/packages/mephisto-task-addons/src/VideoAnnotator/AnnotationTrack.css
@@ -100,6 +100,11 @@
opacity: 0.2;
}
+.annotation-track .overlapping-segments.is-invalid {
+ display: flex;
+ justify-content: center;
+}
+
.annotation-track .segment-info {
position: relative;
display: flex;
diff --git a/packages/mephisto-task-addons/src/VideoAnnotator/AnnotationTrack.jsx b/packages/mephisto-task-addons/src/VideoAnnotator/AnnotationTrack.jsx
index 02ddb82b0..f1ce14cb7 100644
--- a/packages/mephisto-task-addons/src/VideoAnnotator/AnnotationTrack.jsx
+++ b/packages/mephisto-task-addons/src/VideoAnnotator/AnnotationTrack.jsx
@@ -14,6 +14,7 @@ import "./AnnotationTrack.css";
import {
COLORS,
DELAY_CLICK_ON_SECTION_MSEC,
+ DELAY_SHOW_OVERLAPPING_MESSAGE_MSEC,
INIT_SECTION,
POPOVER_INVALID_SEGMENT_CLASS,
POPOVER_INVALID_SEGMENT_PROPS,
@@ -21,6 +22,7 @@ import {
} from "./constants";
import { secontsToTime } from "./helpers.jsx";
import TrackSegment from "./TrackSegment.jsx";
+import { validateTimeFieldsOnSave } from "./utils";
function AnnotationTrack({
annotationTrack,
@@ -46,6 +48,10 @@ function AnnotationTrack({
const [inEditState, setInEditState] = React.useState(false);
const [selectedSegment, setSelectedSegment] = React.useState(null);
+ const [
+ overlappingSegmentErrors,
+ setOverlappingSegmentErrors,
+ ] = React.useState([]);
const [segmentToChangeErrors, setSegmentToChangeErrors] = React.useState([]);
const [segmentToChange, setSegmentToChange] = React.useState(null);
// Invalid fields (having error messages after form validation)
@@ -110,6 +116,7 @@ function AnnotationTrack({
setInEditState(false);
setSegmentValidation({});
+ setOverlappingSegmentErrors([]);
}
}
@@ -127,6 +134,7 @@ function AnnotationTrack({
setSelectedSegment(null);
setSegmentToChange(null);
setSegmentValidation({});
+ setOverlappingSegmentErrors([]);
}
}
@@ -178,56 +186,20 @@ function AnnotationTrack({
setSelectedSegment(newSegmentIndex);
setSegmentToChange(newSegment);
onSelectSegment && onSelectSegment(newSegment);
- }
-
- function validateTimeFieldsOnSave() {
- const errors = [];
- const validation = {};
- // If start is greater than end
- if (segmentToChange.start_sec > segmentToChange.end_sec) {
- errors.push(`Start of the segment cannot be greater than end of it.`);
- validation.end_sec = false;
+ // Show overlapping message under segments progress bar
+ const timeFieldsValidationResults = validateTimeFieldsOnSave(
+ annotationTrack,
+ newSegment,
+ newSegmentIndex
+ );
+ if (timeFieldsValidationResults.errors.length > 0) {
+ setTimeout(() => {
+ setOverlappingSegmentErrors([
+ "Overlapping segment should be added to a different track",
+ ]);
+ }, DELAY_SHOW_OVERLAPPING_MESSAGE_MSEC);
}
-
- // If segment is inside another segment
- Object.entries(annotationTrack.segments).map(([segmentIndex, segment]) => {
- // Skip currently saving segment
- if (String(segmentIndex) === String(selectedSegment)) {
- return;
- }
-
- if (
- segmentToChange.start_sec > segment.start_sec &&
- segmentToChange.start_sec < segment.end_sec
- ) {
- errors.push(
- `You cannot start a segment in already created segment before: ` +
- `${segment.title} ${secontsToTime(
- segment.start_sec
- )} - ${secontsToTime(segment.end_sec)}`
- );
- validation.start_sec = false;
- }
-
- if (
- segmentToChange.end_sec > segment.start_sec &&
- segmentToChange.end_sec < segment.end_sec
- ) {
- errors.push(
- `You cannot end a segment in already created segment before: ` +
- `${segment.title} ${secontsToTime(
- segment.start_sec
- )} - ${secontsToTime(segment.end_sec)}`
- );
- validation.end_sec = false;
- }
- });
-
- // Update segment validation results
- setSegmentValidation(validation);
-
- return errors;
}
function onClickSegment(e, segmentIndex) {
@@ -267,8 +239,13 @@ function AnnotationTrack({
}
// Validate current segment
- const timeFieldsErrors = validateTimeFieldsOnSave();
- setSegmentToChangeErrors(timeFieldsErrors);
+ const timeFieldsValidationResults = validateTimeFieldsOnSave(
+ annotationTrack,
+ segmentToChange,
+ selectedSegment
+ );
+ setSegmentValidation(timeFieldsValidationResults.fields);
+ setSegmentToChangeErrors(timeFieldsValidationResults.errors);
// Validate dynamic segment fields
const dynamicFieldsErrorsByField = validateFormFields(
@@ -278,6 +255,9 @@ function AnnotationTrack({
);
setInvalidSegmentFields(dynamicFieldsErrorsByField);
+ // Clean overlapping message
+ setOverlappingSegmentErrors([]);
+
// Update segment validation results
setSegmentValidation((prevState) => {
return {
@@ -432,6 +412,14 @@ function AnnotationTrack({
)}
+
+
{showSegmentToChangeInfo && (
segmentToChange.end_sec) {
+ errors.push(`Start of the segment cannot be greater than end of it.`);
+ validation.end_sec = false;
+ }
+
+ // If segment is inside another segment
+ Object.entries(annotationTrack.segments).map(([segmentIndex, segment]) => {
+ // Skip currently saving segment
+ if (String(segmentIndex) === String(selectedSegment)) {
+ return;
+ }
+
+ let hasOverlapping = false;
+
+ if (
+ segmentToChange.start_sec > segment.start_sec &&
+ segmentToChange.start_sec < segment.end_sec
+ ) {
+ hasOverlapping = true;
+ validation.start_sec = false;
+ }
+
+ if (
+ segmentToChange.end_sec > segment.start_sec &&
+ segmentToChange.end_sec < segment.end_sec
+ ) {
+ hasOverlapping = true;
+ validation.end_sec = false;
+ }
+
+ if (hasOverlapping) {
+ errors.push(
+ `You cannot start or end a segment in already created segment before: ` +
+ `${segment.title} ${secontsToTime(
+ segment.start_sec
+ )} - ${secontsToTime(segment.end_sec)}`
+ );
+ }
+ });
+
+ return {
+ errors: errors,
+ fields: validation,
+ };
+}
diff --git a/packages/mephisto-task-addons/src/constants/files.js b/packages/mephisto-task-addons/src/constants/files.js
new file mode 100644
index 000000000..207e83215
--- /dev/null
+++ b/packages/mephisto-task-addons/src/constants/files.js
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) Meta Platforms and its affiliates.
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+export const VIDEO_TYPES_BY_EXT = {
+ avi: "video/x-msvideo",
+ mkv: "video/x-matroska",
+ mov: "video/quicktime",
+ mp4: "video/mp4",
+ mpeg: "video/mpeg",
+ webm: "video/webm",
+};
diff --git a/packages/mephisto-task-addons/src/constants/index.js b/packages/mephisto-task-addons/src/constants/index.js
new file mode 100644
index 000000000..3ad2a5b2c
--- /dev/null
+++ b/packages/mephisto-task-addons/src/constants/index.js
@@ -0,0 +1,7 @@
+/*
+ * Copyright (c) Meta Platforms and its affiliates.
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+export * from "./files";
diff --git a/packages/mephisto-task-addons/src/index.jsx b/packages/mephisto-task-addons/src/index.jsx
index 4d1676cae..1d2e0b2f4 100644
--- a/packages/mephisto-task-addons/src/index.jsx
+++ b/packages/mephisto-task-addons/src/index.jsx
@@ -4,6 +4,7 @@
* LICENSE file in the root directory of this source tree.
*/
+import * as constants from "./constants";
import {
TOKEN_END_REGEX,
TOKEN_END_SYMBOLS,
@@ -11,7 +12,6 @@ import {
TOKEN_START_SYMBOLS,
} from "./FormComposer/constants";
import * as FormComposerFields from "./FormComposer/fields";
-import * as helpers from "./helpers";
import { Errors as ListErrors } from "./FormComposer/fields/Errors";
import { FormComposer } from "./FormComposer/FormComposer";
import {
@@ -20,12 +20,13 @@ import {
procedureTokenRegex,
validateFieldValue,
} from "./FormComposer/utils";
+import * as helpers from "./helpers";
import TaskInstructionButton from "./TaskInstructionModal/TaskInstructionButton";
import TaskInstructionModal from "./TaskInstructionModal/TaskInstructionModal";
+import { prepareVideoAnnotatorData } from "./VideoAnnotator/utils";
import VideoAnnotator from "./VideoAnnotator/VideoAnnotator.jsx";
import VideoPlayer from "./VideoAnnotator/VideoPlayer.jsx";
import WorkerOpinion from "./WorkerOpinion/WorkerOpinion";
-import { prepareVideoAnnotatorData } from "./VideoAnnotator/utils";
export {
FormComposer,
@@ -40,6 +41,7 @@ export {
VideoAnnotator,
VideoPlayer,
WorkerOpinion,
+ constants,
helpers,
prepareFormData,
prepareRemoteProcedures,
diff --git a/test/generators/__init__.py b/test/generators/__init__.py
index cfaca7562..e69de29bb 100644
--- a/test/generators/__init__.py
+++ b/test/generators/__init__.py
@@ -1,5 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright (c) Meta Platforms and its affiliates.
-# This source code is licensed under the MIT license found in the
-# LICENSE file in the root directory of this source tree.
diff --git a/test/generators/form_composer/__init__.py b/test/generators/form_composer/__init__.py
index cfaca7562..e69de29bb 100644
--- a/test/generators/form_composer/__init__.py
+++ b/test/generators/form_composer/__init__.py
@@ -1,5 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright (c) Meta Platforms and its affiliates.
-# This source code is licensed under the MIT license found in the
-# LICENSE file in the root directory of this source tree.
diff --git a/test/generators/form_composer/config_validation/__init__.py b/test/generators/form_composer/config_validation/__init__.py
index cfaca7562..e69de29bb 100644
--- a/test/generators/form_composer/config_validation/__init__.py
+++ b/test/generators/form_composer/config_validation/__init__.py
@@ -1,5 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright (c) Meta Platforms and its affiliates.
-# This source code is licensed under the MIT license found in the
-# LICENSE file in the root directory of this source tree.
diff --git a/test/generators/form_composer/config_validation/test_task_data_config.py b/test/generators/form_composer/config_validation/test_task_data_config.py
index 97157a07c..1b8bad14a 100644
--- a/test/generators/form_composer/config_validation/test_task_data_config.py
+++ b/test/generators/form_composer/config_validation/test_task_data_config.py
@@ -11,72 +11,42 @@
import tempfile
import unittest
from copy import deepcopy
-from unittest.mock import patch
+from mephisto.client.cli_form_composer_commands import set_form_composer_env_vars
from mephisto.client.cli_form_composer_commands import FORM_COMPOSER__DATA_CONFIG_NAME
from mephisto.client.cli_form_composer_commands import (
FORM_COMPOSER__SEPARATE_TOKEN_VALUES_CONFIG_NAME,
)
from mephisto.client.cli_form_composer_commands import FORM_COMPOSER__TOKEN_SETS_VALUES_CONFIG_NAME
from mephisto.client.cli_form_composer_commands import FORM_COMPOSER__UNIT_CONFIG_NAME
-from mephisto.client.cli_form_composer_commands import set_form_composer_env_vars
from mephisto.generators.form_composer.config_validation.task_data_config import (
collect_unit_config_items_to_extrapolate,
)
from mephisto.generators.form_composer.config_validation.task_data_config import (
verify_form_composer_configs,
)
-from mephisto.generators.generators_utils.config_validation.task_data_config import (
- _collect_tokens_from_unit_config,
-)
-from mephisto.generators.generators_utils.config_validation.task_data_config import (
- _combine_extrapolated_unit_configs,
-)
-from mephisto.generators.generators_utils.config_validation.task_data_config import (
- _extrapolate_tokens_in_unit_config,
-)
-from mephisto.generators.generators_utils.config_validation.task_data_config import (
- _extrapolate_tokens_values,
-)
-from mephisto.generators.generators_utils.config_validation.task_data_config import (
- _set_tokens_in_unit_config_item,
-)
-from mephisto.generators.generators_utils.config_validation.task_data_config import (
- _validate_tokens_in_both_configs,
-)
-from mephisto.generators.generators_utils.config_validation.task_data_config import (
- create_extrapolated_config,
-)
-from mephisto.generators.generators_utils.config_validation.task_data_config import (
- prepare_task_config_for_review_app,
-)
-from mephisto.generators.generators_utils.config_validation.task_data_config import (
- validate_task_data_config,
-)
-from mephisto.generators.generators_utils.config_validation.task_data_config import (
- verify_generator_configs,
-)
+
CORRECT_CONFIG_DATA_WITH_TOKENS = {
"form": {
- "title": "Form title {{token_1}}",
- "instruction": "Form instruction {{token_2}}",
+ "title": "Form title {{title_token}}",
+ "instruction": "Form instruction {{instruction_token}}",
"sections": [
{
"name": "section_name",
- "title": "Section title {{token_3}}",
+ "title": "Section title {{section_title_token}}",
"instruction": "Section instruction",
"collapsable": False,
"initially_collapsed": True,
"fieldsets": [
{
- "title": "Fieldset title {{token_4}}",
+ "title": "Fieldset title {{fieldset_title_token}}",
"instruction": "Fieldset instruction",
"rows": [
{
"fields": [
{
- "help": "Field help {{token_5}}",
+ "help": "Field help {{field_help_token}}",
"id": "id_field",
"label": "Field label",
"name": "field_name",
@@ -95,7 +65,7 @@
},
],
"submit_button": {
- "instruction": "Submit instruction {{token_5}}",
+ "instruction": "Submit instruction {{submit_instruction_token}}",
"text": "Submit",
"tooltip": "Submit tooltip",
},
@@ -106,756 +76,18 @@
class TestTaskDataConfig(unittest.TestCase):
def setUp(self):
self.data_dir = tempfile.mkdtemp()
- set_form_composer_env_vars()
+ set_form_composer_env_vars(use_validation_mapping_cache=False)
def tearDown(self):
shutil.rmtree(self.data_dir, ignore_errors=True)
- def test__extrapolate_tokens_values_simple(self, *args, **kwargs):
- text = "Test {{token_1}} and {{token_2}}"
- tokens_values = {
- "token_1": "value 1",
- "token_2": "value 2",
- }
- result = _extrapolate_tokens_values(text, tokens_values)
-
- self.assertEqual(result, "Test value 1 and value 2")
-
- def test__extrapolate_tokens_values_with_spaces_around(self, *args, **kwargs):
- text = "Test {{ token_1 }} and {{ token_2 }}"
- tokens_values = {
- "token_1": "value 1",
- "token_2": "value 2",
- }
- result = _extrapolate_tokens_values(text, tokens_values)
-
- self.assertEqual(result, "Test value 1 and value 2")
-
- def test__extrapolate_tokens_values_with_new_lines_around(self, *args, **kwargs):
- text = "Test {{\ntoken_1\n}} and {{\n\ntoken_2\n\n}}"
- tokens_values = {
- "token_1": "value 1",
- "token_2": "value 2",
- }
- result = _extrapolate_tokens_values(text, tokens_values)
-
- self.assertEqual(result, "Test value 1 and value 2")
-
- def test__extrapolate_tokens_values_with_tabs_around(self, *args, **kwargs):
- text = "Test {{\ttoken_1\t}} and {{\t\ttoken_2\t\t}}"
- tokens_values = {
- "token_1": "value 1",
- "token_2": "value 2",
- }
- result = _extrapolate_tokens_values(text, tokens_values)
-
- self.assertEqual(result, "Test value 1 and value 2")
-
- def test__extrapolate_tokens_values_with_bracketed_values(self, *args, **kwargs):
- text = "Test {{token_1}} and {{token_2}}"
- tokens_values = {
- "token_1": "{{value 1}}",
- "token_2": "{{value 2}}",
- }
- result = _extrapolate_tokens_values(text, tokens_values)
-
- self.assertEqual(result, "Test {{value 1}} and {{value 2}}")
-
- def test__extrapolate_tokens_values_with_procedure_tokens(self, *args, **kwargs):
- text = 'Test {{someProcedureWithArguments({"arg": 1})}} and {{otherProcedure(True)}}'
- tokens_values = {
- 'someProcedureWithArguments({"arg": 1})': "value 1",
- "otherProcedure(True)": "value 2",
- }
- result = _extrapolate_tokens_values(text, tokens_values)
-
- self.assertEqual(result, "Test value 1 and value 2")
-
- def test__set_tokens_in_unit_config_item(self, *args, **kwargs):
- item = {
- "title": "Form title {{token_1}} and {{token_2}}",
- "instruction": "Form instruction {{token_2}}",
- }
- tokens_values = {
- "token_1": "value 1",
- "token_2": "value 2",
- }
-
- _set_tokens_in_unit_config_item(item, tokens_values)
-
- self.assertEqual(
- item,
- {
- "title": "Form title value 1 and value 2",
- "instruction": "Form instruction value 2",
- },
- )
-
def test__collect_unit_config_items_to_extrapolate(self, *args, **kwargs):
- config_data = {
- "form": {
- "title": "Form title",
- "instruction": "Form instruction",
- "sections": [
- {
- "name": "section_name",
- "title": "Section title",
- "instruction": "Section instruction",
- "collapsable": False,
- "initially_collapsed": True,
- "fieldsets": [
- {
- "title": "Fieldset title",
- "instruction": "Fieldset instruction",
- "rows": [
- {
- "fields": [
- {
- "help": "Field help",
- "id": "id_field",
- "label": "Field label",
- "name": "field_name",
- "placeholder": "Field placeholder",
- "tooltip": "Field tooltip",
- "type": "file",
- "value": "",
- }
- ],
- "help": "Row help",
- },
- ],
- "help": "Fieldset help",
- },
- ],
- },
- ],
- "submit_button": {
- "instruction": "Submit instruction",
- "text": "Submit",
- "tooltip": "Submit tooltip",
- },
- },
- }
+ config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
items = collect_unit_config_items_to_extrapolate(config_data)
self.assertEqual(len(items), 6)
- def test__collect_tokens_from_unit_config_success(self, *args, **kwargs):
- config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
-
- tokens, errors = _collect_tokens_from_unit_config(config_data)
-
- self.assertEqual(tokens, {"token_1", "token_2", "token_3", "token_4", "token_5"})
- self.assertEqual(errors, [])
-
- def test__collect_tokens_from_unit_config_with_errors(self, *args, **kwargs):
- config_data = {
- "form": {
- "title": "Form title {{token_1}}",
- "instruction": "Form instruction {{token_2}}",
- "sections": [
- {
- "name": "section_name {{token_6}}",
- "title": "Section title {{token_3}}",
- "instruction": "Section instruction",
- "collapsable": False,
- "initially_collapsed": True,
- "fieldsets": [
- {
- "title": "Fieldset title {{token_4}}",
- "instruction": "Fieldset instruction",
- "rows": [
- {
- "fields": [
- {
- "help": "Field help {{token_5}}",
- "id": "id_field {{token_7}}",
- "label": "Field label",
- "name": "field_name {{token_8}}",
- "placeholder": "Field placeholder",
- "tooltip": "Field tooltip",
- "type": "file {{token_9}}",
- "value": "",
- }
- ],
- "help": "Row help",
- },
- ],
- "help": "Fieldset help",
- },
- ],
- },
- ],
- "submit_button": {
- "instruction": "Submit instruction {{token_5}}",
- "text": "Submit",
- "tooltip": "Submit tooltip",
- },
- },
- }
-
- tokens, errors = _collect_tokens_from_unit_config(config_data)
-
- self.assertEqual(tokens, {"token_1", "token_2", "token_3", "token_4", "token_5"})
- self.assertEqual(
- sorted(errors),
- sorted(
- [
- (
- "You tried to set tokens 'token_6' in attribute 'name' with value "
- "'section_name {{token_6}}'. You can use tokens only in following attributes: "
- "help, instruction, label, title, tooltip"
- ),
- (
- "You tried to set tokens 'token_8' in attribute 'name' with value "
- "'field_name {{token_8}}'. You can use tokens only in following attributes: "
- "help, instruction, label, title, tooltip"
- ),
- (
- "You tried to set tokens 'token_7' in attribute 'id' with value "
- "'id_field {{token_7}}'. You can use tokens only in following attributes: "
- "help, instruction, label, title, tooltip"
- ),
- (
- "You tried to set tokens 'token_9' in attribute 'type' with value "
- "'file {{token_9}}'. You can use tokens only in following attributes: "
- "help, instruction, label, title, tooltip"
- ),
- ]
- ),
- )
-
- def test__extrapolate_tokens_in_unit_config_success(self, *args, **kwargs):
- config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
- tokens_values = {
- "token_1": "value 1",
- "token_2": "value 2",
- "token_3": "value 3",
- "token_4": "value 4",
- "token_5": "value 5",
- }
-
- result = _extrapolate_tokens_in_unit_config(config_data, tokens_values)
-
- self.assertEqual(
- result,
- {
- "form": {
- "title": "Form title value 1",
- "instruction": "Form instruction value 2",
- "sections": [
- {
- "name": "section_name",
- "title": "Section title value 3",
- "instruction": "Section instruction",
- "collapsable": False,
- "initially_collapsed": True,
- "fieldsets": [
- {
- "title": "Fieldset title value 4",
- "instruction": "Fieldset instruction",
- "rows": [
- {
- "fields": [
- {
- "help": "Field help value 5",
- "id": "id_field",
- "label": "Field label",
- "name": "field_name",
- "placeholder": "Field placeholder",
- "tooltip": "Field tooltip",
- "type": "file",
- "value": "",
- }
- ],
- "help": "Row help",
- },
- ],
- "help": "Fieldset help",
- },
- ],
- },
- ],
- "submit_button": {
- "instruction": "Submit instruction value 5",
- "text": "Submit",
- "tooltip": "Submit tooltip",
- },
- },
- },
- )
-
- def test__validate_tokens_in_both_configs_success(self, *args, **kwargs):
- config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
- token_sets_values_config_data = [
- {
- "tokens_values": {
- "token_1": "value 1",
- "token_2": "value 2",
- "token_3": "value 3",
- "token_4": "value 4",
- "token_5": "value 5",
- },
- },
- ]
-
- (
- overspecified_tokens,
- underspecified_tokens,
- tokens_in_unexpected_attrs_errors,
- ) = _validate_tokens_in_both_configs(config_data, token_sets_values_config_data)
-
- self.assertEqual(len(overspecified_tokens), 0)
- self.assertEqual(len(underspecified_tokens), 0)
- self.assertEqual(tokens_in_unexpected_attrs_errors, [])
-
- def test__validate_tokens_in_both_configs_with_errors(self, *args, **kwargs):
- config_data = {
- "form": {
- "title": "Form title",
- "instruction": "Form instruction {{token_2}}",
- "sections": [
- {
- "name": "section_name",
- "title": "Section title {{token_3}}",
- "instruction": "Section instruction",
- "collapsable": False,
- "initially_collapsed": True,
- "fieldsets": [
- {
- "title": "Fieldset title {{token_4}}",
- "instruction": "Fieldset instruction",
- "rows": [
- {
- "fields": [
- {
- "help": "Field help {{token_5}}",
- "id": "id_field {{token_5}}",
- "label": "Field label {{token_6}}",
- "name": "field_name",
- "placeholder": "Field placeholder",
- "tooltip": "Field tooltip",
- "type": "file",
- "value": "",
- }
- ],
- "help": "Row help",
- },
- ],
- "help": "Fieldset help",
- },
- ],
- },
- ],
- "submit_button": {
- "instruction": "Submit instruction {{token_5}}",
- "text": "Submit",
- "tooltip": "Submit tooltip",
- },
- },
- }
- token_sets_values_config_data = [
- {
- "tokens_values": {
- "token_1": "value 1",
- "token_2": "value 2",
- "token_3": "value 3",
- "token_4": "value 4",
- "token_5": "value 5",
- },
- },
- ]
-
- (
- overspecified_tokens,
- underspecified_tokens,
- tokens_in_unexpected_attrs_errors,
- ) = _validate_tokens_in_both_configs(config_data, token_sets_values_config_data)
-
- self.assertEqual(overspecified_tokens, {"token_1"})
- self.assertEqual(underspecified_tokens, {"token_6"})
- self.assertEqual(
- tokens_in_unexpected_attrs_errors,
- [
- (
- "You tried to set tokens 'token_5' in attribute 'id' with value 'id_field "
- "{{token_5}}'. You can use tokens only in following attributes: help, "
- "instruction, label, title, tooltip"
- ),
- ],
- )
-
- def test__combine_extrapolated_unit_configs_success(self, *args, **kwargs):
- config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
- token_sets_values_config_data = [
- {
- "tokens_values": {
- "token_1": "value 1",
- "token_2": "value 2",
- "token_3": "value 3",
- "token_4": "value 4",
- "token_5": "value 5",
- },
- },
- ]
-
- result = _combine_extrapolated_unit_configs(config_data, token_sets_values_config_data)
-
- self.assertEqual(
- result,
- [
- {
- "form": {
- "title": "Form title value 1",
- "instruction": "Form instruction value 2",
- "sections": [
- {
- "collapsable": False,
- "fieldsets": [
- {
- "help": "Fieldset help",
- "instruction": "Fieldset instruction",
- "rows": [
- {
- "fields": [
- {
- "help": "Field help value 5",
- "id": "id_field",
- "label": "Field label",
- "name": "field_name",
- "placeholder": "Field placeholder",
- "tooltip": "Field tooltip",
- "type": "file",
- "value": "",
- },
- ],
- "help": "Row help",
- },
- ],
- "title": "Fieldset title value 4",
- },
- ],
- "initially_collapsed": True,
- "instruction": "Section instruction",
- "name": "section_name",
- "title": "Section title value 3",
- },
- ],
- "submit_button": {
- "instruction": "Submit instruction value 5",
- "text": "Submit",
- "tooltip": "Submit tooltip",
- },
- },
- "form_metadata": {
- "tokens_values": {
- "token_1": "value 1",
- "token_2": "value 2",
- "token_3": "value 3",
- "token_4": "value 4",
- "token_5": "value 5",
- }
- },
- },
- ],
- )
-
- def test__replace_html_paths_with_html_file_content_success(self, *args, **kwargs):
- value_with_file_path_1 = "insertions/test1.html"
- value_with_file_path_2 = "insertions/test2.html"
- html_content_1 = "Test {{token_1}}"
- html_content_2 = "Test {{token_2}}"
-
- html_path_1 = os.path.abspath(os.path.join(self.data_dir, value_with_file_path_1))
- html_path_2 = os.path.abspath(os.path.join(self.data_dir, value_with_file_path_2))
-
- os.makedirs(os.path.dirname(html_path_1), exist_ok=True)
- f = open(html_path_1, "w")
- f.write(html_content_1)
- f.close()
- f = open(html_path_2, "w")
- f.write(html_content_2)
- f.close()
-
- unit_config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
- unit_config_data["form"]["title"] = value_with_file_path_1
- unit_config_data["form"]["instruction"] = value_with_file_path_2
-
- token_sets_values_config_data = [
- {
- "tokens_values": {
- "token_1": "value 1",
- "token_2": "value 2",
- "token_3": "value 3",
- "token_4": "value 4",
- "token_5": "value 5",
- },
- },
- ]
-
- unit_config_path = os.path.join(
- self.data_dir,
- FORM_COMPOSER__UNIT_CONFIG_NAME,
- )
- token_sets_values_config_path = os.path.join(
- self.data_dir,
- FORM_COMPOSER__TOKEN_SETS_VALUES_CONFIG_NAME,
- )
- task_data_config_path = os.path.join(
- self.data_dir,
- FORM_COMPOSER__DATA_CONFIG_NAME,
- )
-
- unit_config_f = open(unit_config_path, "w")
- unit_config_f.write(json.dumps(unit_config_data))
- unit_config_f.close()
-
- token_sets_values_config_f = open(token_sets_values_config_path, "w")
- token_sets_values_config_f.write(json.dumps(token_sets_values_config_data))
- token_sets_values_config_f.close()
-
- create_extrapolated_config(
- unit_config_path=unit_config_path,
- token_sets_values_config_path=token_sets_values_config_path,
- task_data_config_path=task_data_config_path,
- data_path=self.data_dir,
- )
-
- f = open(task_data_config_path, "r")
- task_config_data = json.loads(f.read())
-
- self.assertEqual(
- task_config_data,
- [
- {
- "form": {
- "title": "Test value 1",
- "instruction": "Test value 2",
- "sections": [
- {
- "collapsable": False,
- "fieldsets": [
- {
- "help": "Fieldset help",
- "instruction": "Fieldset instruction",
- "rows": [
- {
- "fields": [
- {
- "help": "Field help value 5",
- "id": "id_field",
- "label": "Field label",
- "name": "field_name",
- "placeholder": "Field placeholder",
- "tooltip": "Field tooltip",
- "type": "file",
- "value": "",
- },
- ],
- "help": "Row help",
- },
- ],
- "title": "Fieldset title value 4",
- },
- ],
- "initially_collapsed": True,
- "instruction": "Section instruction",
- "name": "section_name",
- "title": "Section title value 3",
- },
- ],
- "submit_button": {
- "instruction": "Submit instruction value 5",
- "text": "Submit",
- "tooltip": "Submit tooltip",
- },
- },
- "form_metadata": {
- "tokens_values": {
- "token_1": "value 1",
- "token_2": "value 2",
- "token_3": "value 3",
- "token_4": "value 4",
- "token_5": "value 5",
- }
- },
- },
- ],
- )
-
- def test_create_extrapolated_config_file_not_found(self, *args, **kwargs):
- unit_config_path = os.path.join(
- self.data_dir,
- FORM_COMPOSER__UNIT_CONFIG_NAME,
- )
- token_sets_values_config_path = os.path.join(
- self.data_dir,
- FORM_COMPOSER__TOKEN_SETS_VALUES_CONFIG_NAME,
- )
- task_data_config_path = os.path.join(
- self.data_dir,
- FORM_COMPOSER__DATA_CONFIG_NAME,
- )
-
- with self.assertRaises(FileNotFoundError) as cm:
- create_extrapolated_config(
- unit_config_path=unit_config_path,
- token_sets_values_config_path=token_sets_values_config_path,
- task_data_config_path=task_data_config_path,
- data_path=self.data_dir,
- )
-
- self.assertEqual(
- cm.exception.__str__(),
- f"Create file '{unit_config_path}' with form configuration.",
- )
-
- def test_create_extrapolated_config_success(self, *args, **kwargs):
- unit_config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
- token_sets_values_config_data = [
- {
- "tokens_values": {
- "token_1": "value 1",
- "token_2": "value 2",
- "token_3": "value 3",
- "token_4": "value 4",
- "token_5": "value 5",
- },
- },
- ]
-
- unit_config_path = os.path.join(
- self.data_dir,
- FORM_COMPOSER__UNIT_CONFIG_NAME,
- )
- token_sets_values_config_path = os.path.join(
- self.data_dir,
- FORM_COMPOSER__TOKEN_SETS_VALUES_CONFIG_NAME,
- )
- task_data_config_path = os.path.join(
- self.data_dir,
- FORM_COMPOSER__DATA_CONFIG_NAME,
- )
-
- unit_config_f = open(unit_config_path, "w")
- unit_config_f.write(json.dumps(unit_config_data))
- unit_config_f.close()
-
- token_sets_values_config_f = open(token_sets_values_config_path, "w")
- token_sets_values_config_f.write(json.dumps(token_sets_values_config_data))
- token_sets_values_config_f.close()
-
- create_extrapolated_config(
- unit_config_path=unit_config_path,
- token_sets_values_config_path=token_sets_values_config_path,
- task_data_config_path=task_data_config_path,
- data_path=self.data_dir,
- )
-
- f = open(task_data_config_path, "r")
- task_config_data = json.loads(f.read())
-
- self.assertEqual(
- task_config_data,
- [
- {
- "form": {
- "title": "Form title value 1",
- "instruction": "Form instruction value 2",
- "sections": [
- {
- "collapsable": False,
- "fieldsets": [
- {
- "help": "Fieldset help",
- "instruction": "Fieldset instruction",
- "rows": [
- {
- "fields": [
- {
- "help": "Field help value 5",
- "id": "id_field",
- "label": "Field label",
- "name": "field_name",
- "placeholder": "Field placeholder",
- "tooltip": "Field tooltip",
- "type": "file",
- "value": "",
- },
- ],
- "help": "Row help",
- },
- ],
- "title": "Fieldset title value 4",
- },
- ],
- "initially_collapsed": True,
- "instruction": "Section instruction",
- "name": "section_name",
- "title": "Section title value 3",
- },
- ],
- "submit_button": {
- "instruction": "Submit instruction value 5",
- "text": "Submit",
- "tooltip": "Submit tooltip",
- },
- },
- "form_metadata": {
- "tokens_values": {
- "token_1": "value 1",
- "token_2": "value 2",
- "token_3": "value 3",
- "token_4": "value 4",
- "token_5": "value 5",
- }
- },
- },
- ],
- )
-
- def test_validate_task_data_config_success(self, *args, **kwargs):
- task_config_data = [deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)]
-
- result, errors = validate_task_data_config(task_config_data)
-
- self.assertTrue(result)
- self.assertEqual(errors, [])
-
- def test_validate_task_data_config_not_list(self, *args, **kwargs):
- task_config_data = {}
-
- result, errors = validate_task_data_config(task_config_data)
-
- self.assertFalse(result)
- self.assertEqual(errors, ["Config must be a JSON Array."])
-
- def test_validate_task_data_config_errors(self, *args, **kwargs):
- task_config_data = [
- {
- "wrong_key": {},
- }
- ]
-
- result, errors = validate_task_data_config(task_config_data)
-
- self.assertFalse(result)
- self.assertEqual(
- errors,
- [
- (
- "Object `form`. Not all required attributes were specified. "
- "Required attributes: form. Passed attributes: wrong_key."
- ),
- (
- "Object `form` has no available attribute with name `wrong_key`. "
- "Available attributes: form, form_metadata."
- ),
- "Unit config must contain only these attributes: form, form_metadata.",
- ],
- )
-
def test_verify_form_composer_configs_errors(self, *args, **kwargs):
task_data_config_path = os.path.join(
self.data_dir,
@@ -961,29 +193,33 @@ def test_verify_form_composer_configs_success(self, *args, **kwargs):
token_sets_values_config_data = [
{
"tokens_values": {
- "token_1": "value 1",
- "token_2": "value 2",
- "token_3": "value 3",
- "token_4": "value 4",
- "token_5": "value 5",
+ "title_token": "value 1",
+ "instruction_token": "value 2",
+ "section_title_token": "value 3",
+ "fieldset_title_token": "value 4",
+ "field_help_token": "value 5",
+ "submit_instruction_token": "value 6",
},
},
]
separate_token_values_config_data = {
- "token_1": [
+ "title_token": [
"value 1",
],
- "token_2": [
+ "instruction_token": [
"value 2",
],
- "token_3": [
+ "section_title_token": [
"value 3",
],
- "token_4": [
+ "fieldset_title_token": [
"value 4",
],
- "token_5": [
+ "field_help_token": [
+ "value 5",
+ ],
+ "submit_instruction_token": [
"value 5",
],
}
@@ -1023,7 +259,7 @@ def test_verify_form_composer_configs_success(self, *args, **kwargs):
captured_print_output = io.StringIO()
sys.stdout = captured_print_output
- verify_generator_configs(
+ verify_form_composer_configs(
task_data_config_path,
unit_config_path,
token_sets_values_config_path,
@@ -1032,110 +268,3 @@ def test_verify_form_composer_configs_success(self, *args, **kwargs):
sys.stdout = sys.__stdout__
self.assertIn("All configs are valid.", captured_print_output.getvalue(), "\n")
-
- @patch(
- "mephisto.generators.generators_utils.config_validation.task_data_config.get_s3_presigned_url"
- )
- def test_prepare_task_config_for_review_app_success(
- self,
- mock_get_s3_presigned_url,
- *args,
- **kwargs,
- ):
- presigned_url_expected = "presigned_url"
- config_data = {
- "form": {
- "title": 'Form title {{getMultiplePresignedUrls("https://example.com/1.jpg")}}',
- "instruction": "Form instruction",
- "sections": [
- {
- "name": "section_name",
- "title": "Section title",
- "instruction": "Section instruction",
- "collapsable": False,
- "initially_collapsed": True,
- "fieldsets": [
- {
- "title": "Fieldset title",
- "instruction": "Fieldset instruction",
- "rows": [
- {
- "fields": [
- {
- "help": "Field help",
- "id": "id_field",
- "label": "Field label",
- "name": "field_name",
- "placeholder": "Field placeholder",
- "tooltip": "Field tooltip",
- "type": "file",
- "value": "",
- }
- ],
- "help": "Row help",
- },
- ],
- "help": "Fieldset help",
- },
- ],
- },
- ],
- "submit_button": {
- "instruction": "Submit instruction",
- "text": "Submit",
- "tooltip": "Submit tooltip",
- },
- },
- }
-
- mock_get_s3_presigned_url.return_value = presigned_url_expected
-
- result = prepare_task_config_for_review_app(config_data)
-
- self.assertEqual(
- result,
- {
- "form": {
- "title": f"Form title {presigned_url_expected}",
- "instruction": "Form instruction",
- "sections": [
- {
- "name": "section_name",
- "title": "Section title",
- "instruction": "Section instruction",
- "collapsable": False,
- "initially_collapsed": True,
- "fieldsets": [
- {
- "title": "Fieldset title",
- "instruction": "Fieldset instruction",
- "rows": [
- {
- "fields": [
- {
- "help": "Field help",
- "id": "id_field",
- "label": "Field label",
- "name": "field_name",
- "placeholder": "Field placeholder",
- "tooltip": "Field tooltip",
- "type": "file",
- "value": "",
- }
- ],
- "help": "Row help",
- },
- ],
- "help": "Fieldset help",
- },
- ],
- },
- ],
- "submit_button": {
- "instruction": "Submit instruction",
- "text": "Submit",
- "tooltip": "Submit tooltip",
- },
- },
- },
- )
diff --git a/test/generators/form_composer/config_validation/test_form_config.py b/test/generators/form_composer/config_validation/test_unit_config.py
similarity index 85%
rename from test/generators/form_composer/config_validation/test_form_config.py
rename to test/generators/form_composer/config_validation/test_unit_config.py
index 0aed7b1fb..d3673dfa3 100644
--- a/test/generators/form_composer/config_validation/test_form_config.py
+++ b/test/generators/form_composer/config_validation/test_unit_config.py
@@ -7,67 +7,11 @@
from mephisto.client.cli_form_composer_commands import set_form_composer_env_vars
from mephisto.generators.form_composer.config_validation.unit_config import validate_unit_config
-from mephisto.generators.generators_utils.config_validation.unit_config import (
- collect_values_for_unique_attrs_from_item,
-)
-from mephisto.generators.generators_utils.config_validation.unit_config import (
- duplicate_values_exist,
-)
-class TestFormConfig(unittest.TestCase):
+class TestUnitConfig(unittest.TestCase):
def setUp(self):
- set_form_composer_env_vars()
-
- def test__collect_values_for_unique_attrs_from_item(self, *args, **kwargs):
- item = {
- "help": "Field help",
- "id": "id_field",
- "label": "Field label",
- "name": "field_name",
- "placeholder": "Field placeholder",
- "tooltip": "Field tooltip",
- "type": "file",
- "validators": {
- "required": True,
- },
- "value": "",
- }
-
- values_for_unique_attrs = {}
- result = collect_values_for_unique_attrs_from_item(
- item=item,
- values_for_unique_attrs=values_for_unique_attrs,
- )
-
- self.assertEqual(result, {"id": ["id_field"], "name": ["field_name"]})
-
- def test__duplicate_values_exist_no_duplicates(self, *args, **kwargs):
- no_duplicates_values_for_unique_attrs = {"id": ["id_field"], "name": ["field_name"]}
- errors = []
-
- result = duplicate_values_exist(no_duplicates_values_for_unique_attrs, errors)
-
- self.assertTrue(result)
- self.assertEqual(errors, [])
-
- def test__duplicate_values_exist_with_duplicates(self, *args, **kwargs):
- no_duplicates_values_for_unique_attrs = {
- "id": ["id_field", "id_field"],
- "name": ["field_name", "field_name"],
- }
- errors = []
-
- result = duplicate_values_exist(no_duplicates_values_for_unique_attrs, errors)
-
- self.assertFalse(result)
- self.assertEqual(
- errors,
- [
- "Found duplicate names for unique attribute 'id' in your form config: id_field",
- "Found duplicate names for unique attribute 'name' in your form config: field_name",
- ],
- )
+ set_form_composer_env_vars(use_validation_mapping_cache=False)
def test_validate_unit_config_not_dict(self, *args, **kwargs):
config_data = []
diff --git a/test/generators/generators_utils/__init__.py b/test/generators/generators_utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/generators/generators_utils/config_validation/__init__.py b/test/generators/generators_utils/config_validation/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/generators/form_composer/config_validation/test_common_validation.py b/test/generators/generators_utils/config_validation/test_common_validation.py
similarity index 99%
rename from test/generators/form_composer/config_validation/test_common_validation.py
rename to test/generators/generators_utils/config_validation/test_common_validation.py
index f1c81b720..b820135ef 100644
--- a/test/generators/form_composer/config_validation/test_common_validation.py
+++ b/test/generators/generators_utils/config_validation/test_common_validation.py
@@ -41,7 +41,7 @@
class TestCommonValidation(unittest.TestCase):
def setUp(self):
self.data_dir = tempfile.mkdtemp()
- set_form_composer_env_vars()
+ set_form_composer_env_vars(use_validation_mapping_cache=False)
def tearDown(self):
shutil.rmtree(self.data_dir, ignore_errors=True)
diff --git a/test/generators/form_composer/config_validation/test_separate_token_values_config.py b/test/generators/generators_utils/config_validation/test_separate_token_values_config.py
similarity index 99%
rename from test/generators/form_composer/config_validation/test_separate_token_values_config.py
rename to test/generators/generators_utils/config_validation/test_separate_token_values_config.py
index cb468b17b..31055301f 100644
--- a/test/generators/form_composer/config_validation/test_separate_token_values_config.py
+++ b/test/generators/generators_utils/config_validation/test_separate_token_values_config.py
@@ -27,7 +27,7 @@
class TestSeparateTokenValuesConfig(unittest.TestCase):
def setUp(self):
self.data_dir = tempfile.mkdtemp()
- set_form_composer_env_vars()
+ set_form_composer_env_vars(use_validation_mapping_cache=False)
def tearDown(self):
shutil.rmtree(self.data_dir, ignore_errors=True)
diff --git a/test/generators/generators_utils/config_validation/test_task_data_config.py b/test/generators/generators_utils/config_validation/test_task_data_config.py
new file mode 100644
index 000000000..9f59c2467
--- /dev/null
+++ b/test/generators/generators_utils/config_validation/test_task_data_config.py
@@ -0,0 +1,900 @@
+#!/usr/bin/env python3
+# Copyright (c) Meta Platforms and its affiliates.
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+import json
+import os
+import shutil
+import tempfile
+import unittest
+from copy import deepcopy
+from unittest.mock import patch
+
+from mephisto.client.cli_form_composer_commands import FORM_COMPOSER__DATA_CONFIG_NAME
+from mephisto.client.cli_form_composer_commands import FORM_COMPOSER__TOKEN_SETS_VALUES_CONFIG_NAME
+from mephisto.client.cli_form_composer_commands import FORM_COMPOSER__UNIT_CONFIG_NAME
+from mephisto.client.cli_form_composer_commands import set_form_composer_env_vars
+from mephisto.generators.generators_utils.config_validation.task_data_config import (
+ _collect_tokens_from_unit_config,
+)
+from mephisto.generators.generators_utils.config_validation.task_data_config import (
+ _combine_extrapolated_unit_configs,
+)
+from mephisto.generators.generators_utils.config_validation.task_data_config import (
+ _extrapolate_tokens_in_unit_config,
+)
+from mephisto.generators.generators_utils.config_validation.task_data_config import (
+ _extrapolate_tokens_values,
+)
+from mephisto.generators.generators_utils.config_validation.task_data_config import (
+ _set_tokens_in_unit_config_item,
+)
+from mephisto.generators.generators_utils.config_validation.task_data_config import (
+ _validate_tokens_in_both_configs,
+)
+from mephisto.generators.generators_utils.config_validation.task_data_config import (
+ create_extrapolated_config,
+)
+from mephisto.generators.generators_utils.config_validation.task_data_config import (
+ prepare_task_config_for_review_app,
+)
+from mephisto.generators.generators_utils.config_validation.task_data_config import (
+ validate_task_data_config,
+)
+
+CORRECT_CONFIG_DATA_WITH_TOKENS = {
+ "form": {
+ "title": "Form title {{token_1}}",
+ "instruction": "Form instruction {{token_2}}",
+ "sections": [
+ {
+ "name": "section_name",
+ "title": "Section title {{token_3}}",
+ "instruction": "Section instruction",
+ "collapsable": False,
+ "initially_collapsed": True,
+ "fieldsets": [
+ {
+ "title": "Fieldset title {{token_4}}",
+ "instruction": "Fieldset instruction",
+ "rows": [
+ {
+ "fields": [
+ {
+ "help": "Field help {{token_5}}",
+ "id": "id_field",
+ "label": "Field label",
+ "name": "field_name",
+ "placeholder": "Field placeholder",
+ "tooltip": "Field tooltip",
+ "type": "file",
+ "value": "",
+ }
+ ],
+ "help": "Row help",
+ },
+ ],
+ "help": "Fieldset help",
+ },
+ ],
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction {{token_5}}",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+}
+
+
+class TestTaskDataConfig(unittest.TestCase):
+ def setUp(self):
+ self.data_dir = tempfile.mkdtemp()
+ set_form_composer_env_vars(use_validation_mapping_cache=False)
+
+ def tearDown(self):
+ shutil.rmtree(self.data_dir, ignore_errors=True)
+
+ def test__extrapolate_tokens_values_simple(self, *args, **kwargs):
+ text = "Test {{token_1}} and {{token_2}}"
+ tokens_values = {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ }
+ result = _extrapolate_tokens_values(text, tokens_values)
+
+ self.assertEqual(result, "Test value 1 and value 2")
+
+ def test__extrapolate_tokens_values_with_spaces_around(self, *args, **kwargs):
+ text = "Test {{ token_1 }} and {{ token_2 }}"
+ tokens_values = {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ }
+ result = _extrapolate_tokens_values(text, tokens_values)
+
+ self.assertEqual(result, "Test value 1 and value 2")
+
+ def test__extrapolate_tokens_values_with_new_lines_around(self, *args, **kwargs):
+ text = "Test {{\ntoken_1\n}} and {{\n\ntoken_2\n\n}}"
+ tokens_values = {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ }
+ result = _extrapolate_tokens_values(text, tokens_values)
+
+ self.assertEqual(result, "Test value 1 and value 2")
+
+ def test__extrapolate_tokens_values_with_tabs_around(self, *args, **kwargs):
+ text = "Test {{\ttoken_1\t}} and {{\t\ttoken_2\t\t}}"
+ tokens_values = {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ }
+ result = _extrapolate_tokens_values(text, tokens_values)
+
+ self.assertEqual(result, "Test value 1 and value 2")
+
+ def test__extrapolate_tokens_values_with_bracketed_values(self, *args, **kwargs):
+ text = "Test {{token_1}} and {{token_2}}"
+ tokens_values = {
+ "token_1": "{{value 1}}",
+ "token_2": "{{value 2}}",
+ }
+ result = _extrapolate_tokens_values(text, tokens_values)
+
+ self.assertEqual(result, "Test {{value 1}} and {{value 2}}")
+
+ def test__extrapolate_tokens_values_with_procedure_tokens(self, *args, **kwargs):
+ text = 'Test {{someProcedureWithArguments({"arg": 1})}} and {{otherProcedure(True)}}'
+ tokens_values = {
+ 'someProcedureWithArguments({"arg": 1})': "value 1",
+ "otherProcedure(True)": "value 2",
+ }
+ result = _extrapolate_tokens_values(text, tokens_values)
+
+ self.assertEqual(result, "Test value 1 and value 2")
+
+ def test__set_tokens_in_unit_config_item(self, *args, **kwargs):
+ item = {
+ "title": "Form title {{token_1}} and {{token_2}}",
+ "instruction": "Form instruction {{token_2}}",
+ }
+ tokens_values = {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ }
+
+ _set_tokens_in_unit_config_item(item, tokens_values)
+
+ self.assertEqual(
+ item,
+ {
+ "title": "Form title value 1 and value 2",
+ "instruction": "Form instruction value 2",
+ },
+ )
+
+ def test__collect_tokens_from_unit_config_success(self, *args, **kwargs):
+ config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
+
+ tokens, errors = _collect_tokens_from_unit_config(config_data)
+
+ self.assertEqual(tokens, {"token_1", "token_2", "token_3", "token_4", "token_5"})
+ self.assertEqual(errors, [])
+
+ def test__collect_tokens_from_unit_config_with_errors(self, *args, **kwargs):
+ config_data = {
+ "form": {
+ "title": "Form title {{token_1}}",
+ "instruction": "Form instruction {{token_2}}",
+ "sections": [
+ {
+ "name": "section_name {{token_6}}",
+ "title": "Section title {{token_3}}",
+ "instruction": "Section instruction",
+ "collapsable": False,
+ "initially_collapsed": True,
+ "fieldsets": [
+ {
+ "title": "Fieldset title {{token_4}}",
+ "instruction": "Fieldset instruction",
+ "rows": [
+ {
+ "fields": [
+ {
+ "help": "Field help {{token_5}}",
+ "id": "id_field {{token_7}}",
+ "label": "Field label",
+ "name": "field_name {{token_8}}",
+ "placeholder": "Field placeholder",
+ "tooltip": "Field tooltip",
+ "type": "file {{token_9}}",
+ "value": "",
+ }
+ ],
+ "help": "Row help",
+ },
+ ],
+ "help": "Fieldset help",
+ },
+ ],
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction {{token_5}}",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+ }
+
+ tokens, errors = _collect_tokens_from_unit_config(config_data)
+
+ self.assertEqual(tokens, {"token_1", "token_2", "token_3", "token_4", "token_5"})
+ self.assertEqual(
+ sorted(errors),
+ sorted(
+ [
+ (
+ "You tried to set tokens 'token_6' in attribute 'name' with value "
+ "'section_name {{token_6}}'. You can use tokens only in following attributes: "
+ "help, instruction, label, title, tooltip"
+ ),
+ (
+ "You tried to set tokens 'token_8' in attribute 'name' with value "
+ "'field_name {{token_8}}'. You can use tokens only in following attributes: "
+ "help, instruction, label, title, tooltip"
+ ),
+ (
+ "You tried to set tokens 'token_7' in attribute 'id' with value "
+ "'id_field {{token_7}}'. You can use tokens only in following attributes: "
+ "help, instruction, label, title, tooltip"
+ ),
+ (
+ "You tried to set tokens 'token_9' in attribute 'type' with value "
+ "'file {{token_9}}'. You can use tokens only in following attributes: "
+ "help, instruction, label, title, tooltip"
+ ),
+ ]
+ ),
+ )
+
+ def test__extrapolate_tokens_in_unit_config_success(self, *args, **kwargs):
+ config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
+ tokens_values = {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ "token_3": "value 3",
+ "token_4": "value 4",
+ "token_5": "value 5",
+ }
+
+ result = _extrapolate_tokens_in_unit_config(config_data, tokens_values)
+
+ self.assertEqual(
+ result,
+ {
+ "form": {
+ "title": "Form title value 1",
+ "instruction": "Form instruction value 2",
+ "sections": [
+ {
+ "name": "section_name",
+ "title": "Section title value 3",
+ "instruction": "Section instruction",
+ "collapsable": False,
+ "initially_collapsed": True,
+ "fieldsets": [
+ {
+ "title": "Fieldset title value 4",
+ "instruction": "Fieldset instruction",
+ "rows": [
+ {
+ "fields": [
+ {
+ "help": "Field help value 5",
+ "id": "id_field",
+ "label": "Field label",
+ "name": "field_name",
+ "placeholder": "Field placeholder",
+ "tooltip": "Field tooltip",
+ "type": "file",
+ "value": "",
+ }
+ ],
+ "help": "Row help",
+ },
+ ],
+ "help": "Fieldset help",
+ },
+ ],
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction value 5",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+ },
+ )
+
+ def test__validate_tokens_in_both_configs_success(self, *args, **kwargs):
+ config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
+ token_sets_values_config_data = [
+ {
+ "tokens_values": {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ "token_3": "value 3",
+ "token_4": "value 4",
+ "token_5": "value 5",
+ },
+ },
+ ]
+
+ (
+ overspecified_tokens,
+ underspecified_tokens,
+ tokens_in_unexpected_attrs_errors,
+ ) = _validate_tokens_in_both_configs(config_data, token_sets_values_config_data)
+
+ self.assertEqual(len(overspecified_tokens), 0)
+ self.assertEqual(len(underspecified_tokens), 0)
+ self.assertEqual(tokens_in_unexpected_attrs_errors, [])
+
+ def test__validate_tokens_in_both_configs_with_errors(self, *args, **kwargs):
+ config_data = {
+ "form": {
+ "title": "Form title",
+ "instruction": "Form instruction {{token_2}}",
+ "sections": [
+ {
+ "name": "section_name",
+ "title": "Section title {{token_3}}",
+ "instruction": "Section instruction",
+ "collapsable": False,
+ "initially_collapsed": True,
+ "fieldsets": [
+ {
+ "title": "Fieldset title {{token_4}}",
+ "instruction": "Fieldset instruction",
+ "rows": [
+ {
+ "fields": [
+ {
+ "help": "Field help {{token_5}}",
+ "id": "id_field {{token_5}}",
+ "label": "Field label {{token_6}}",
+ "name": "field_name",
+ "placeholder": "Field placeholder",
+ "tooltip": "Field tooltip",
+ "type": "file",
+ "value": "",
+ }
+ ],
+ "help": "Row help",
+ },
+ ],
+ "help": "Fieldset help",
+ },
+ ],
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction {{token_5}}",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+ }
+ token_sets_values_config_data = [
+ {
+ "tokens_values": {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ "token_3": "value 3",
+ "token_4": "value 4",
+ "token_5": "value 5",
+ },
+ },
+ ]
+
+ (
+ overspecified_tokens,
+ underspecified_tokens,
+ tokens_in_unexpected_attrs_errors,
+ ) = _validate_tokens_in_both_configs(config_data, token_sets_values_config_data)
+
+ self.assertEqual(overspecified_tokens, {"token_1"})
+ self.assertEqual(underspecified_tokens, {"token_6"})
+ self.assertEqual(
+ tokens_in_unexpected_attrs_errors,
+ [
+ (
+ "You tried to set tokens 'token_5' in attribute 'id' with value 'id_field "
+ "{{token_5}}'. You can use tokens only in following attributes: help, "
+ "instruction, label, title, tooltip"
+ ),
+ ],
+ )
+
+ def test__combine_extrapolated_unit_configs_success(self, *args, **kwargs):
+ config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
+ token_sets_values_config_data = [
+ {
+ "tokens_values": {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ "token_3": "value 3",
+ "token_4": "value 4",
+ "token_5": "value 5",
+ },
+ },
+ ]
+
+ result = _combine_extrapolated_unit_configs(config_data, token_sets_values_config_data)
+
+ self.assertEqual(
+ result,
+ [
+ {
+ "form": {
+ "title": "Form title value 1",
+ "instruction": "Form instruction value 2",
+ "sections": [
+ {
+ "collapsable": False,
+ "fieldsets": [
+ {
+ "help": "Fieldset help",
+ "instruction": "Fieldset instruction",
+ "rows": [
+ {
+ "fields": [
+ {
+ "help": "Field help value 5",
+ "id": "id_field",
+ "label": "Field label",
+ "name": "field_name",
+ "placeholder": "Field placeholder",
+ "tooltip": "Field tooltip",
+ "type": "file",
+ "value": "",
+ },
+ ],
+ "help": "Row help",
+ },
+ ],
+ "title": "Fieldset title value 4",
+ },
+ ],
+ "initially_collapsed": True,
+ "instruction": "Section instruction",
+ "name": "section_name",
+ "title": "Section title value 3",
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction value 5",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+ "form_metadata": {
+ "tokens_values": {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ "token_3": "value 3",
+ "token_4": "value 4",
+ "token_5": "value 5",
+ }
+ },
+ },
+ ],
+ )
+
+ def test__replace_html_paths_with_html_file_content_success(self, *args, **kwargs):
+ value_with_file_path_1 = "insertions/test1.html"
+ value_with_file_path_2 = "insertions/test2.html"
+ html_content_1 = "Test {{token_1}}"
+ html_content_2 = "Test {{token_2}}"
+
+ html_path_1 = os.path.abspath(os.path.join(self.data_dir, value_with_file_path_1))
+ html_path_2 = os.path.abspath(os.path.join(self.data_dir, value_with_file_path_2))
+
+ os.makedirs(os.path.dirname(html_path_1), exist_ok=True)
+ f = open(html_path_1, "w")
+ f.write(html_content_1)
+ f.close()
+ f = open(html_path_2, "w")
+ f.write(html_content_2)
+ f.close()
+
+ unit_config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
+ unit_config_data["form"]["title"] = value_with_file_path_1
+ unit_config_data["form"]["instruction"] = value_with_file_path_2
+
+ token_sets_values_config_data = [
+ {
+ "tokens_values": {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ "token_3": "value 3",
+ "token_4": "value 4",
+ "token_5": "value 5",
+ },
+ },
+ ]
+
+ unit_config_path = os.path.join(
+ self.data_dir,
+ FORM_COMPOSER__UNIT_CONFIG_NAME,
+ )
+ token_sets_values_config_path = os.path.join(
+ self.data_dir,
+ FORM_COMPOSER__TOKEN_SETS_VALUES_CONFIG_NAME,
+ )
+ task_data_config_path = os.path.join(
+ self.data_dir,
+ FORM_COMPOSER__DATA_CONFIG_NAME,
+ )
+
+ unit_config_f = open(unit_config_path, "w")
+ unit_config_f.write(json.dumps(unit_config_data))
+ unit_config_f.close()
+
+ token_sets_values_config_f = open(token_sets_values_config_path, "w")
+ token_sets_values_config_f.write(json.dumps(token_sets_values_config_data))
+ token_sets_values_config_f.close()
+
+ create_extrapolated_config(
+ unit_config_path=unit_config_path,
+ token_sets_values_config_path=token_sets_values_config_path,
+ task_data_config_path=task_data_config_path,
+ data_path=self.data_dir,
+ )
+
+ f = open(task_data_config_path, "r")
+ task_config_data = json.loads(f.read())
+
+ self.assertEqual(
+ task_config_data,
+ [
+ {
+ "form": {
+ "title": "Test value 1",
+ "instruction": "Test value 2",
+ "sections": [
+ {
+ "collapsable": False,
+ "fieldsets": [
+ {
+ "help": "Fieldset help",
+ "instruction": "Fieldset instruction",
+ "rows": [
+ {
+ "fields": [
+ {
+ "help": "Field help value 5",
+ "id": "id_field",
+ "label": "Field label",
+ "name": "field_name",
+ "placeholder": "Field placeholder",
+ "tooltip": "Field tooltip",
+ "type": "file",
+ "value": "",
+ },
+ ],
+ "help": "Row help",
+ },
+ ],
+ "title": "Fieldset title value 4",
+ },
+ ],
+ "initially_collapsed": True,
+ "instruction": "Section instruction",
+ "name": "section_name",
+ "title": "Section title value 3",
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction value 5",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+ "form_metadata": {
+ "tokens_values": {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ "token_3": "value 3",
+ "token_4": "value 4",
+ "token_5": "value 5",
+ }
+ },
+ },
+ ],
+ )
+
+ def test_create_extrapolated_config_file_not_found(self, *args, **kwargs):
+ unit_config_path = os.path.join(
+ self.data_dir,
+ FORM_COMPOSER__UNIT_CONFIG_NAME,
+ )
+ token_sets_values_config_path = os.path.join(
+ self.data_dir,
+ FORM_COMPOSER__TOKEN_SETS_VALUES_CONFIG_NAME,
+ )
+ task_data_config_path = os.path.join(
+ self.data_dir,
+ FORM_COMPOSER__DATA_CONFIG_NAME,
+ )
+
+ with self.assertRaises(FileNotFoundError) as cm:
+ create_extrapolated_config(
+ unit_config_path=unit_config_path,
+ token_sets_values_config_path=token_sets_values_config_path,
+ task_data_config_path=task_data_config_path,
+ data_path=self.data_dir,
+ )
+
+ self.assertEqual(
+ cm.exception.__str__(),
+ f"Create file '{unit_config_path}' with form configuration.",
+ )
+
+ def test_create_extrapolated_config_success(self, *args, **kwargs):
+ unit_config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
+ token_sets_values_config_data = [
+ {
+ "tokens_values": {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ "token_3": "value 3",
+ "token_4": "value 4",
+ "token_5": "value 5",
+ },
+ },
+ ]
+
+ unit_config_path = os.path.join(
+ self.data_dir,
+ FORM_COMPOSER__UNIT_CONFIG_NAME,
+ )
+ token_sets_values_config_path = os.path.join(
+ self.data_dir,
+ FORM_COMPOSER__TOKEN_SETS_VALUES_CONFIG_NAME,
+ )
+ task_data_config_path = os.path.join(
+ self.data_dir,
+ FORM_COMPOSER__DATA_CONFIG_NAME,
+ )
+
+ unit_config_f = open(unit_config_path, "w")
+ unit_config_f.write(json.dumps(unit_config_data))
+ unit_config_f.close()
+
+ token_sets_values_config_f = open(token_sets_values_config_path, "w")
+ token_sets_values_config_f.write(json.dumps(token_sets_values_config_data))
+ token_sets_values_config_f.close()
+
+ create_extrapolated_config(
+ unit_config_path=unit_config_path,
+ token_sets_values_config_path=token_sets_values_config_path,
+ task_data_config_path=task_data_config_path,
+ data_path=self.data_dir,
+ )
+
+ f = open(task_data_config_path, "r")
+ task_config_data = json.loads(f.read())
+
+ self.assertEqual(
+ task_config_data,
+ [
+ {
+ "form": {
+ "title": "Form title value 1",
+ "instruction": "Form instruction value 2",
+ "sections": [
+ {
+ "collapsable": False,
+ "fieldsets": [
+ {
+ "help": "Fieldset help",
+ "instruction": "Fieldset instruction",
+ "rows": [
+ {
+ "fields": [
+ {
+ "help": "Field help value 5",
+ "id": "id_field",
+ "label": "Field label",
+ "name": "field_name",
+ "placeholder": "Field placeholder",
+ "tooltip": "Field tooltip",
+ "type": "file",
+ "value": "",
+ },
+ ],
+ "help": "Row help",
+ },
+ ],
+ "title": "Fieldset title value 4",
+ },
+ ],
+ "initially_collapsed": True,
+ "instruction": "Section instruction",
+ "name": "section_name",
+ "title": "Section title value 3",
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction value 5",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+ "form_metadata": {
+ "tokens_values": {
+ "token_1": "value 1",
+ "token_2": "value 2",
+ "token_3": "value 3",
+ "token_4": "value 4",
+ "token_5": "value 5",
+ }
+ },
+ },
+ ],
+ )
+
+ def test_validate_task_data_config_success(self, *args, **kwargs):
+ task_config_data = [deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)]
+
+ result, errors = validate_task_data_config(task_config_data)
+
+ self.assertTrue(result)
+ self.assertEqual(errors, [])
+
+ def test_validate_task_data_config_not_list(self, *args, **kwargs):
+ task_config_data = {}
+
+ result, errors = validate_task_data_config(task_config_data)
+
+ self.assertFalse(result)
+ self.assertEqual(errors, ["Config must be a JSON Array."])
+
+ def test_validate_task_data_config_errors(self, *args, **kwargs):
+ task_config_data = [
+ {
+ "wrong_key": {},
+ }
+ ]
+
+ result, errors = validate_task_data_config(task_config_data)
+
+ self.assertFalse(result)
+ self.assertEqual(
+ errors,
+ [
+ (
+ "Object `form`. Not all required attributes were specified. "
+ "Required attributes: form. Passed attributes: wrong_key."
+ ),
+ (
+ "Object `form` has no available attribute with name `wrong_key`. "
+ "Available attributes: form, form_metadata."
+ ),
+ "Unit config must contain only these attributes: form, form_metadata.",
+ ],
+ )
+
+ @patch(
+ "mephisto.generators.generators_utils.config_validation.task_data_config.get_s3_presigned_url"
+ )
+ def test_prepare_task_config_for_review_app_success(
+ self,
+ mock_get_s3_presigned_url,
+ *args,
+ **kwargs,
+ ):
+ presigned_url_expected = "presigned_url"
+ config_data = {
+ "form": {
+ "title": 'Form title {{getMultiplePresignedUrls("https://example.com/1.jpg")}}',
+ "instruction": "Form instruction",
+ "sections": [
+ {
+ "name": "section_name",
+ "title": "Section title",
+ "instruction": "Section instruction",
+ "collapsable": False,
+ "initially_collapsed": True,
+ "fieldsets": [
+ {
+ "title": "Fieldset title",
+ "instruction": "Fieldset instruction",
+ "rows": [
+ {
+ "fields": [
+ {
+ "help": "Field help",
+ "id": "id_field",
+ "label": "Field label",
+ "name": "field_name",
+ "placeholder": "Field placeholder",
+ "tooltip": "Field tooltip",
+ "type": "file",
+ "value": "",
+ }
+ ],
+ "help": "Row help",
+ },
+ ],
+ "help": "Fieldset help",
+ },
+ ],
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+ }
+
+ mock_get_s3_presigned_url.return_value = presigned_url_expected
+
+ result = prepare_task_config_for_review_app(config_data)
+
+ self.assertEqual(
+ result,
+ {
+ "form": {
+ "title": f"Form title {presigned_url_expected}",
+ "instruction": "Form instruction",
+ "sections": [
+ {
+ "name": "section_name",
+ "title": "Section title",
+ "instruction": "Section instruction",
+ "collapsable": False,
+ "initially_collapsed": True,
+ "fieldsets": [
+ {
+ "title": "Fieldset title",
+ "instruction": "Fieldset instruction",
+ "rows": [
+ {
+ "fields": [
+ {
+ "help": "Field help",
+ "id": "id_field",
+ "label": "Field label",
+ "name": "field_name",
+ "placeholder": "Field placeholder",
+ "tooltip": "Field tooltip",
+ "type": "file",
+ "value": "",
+ }
+ ],
+ "help": "Row help",
+ },
+ ],
+ "help": "Fieldset help",
+ },
+ ],
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+ },
+ )
diff --git a/test/generators/form_composer/config_validation/test_token_sets_values_config.py b/test/generators/generators_utils/config_validation/test_token_sets_values_config.py
similarity index 98%
rename from test/generators/form_composer/config_validation/test_token_sets_values_config.py
rename to test/generators/generators_utils/config_validation/test_token_sets_values_config.py
index a400cea1c..88743d2ad 100644
--- a/test/generators/form_composer/config_validation/test_token_sets_values_config.py
+++ b/test/generators/generators_utils/config_validation/test_token_sets_values_config.py
@@ -28,7 +28,7 @@
class TestTokenSetsValuesConfig(unittest.TestCase):
def setUp(self):
self.data_dir = tempfile.mkdtemp()
- set_form_composer_env_vars()
+ set_form_composer_env_vars(use_validation_mapping_cache=False)
def tearDown(self):
shutil.rmtree(self.data_dir, ignore_errors=True)
diff --git a/test/generators/generators_utils/config_validation/test_unit_config.py b/test/generators/generators_utils/config_validation/test_unit_config.py
new file mode 100644
index 000000000..a4d925a10
--- /dev/null
+++ b/test/generators/generators_utils/config_validation/test_unit_config.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+# Copyright (c) Meta Platforms and its affiliates.
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+import unittest
+
+from mephisto.client.cli_form_composer_commands import set_form_composer_env_vars
+from mephisto.generators.generators_utils.config_validation.unit_config import (
+ collect_values_for_unique_attrs_from_item,
+)
+from mephisto.generators.generators_utils.config_validation.unit_config import (
+ duplicate_values_exist,
+)
+
+
+class TestUnitConfig(unittest.TestCase):
+ def setUp(self):
+ set_form_composer_env_vars(use_validation_mapping_cache=False)
+
+ def test__collect_values_for_unique_attrs_from_item(self, *args, **kwargs):
+ item = {
+ "help": "Field help",
+ "id": "id_field",
+ "label": "Field label",
+ "name": "field_name",
+ "placeholder": "Field placeholder",
+ "tooltip": "Field tooltip",
+ "type": "file",
+ "validators": {
+ "required": True,
+ },
+ "value": "",
+ }
+
+ values_for_unique_attrs = {}
+ result = collect_values_for_unique_attrs_from_item(
+ item=item,
+ values_for_unique_attrs=values_for_unique_attrs,
+ )
+
+ self.assertEqual(result, {"id": ["id_field"], "name": ["field_name"]})
+
+ def test__duplicate_values_exist_no_duplicates(self, *args, **kwargs):
+ no_duplicates_values_for_unique_attrs = {"id": ["id_field"], "name": ["field_name"]}
+ errors = []
+
+ result = duplicate_values_exist(no_duplicates_values_for_unique_attrs, errors)
+
+ self.assertTrue(result)
+ self.assertEqual(errors, [])
+
+ def test__duplicate_values_exist_with_duplicates(self, *args, **kwargs):
+ no_duplicates_values_for_unique_attrs = {
+ "id": ["id_field", "id_field"],
+ "name": ["field_name", "field_name"],
+ }
+ errors = []
+
+ result = duplicate_values_exist(no_duplicates_values_for_unique_attrs, errors)
+
+ self.assertFalse(result)
+ self.assertEqual(
+ errors,
+ [
+ "Found duplicate names for unique attribute 'id' in your form config: id_field",
+ "Found duplicate names for unique attribute 'name' in your form config: field_name",
+ ],
+ )
diff --git a/test/generators/form_composer/config_validation/test_utils.py b/test/generators/generators_utils/config_validation/test_utils.py
similarity index 100%
rename from test/generators/form_composer/config_validation/test_utils.py
rename to test/generators/generators_utils/config_validation/test_utils.py
diff --git a/test/generators/form_composer/test_remote_procedures.py b/test/generators/generators_utils/test_remote_procedures.py
similarity index 100%
rename from test/generators/form_composer/test_remote_procedures.py
rename to test/generators/generators_utils/test_remote_procedures.py
diff --git a/test/generators/video_annotator/__init__.py b/test/generators/video_annotator/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/generators/video_annotator/config_validation/__init__.py b/test/generators/video_annotator/config_validation/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/generators/video_annotator/config_validation/test_task_data_config.py b/test/generators/video_annotator/config_validation/test_task_data_config.py
new file mode 100644
index 000000000..8e19b0b0e
--- /dev/null
+++ b/test/generators/video_annotator/config_validation/test_task_data_config.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3
+# Copyright (c) Meta Platforms and its affiliates.
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+import io
+import json
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+from copy import deepcopy
+
+from mephisto.client.cli_video_annotator_commands import set_video_annotator_env_vars
+from mephisto.client.cli_video_annotator_commands import VIDEO_ANNOTATOR__DATA_CONFIG_NAME
+from mephisto.client.cli_video_annotator_commands import (
+ VIDEO_ANNOTATOR__SEPARATE_TOKEN_VALUES_CONFIG_NAME,
+)
+from mephisto.client.cli_video_annotator_commands import (
+ VIDEO_ANNOTATOR__TOKEN_SETS_VALUES_CONFIG_NAME,
+)
+from mephisto.client.cli_video_annotator_commands import VIDEO_ANNOTATOR__UNIT_CONFIG_NAME
+from mephisto.generators.video_annotator.config_validation.task_data_config import (
+ collect_unit_config_items_to_extrapolate,
+)
+from mephisto.generators.video_annotator.config_validation.task_data_config import (
+ verify_video_annotator_configs,
+)
+
+CORRECT_CONFIG_DATA_WITH_TOKENS = {
+ "annotator": {
+ "title": "Video Annotator test",
+ "instruction": "Instruction {{instruction_token}}",
+ "video": "{{video_path}}{{video_file}}",
+ "segment_fields": [
+ {"id": "id_title", "label": "Segment name", "name": "title", "type": "input"},
+ {
+ "id": "id_description",
+ "label": "Segment description",
+ "name": "description",
+ "type": "textarea",
+ },
+ {
+ "id": "id_can_understand",
+ "label": "Segment understand",
+ "name": "can_understand",
+ "type": "radio",
+ "options": [{"label": "Yes", "value": "yes"}, {"label": "No", "value": "no"}],
+ },
+ {
+ "id": "id_person_name",
+ "label": "Segment person name",
+ "help": "Segment person name help {{person_name_help_token}}",
+ "multiple": False,
+ "name": "person_name",
+ "type": "select",
+ "options": [
+ {"label": "---", "value": ""},
+ {"label": "Bunny", "value": "bunny"},
+ {"label": "Squirrel", "value": "squirrel"},
+ ],
+ "value": "",
+ },
+ ],
+ "show_instructions_as_modal": True,
+ "submit_button": {
+ "instruction": "Submit button instruction {{submit_instruction_token}}",
+ "text": "Submit",
+ "tooltip": "Submit annotations",
+ },
+ },
+}
+
+
+class TestTaskDataConfig(unittest.TestCase):
+ def setUp(self):
+ self.data_dir = tempfile.mkdtemp()
+ set_video_annotator_env_vars(use_validation_mapping_cache=False)
+
+ def tearDown(self):
+ shutil.rmtree(self.data_dir, ignore_errors=True)
+
+ def test__collect_unit_config_items_to_extrapolate(self, *args, **kwargs):
+ config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
+
+ items = collect_unit_config_items_to_extrapolate(config_data)
+
+ self.assertEqual(len(items), 6)
+
+ def test_verify_video_annotator_configs_errors(self, *args, **kwargs):
+ task_data_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__DATA_CONFIG_NAME,
+ )
+ unit_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__UNIT_CONFIG_NAME,
+ )
+ token_sets_values_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__TOKEN_SETS_VALUES_CONFIG_NAME,
+ )
+ separate_token_values_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__SEPARATE_TOKEN_VALUES_CONFIG_NAME,
+ )
+
+ captured_print_output = io.StringIO()
+ sys.stdout = captured_print_output
+ verify_video_annotator_configs(
+ task_data_config_path,
+ unit_config_path,
+ token_sets_values_config_path,
+ separate_token_values_config_path,
+ task_data_config_only=False,
+ )
+ sys.stdout = sys.__stdout__
+
+ self.assertIn(
+ "Required file not found:",
+ captured_print_output.getvalue(),
+ )
+ self.assertIn(
+ f"'{self.data_dir}/task_data.json'",
+ captured_print_output.getvalue(),
+ )
+ self.assertIn(
+ f"'{self.data_dir}/unit_config.json'",
+ captured_print_output.getvalue(),
+ )
+ self.assertIn(
+ f"'{self.data_dir}/token_sets_values_config.json'",
+ captured_print_output.getvalue(),
+ )
+ self.assertIn(
+ f"'{self.data_dir}/separate_token_values_config.json'",
+ captured_print_output.getvalue(),
+ )
+ self.assertIn(
+ "Provided Video Annotator config files are invalid:",
+ captured_print_output.getvalue(),
+ )
+ self.assertIn(
+ (
+ "- Separate token values config is invalid. Errors:\n "
+ "- Config must be a key/value JSON Object.\n\n"
+ ),
+ captured_print_output.getvalue(),
+ )
+
+ def test_verify_video_annotator_configs_errors_task_data_config_only(self, *args, **kwargs):
+ task_data_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__DATA_CONFIG_NAME,
+ )
+ unit_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__UNIT_CONFIG_NAME,
+ )
+ token_sets_values_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__TOKEN_SETS_VALUES_CONFIG_NAME,
+ )
+ separate_token_values_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__SEPARATE_TOKEN_VALUES_CONFIG_NAME,
+ )
+
+ captured_print_output = io.StringIO()
+ sys.stdout = captured_print_output
+ verify_video_annotator_configs(
+ task_data_config_path,
+ unit_config_path,
+ token_sets_values_config_path,
+ separate_token_values_config_path,
+ task_data_config_only=True,
+ )
+ sys.stdout = sys.__stdout__
+
+ self.assertIn(
+ "Required file not found:",
+ captured_print_output.getvalue(),
+ )
+ self.assertIn(
+ f"'{self.data_dir}/task_data.json'",
+ captured_print_output.getvalue(),
+ )
+
+ def test_verify_video_annotator_configs_success(self, *args, **kwargs):
+ task_data_config_data = [deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)]
+ unit_config_data = deepcopy(CORRECT_CONFIG_DATA_WITH_TOKENS)
+ token_sets_values_config_data = [
+ {
+ "tokens_values": {
+ "instruction_token": "value 1",
+ "video_path": "https://your-bucket.s3.amazonaws.com/your/folder/path/",
+ "video_file": "file.mp4",
+ "submit_instruction_token": "value 4",
+ "person_name_help_token": "value 5",
+ },
+ },
+ ]
+
+ separate_token_values_config_data = {
+ "instruction_token": [
+ "value 1",
+ ],
+ "video_path": [
+ "https://your-bucket.s3.amazonaws.com/your/folder/path/",
+ ],
+ "video_file": [
+ "file.mp4",
+ ],
+ "submit_instruction_token": [
+ "value 4",
+ ],
+ "person_name_help_token": [
+ "value 5",
+ ],
+ }
+
+ task_data_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__DATA_CONFIG_NAME,
+ )
+ unit_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__UNIT_CONFIG_NAME,
+ )
+ token_sets_values_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__TOKEN_SETS_VALUES_CONFIG_NAME,
+ )
+ separate_token_values_config_path = os.path.join(
+ self.data_dir,
+ VIDEO_ANNOTATOR__SEPARATE_TOKEN_VALUES_CONFIG_NAME,
+ )
+
+ task_data_config_f = open(task_data_config_path, "w")
+ task_data_config_f.write(json.dumps(task_data_config_data))
+ task_data_config_f.close()
+
+ unit_config_f = open(unit_config_path, "w")
+ unit_config_f.write(json.dumps(unit_config_data))
+ unit_config_f.close()
+
+ token_sets_values_config_f = open(token_sets_values_config_path, "w")
+ token_sets_values_config_f.write(json.dumps(token_sets_values_config_data))
+ token_sets_values_config_f.close()
+
+ separate_token_values_config_f = open(separate_token_values_config_path, "w")
+ separate_token_values_config_f.write(json.dumps(separate_token_values_config_data))
+ separate_token_values_config_f.close()
+
+ captured_print_output = io.StringIO()
+ sys.stdout = captured_print_output
+ verify_video_annotator_configs(
+ task_data_config_path,
+ unit_config_path,
+ token_sets_values_config_path,
+ separate_token_values_config_path,
+ )
+ sys.stdout = sys.__stdout__
+
+ self.assertIn("All configs are valid.", captured_print_output.getvalue(), "\n")
diff --git a/test/generators/video_annotator/config_validation/test_unit_config.py b/test/generators/video_annotator/config_validation/test_unit_config.py
new file mode 100644
index 000000000..c7c5f673d
--- /dev/null
+++ b/test/generators/video_annotator/config_validation/test_unit_config.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python3
+# Copyright (c) Meta Platforms and its affiliates.
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+import unittest
+
+from mephisto.client.cli_video_annotator_commands import set_video_annotator_env_vars
+from mephisto.generators.video_annotator.config_validation.unit_config import validate_unit_config
+
+
+class TestUnitConfig(unittest.TestCase):
+ def setUp(self):
+ set_video_annotator_env_vars(use_validation_mapping_cache=False)
+
+ def test_validate_unit_config_not_dict(self, *args, **kwargs):
+ config_data = []
+
+ result, errors = validate_unit_config(config_data)
+
+ self.assertFalse(result)
+ self.assertEqual(errors, ["Annotator config must be a key/value JSON Object."])
+
+ def test_validate_unit_config_wrong_keys(self, *args, **kwargs):
+ config_data = {
+ "wrong_key": {},
+ }
+
+ result, errors = validate_unit_config(config_data)
+
+ self.assertFalse(result)
+ self.assertEqual(
+ errors,
+ [
+ (
+ "Object `annotator`. Not all required attributes were specified. "
+ "Required attributes: annotator. Passed attributes: wrong_key."
+ ),
+ (
+ "Object `annotator` has no available attribute with name `wrong_key`. "
+ "Available attributes: annotator, annotator_metadata."
+ ),
+ (
+ "Annotator config must contain only these attributes: "
+ "annotator, annotator_metadata."
+ ),
+ ],
+ )
+
+ def test_validate_unit_config_not_all_required_fields(self, *args, **kwargs):
+ config_data = {
+ "annotator": {
+ "video": "https://your-bucket.s3.amazonaws.com/your/folder/path/file.mp4",
+ "segment_fields": [
+ {
+ "help": "Title help",
+ "id": "id_title",
+ "name": "title",
+ "placeholder": "Title placeholder",
+ "tooltip": "Title tooltip",
+ "type": "input",
+ "value": "",
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+ }
+
+ result, errors = validate_unit_config(config_data)
+
+ self.assertFalse(result)
+ self.assertEqual(
+ errors,
+ [
+ (
+ "Object `annotator`. Not all required attributes were specified. "
+ "Required attributes: submit_button, title, video. "
+ "Passed attributes: video, segment_fields, submit_button."
+ ),
+ (
+ "Object `field` with name `title`. "
+ "Not all required attributes were specified. "
+ "Required attributes: label, name, type. "
+ "Passed attributes: help, id, name, placeholder, tooltip, type, value."
+ ),
+ ],
+ )
+
+ def test_validate_unit_config_no_title_field(self, *args, **kwargs):
+ config_data = {
+ "annotator": {
+ "title": "Video Annotator test",
+ "video": "https://your-bucket.s3.amazonaws.com/your/folder/path/file.mp4",
+ "segment_fields": [
+ {
+ "help": "Name help",
+ "id": "id_name",
+ "name": "name",
+ "placeholder": "Name placeholder",
+ "tooltip": "Name tooltip",
+ "type": "input",
+ "value": "",
+ },
+ ],
+ "submit_button": {
+ "instruction": "Submit instruction",
+ "text": "Submit",
+ "tooltip": "Submit tooltip",
+ },
+ },
+ }
+
+ result, errors = validate_unit_config(config_data)
+
+ self.assertFalse(result)
+ self.assertEqual(
+ errors,
+ [
+ (
+ 'First field must be "title". '
+ "If you have it, move it above all fields, or add a new one"
+ ),
+ ],
+ )
+
+ def test_validate_unit_config_with_duplicates(self, *args, **kwargs):
+ config_data = {
+ "annotator": {
+ "title": "Video Annotator test",
+ "instruction": "Instruction",
+ "video": "https://your-bucket.s3.amazonaws.com/your/folder/path/file.mp4",
+ "segment_fields": [
+ {"id": "id_title", "label": "Segment name", "name": "title", "type": "input"},
+ {
+ "id": "id_title",
+ "label": "Segment description",
+ "name": "title",
+ "type": "textarea",
+ },
+ ],
+ "show_instructions_as_modal": True,
+ "submit_button": {
+ "instruction": "Submit button instruction",
+ "text": "Submit",
+ "tooltip": "Submit annotations",
+ },
+ },
+ }
+
+ result, errors = validate_unit_config(config_data)
+
+ self.assertFalse(result)
+ self.assertEqual(
+ errors,
+ [
+ "Found duplicate names for unique attribute 'id' in your form config: id_title",
+ "Found duplicate names for unique attribute 'name' in your form config: title",
+ ],
+ )
+
+ def test_validate_unit_config_incorrent_field_type(self, *args, **kwargs):
+ config_data = {
+ "annotator": {
+ "title": "Video Annotator test",
+ "instruction": "Instruction",
+ "video": "https://your-bucket.s3.amazonaws.com/your/folder/path/file.mp4",
+ "segment_fields": [
+ {
+ "id": "id_title",
+ "label": "Segment name",
+ "name": "title",
+ "type": "incorrect_field_type",
+ },
+ ],
+ "show_instructions_as_modal": True,
+ "submit_button": {
+ "instruction": "Submit button instruction {{submit_instruction_token}}",
+ "text": "Submit",
+ "tooltip": "Submit annotations",
+ },
+ },
+ }
+
+ result, errors = validate_unit_config(config_data)
+
+ self.assertFalse(result)
+ self.assertEqual(
+ errors,
+ ["Object 'field' has unsupported 'type' attribute value: incorrect_field_type"],
+ )
+
+ def test_validate_unit_config_success(self, *args, **kwargs):
+ config_data = {
+ "annotator": {
+ "title": "Video Annotator test",
+ "instruction": "Instruction",
+ "video": "https://your-bucket.s3.amazonaws.com/your/folder/path/file.mp4",
+ "segment_fields": [
+ {"id": "id_title", "label": "Segment name", "name": "title", "type": "input"},
+ ],
+ "show_instructions_as_modal": True,
+ "submit_button": {
+ "instruction": "Submit button instruction",
+ "text": "Submit",
+ "tooltip": "Submit annotations",
+ },
+ },
+ }
+
+ result, errors = validate_unit_config(config_data)
+
+ self.assertTrue(result)
+ self.assertEqual(errors, [])