From ae34a8c7b7168707934638abf0a5c946de2edee3 Mon Sep 17 00:00:00 2001 From: Paul Abumov Date: Fri, 1 Dec 2023 13:30:29 -0500 Subject: [PATCH 01/24] [Form Builder] Initial example with simple config --- examples/form_composer/README.md | 30 +++ examples/form_composer/__init__.py | 0 examples/form_composer/data/data.json | 148 ++++++++++ .../hydra_configs/conf/example.yaml | 25 ++ examples/form_composer/run_task.py | 29 ++ examples/form_composer/webapp/.babelrc | 4 + .../webapp/link_mephisto_task.sh | 7 + examples/form_composer/webapp/package.json | 34 +++ examples/form_composer/webapp/src/app.jsx | 39 +++ .../webapp/src/components/core_components.jsx | 252 ++++++++++++++++++ .../form_composer/webapp/src/css/style.css | 133 +++++++++ examples/form_composer/webapp/src/main.js | 9 + .../webapp/src/static/index.html | 25 ++ .../form_composer/webapp/webpack.config.js | 48 ++++ mephisto/client/cli.py | 31 ++- 15 files changed, 810 insertions(+), 4 deletions(-) create mode 100644 examples/form_composer/README.md create mode 100644 examples/form_composer/__init__.py create mode 100644 examples/form_composer/data/data.json create mode 100644 examples/form_composer/hydra_configs/conf/example.yaml create mode 100644 examples/form_composer/run_task.py create mode 100644 examples/form_composer/webapp/.babelrc create mode 100644 examples/form_composer/webapp/link_mephisto_task.sh create mode 100644 examples/form_composer/webapp/package.json create mode 100644 examples/form_composer/webapp/src/app.jsx create mode 100644 examples/form_composer/webapp/src/components/core_components.jsx create mode 100644 examples/form_composer/webapp/src/css/style.css create mode 100644 examples/form_composer/webapp/src/main.js create mode 100644 examples/form_composer/webapp/src/static/index.html create mode 100644 examples/form_composer/webapp/webpack.config.js diff --git a/examples/form_composer/README.md b/examples/form_composer/README.md new file mode 100644 index 000000000..5f606eaca --- /dev/null +++ b/examples/form_composer/README.md @@ -0,0 +1,30 @@ +# Form Composer + +This is a Task generator that provides Tasks for simple questionnaire projects. + +--- + +### How to run + +1. In repo root, launch containers: `docker-compose -f docker/docker-compose.dev.yml up` +2. SSH into running container to run server: `docker exec -it mephisto_dc bash` +3. Inside the container run server: `cd /mephisto/examples/react_auto_composing_forms && python ./run_task.py` + +--- + +### How to configure + +All you need to do is provide Form Builder with a JSON configuration of your form fields. + +An example is found in `examples/form_composer/data/data.json` file. + +--- + +### How form is composed + +Form Builder supports several layers of hierarchy: + +1. Section +2. Fieldset +3. Fields Row +4. Field diff --git a/examples/form_composer/__init__.py b/examples/form_composer/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/form_composer/data/data.json b/examples/form_composer/data/data.json new file mode 100644 index 000000000..8387b9c41 --- /dev/null +++ b/examples/form_composer/data/data.json @@ -0,0 +1,148 @@ +[ + { + "form": { + "name": "Form example", + "instruction": "Please answer all questions to the best of your ability as part of our study.", + "sections": [ + { + "name": "About you", + "instruction": "Please introduce yourself. We would like to know more about your personal information, cultural bacground, etc.", + "fieldsets": [ + { + "name": "Personal information", + "instruction": "", + "rows": [ + { + "fields": [ + { + "class": "", + "help": "", + "icon": "", + "id": "id_name_first", + "label": "First name", + "name": "name_first", + "placeholder": "Type first name", + "required": true, + "style": {}, + "title": "First name of a person", + "type": "input", + "value": "" + }, + { + "class": "", + "help": "Optional", + "icon": "", + "id": "id_name_last", + "label": "Last name", + "name": "name_last", + "placeholder": "Type last name", + "required": true, + "style": {}, + "title": "Last name of a person", + "type": "input", + "value": "" + } + ], + "help": "Please use your legal name" + }, + { + "fields": [ + { + "class": "", + "help": "We may contact you later for additional information", + "icon": "", + "id": "id_email", + "label": "Email address", + "name": "email", + "placeholder": "user@example.com", + "required": true, + "style": {}, + "title": "Email address", + "type": "email", + "value": "" + } + ] + } + ] + }, + { + "name": "Cultural background", + "instruction": "Please tell us about your cultural affiliations and values that you use in your daily life.", + "rows": [ + { + "fields": [ + { + "class": "", + "help": "Select country of your residence", + "icon": "", + "id": "id_country", + "label": "Country", + "name": "country", + "options": [ + { + "name": "---", + "value": "" + }, + { + "name": "United States of America", + "value": "USA" + }, + { + "name": "Canada", + "value": "CAN" + } + ], + "placeholder": "", + "required": false, + "style": {}, + "title": "Country", + "type": "select", + "value": "" + }, + { + "class": "", + "help": "Select language spoken in your local community", + "icon": "", + "id": "id_language", + "label": "Language", + "name": "language", + "options": [ + { + "name": "---", + "value": "" + }, + { + "name": "English", + "value": "en" + }, + { + "name": "French", + "value": "fr" + }, + { + "name": "Spanish", + "value": "es" + } + ], + "placeholder": "", + "required": false, + "style": {}, + "title": "Language", + "type": "select", + "value": "" + } + ] + } + ], + "help": "This information will help us compose statistics" + } + ] + } + ], + "submit_button": { + "text": "Submit", + "title": "Submit form" + } + } + } +] diff --git a/examples/form_composer/hydra_configs/conf/example.yaml b/examples/form_composer/hydra_configs/conf/example.yaml new file mode 100644 index 000000000..118d2a87d --- /dev/null +++ b/examples/form_composer/hydra_configs/conf/example.yaml @@ -0,0 +1,25 @@ +#@package _global_ + +# 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. + +defaults: + - /mephisto/blueprint: static_react_task + - /mephisto/architect: local + - /mephisto/provider: mock + +mephisto: + blueprint: + data_json: ${task_dir}/data/data.json + task_source: ${task_dir}/webapp/build/bundle.js + link_task_source: false + extra_source_dir: ${task_dir}/webapp/src/static + units_per_assignment: 1 + task: + task_name: react_auto_composing_forms + task_title: "Example how to easily create simple form-based Tasks" + task_description: "In this Task, we use FormComposer feature." + task_reward: 0 + task_tags: "test,simple,form" + force_rebuild: true diff --git a/examples/form_composer/run_task.py b/examples/form_composer/run_task.py new file mode 100644 index 000000000..690fcdffe --- /dev/null +++ b/examples/form_composer/run_task.py @@ -0,0 +1,29 @@ +#!/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. + +from omegaconf import DictConfig + +from mephisto.operations.operator import Operator +from mephisto.tools.scripts import build_custom_bundle +from mephisto.tools.scripts import task_script + + +@task_script(default_config_file="example.yaml") +def main(operator: Operator, cfg: DictConfig) -> None: + task_dir = cfg.task_dir + + build_custom_bundle( + task_dir, + force_rebuild=cfg.mephisto.task.force_rebuild, + post_install_script=cfg.mephisto.task.post_install_script, + ) + + operator.launch_task_run(cfg.mephisto) + operator.wait_for_runs_then_shutdown(skip_input=True, log_rate=30) + + +if __name__ == "__main__": + main() diff --git a/examples/form_composer/webapp/.babelrc b/examples/form_composer/webapp/.babelrc new file mode 100644 index 000000000..5507f2e86 --- /dev/null +++ b/examples/form_composer/webapp/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@babel/env", "@babel/preset-react"], + "plugins": ["@babel/plugin-proposal-class-properties"] +} diff --git a/examples/form_composer/webapp/link_mephisto_task.sh b/examples/form_composer/webapp/link_mephisto_task.sh new file mode 100644 index 000000000..6936741b2 --- /dev/null +++ b/examples/form_composer/webapp/link_mephisto_task.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# 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. + +npm link mephisto-task diff --git a/examples/form_composer/webapp/package.json b/examples/form_composer/webapp/package.json new file mode 100644 index 000000000..058a0fa2e --- /dev/null +++ b/examples/form_composer/webapp/package.json @@ -0,0 +1,34 @@ +{ + "name": "react_auto_composing_forms", + "version": "1.0.0", + "description": "", + "main": "webpack.config.js", + "scripts": { + "dev": "webpack --mode development", + "dev:watch": "webpack --mode development --watch", + "test": "cypress open" + }, + "keywords": [], + "author": "", + "dependencies": { + "bootstrap": "^4.3.1", + "mephisto-task": "2.0.4", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@babel/cli": "^7.1.0", + "@babel/core": "^7.1.0", + "@babel/plugin-proposal-class-properties": "^7.1.0", + "@babel/preset-env": "^7.1.0", + "@babel/preset-react": "^7.0.0", + "babel-loader": "^8.0.2", + "css-loader": "^6.7.3", + "cypress": "^10.1.0", + "file-loader": "^6.0.0", + "style-loader": "^1.3.0", + "url-loader": "^4.1.0", + "webpack": "^5.68.0", + "webpack-cli": "^4.9.0" + } +} diff --git a/examples/form_composer/webapp/src/app.jsx b/examples/form_composer/webapp/src/app.jsx new file mode 100644 index 000000000..7f44e5ecb --- /dev/null +++ b/examples/form_composer/webapp/src/app.jsx @@ -0,0 +1,39 @@ +/* + * 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 React from "react"; +import ReactDOM from "react-dom"; +import { AutoComposingFormFrontend, LoadingScreen } from "./components/core_components.jsx"; +import { useMephistoTask, ErrorBoundary } from "mephisto-task"; + +/* ================= Application Components ================= */ + +function MainApp() { + const { + isLoading, + initialTaskData, + handleSubmit, + handleFatalError, + } = useMephistoTask(); + + if (isLoading || !initialTaskData) { + return ; + } + + return ( +
+ + + +
+ ); +} + +ReactDOM.render(, document.getElementById("app")); diff --git a/examples/form_composer/webapp/src/components/core_components.jsx b/examples/form_composer/webapp/src/components/core_components.jsx new file mode 100644 index 000000000..73148226b --- /dev/null +++ b/examples/form_composer/webapp/src/components/core_components.jsx @@ -0,0 +1,252 @@ +/* + * 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 React from "react"; + +function LoadingScreen() { + return Loading...; +} + +function Directions({ children }) { + return ( +
+
+
+

{children}

+
+
+
+ ); +} + +function FormComposer({ data, onSubmit }) { + const [form, setForm] = React.useState({}); + + let formName = data.name; + let formInstruction = data.instruction; + let formSections = data.sections; + let formSubmitButton = data.submit_button; + + function updateFormData(e, fieldName) { + setForm((prevState) => { + return {...prevState, ...{ [fieldName]: e.target.value} } + }); + } + + React.useEffect(() => { + if (formSections.length) { + const initialFormData = {}; + + formSections.map((section) => { + section.fieldsets.map((fieldset) => { + fieldset.rows.map((row) => { + row.fields.map((field) => { + initialFormData[field.name] = field.value; + }); + }); + }); + }); + + setForm(initialFormData); + } + }, [formSections]); + + return ( +
{ + e.preventDefault(); + onSubmit(form); + }} + > + {(formName || formInstruction) && ( +
+ {formName && ( +

{formName}

+ )} + + {formName && formInstruction &&
} + + {formInstruction && ( +

{formInstruction}

+ )} +
+ )} + + {formSections.map(( section, index ) => { + let sectionName = section.name; + let sectionInstruction = section.instruction; + let fieldsets = section.fieldsets; + + return ( +
+ {(sectionName || sectionInstruction) && ( +
+ {sectionName && ( +

{sectionName}

+ )} + + {sectionName && sectionInstruction &&
} + + {sectionInstruction && ( +

{sectionInstruction}

+ )} +
+ )} + + {fieldsets.map(( fieldset, index ) => { + let fieldsetName = fieldset.name; + let fieldsetInstruction = fieldset.instruction; + let rows = fieldset.rows; + + return ( +
+ {(fieldsetName || fieldsetInstruction) && ( +
+ {fieldsetName && ( +
{fieldsetName}
+ )} + + {fieldsetName && fieldsetInstruction &&
} + + {fieldsetInstruction && ( +

{fieldsetInstruction}

+ )} +
+ )} + + {rows.map(( row, index ) => { + let rowHelp = row.help; + let fields = row.fields; + + return ( +
+ {fields.map(( field, index ) => { + let fieldHelp = field.help; + + return ( +
+ + {field.icon} + + + {["input", "email", "password", "number"].includes(field.type) && ( + updateFormData(e, field.name)} + /> + )} + + {field.type === "select" && ( + + )} + + {fieldHelp && ( + + {fieldHelp} + + )} +
+ ); + })} + + {rowHelp && ( +
+ {rowHelp} +
+ )} +
+ ); + })} +
+ ); + })} +
+ ); + })} + + {formSubmitButton && (<> +
+ +
+ +
+ )} +
+ ); +} + +function AutoComposingFormFrontend({ taskData, onSubmit, onError }) { + let formData = taskData.form; + + if (!formData) { + return ( +
+ Passed form data is invalid... Recheck your task config. +
+ ); + } + + return ( +
+ +
+ ); +} + +export { LoadingScreen, AutoComposingFormFrontend }; diff --git a/examples/form_composer/webapp/src/css/style.css b/examples/form_composer/webapp/src/css/style.css new file mode 100644 index 000000000..1340ab4cb --- /dev/null +++ b/examples/form_composer/webapp/src/css/style.css @@ -0,0 +1,133 @@ +/* + * 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. + */ + + +/* --- Form --- */ +.form { + /* Variables */ + --input-bg-color: #fafafa; + + margin: 0 auto; + padding-top: 20px; + display: flex; + flex-direction: column; + justify-content: center; + max-width: 1280px; +} + +.form .form-name { + font-size: 22px; +} + +.form .form-instruction { + +} + +/* --- Section --- */ +.form .section { + +} + +.form .section .section-name { + font-size: 18px; +} + +.form .section .section-instruction { + +} + +/* --- Fieldset --- */ +.form .section .fieldset { + margin-bottom: 20px; +} + +.form .section .fieldset .fieldset-header { + background-color: #d9e0df; +} + +.form .section .fieldset .fieldset-name { + margin: 0; +} + +.form .section .fieldset .fieldset-instruction { + +} + +/* --- Row --- */ +.form .section .fieldset .row { + +} + +.form .section .fieldset .row:not(:last-child) { + margin-bottom: 20px; +} + +.form .section .fieldset .row .row-instruction { + margin-bottom: 10px; + font-size: 15px; +} + +.form .section .fieldset .row .row-help { + padding-top: 5px; + padding-left: 0; + padding-right: 0; + margin-left: 15px; + margin-right: 15px; + font-size: 12px; + font-style: italic; + color: grey; + border-top: 1px solid #ccc; +} + +/* --- Field --- */ +.form .section .fieldset .field { + margin-bottom: 10px; +} + +.form .section .fieldset .field .field-help { + font-size: 10px; +} + +.form .section .fieldset .field { + +} + +.form .section .fieldset .field label { + width: 100%; +} + +.form .section .fieldset .field.required label:after { + content: "*"; + color: red; + margin-left: 3px; +} + +.form .section .fieldset .field input { + width: 100%; +} + +.form .section .fieldset .field input, +.form .section .fieldset .field select { + background-color: var(--input-bg-color); +} + +.form .form-buttons-separator { + margin-bottom: 40px; + width: 70%; + border: 1px solid black; + opacity: 0.2; +} + +/* --- Buttons --- */ +.form .form-buttons { + margin-bottom: 40px; + display: flex; + justify-content: center; +} + +.form .form-buttons .button-submit { + +} diff --git a/examples/form_composer/webapp/src/main.js b/examples/form_composer/webapp/src/main.js new file mode 100644 index 000000000..03dd7e4fd --- /dev/null +++ b/examples/form_composer/webapp/src/main.js @@ -0,0 +1,9 @@ +/* + * 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 "bootstrap/dist/css/bootstrap.css"; +import "./app.jsx"; +import "./css/style.css"; diff --git a/examples/form_composer/webapp/src/static/index.html b/examples/form_composer/webapp/src/static/index.html new file mode 100644 index 000000000..2e4df25f8 --- /dev/null +++ b/examples/form_composer/webapp/src/static/index.html @@ -0,0 +1,25 @@ + + + + + + + + Form Composer + + + + + +
+ + + diff --git a/examples/form_composer/webapp/webpack.config.js b/examples/form_composer/webapp/webpack.config.js new file mode 100644 index 000000000..7c92f67df --- /dev/null +++ b/examples/form_composer/webapp/webpack.config.js @@ -0,0 +1,48 @@ +/* + * 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. + */ + +var path = require("path"); +var webpack = require("webpack"); + +module.exports = { + entry: "./src/main.js", + output: { + path: __dirname, + filename: "build/bundle.js", + }, + resolve: { + alias: { + react: path.resolve("./node_modules/react"), + }, + fallback: { + net: false, + dns: false, + }, + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + loader: "babel-loader", + exclude: /node_modules/, + options: { presets: ["@babel/env"] }, + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"], + }, + { + test: /\.(svg|png|jpe?g|ttf)$/, + loader: "url-loader", + options: { limit: 100000 }, + }, + { + test: /\.jpg$/, + loader: "file-loader", + }, + ], + }, +}; diff --git a/mephisto/client/cli.py b/mephisto/client/cli.py index 0a7de5dfe..b8390aa54 100644 --- a/mephisto/client/cli.py +++ b/mephisto/client/cli.py @@ -8,6 +8,9 @@ from typing import List from flask.cli import pass_script_info +from omegaconf import DictConfig + +from mephisto.tools.scripts import task_script from rich import print from mephisto.client.cli_commands import get_wut_arguments @@ -320,10 +323,7 @@ def metrics_cli(args): shutdown_grafana_server() -@cli.command( - "review_app", - cls=RichCommand, -) +@cli.command("review_app", cls=RichCommand) @click.option("-h", "--host", type=(str), default="127.0.0.1") @click.option("-p", "--port", type=(int), default=5000) @click.option("-d", "--debug", type=(bool), default=None) @@ -408,5 +408,28 @@ def review_app( ) +@cli.command("form_composer", cls=RichCommand) +@click.option("-c", "--config-path", type=(str)) +@click.option("-f", "--force-rebuild", type=(bool), default=False) +def form_composer(config_path, force_rebuild): + # TODO [form-composer-example]: This is just an example (work in progress) + from mephisto.operations.operator import Operator + + @task_script(default_config_file=config_path) + def main(operator: Operator, cfg: DictConfig) -> None: + task_dir = cfg.task_dir + + build_custom_bundle( + task_dir, + force_rebuild=force_rebuild or cfg.mephisto.task.force_rebuild, + post_install_script=cfg.mephisto.task.post_install_script, + ) + + operator.launch_task_run(cfg.mephisto) + operator.wait_for_runs_then_shutdown(skip_input=True, log_rate=30) + + main() + + if __name__ == "__main__": cli() From 2b1e9b049a406bd8e5a1d10539cd85cd99f69171 Mon Sep 17 00:00:00 2001 From: Paul Abumov Date: Tue, 5 Dec 2023 12:28:42 -0500 Subject: [PATCH 02/24] [Form Builder] Added form fields, moved review_app directory --- .../README.md | 2 +- .../__init__.py | 0 .../data/data.json | 105 ++++++++++++++++++ .../hydra_configs/conf/example.yaml | 0 .../run_task.py | 0 .../webapp/.babelrc | 0 .../webapp/link_mephisto_task.sh | 0 .../webapp/package.json | 0 .../webapp/src/app.jsx | 0 .../components/FormComposer/FormComposer.js} | 91 +++++---------- .../FormComposer/fields/CheckboxField.js | 37 ++++++ .../FormComposer/fields/FileField.js | 29 +++++ .../FormComposer/fields/InputField.js | 24 ++++ .../FormComposer/fields/RadioField.js | 37 ++++++ .../FormComposer/fields/SelectField.js | 34 ++++++ .../FormComposer/fields/TextareaField.js | 23 ++++ .../webapp/src/components/core_components.jsx | 44 ++++++++ .../webapp/src/css/style.css | 0 .../webapp/src/main.js | 0 .../webapp/src/static/index.html | 0 .../webapp/webpack.config.js | 0 mephisto/client/cli.py | 4 +- mephisto/{client => }/review_app/README.md | 2 +- mephisto/{client => }/review_app/__init__.py | 0 .../{client => }/review_app/client/README.md | 0 .../review_app/client/package-lock.json | 0 .../review_app/client/package.json | 0 .../review_app/client/public/index.html | 0 .../review_app/client/src/App/App.css | 0 .../review_app/client/src/App/App.tsx | 0 .../ClosableErrorAlert/ClosableErrorAlert.css | 0 .../ClosableErrorAlert/ClosableErrorAlert.tsx | 0 .../client/src/components/Errors/Errors.css | 0 .../client/src/components/Errors/Errors.tsx | 0 .../review_app/client/src/consts/http.ts | 0 .../review_app/client/src/consts/review.ts | 0 .../review_app/client/src/default.css | 0 .../review_app/client/src/index.tsx | 0 .../client/src/pages/HomePage/HomePage.css | 0 .../client/src/pages/HomePage/HomePage.tsx | 0 .../pages/TaskPage/ModalForm/ModalForm.css | 0 .../pages/TaskPage/ModalForm/ModalForm.tsx | 0 .../TaskPage/ReviewModal/ReviewModal.css | 0 .../TaskPage/ReviewModal/ReviewModal.tsx | 0 .../pages/TaskPage/TaskHeader/TaskHeader.css | 0 .../pages/TaskPage/TaskHeader/TaskHeader.tsx | 0 .../client/src/pages/TaskPage/TaskPage.css | 0 .../client/src/pages/TaskPage/TaskPage.tsx | 0 .../client/src/pages/TaskPage/helpers.ts | 0 .../client/src/pages/TaskPage/modalData.tsx | 0 .../TasksPage/TasksHeader/TasksHeader.css | 0 .../TasksPage/TasksHeader/TasksHeader.tsx | 0 .../client/src/pages/TasksPage/TasksPage.css | 0 .../client/src/pages/TasksPage/TasksPage.tsx | 0 .../client/src/requests/generateURL.tsx | 0 .../client/src/requests/makeRequest.ts | 0 .../client/src/requests/mockResponses.ts | 0 .../client/src/requests/qualifications.ts | 0 .../review_app/client/src/requests/stats.ts | 0 .../review_app/client/src/requests/tasks.ts | 0 .../review_app/client/src/requests/units.ts | 0 .../review_app/client/src/requests/workers.ts | 0 .../client/src/static/images/logo.svg | 0 .../client/src/types/qualifications.d.ts | 0 .../review_app/client/src/types/requests.d.ts | 0 .../client/src/types/reviewModal.d.ts | 0 .../review_app/client/src/types/static.d.ts | 0 .../review_app/client/src/types/tasks.d.ts | 0 .../review_app/client/src/types/units.d.ts | 0 .../review_app/client/src/types/workers.d.ts | 0 .../review_app/client/src/urls.ts | 0 .../review_app/client/tsconfig.json | 0 .../{client => }/review_app/server/README.md | 0 .../review_app/server/__init__.py | 2 +- .../review_app/server/api/__init__.py | 0 .../review_app/server/api/views/__init__.py | 0 .../review_app/server/api/views/home_view.py | 0 .../api/views/qualification_workers_view.py | 0 .../server/api/views/qualifications_view.py | 0 .../server/api/views/qualify_worker_view.py | 0 .../review_app/server/api/views/stats_view.py | 0 .../views/task_export_results_json_view.py | 0 .../api/views/task_export_results_view.py | 2 +- .../review_app/server/api/views/task_view.py | 0 .../review_app/server/api/views/tasks_view.py | 2 +- .../api/views/tasks_worker_units_view.py | 2 +- .../api/views/unit_review_bundle_view.py | 0 .../server/api/views/unit_review_html_view.py | 0 .../server/api/views/units_approve_view.py | 0 .../server/api/views/units_details_view.py | 0 .../server/api/views/units_reject_view.py | 0 .../api/views/units_soft_reject_view.py | 0 .../review_app/server/api/views/units_view.py | 0 .../server/api/views/worker_block_view.py | 0 .../review_app/server/db_queries.py | 0 .../review_app/server/settings/__init__.py | 0 .../review_app/server/settings/base.py | 0 .../review_app/server/static/index.html | 0 .../{client => }/review_app/server/urls.py | 0 99 files changed, 367 insertions(+), 73 deletions(-) rename examples/{form_composer => react_auto_composing_forms}/README.md (88%) rename examples/{form_composer => react_auto_composing_forms}/__init__.py (100%) rename examples/{form_composer => react_auto_composing_forms}/data/data.json (58%) rename examples/{form_composer => react_auto_composing_forms}/hydra_configs/conf/example.yaml (100%) rename examples/{form_composer => react_auto_composing_forms}/run_task.py (100%) rename examples/{form_composer => react_auto_composing_forms}/webapp/.babelrc (100%) rename examples/{form_composer => react_auto_composing_forms}/webapp/link_mephisto_task.sh (100%) rename examples/{form_composer => react_auto_composing_forms}/webapp/package.json (100%) rename examples/{form_composer => react_auto_composing_forms}/webapp/src/app.jsx (100%) rename examples/{form_composer/webapp/src/components/core_components.jsx => react_auto_composing_forms/webapp/src/components/FormComposer/FormComposer.js} (70%) create mode 100644 examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/CheckboxField.js create mode 100644 examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/FileField.js create mode 100644 examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/InputField.js create mode 100644 examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/RadioField.js create mode 100644 examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/SelectField.js create mode 100644 examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/TextareaField.js create mode 100644 examples/react_auto_composing_forms/webapp/src/components/core_components.jsx rename examples/{form_composer => react_auto_composing_forms}/webapp/src/css/style.css (100%) rename examples/{form_composer => react_auto_composing_forms}/webapp/src/main.js (100%) rename examples/{form_composer => react_auto_composing_forms}/webapp/src/static/index.html (100%) rename examples/{form_composer => react_auto_composing_forms}/webapp/webpack.config.js (100%) rename mephisto/{client => }/review_app/README.md (96%) rename mephisto/{client => }/review_app/__init__.py (100%) rename mephisto/{client => }/review_app/client/README.md (100%) rename mephisto/{client => }/review_app/client/package-lock.json (100%) rename mephisto/{client => }/review_app/client/package.json (100%) rename mephisto/{client => }/review_app/client/public/index.html (100%) rename mephisto/{client => }/review_app/client/src/App/App.css (100%) rename mephisto/{client => }/review_app/client/src/App/App.tsx (100%) rename mephisto/{client => }/review_app/client/src/components/ClosableErrorAlert/ClosableErrorAlert.css (100%) rename mephisto/{client => }/review_app/client/src/components/ClosableErrorAlert/ClosableErrorAlert.tsx (100%) rename mephisto/{client => }/review_app/client/src/components/Errors/Errors.css (100%) rename mephisto/{client => }/review_app/client/src/components/Errors/Errors.tsx (100%) rename mephisto/{client => }/review_app/client/src/consts/http.ts (100%) rename mephisto/{client => }/review_app/client/src/consts/review.ts (100%) rename mephisto/{client => }/review_app/client/src/default.css (100%) rename mephisto/{client => }/review_app/client/src/index.tsx (100%) rename mephisto/{client => }/review_app/client/src/pages/HomePage/HomePage.css (100%) rename mephisto/{client => }/review_app/client/src/pages/HomePage/HomePage.tsx (100%) rename mephisto/{client => }/review_app/client/src/pages/TaskPage/ModalForm/ModalForm.css (100%) rename mephisto/{client => }/review_app/client/src/pages/TaskPage/ModalForm/ModalForm.tsx (100%) rename mephisto/{client => }/review_app/client/src/pages/TaskPage/ReviewModal/ReviewModal.css (100%) rename mephisto/{client => }/review_app/client/src/pages/TaskPage/ReviewModal/ReviewModal.tsx (100%) rename mephisto/{client => }/review_app/client/src/pages/TaskPage/TaskHeader/TaskHeader.css (100%) rename mephisto/{client => }/review_app/client/src/pages/TaskPage/TaskHeader/TaskHeader.tsx (100%) rename mephisto/{client => }/review_app/client/src/pages/TaskPage/TaskPage.css (100%) rename mephisto/{client => }/review_app/client/src/pages/TaskPage/TaskPage.tsx (100%) rename mephisto/{client => }/review_app/client/src/pages/TaskPage/helpers.ts (100%) rename mephisto/{client => }/review_app/client/src/pages/TaskPage/modalData.tsx (100%) rename mephisto/{client => }/review_app/client/src/pages/TasksPage/TasksHeader/TasksHeader.css (100%) rename mephisto/{client => }/review_app/client/src/pages/TasksPage/TasksHeader/TasksHeader.tsx (100%) rename mephisto/{client => }/review_app/client/src/pages/TasksPage/TasksPage.css (100%) rename mephisto/{client => }/review_app/client/src/pages/TasksPage/TasksPage.tsx (100%) rename mephisto/{client => }/review_app/client/src/requests/generateURL.tsx (100%) rename mephisto/{client => }/review_app/client/src/requests/makeRequest.ts (100%) rename mephisto/{client => }/review_app/client/src/requests/mockResponses.ts (100%) rename mephisto/{client => }/review_app/client/src/requests/qualifications.ts (100%) rename mephisto/{client => }/review_app/client/src/requests/stats.ts (100%) rename mephisto/{client => }/review_app/client/src/requests/tasks.ts (100%) rename mephisto/{client => }/review_app/client/src/requests/units.ts (100%) rename mephisto/{client => }/review_app/client/src/requests/workers.ts (100%) rename mephisto/{client => }/review_app/client/src/static/images/logo.svg (100%) rename mephisto/{client => }/review_app/client/src/types/qualifications.d.ts (100%) rename mephisto/{client => }/review_app/client/src/types/requests.d.ts (100%) rename mephisto/{client => }/review_app/client/src/types/reviewModal.d.ts (100%) rename mephisto/{client => }/review_app/client/src/types/static.d.ts (100%) rename mephisto/{client => }/review_app/client/src/types/tasks.d.ts (100%) rename mephisto/{client => }/review_app/client/src/types/units.d.ts (100%) rename mephisto/{client => }/review_app/client/src/types/workers.d.ts (100%) rename mephisto/{client => }/review_app/client/src/urls.ts (100%) rename mephisto/{client => }/review_app/client/tsconfig.json (100%) rename mephisto/{client => }/review_app/server/README.md (100%) rename mephisto/{client => }/review_app/server/__init__.py (98%) rename mephisto/{client => }/review_app/server/api/__init__.py (100%) rename mephisto/{client => }/review_app/server/api/views/__init__.py (100%) rename mephisto/{client => }/review_app/server/api/views/home_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/qualification_workers_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/qualifications_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/qualify_worker_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/stats_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/task_export_results_json_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/task_export_results_view.py (97%) rename mephisto/{client => }/review_app/server/api/views/task_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/tasks_view.py (96%) rename mephisto/{client => }/review_app/server/api/views/tasks_worker_units_view.py (95%) rename mephisto/{client => }/review_app/server/api/views/unit_review_bundle_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/unit_review_html_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/units_approve_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/units_details_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/units_reject_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/units_soft_reject_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/units_view.py (100%) rename mephisto/{client => }/review_app/server/api/views/worker_block_view.py (100%) rename mephisto/{client => }/review_app/server/db_queries.py (100%) rename mephisto/{client => }/review_app/server/settings/__init__.py (100%) rename mephisto/{client => }/review_app/server/settings/base.py (100%) rename mephisto/{client => }/review_app/server/static/index.html (100%) rename mephisto/{client => }/review_app/server/urls.py (100%) diff --git a/examples/form_composer/README.md b/examples/react_auto_composing_forms/README.md similarity index 88% rename from examples/form_composer/README.md rename to examples/react_auto_composing_forms/README.md index 5f606eaca..d8bd47799 100644 --- a/examples/form_composer/README.md +++ b/examples/react_auto_composing_forms/README.md @@ -16,7 +16,7 @@ This is a Task generator that provides Tasks for simple questionnaire projects. All you need to do is provide Form Builder with a JSON configuration of your form fields. -An example is found in `examples/form_composer/data/data.json` file. +An example is found in `examples/react_auto_composing_forms/data/data.json` file. --- diff --git a/examples/form_composer/__init__.py b/examples/react_auto_composing_forms/__init__.py similarity index 100% rename from examples/form_composer/__init__.py rename to examples/react_auto_composing_forms/__init__.py diff --git a/examples/form_composer/data/data.json b/examples/react_auto_composing_forms/data/data.json similarity index 58% rename from examples/form_composer/data/data.json rename to examples/react_auto_composing_forms/data/data.json index 8387b9c41..468bbfb8b 100644 --- a/examples/form_composer/data/data.json +++ b/examples/react_auto_composing_forms/data/data.json @@ -135,6 +135,111 @@ } ], "help": "This information will help us compose statistics" + }, + { + "name": "Additional information", + "instruction": "Optional details about you. You can tell us everything you want.", + "rows": [ + { + "fields": [ + { + "class": "", + "help": "", + "icon": "", + "id": "id_bio", + "label": "Biography", + "name": "bio", + "placeholder": "", + "required": false, + "style": {}, + "title": "Bio", + "type": "textarea", + "value": "" + } + ] + }, + { + "fields": [ + { + "checked": false, + "class": "", + "help": "", + "icon": "", + "id": "id_marital_status", + "label": "Marital status", + "name": "marital_status", + "options": [ + { + "checked": false, + "label": "Married" + } + ], + "required": false, + "style": {}, + "title": "Marital status", + "type": "checkbox", + "value": "married" + } + ] + }, + { + "fields": [ + { + "class": "", + "help": "", + "icon": "", + "id": "id_kids", + "label": "How many children do you have?", + "name": "kids", + "options": [ + { + "checked": false, + "label": "None", + "value": "0" + }, + { + "checked": false, + "label": "One", + "value": "1" + }, + { + "checked": true, + "label": "Two", + "value": "2" + }, + { + "checked": false, + "label": "Three or more", + "value": ">=3" + } + ], + "required": false, + "style": {}, + "title": "How many children do you have?", + "type": "radio" + } + ] + }, + { + "fields": [ + { + "class": "", + "help": "", + "icon": "", + "id": "id_avatar", + "label": "Avatar", + "name": "avatar", + "placeholder": "Select a file", + "required": false, + "style": {}, + "title": "Avatar", + "type": "file", + "value": "" + } + ] + } + ], + "help": "Interesting details about your personality" } ] } diff --git a/examples/form_composer/hydra_configs/conf/example.yaml b/examples/react_auto_composing_forms/hydra_configs/conf/example.yaml similarity index 100% rename from examples/form_composer/hydra_configs/conf/example.yaml rename to examples/react_auto_composing_forms/hydra_configs/conf/example.yaml diff --git a/examples/form_composer/run_task.py b/examples/react_auto_composing_forms/run_task.py similarity index 100% rename from examples/form_composer/run_task.py rename to examples/react_auto_composing_forms/run_task.py diff --git a/examples/form_composer/webapp/.babelrc b/examples/react_auto_composing_forms/webapp/.babelrc similarity index 100% rename from examples/form_composer/webapp/.babelrc rename to examples/react_auto_composing_forms/webapp/.babelrc diff --git a/examples/form_composer/webapp/link_mephisto_task.sh b/examples/react_auto_composing_forms/webapp/link_mephisto_task.sh similarity index 100% rename from examples/form_composer/webapp/link_mephisto_task.sh rename to examples/react_auto_composing_forms/webapp/link_mephisto_task.sh diff --git a/examples/form_composer/webapp/package.json b/examples/react_auto_composing_forms/webapp/package.json similarity index 100% rename from examples/form_composer/webapp/package.json rename to examples/react_auto_composing_forms/webapp/package.json diff --git a/examples/form_composer/webapp/src/app.jsx b/examples/react_auto_composing_forms/webapp/src/app.jsx similarity index 100% rename from examples/form_composer/webapp/src/app.jsx rename to examples/react_auto_composing_forms/webapp/src/app.jsx diff --git a/examples/form_composer/webapp/src/components/core_components.jsx b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/FormComposer.js similarity index 70% rename from examples/form_composer/webapp/src/components/core_components.jsx rename to examples/react_auto_composing_forms/webapp/src/components/FormComposer/FormComposer.js index 73148226b..1a5c4a263 100644 --- a/examples/form_composer/webapp/src/components/core_components.jsx +++ b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/FormComposer.js @@ -5,25 +5,16 @@ */ import React from "react"; +import { CheckboxField } from "./fields/CheckboxField"; +import { FileField } from "./fields/FileField"; +import { InputField } from "./fields/InputField"; +import { RadioField } from "./fields/RadioField"; +import { SelectField } from "./fields/SelectField"; +import { TextareaField } from "./fields/TextareaField"; -function LoadingScreen() { - return Loading...; -} - -function Directions({ children }) { - return ( -
-
-
-

{children}

-
-
-
- ); -} function FormComposer({ data, onSubmit }) { - const [form, setForm] = React.useState({}); + const [form, setForm] = React.useState({}); let formName = data.name; let formInstruction = data.instruction; @@ -155,39 +146,27 @@ function FormComposer({ data, onSubmit }) { {["input", "email", "password", "number"].includes(field.type) && ( - updateFormData(e, field.name)} - /> + + )} + + {field.type === "textarea" && ( + + )} + + {field.type === "checkbox" && ( + + )} + + {field.type === "radio" && ( + )} {field.type === "select" && ( - + + )} + + {field.type === "file" && ( + )} {fieldHelp && ( @@ -231,22 +210,4 @@ function FormComposer({ data, onSubmit }) { ); } -function AutoComposingFormFrontend({ taskData, onSubmit, onError }) { - let formData = taskData.form; - - if (!formData) { - return ( -
- Passed form data is invalid... Recheck your task config. -
- ); - } - - return ( -
- -
- ); -} - -export { LoadingScreen, AutoComposingFormFrontend }; +export { FormComposer }; diff --git a/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/CheckboxField.js b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/CheckboxField.js new file mode 100644 index 000000000..f49f69f15 --- /dev/null +++ b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/CheckboxField.js @@ -0,0 +1,37 @@ +/* + * 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 React from "react"; + +function CheckboxField({ field, updateFormData }) { + return ( + field.options.map(( option, index ) => { + return ( +
+ updateFormData(e, field.name)} + /> + +
+ ); + }) + ); +} + +export { CheckboxField }; diff --git a/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/FileField.js b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/FileField.js new file mode 100644 index 000000000..8c6f076a6 --- /dev/null +++ b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/FileField.js @@ -0,0 +1,29 @@ +/* + * 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 React from "react"; + +function FileField({ field, updateFormData }) { + return ( +
+ updateFormData(e, field.name)} + /> + +
+ ); +} + +export { FileField }; diff --git a/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/InputField.js b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/InputField.js new file mode 100644 index 000000000..fee221e47 --- /dev/null +++ b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/InputField.js @@ -0,0 +1,24 @@ +/* + * 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 React from "react"; + +function InputField({ field, updateFormData }) { + return ( + updateFormData(e, field.name)} + /> + ); +} + +export { InputField }; diff --git a/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/RadioField.js b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/RadioField.js new file mode 100644 index 000000000..6611983b5 --- /dev/null +++ b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/RadioField.js @@ -0,0 +1,37 @@ +/* + * 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 React from "react"; + +function RadioField({ field, updateFormData }) { + return ( + field.options.map(( option, index ) => { + return ( +
+ updateFormData(e, field.name)} + /> + +
+ ); + }) + ); +} + +export { RadioField }; diff --git a/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/SelectField.js b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/SelectField.js new file mode 100644 index 000000000..a1d82d342 --- /dev/null +++ b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/SelectField.js @@ -0,0 +1,34 @@ +/* + * 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 React from "react"; + +function SelectField({ field, updateFormData }) { + return ( + + ); +} + +export { SelectField }; diff --git a/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/TextareaField.js b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/TextareaField.js new file mode 100644 index 000000000..f866c8a4c --- /dev/null +++ b/examples/react_auto_composing_forms/webapp/src/components/FormComposer/fields/TextareaField.js @@ -0,0 +1,23 @@ +/* + * 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 React from "react"; + +function TextareaField({ field, updateFormData }) { + return ( +