diff --git a/docs/web/docs/guides/how_to_contribute/known_issues.md b/docs/web/docs/guides/how_to_contribute/known_issues.md new file mode 100644 index 000000000..3acc288ef --- /dev/null +++ b/docs/web/docs/guides/how_to_contribute/known_issues.md @@ -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. + +sidebar_position: 6 +--- + +# Known issues + +While we strive to make Mephisto work its best, there are a few "perennial" issues to be aware of: + +1. Sometimes Tasks consisting of only 1 Unit don't shut themselves down upon that Unit completion. diff --git a/docs/web/docs/guides/how_to_use/form_composer/overview.md b/docs/web/docs/guides/how_to_use/form_composer/overview.md index 974b670c4..5d669b10f 100644 --- a/docs/web/docs/guides/how_to_use/form_composer/overview.md +++ b/docs/web/docs/guides/how_to_use/form_composer/overview.md @@ -15,3 +15,37 @@ You can easily generate form-based Tasks using our FormComposer task generator f You can find working demos of FormComposer in `examples/form_composer_demo` repo directory. For details on how to run these examples, refer to the demo's [README.md](https://github.com/facebookresearch/Mephisto/blob/main/examples/form_composer_demo/README.md) + +### FormComposer app UI + +Here is how FormComposer app UI looks like. + +### Just started task + +![List of tasks](./screenshots/initial_view.png) +
+
+ +### Filled form + +![List of tasks](./screenshots/in_progress_view.png) +
+
+ +### TaskReview app + +![List of tasks](./screenshots/review.png) +
+
+ +### TaskReview app. List of Units + +![TaskReview app. List of Units](./screenshots/units_list.png) +
+
+ +### TaskReview app. Unit page + +![TaskReview app. Unit page](./screenshots/unit_page.png) +
+
diff --git a/docs/web/docs/guides/how_to_use/form_composer/screenshots/in_progress_view.png b/docs/web/docs/guides/how_to_use/form_composer/screenshots/in_progress_view.png new file mode 100644 index 000000000..cb7d2338a Binary files /dev/null and b/docs/web/docs/guides/how_to_use/form_composer/screenshots/in_progress_view.png differ diff --git a/docs/web/docs/guides/how_to_use/form_composer/screenshots/initial_view.png b/docs/web/docs/guides/how_to_use/form_composer/screenshots/initial_view.png new file mode 100644 index 000000000..de7052c9f Binary files /dev/null and b/docs/web/docs/guides/how_to_use/form_composer/screenshots/initial_view.png differ diff --git a/docs/web/docs/guides/how_to_use/form_composer/screenshots/review.png b/docs/web/docs/guides/how_to_use/form_composer/screenshots/review.png new file mode 100644 index 000000000..e34163314 Binary files /dev/null and b/docs/web/docs/guides/how_to_use/form_composer/screenshots/review.png differ diff --git a/docs/web/docs/guides/how_to_use/form_composer/screenshots/unit_page.png b/docs/web/docs/guides/how_to_use/form_composer/screenshots/unit_page.png new file mode 100644 index 000000000..229c31d24 Binary files /dev/null and b/docs/web/docs/guides/how_to_use/form_composer/screenshots/unit_page.png differ diff --git a/docs/web/docs/guides/how_to_use/form_composer/screenshots/units_list.png b/docs/web/docs/guides/how_to_use/form_composer/screenshots/units_list.png new file mode 100644 index 000000000..73d44dabc Binary files /dev/null and b/docs/web/docs/guides/how_to_use/form_composer/screenshots/units_list.png differ diff --git a/docs/web/docs/guides/how_to_use/video_annotator/overview.md b/docs/web/docs/guides/how_to_use/video_annotator/overview.md index 22c50e0f7..1ab742403 100644 --- a/docs/web/docs/guides/how_to_use/video_annotator/overview.md +++ b/docs/web/docs/guides/how_to_use/video_annotator/overview.md @@ -23,13 +23,42 @@ Here is how VideoAnnotator app UI looks like. ### Just started task -![List of tasks](./screenshots/initial_view.png) +![Just started task](./screenshots/initial_view.png)

### Annotated video -![List of tasks](./screenshots/in_progress_view.png) +![Annotated video](./screenshots/in_progress_view.png)

+### TaskReview app. Collapsed + +![TaskReview app. Collapsed](./screenshots/review_collapsed.png) +
+
+ +### TaskReview app. Open segment + +![TaskReview app. Open segment](./screenshots/review_open_segment.png) +
+
+ +### TaskReview app. Open WebVTT + +![TaskReview app. Open WebVTT](./screenshots/review_open_webvtt.png) +
+
+ +### TaskReview app. List of Units + +![TaskReview app. List of Units](./screenshots/units_list.png) +
+
+ +### TaskReview app. Unit page + +![TaskReview app. Unit page](./screenshots/unit_page.png) +
+
diff --git a/docs/web/docs/guides/how_to_use/video_annotator/screenshots/review_collapsed.png b/docs/web/docs/guides/how_to_use/video_annotator/screenshots/review_collapsed.png new file mode 100644 index 000000000..effa9a188 Binary files /dev/null and b/docs/web/docs/guides/how_to_use/video_annotator/screenshots/review_collapsed.png differ diff --git a/docs/web/docs/guides/how_to_use/video_annotator/screenshots/review_open_segment.png b/docs/web/docs/guides/how_to_use/video_annotator/screenshots/review_open_segment.png new file mode 100644 index 000000000..2b2b027ff Binary files /dev/null and b/docs/web/docs/guides/how_to_use/video_annotator/screenshots/review_open_segment.png differ diff --git a/docs/web/docs/guides/how_to_use/video_annotator/screenshots/review_open_webvtt.png b/docs/web/docs/guides/how_to_use/video_annotator/screenshots/review_open_webvtt.png new file mode 100644 index 000000000..7c6e5b4fd Binary files /dev/null and b/docs/web/docs/guides/how_to_use/video_annotator/screenshots/review_open_webvtt.png differ diff --git a/docs/web/docs/guides/how_to_use/video_annotator/screenshots/unit_page.png b/docs/web/docs/guides/how_to_use/video_annotator/screenshots/unit_page.png new file mode 100644 index 000000000..3671eb73a Binary files /dev/null and b/docs/web/docs/guides/how_to_use/video_annotator/screenshots/unit_page.png differ diff --git a/docs/web/docs/guides/how_to_use/video_annotator/screenshots/units_list.png b/docs/web/docs/guides/how_to_use/video_annotator/screenshots/units_list.png new file mode 100644 index 000000000..20df70a3d Binary files /dev/null and b/docs/web/docs/guides/how_to_use/video_annotator/screenshots/units_list.png differ diff --git a/examples/video_annotator_demo/data/simple/task_data.json b/examples/video_annotator_demo/data/simple/task_data.json index a46be53ef..29878d4d4 100644 --- a/examples/video_annotator_demo/data/simple/task_data.json +++ b/examples/video_annotator_demo/data/simple/task_data.json @@ -3,7 +3,7 @@ "annotator": { "title": "Video Annotator Demo", "instruction": "
\n Please annotate everything you think is necessary.\n
\n\n\n", - "video": "https://d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.mp4", + "video": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", "segment_fields": [ { "id": "id_title", @@ -66,8 +66,8 @@ }, "annotator_metadata": { "tokens_values": { - "video_path": "https://d2zihajmogu5jn.cloudfront.net/elephantsdream/", - "video_file": "ed_hd.mp4" + "video_path": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/", + "video_file": "BigBuckBunny.mp4" } } } diff --git a/mephisto/abstractions/providers/inhouse/inhouse_unit.py b/mephisto/abstractions/providers/inhouse/inhouse_unit.py index af173cca9..56d48b4c3 100644 --- a/mephisto/abstractions/providers/inhouse/inhouse_unit.py +++ b/mephisto/abstractions/providers/inhouse/inhouse_unit.py @@ -70,7 +70,7 @@ def launch(self, task_url: str) -> None: ui_base_url = task_run.args.provider.ui_base_url # This param `id` will only be used by `getAssignmentId` from `wrap_crowd_source.js` # as any random pseudo id to pass server validation - unit_url = f"{ui_base_url}?worker_id=&id={self.assignment_id}" + unit_url = f"{ui_base_url}?worker_id=WORKER_USERNAME&id={self.assignment_id}" logger.info(f'Unit "{self.db_id}" launched: {unit_url}') return None diff --git a/mephisto/client/cli_form_composer_commands.py b/mephisto/client/cli_form_composer_commands.py index 40ea511f2..e89f4e4a7 100644 --- a/mephisto/client/cli_form_composer_commands.py +++ b/mephisto/client/cli_form_composer_commands.py @@ -66,6 +66,8 @@ def set_form_composer_env_vars(use_validation_mapping_cache: bool = True): if use_validation_mapping_cache: os.environ["VALIDATION_MAPPING_USE_CACHE"] = "true" + else: + os.environ["VALIDATION_MAPPING_USE_CACHE"] = "false" def _get_form_composer_app_path() -> str: diff --git a/mephisto/client/cli_video_annotator_commands.py b/mephisto/client/cli_video_annotator_commands.py index 2eb755406..ea9b9a9a0 100644 --- a/mephisto/client/cli_video_annotator_commands.py +++ b/mephisto/client/cli_video_annotator_commands.py @@ -51,6 +51,8 @@ def set_video_annotator_env_vars(use_validation_mapping_cache: bool = True): if use_validation_mapping_cache: os.environ["VALIDATION_MAPPING_USE_CACHE"] = "true" + else: + os.environ["VALIDATION_MAPPING_USE_CACHE"] = "false" def _get_video_annotator_app_path() -> str: diff --git a/mephisto/generators/generators_utils/config_validation/task_data_config.py b/mephisto/generators/generators_utils/config_validation/task_data_config.py index 3ae7454bf..6d7ada366 100644 --- a/mephisto/generators/generators_utils/config_validation/task_data_config.py +++ b/mephisto/generators/generators_utils/config_validation/task_data_config.py @@ -10,12 +10,11 @@ from typing import Optional from typing import Tuple -from rich import print - from mephisto.generators.generators_utils.constants import S3_URL_EXPIRATION_MINUTES_MAX from mephisto.generators.generators_utils.constants import TOKEN_END_REGEX from mephisto.generators.generators_utils.constants import TOKEN_START_REGEX from mephisto.generators.generators_utils.remote_procedures import ProcedureName +from mephisto.utils.console_writer import ConsoleWriter from .common_validation import replace_path_to_file_with_its_content from .config_validation_constants import TOKENS_VALUES_KEY from .separate_token_values_config import validate_separate_token_values_config @@ -26,6 +25,8 @@ from .utils import read_config_file from .utils import write_config_to_file +logger = ConsoleWriter() + def _extrapolate_tokens_values( text: str, @@ -305,7 +306,7 @@ def create_extrapolated_config( ) write_config_to_file(extrapolated_unit_config_data, task_data_config_path) except (ValueError, FileNotFoundError) as e: - print(f"\n[red]Could not extrapolate form configs:[/red] {e}\n") + logger.info(f"\n[red]Could not extrapolate form configs:[/red] {e}\n") exit() @@ -463,10 +464,10 @@ def verify_generator_configs( if errors: raise ValueError(make_error_message("", errors)) else: - print(f"[green]All configs are valid.[/green]") + logger.info(f"[green]All configs are valid.[/green]") except ValueError as e: - print(error_message.format(exc=e)) + logger.info(error_message.format(exc=e)) if force_exit: exit() diff --git a/mephisto/review_app/client/src/consts/review.ts b/mephisto/review_app/client/src/consts/review.ts index e0b27f2d0..c2b1f030e 100644 --- a/mephisto/review_app/client/src/consts/review.ts +++ b/mephisto/review_app/client/src/consts/review.ts @@ -46,9 +46,10 @@ export const AUDIO_TYPES_BY_EXT = { }; 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", - mov: "video/quicktime", - avi: "video/x-msvideo", }; diff --git a/mephisto/review_app/client/src/pages/TaskPage/ModalForm/ModalForm.tsx b/mephisto/review_app/client/src/pages/TaskPage/ModalForm/ModalForm.tsx index 649bbb9ae..9262ac731 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/ModalForm/ModalForm.tsx +++ b/mephisto/review_app/client/src/pages/TaskPage/ModalForm/ModalForm.tsx @@ -97,6 +97,19 @@ function ModalForm(props: ModalFormProps) { const onChangeBanWorker = (value: boolean) => { let prevFormData: FormType = Object(props.data.form); prevFormData.checkboxBanWorker = value; + + if (props.data.type === ReviewType.REJECT) { + if (value === true) { + // Open review note + prevFormData.checkboxReviewNote = true; + } else { + // Close review note only if there is no note typed already + if (!prevFormData.reviewNote) { + prevFormData.checkboxReviewNote = false; + } + } + } + props.setData({ ...props.data, form: prevFormData }); }; diff --git a/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx b/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx index c17cfb19b..cec96e49f 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx +++ b/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx @@ -356,7 +356,9 @@ function TaskPage(props: TaskPagePropsType) { } function onModalSubmit() { - setModalShow(false); + props.setErrors([]); + + let hasErrors = false; const unitIds = getUnitsIdsByApplyToNext(modalData.applyToNext); @@ -388,23 +390,35 @@ function TaskPage(props: TaskPagePropsType) { } ); } else if (modalData.type === ReviewType.REJECT) { - postUnitsReject( - () => onReviewSuccess(modalData, unitIds), - setLoading, - onError, - { - review_note: modalData.form.checkboxReviewNote - ? modalData.form.reviewNote - : null, - send_to_worker: modalData.form.checkboxReviewNoteSend, - unit_ids: unitIds, - } - ); + if (modalData.form.checkboxBanWorker && !modalData.form.reviewNote) { + hasErrors = true; + props.setErrors(["'Write Note' is required if you ban Worker"]); + } + + if (!hasErrors) { + postUnitsReject( + () => onReviewSuccess(modalData, unitIds), + setLoading, + onError, + { + review_note: modalData.form.checkboxReviewNote + ? modalData.form.reviewNote + : null, + send_to_worker: modalData.form.checkboxReviewNoteSend, + unit_ids: unitIds, + } + ); + } } - // Save current state of the modal data - updateModalState(setModalState, modalData.type, modalData); - setIframeLoaded(false); + if (!hasErrors) { + // Close modal + setModalShow(false); + + // Save current state of the modal data + updateModalState(setModalState, modalData.type, modalData); + setIframeLoaded(false); + } } function onError(errorResponse: ErrorResponseType | null) { diff --git a/packages/mephisto-task-addons/src/FormComposer/fields/Errors.js b/packages/mephisto-task-addons/src/FormComposer/fields/Errors.js index ca0ffc597..8ef8b69eb 100644 --- a/packages/mephisto-task-addons/src/FormComposer/fields/Errors.js +++ b/packages/mephisto-task-addons/src/FormComposer/fields/Errors.js @@ -6,14 +6,14 @@ import React from "react"; -export function Errors({ messages }) { +export function Errors({ className, messages }) { return ( // bootstrap classes: // - invalid-feedback <> {messages && messages.length > 0 && ( -
+
{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 && (