diff --git a/docker/docker-compose.dev.vscode.yml b/docker/docker-compose.dev.vscode.yml index f421ac7bf..46896bfd8 100644 --- a/docker/docker-compose.dev.vscode.yml +++ b/docker/docker-compose.dev.vscode.yml @@ -2,8 +2,6 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -version: "3" - services: mephisto_dc_vscode: container_name: mephisto_dc_vscode diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 5b2521dec..ac500dc27 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -2,8 +2,6 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -version: "3" - services: mephisto_dc: container_name: mephisto_dc diff --git a/docs/web/docs/guides/how_to_use/review_app/overview.md b/docs/web/docs/guides/how_to_use/review_app/overview.md index a5d036420..64204daf0 100644 --- a/docs/web/docs/guides/how_to_use/review_app/overview.md +++ b/docs/web/docs/guides/how_to_use/review_app/overview.md @@ -69,3 +69,9 @@ _Note that a custom view of Task results is included (at the bottom) only if you ![Task statistics](./screenshots/task_stats.png)

+ +### Task worker opinions + +![Task statistics](./screenshots/task_worker_opinions.png) +
+
diff --git a/docs/web/docs/guides/how_to_use/review_app/screenshots/task_worker_opinions.png b/docs/web/docs/guides/how_to_use/review_app/screenshots/task_worker_opinions.png new file mode 100644 index 000000000..389393987 Binary files /dev/null and b/docs/web/docs/guides/how_to_use/review_app/screenshots/task_worker_opinions.png differ diff --git a/docs/web/docs/guides/how_to_use/review_app/screenshots/tasks_list.png b/docs/web/docs/guides/how_to_use/review_app/screenshots/tasks_list.png index f50986f2c..7ef8b16ae 100644 Binary files a/docs/web/docs/guides/how_to_use/review_app/screenshots/tasks_list.png and b/docs/web/docs/guides/how_to_use/review_app/screenshots/tasks_list.png differ diff --git a/docs/web/docs/guides/how_to_use/worker_experience/feedback.md b/docs/web/docs/guides/how_to_use/worker_experience/feedback.md deleted file mode 100644 index 98502d603..000000000 --- a/docs/web/docs/guides/how_to_use/worker_experience/feedback.md +++ /dev/null @@ -1,104 +0,0 @@ ---- - -# 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: 3 ---- - -# Adding Feedback -To allow for greater communication between workers and researchers it is recommended to use the Feedback component. - -### How to add Feedback: -1. Add the Feedback component to your task's react webapp code and define questions. -2. Workers submit feedback related to the feedback questions. -3. **Once the task is shut down**, the researcher can review the feedback for the task. - -### Feedback Code Snippet: -```jsx -import { Feedback } from "mephisto-worker-addons"; - -return( -
- - -
-) -``` -To learn more about the props in the Feedback component you can check out its [readme](https://github.com/facebookresearch/Mephisto/blob/main/packages/mephisto-worker-addons/README.md#documentation-1). - -Image of feedback component: - -![](/feedback_component.png) - -### Reviewing the feedback -To review feedback run `mephisto scripts local_db review_feedback` in the terminal and follow the prompts. - -Example output: -```bash -╔═════════════════════════════════════════════════════════════════════════════════════════════╗ -║ Feedback Review ║ -╚═════════════════════════════════════════════════════════════════════════════════════════════╝ - - - Task Names: - - • react-static-task-with-tips - - -Enter the name of the task that you want to review the tips of: react-static-task-with-tips - - - Questions - - 0 Were you satisfied with this task? - 1 What is your favorite part of this task? - -If you want to filter feedback by a question, then enter the question number to filter on. -If you want to see feedback to all questions, enter "-1" (Default: -1) [-1/0/1]: -1 -Do you want to filter out toxic comments? (Default: n) [y/n]: n -Do you want to see (r)eviewed or (u)nreviewed feedback? (Default: u) [r/u]: u - - Unreviewed Feedback 1 of 2 From Agent 4 -╭──────────────────┬──────────────────────────────────────────────────────────────────────────╮ -│ Property │ Value │ -├──────────────────┼──────────────────────────────────────────────────────────────────────────┤ -│ Id │ ee1679af-308c-4705-bb22-054b1406e043 │ -├──────────────────┼──────────────────────────────────────────────────────────────────────────┤ -│ Question │ What is your favorite part of this task? │ -├──────────────────┼──────────────────────────────────────────────────────────────────────────┤ -│ Text │ The green button is vibrant! │ -├──────────────────┼──────────────────────────────────────────────────────────────────────────┤ -│ Toxicity │ 0.0012170307 │ -╰──────────────────┴──────────────────────────────────────────────────────────────────────────╯ - -Do you want to mark this feedback as reviewed? (Default: y) [y/n]: y - -Marked the feedback as reviewed! - - Unreviewed Feedback 2 of 2 From Agent 4 -╭──────────────────┬──────────────────────────────────────────────────────────────────────────╮ -│ Property │ Value │ -├──────────────────┼──────────────────────────────────────────────────────────────────────────┤ -│ Id │ 373a1776-9ef4-4b4a-aba2-f2829ce3cd21 │ -├──────────────────┼──────────────────────────────────────────────────────────────────────────┤ -│ Question │ Were you satisfied with this task? │ -├──────────────────┼──────────────────────────────────────────────────────────────────────────┤ -│ Text │ Yeah, it was easy and simple to complete. │ -├──────────────────┼──────────────────────────────────────────────────────────────────────────┤ -│ Toxicity │ 0.0006023369 │ -╰──────────────────┴──────────────────────────────────────────────────────────────────────────╯ - -Do you want to mark this feedback as reviewed? (Default: y) [y/n]: n - -Did not mark the feedback as reviewed! - -You went through all the unreviewed feedback! - -``` diff --git a/docs/web/docs/guides/how_to_use/worker_experience/mephisto-task-addons.md b/docs/web/docs/guides/how_to_use/worker_experience/mephisto-task-addons.md new file mode 100644 index 000000000..6a137d920 --- /dev/null +++ b/docs/web/docs/guides/how_to_use/worker_experience/mephisto-task-addons.md @@ -0,0 +1,50 @@ +--- + +# 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: 1 +--- + +# Mephisto Task Addons + +## Overview + +The `mephisto-task-addons` package provides: +- `WorkerOpinion` widget: collect workers' feedback for each completed unit + +## Usage + +1. Add `mephisto-task-addons` library to your webpack config: +```js +// Specifies location of your packages (e.g. `../../dir`) +var PATH_TO_PACKAGES = "" + +module.exports = { + ... + resolve: { + alias: { + ... + "mephisto-task-addons": path.resolve( + __dirname, + `${PATH_TO_PACKAGES}/packages/mephisto-task-addons` + ), + } + } +}; +``` + +2. Import desired widgets from `mephisto-task-addons` in your code like so: + +```jsx +import { WorkerOpinion } from "mephisto-task-addons"; +... + +``` diff --git a/docs/web/docs/guides/how_to_use/worker_experience/mephisto-worker-addons.md b/docs/web/docs/guides/how_to_use/worker_experience/mephisto-worker-addons.md deleted file mode 100644 index 85cb5f5b2..000000000 --- a/docs/web/docs/guides/how_to_use/worker_experience/mephisto-worker-addons.md +++ /dev/null @@ -1,21 +0,0 @@ ---- - -# 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: 1 ---- - -# Mephisto Worker Addons - -## Overview -The `mephisto-worker-addons` package provides the tips and feedback components to allow for collection of tips and feedback from workers. - -This is useful if you want to improve the worker to worker and worker to researcher experience on your task. - -## Installation -Make sure to install the `mephisto-worker-addons` library as this is where the components will come from. -```bash -npm install mephisto-worker-addons -``` diff --git a/docs/web/docs/guides/how_to_use/worker_experience/screenshots/worker_opinion_widget.png b/docs/web/docs/guides/how_to_use/worker_experience/screenshots/worker_opinion_widget.png new file mode 100644 index 000000000..a41ea7611 Binary files /dev/null and b/docs/web/docs/guides/how_to_use/worker_experience/screenshots/worker_opinion_widget.png differ diff --git a/docs/web/docs/guides/how_to_use/worker_experience/tips.md b/docs/web/docs/guides/how_to_use/worker_experience/tips.md deleted file mode 100644 index 657e51cf9..000000000 --- a/docs/web/docs/guides/how_to_use/worker_experience/tips.md +++ /dev/null @@ -1,94 +0,0 @@ ---- - -# 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: 2 ---- - -# Adding Tips -To allow for greater communication between workers and workers and it is recommended to use the Tips component. - -### How to add Tips: -1. Add the Tips component to your task's react webapp code -2. Workers submit tips related to the task that they are working on. -3. **Once the task is shut down**, the researcher can review the submitted tips to either accept/reject them -4. The accepted tips are displayed by the Tips component when the task is launched again. - -### Tips Code Snippet: -```jsx -import { Tips } from "mephisto-worker-addons"; - -return( -
- - -
-) -``` - -To learn more about the props in the Tips component you can check out its [readme](https://github.com/facebookresearch/Mephisto/blob/main/packages/mephisto-worker-addons/README.md#documentation). - -How the component looks like: - -![](/tips_component.png) - -### Reviewing the worker submitted tips -To review and accept/reject tips run `mephisto scripts local_db review_tips` in the terminal and follow the prompts. - -Example output: -```bash -╔════════════════════════════════════════════════════════════════════════════════════════════╗ -║ Tips Review ║ -╚════════════════════════════════════════════════════════════════════════════════════════════╝ - - - Task Names: - - • react-static-task-with-tips - - -Enter the name of the task that you want to review the tips of: react-static-task-with-tips - - Tip 1 of 2 -╭────────────────────────┬───────────────────────────────────────────────────────────────────╮ -│ Property │ Value │ -├────────────────────────┼───────────────────────────────────────────────────────────────────┤ -│ Tip Id │ a1a2f011-a7e9-465a-a8e0-9c079b081075 │ -├────────────────────────┼───────────────────────────────────────────────────────────────────┤ -│ Tip Header │ 🎉 This is my first tip │ -├────────────────────────┼───────────────────────────────────────────────────────────────────┤ -│ Tip Text │ This is my tip body │ -╰────────────────────────┴───────────────────────────────────────────────────────────────────╯ - -Do you want to (a)ccept, (r)eject, or (s)kip this tip? (Default: s) [a/r/s]: a - -Tip Accepted - -How much would you like to bonus the tip submitter? (Default: 0.0): 5 - -What reason would you like to give the worker for this tip? NOTE: This will be shared with -the worker.(Default: Thank you for submitting a tip!): - -Bonus Successfully Paid! - - Tip 2 of 2 -╭────────────────────────┬────────────────────────────────────────────────────────────────────╮ -│ Property │ Value │ -├────────────────────────┼────────────────────────────────────────────────────────────────────┤ -│ Tip Id │ 8c33b8a3-5e7f-4156-8509-8b7b0fd2a347 │ -├────────────────────────┼────────────────────────────────────────────────────────────────────┤ -│ Tip Header │ Directions │ -├────────────────────────┼────────────────────────────────────────────────────────────────────┤ -│ Tip Text │ Make sure to follow the directions! │ -╰────────────────────────┴────────────────────────────────────────────────────────────────────╯ - -Do you want to (a)ccept, (r)eject, or (s)kip this tip? (Default: s) [a/r/s]: r - -Tip Rejected - -There are no more tips to review -``` - -If you want to remove a tip that you accepted, then you can run `mephisto scripts local_db remove_tip` in the terminal and follow the prompts. diff --git a/docs/web/docs/guides/how_to_use/worker_experience/worker_opinion.md b/docs/web/docs/guides/how_to_use/worker_experience/worker_opinion.md new file mode 100644 index 000000000..b6388c365 --- /dev/null +++ b/docs/web/docs/guides/how_to_use/worker_experience/worker_opinion.md @@ -0,0 +1,78 @@ +--- + +# 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: 2 +--- + +# WorkerOpinion widget + +Workers can leave their feedback about a Task if you add a `WorkerOpinion` feature to it. + +WorkerOpinion widget currently may contain a set of textarea form fields, and a multi-file attachment form field. + + +## How to enable `WorkerOpinion` + +1. Add `mephisto-task-addons` library to your webpack config +2. Import `WorkerOpinion` component to your Task's React application code and define questions. + +### 1. Webpack config + +```js +// Specifies location of your packages (e.g. `../../dir`) +var PATH_TO_PACKAGES = "" + +module.exports = { + ... + resolve: { + alias: { + ... + "mephisto-task-addons": path.resolve( + __dirname, + `${PATH_TO_PACKAGES}/packages/mephisto-task-addons` + ), + } + } +}; +``` + +### 2. `WorkerOpinion` component + +```jsx +import { WorkerOpinion } from "mephisto-task-addons"; +... +return( +
+ +
+) +``` + +Supported properties for `WorkerOpinion` component: + +- `handleSubmit` (optional) - your custom callback that will run on opinion submit +- `maxTextLength` (optional) - max amount of characters for all textarea fields +- `questions` (optional, array of strings) - list of questions for the worker, where each response is a textarea field +- `required` (optional) - if `false`, adds `" (optional)"` suffix to question text (default: `false`) +- `textAreaWidth` (optional) - width of all textarea fields (default: `100%`) +- `title` (optional) - title of the WorkerOpinion widget. Default: `Your Feedback` + +Here's how `WorkerOpinion` component looks like: + +![Worker Opinion](./screenshots/worker_opinion_widget.png) + + +## How `WorkerOpinion` works + +1. After completing each unit, workers can submit an opinion about the completed unit. +2. **Once the task is shut down**, you can review all collected opinions for the task in TaskReview app. +3. When reviewing units in TaskReview app, you will see an extra accordion section "Worker Opinion" if a worker submitted their opinion. \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 486853462..ac0c01d22 100644 --- a/examples/README.md +++ b/examples/README.md @@ -93,7 +93,77 @@ This is a single-version form containing no variable tokens. - Browser page (for the first task unit): [http://localhost:3001/?worker_id=x&assignment_id=1](http://localhost:3001/?worker_id=x&assignment_id=1). - You should see a Bootstrap-themed form with fields and sections corresponding to the form config in its [task_data.json](/examples/form_composer_demo/data/simple/task_data.json) file. -##### 3.2. Dynamic form +##### 3.2. Simple form with Gold Units + +Same example as 3.1, but one of the Units will be marked as Gold. + +- Default config file: [example_local_mock.yaml](/examples/form_composer_demo/hydra_configs/conf/example_local_mock_with_gold_unit.yaml). +- Launch command: + ```shell + docker-compose -f docker/docker-compose.dev.yml run \ + --build \ + --publish 3001:3000 \ + --rm mephisto_dc \ + python /mephisto/examples/form_composer_demo/run_task_with_gold_unit.py + ``` +- Browser page (for the first task unit): [http://localhost:3001/?worker_id=x&assignment_id=1](http://localhost:3001/?worker_id=x&assignment_id=1). +- You should see a Bootstrap-themed form with fields and sections corresponding to the form config in its [task_data.json](/examples/form_composer_demo/data/simple/task_data.json) file +and Gold units made from [gold_units_data.json](/examples/form_composer_demo/data/simple/gold_units/gold_units_data.json) +with special validation for them from [gold_units_validation.py](/examples/form_composer_demo/data/simple/gold_units/gold_units_validation.py). + +##### 3.3. Simple form with Onboarding + +Same example as 3.1, but before Units a Worker will see Onboarding widget. + +- Default config file: [example_local_mock.yaml](/examples/form_composer_demo/hydra_configs/conf/example_local_mock_with_oboarding.yaml). +- Launch command: + ```shell + docker-compose -f docker/docker-compose.dev.yml run \ + --build \ + --publish 3001:3000 \ + --rm mephisto_dc \ + python /mephisto/examples/form_composer_demo/run_task_with_onboarding.py + ``` +- Browser page (for the first task unit): [http://localhost:3001/?worker_id=x&assignment_id=1](http://localhost:3001/?worker_id=x&assignment_id=1). +- You should see Onboarding widget and after answering the onboarding question you will be blocked or passed further and will see +a Bootstrap-themed form with fields and sections corresponding to the form config in its [task_data.json](/examples/form_composer_demo/data/simple/task_data.json) file. + +##### 3.4. Simple form with Screening units + +Same example as 3.1, but first time before real Units a Worker will see Screening units. + +- Default config file: [example_local_mock.yaml](/examples/form_composer_demo/hydra_configs/conf/example_local_mock_with_screening.yaml). +- Launch command: + ```shell + docker-compose -f docker/docker-compose.dev.yml run \ + --build \ + --publish 3001:3000 \ + --rm mephisto_dc \ + python /mephisto/examples/form_composer_demo/run_task_with_screening.py + ``` +- Browser page (for the first task unit): [http://localhost:3001/?worker_id=x&assignment_id=1](http://localhost:3001/?worker_id=x&assignment_id=1). +- You should see Onboarding widget and after answering the onboarding question you will be blocked or passed further and will see +a Bootstrap-themed form with fields and sections corresponding to the form config in its [task_data.json](/examples/form_composer_demo/data/simple/task_data.json) file +and Screening units made from [screening_units_data.json](/examples/form_composer_demo/data/simple/screening_units/screening_units_data.json) +with special validation for them from [screening_units_validation.py](/examples/form_composer_demo/data/simple/screening_units/screening_units_validation.py). + +##### 3.5. Simple form with Worker Opinion widget + +Same example as 3.1, but after completing a Unit, a Worker will see a form below the main form where they can leave their opinion and screenshots. + +- Default config file: [example_local_mock.yaml](/examples/form_composer_demo/hydra_configs/conf/example_local_mock_with_oboarding.yaml). +- Launch command: + ```shell + docker-compose -f docker/docker-compose.dev.yml run \ + --build \ + --publish 3001:3000 \ + --rm mephisto_dc \ + python /mephisto/examples/form_composer_demo/run_task_with_worker_opinion.py + ``` +- Browser page (for the first task unit): [http://localhost:3001/?worker_id=x&assignment_id=1](http://localhost:3001/?worker_id=x&assignment_id=1). +- You should see a Bootstrap-themed form with fields and sections corresponding to the form config in its [task_data.json](/examples/form_composer_demo/data/simple/task_data.json) file. + +##### 3.6. Dynamic form Dynamic form means a multi-version form, where versions are generated by varying values of special tokens embedded into the form config. @@ -113,7 +183,7 @@ There are variations of dynamic form config that use different providers. To try - `run_task_dynamic_ec2_prolific.py` for Prolific (requires valid EC2 credentials) - `run_task_dynamic_ec2_mturk_sandbox.py` for Mechanical Turk sandbox (requires valid EC2 credentials) -##### 3.3. Dynamic form with presigned URLs +##### 3.7. Dynamic form with presigned URLs This example builds further upon the Dynamic form example. Here we use presigned URLs (i.e. short-lived URLs for S3 AWS files) in the displayed forms, for data security. diff --git a/examples/form_composer_demo/webapp/webpack.config.js b/examples/form_composer_demo/webapp/webpack.config.js index bbfa1e7c2..81ce2491f 100644 --- a/examples/form_composer_demo/webapp/webpack.config.js +++ b/examples/form_composer_demo/webapp/webpack.config.js @@ -77,7 +77,5 @@ module.exports = { }, ], }, - plugins: [ - new webpack.EnvironmentPlugin({ ...process.env }) - ] + plugins: [new webpack.EnvironmentPlugin({ ...process.env })], }; diff --git a/examples/form_composer_demo/webapp/webpack.config.presigned_urls.js b/examples/form_composer_demo/webapp/webpack.config.presigned_urls.js index d619531dd..568fc1200 100644 --- a/examples/form_composer_demo/webapp/webpack.config.presigned_urls.js +++ b/examples/form_composer_demo/webapp/webpack.config.presigned_urls.js @@ -54,7 +54,5 @@ module.exports = { }, ], }, - plugins: [ - new webpack.EnvironmentPlugin({ ...process.env }) - ] + plugins: [new webpack.EnvironmentPlugin({ ...process.env })], }; diff --git a/examples/form_composer_demo/webapp/webpack.config.review.js b/examples/form_composer_demo/webapp/webpack.config.review.js index 1ef6d3bde..7ade6687f 100644 --- a/examples/form_composer_demo/webapp/webpack.config.review.js +++ b/examples/form_composer_demo/webapp/webpack.config.review.js @@ -67,7 +67,5 @@ module.exports = { }, ], }, - plugins: [ - new webpack.EnvironmentPlugin({ ...process.env }) - ] + plugins: [new webpack.EnvironmentPlugin({ ...process.env })], }; diff --git a/examples/static_react_task_with_worker_opinion/README.md b/examples/static_react_task_with_worker_opinion/README.md index b95332570..f9ee31774 100644 --- a/examples/static_react_task_with_worker_opinion/README.md +++ b/examples/static_react_task_with_worker_opinion/README.md @@ -4,26 +4,15 @@ LICENSE file in the root directory of this source tree. --> -# Simple Static React Task With Tips -This task is essentially the same as the "static-react-task" task except that there is a tips button that allows for the viewing and submission of tips. +# Simple Static React Task with Worker Opinion -The [static-react-task readme](https://github.com/facebookresearch/Mephisto/blob/add-tips-example/examples/static_react_task/README.md) should be read before running this task. +This task is essentially the same as the `static-react-task` task except that there is a worker opinon form after finishing a Task. -### Adding Tips -Tips can be imported from `mephisto-worker-addons` to be used. -```js -import { Tips } from "mephisto-worker-addons"; -``` -Then adding the following to inside your BaseFrontend should show the tips button. -```js - -``` -To see some of the other props of the Tips component see the -[mephisto-worker-addons readme](https://github.com/facebookresearch/Mephisto/blob/add-tips-example/packages/mephisto-worker-addons/README.md) +The [static-react-task README](../static_react_task/README.md) should be read before running this task. -Once a worker submits a tip from the popup that shows when clicking on the tips button, then these tips can be reviewed to either accept or reject them. +### Adding `WorkerOpinion` widget -This can be done by running the [review_tips_for_task.py](https://github.com/facebookresearch/Mephisto/blob/add-tips-example/mephisto/scripts/local_db/review_tips_for_task.py) script and following the instructions. If a tip is accepted, then running python run_task.py on this task should show the accepted tip when opening the tips popup. - -If you accepted a tip by accident, then running the [remove_accepted_tip.py](https://github.com/facebookresearch/Mephisto/blob/add-tips-example/mephisto/scripts/local_db/remove_accepted_tip.py) script and following the instructions will allow you to remove the accepted tip. Running the task again and opening the tips popup will confirm that the accepted tip was removed. +To see how to add this widget, read [mephisto-task-addons README](../../packages/mephisto-task-addons/README.md) +Once a worker submits an opinion from the form below the main task that will be available to the researcher in TaskReview app. +There is a page with all opinions for a Task and "Worker Opinion" collapsable block on Unit review page if a worker left their opinion. diff --git a/examples/static_react_task_with_worker_opinion/webapp/cypress/e2e/post_submission_tests/static_react_task_with_worker_opinion_post_worker_opinion_submission.cy.js b/examples/static_react_task_with_worker_opinion/webapp/cypress/e2e/post_submission_tests/static_react_task_with_worker_opinion_post_worker_opinion_submission.cy.js index 23b51abef..70c1b7b95 100644 --- a/examples/static_react_task_with_worker_opinion/webapp/cypress/e2e/post_submission_tests/static_react_task_with_worker_opinion_post_worker_opinion_submission.cy.js +++ b/examples/static_react_task_with_worker_opinion/webapp/cypress/e2e/post_submission_tests/static_react_task_with_worker_opinion_post_worker_opinion_submission.cy.js @@ -17,20 +17,21 @@ describe("Checking for tips", () => { The whole process is gone through in the cypress-end-to-end-tests.yml github actions file. */ - it("Checks for recently added tip", () => { - cy.visit("/"); - cy.get(`.${tipClassNamePrefix}button`).as("tipsButton"); - cy.get("@tipsButton").click(); - - cy.get(`.${tipClassNamePrefix}tip`).eq(-1).as("lastTip"); - - cy.get("@lastTip").find("h2").as("lastTipHeader"); - cy.get("@lastTip").find("p").as("lastTipBody"); - - cy.get("@lastTipHeader").should( - "have.text", - "🎉 This is my test tip header" - ); - cy.get("@lastTipBody").should("have.text", "🎈 This is my test tip body"); - }); + // TODO: TODO: Fix tests for WorkerOpinion widget + // it("Checks for recently added tip", () => { + // cy.visit("/"); + // cy.get(`.${tipClassNamePrefix}button`).as("tipsButton"); + // cy.get("@tipsButton").click(); + // + // cy.get(`.${tipClassNamePrefix}tip`).eq(-1).as("lastTip"); + // + // cy.get("@lastTip").find("h2").as("lastTipHeader"); + // cy.get("@lastTip").find("p").as("lastTipBody"); + // + // cy.get("@lastTipHeader").should( + // "have.text", + // "🎉 This is my test tip header" + // ); + // cy.get("@lastTipBody").should("have.text", "🎈 This is my test tip body"); + // }); }); diff --git a/examples/static_react_task_with_worker_opinion/webapp/cypress/e2e/pre_submission_tests/0-static_react_task_with_worker_opinion_pre_worker_opinion_submission.cy.js b/examples/static_react_task_with_worker_opinion/webapp/cypress/e2e/pre_submission_tests/0-static_react_task_with_worker_opinion_pre_worker_opinion_submission.cy.js index 2eb595491..ff5481091 100644 --- a/examples/static_react_task_with_worker_opinion/webapp/cypress/e2e/pre_submission_tests/0-static_react_task_with_worker_opinion_pre_worker_opinion_submission.cy.js +++ b/examples/static_react_task_with_worker_opinion/webapp/cypress/e2e/pre_submission_tests/0-static_react_task_with_worker_opinion_pre_worker_opinion_submission.cy.js @@ -28,142 +28,145 @@ describe("Loads `static_react_task_with_worker_opinion`", () => { }); }); - it("Loads correct task react elements", () => { - cy.visit("/"); - cy.get('[data-cy="directions-container"]'); - cy.get('[data-cy="task-data-text"]'); - cy.get('[data-cy="good-button"]'); - cy.get('[data-cy="bad-button"]'); - }); - - it("Loads correct tip react elements", () => { - cy.visit("/"); - cy.get(`.${tipClassNamePrefix}button`); - }); + // TODO: TODO: Fix tests for WorkerOpinion widget + // it("Loads correct task react elements", () => { + // cy.visit("/"); + // cy.get('[data-cy="directions-container"]'); + // cy.get('[data-cy="task-data-text"]'); + // cy.get('[data-cy="good-button"]'); + // cy.get('[data-cy="bad-button"]'); + // }); + + // TODO: TODO: Fix tests for WorkerOpinion widget + // it("Loads correct tip react elements", () => { + // cy.visit("/"); + // cy.get(`.${tipClassNamePrefix}button`); + // }); }); -describe("Tips Popup", () => { - it("Opening/Closing tips popup", () => { - cy.visit("/"); - cy.get(`.${tipClassNamePrefix}button`).as("tipsButton"); - cy.get("@tipsButton").click(); - - cy.get(`.${tipClassNamePrefix}container`).as("tipsContainer"); - cy.get("@tipsContainer").should("exist"); - cy.get("h1").contains("Task Tips:"); - cy.get("h1").contains("Submit A Tip:"); - cy.get("label").contains("Tip Headline:"); - cy.get("label").contains("Tip Body:"); - cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); - - // Closing popup by clicking the hide tips button - cy.get("@tipsButton").click(); - cy.get("@tipsContainer").should("not.exist"); - - cy.get("@tipsButton").click(); - cy.get("@tipsContainer").should("exist"); - cy.get("h1").contains("Task Tips:"); - cy.get("h1").contains("Submit A Tip:"); - cy.get("label").contains("Tip Headline:"); - cy.get("label").contains("Tip Body:"); - cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); - - // Closing popup by clicking close button - cy.get(`.${tipClassNamePrefix}close-icon-container`).click(); - cy.get("@tipsContainer").should("not.exist"); - }); - - it("Checking if tips header is too long", () => { - cy.visit("/"); - cy.get(`.${tipClassNamePrefix}button`).as("tipsButton"); - cy.get("@tipsButton").click(); - - cy.get(`#${tipClassNamePrefix}header-input`).as("tipsHeaderInput"); - cy.get("@tipsHeaderInput").type(headerError); - cy.get(`.${tipClassNamePrefix}red-box`).should( - "have.text", - "📝 Your tip header is too long" - ); - cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); - - /* - There needs to be {force:true} in the clear because otherwise there is a - "cy.type() failed because it targeted a disabled element" error - - These issues are both related to it, there seems to be no clear solution: - https://github.com/cypress-io/cypress/issues/5830 - https://github.com/cypress-io/cypress/issues/21433 - - */ - cy.get("@tipsHeaderInput").clear({ force: true }); - cy.get(`.${tipClassNamePrefix}red-box`).should("not.exist"); - }); - - it("Checking if tips body is too long", () => { - cy.visit("/"); - cy.get(`.${tipClassNamePrefix}button`).as("tipsButton"); - cy.get("@tipsButton").click(); - - cy.get(`#${tipClassNamePrefix}text-input`).as("tipsBodyInput"); - cy.get("@tipsBodyInput").type(bodyError); - cy.get(`.${tipClassNamePrefix}red-box`).should( - "have.text", - "📝 Your tip body is too long" - ); - cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); - cy.get("@tipsBodyInput").clear({ force: true }); - cy.get(`.${tipClassNamePrefix}red-box`).should("not.exist"); - }); - - it("Checking if both tips header and tips body is too long", () => { - cy.visit("/"); - cy.get(`.${tipClassNamePrefix}button`).as("tipsButton"); - cy.get("@tipsButton").click(); - - cy.get(`#${tipClassNamePrefix}header-input`).as("tipsHeaderInput"); - cy.get("@tipsHeaderInput").should("not.be.disabled"); - cy.get("@tipsHeaderInput").type(headerError); - cy.get(`.${tipClassNamePrefix}red-box`).should( - "have.text", - "📝 Your tip header is too long" - ); - cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); - - cy.get(`#${tipClassNamePrefix}text-input`).as("tipsBodyInput"); - cy.get("@tipsBodyInput").should("not.be.disabled"); - cy.get("@tipsBodyInput").type(bodyError); - - cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); - cy.get(`.${tipClassNamePrefix}red-box`).should( - "have.text", - "📝 Your tip header is too long" - ); - cy.get("@tipsHeaderInput").clear({ force: true }); - cy.get(`.${tipClassNamePrefix}red-box`).should( - "have.text", - "📝 Your tip body is too long" - ); - cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); - cy.get("@tipsBodyInput").clear({ force: true }); - cy.get(`.${tipClassNamePrefix}red-box`).should("not.exist"); - }); - - it("Submitting three tips", () => { - cy.intercept({ pathname: "/submit_metadata" }).as("submitMetadataRequest"); - cy.visit("/"); - cy.get(`.${tipClassNamePrefix}button`) - .contains("Show Tips") - .as("tipsButton"); - cy.get("@tipsButton").click(); - cy.get(`.${tipClassNamePrefix}button`) - .contains("Submit Tip") - .as("submitButton"); - cy.get("@submitButton").should("be.disabled"); - cy.get(`#${tipClassNamePrefix}header-input`).as("tipsHeaderInput"); - - cy.get(`#${tipClassNamePrefix}text-input`).as("tipsBodyInput"); - cy.submitTip(firstTipHeader, firstTipBody); - cy.submitTip(secondTipHeader, secondTipBody); - cy.submitTip(thirdTipHeader, thirdTipBody); - }); -}); +// TODO: Fix tests for WorkerOpinion widget +// describe("Tips Popup", () => { +// it("Opening/Closing tips popup", () => { +// cy.visit("/"); +// cy.get(`.${tipClassNamePrefix}button`).as("tipsButton"); +// cy.get("@tipsButton").click(); +// +// cy.get(`.${tipClassNamePrefix}container`).as("tipsContainer"); +// cy.get("@tipsContainer").should("exist"); +// cy.get("h1").contains("Task Tips:"); +// cy.get("h1").contains("Submit A Tip:"); +// cy.get("label").contains("Tip Headline:"); +// cy.get("label").contains("Tip Body:"); +// cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); +// +// // Closing popup by clicking the hide tips button +// cy.get("@tipsButton").click(); +// cy.get("@tipsContainer").should("not.exist"); +// +// cy.get("@tipsButton").click(); +// cy.get("@tipsContainer").should("exist"); +// cy.get("h1").contains("Task Tips:"); +// cy.get("h1").contains("Submit A Tip:"); +// cy.get("label").contains("Tip Headline:"); +// cy.get("label").contains("Tip Body:"); +// cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); +// +// // Closing popup by clicking close button +// cy.get(`.${tipClassNamePrefix}close-icon-container`).click(); +// cy.get("@tipsContainer").should("not.exist"); +// }); +// +// it("Checking if tips header is too long", () => { +// cy.visit("/"); +// cy.get(`.${tipClassNamePrefix}button`).as("tipsButton"); +// cy.get("@tipsButton").click(); +// +// cy.get(`#${tipClassNamePrefix}header-input`).as("tipsHeaderInput"); +// cy.get("@tipsHeaderInput").type(headerError); +// cy.get(`.${tipClassNamePrefix}red-box`).should( +// "have.text", +// "📝 Your tip header is too long" +// ); +// cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); +// +// /* +// There needs to be {force:true} in the clear because otherwise there is a +// "cy.type() failed because it targeted a disabled element" error +// +// These issues are both related to it, there seems to be no clear solution: +// https://github.com/cypress-io/cypress/issues/5830 +// https://github.com/cypress-io/cypress/issues/21433 +// +// */ +// cy.get("@tipsHeaderInput").clear({ force: true }); +// cy.get(`.${tipClassNamePrefix}red-box`).should("not.exist"); +// }); +// +// it("Checking if tips body is too long", () => { +// cy.visit("/"); +// cy.get(`.${tipClassNamePrefix}button`).as("tipsButton"); +// cy.get("@tipsButton").click(); +// +// cy.get(`#${tipClassNamePrefix}text-input`).as("tipsBodyInput"); +// cy.get("@tipsBodyInput").type(bodyError); +// cy.get(`.${tipClassNamePrefix}red-box`).should( +// "have.text", +// "📝 Your tip body is too long" +// ); +// cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); +// cy.get("@tipsBodyInput").clear({ force: true }); +// cy.get(`.${tipClassNamePrefix}red-box`).should("not.exist"); +// }); +// +// it("Checking if both tips header and tips body is too long", () => { +// cy.visit("/"); +// cy.get(`.${tipClassNamePrefix}button`).as("tipsButton"); +// cy.get("@tipsButton").click(); +// +// cy.get(`#${tipClassNamePrefix}header-input`).as("tipsHeaderInput"); +// cy.get("@tipsHeaderInput").should("not.be.disabled"); +// cy.get("@tipsHeaderInput").type(headerError); +// cy.get(`.${tipClassNamePrefix}red-box`).should( +// "have.text", +// "📝 Your tip header is too long" +// ); +// cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); +// +// cy.get(`#${tipClassNamePrefix}text-input`).as("tipsBodyInput"); +// cy.get("@tipsBodyInput").should("not.be.disabled"); +// cy.get("@tipsBodyInput").type(bodyError); +// +// cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); +// cy.get(`.${tipClassNamePrefix}red-box`).should( +// "have.text", +// "📝 Your tip header is too long" +// ); +// cy.get("@tipsHeaderInput").clear({ force: true }); +// cy.get(`.${tipClassNamePrefix}red-box`).should( +// "have.text", +// "📝 Your tip body is too long" +// ); +// cy.get(`.${tipClassNamePrefix}button`).should("be.disabled"); +// cy.get("@tipsBodyInput").clear({ force: true }); +// cy.get(`.${tipClassNamePrefix}red-box`).should("not.exist"); +// }); +// +// it("Submitting three tips", () => { +// cy.intercept({ pathname: "/submit_metadata" }).as("submitMetadataRequest"); +// cy.visit("/"); +// cy.get(`.${tipClassNamePrefix}button`) +// .contains("Show Tips") +// .as("tipsButton"); +// cy.get("@tipsButton").click(); +// cy.get(`.${tipClassNamePrefix}button`) +// .contains("Submit Tip") +// .as("submitButton"); +// cy.get("@submitButton").should("be.disabled"); +// cy.get(`#${tipClassNamePrefix}header-input`).as("tipsHeaderInput"); +// +// cy.get(`#${tipClassNamePrefix}text-input`).as("tipsBodyInput"); +// cy.submitTip(firstTipHeader, firstTipBody); +// cy.submitTip(secondTipHeader, secondTipBody); +// cy.submitTip(thirdTipHeader, thirdTipBody); +// }); +// }); diff --git a/examples/static_react_task_with_worker_opinion/webapp/package.json b/examples/static_react_task_with_worker_opinion/webapp/package.json index 2a57860aa..270e6a82f 100644 --- a/examples/static_react_task_with_worker_opinion/webapp/package.json +++ b/examples/static_react_task_with_worker_opinion/webapp/package.json @@ -12,7 +12,6 @@ "author": "", "dependencies": { "bootstrap": "^4.6.2", - "mephisto-worker-addons": "^1.0.2", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/mephisto/abstractions/_subcomponents/agent_state.py b/mephisto/abstractions/_subcomponents/agent_state.py index c53fd95b2..32314815c 100644 --- a/mephisto/abstractions/_subcomponents/agent_state.py +++ b/mephisto/abstractions/_subcomponents/agent_state.py @@ -6,6 +6,7 @@ import os.path import time +import warnings import weakref from abc import ABC from abc import abstractmethod @@ -329,12 +330,14 @@ def get_tips(self) -> Optional[List[Dict[str, Any]]]: """ Return the tips for this task, if it is available """ + warnings.warn("No longer supported.", DeprecationWarning) return self.metadata.tips def get_feedback(self) -> Optional[List[Dict[str, Any]]]: """ Return the tips for this task, if it is available """ + warnings.warn("No longer supported.", DeprecationWarning) return self.metadata.feedback def get_worker_opinion(self) -> Optional[List[Dict[str, Any]]]: diff --git a/mephisto/abstractions/providers/prolific/api/base_api_resource.py b/mephisto/abstractions/providers/prolific/api/base_api_resource.py index 6efa20eb2..3533766a3 100644 --- a/mephisto/abstractions/providers/prolific/api/base_api_resource.py +++ b/mephisto/abstractions/providers/prolific/api/base_api_resource.py @@ -12,8 +12,8 @@ import requests +from mephisto.utils import http_status from mephisto.utils.logger_core import get_logger -from . import status from .exceptions import ProlificAPIKeyError from .exceptions import ProlificAuthenticationError from .exceptions import ProlificException @@ -94,7 +94,7 @@ def _base_request( raise ProlificException("Invalid HTTP method.") response.raise_for_status() - if response.status_code == status.HTTP_204_NO_CONTENT and not response.content: + if response.status_code == http_status.HTTP_204_NO_CONTENT and not response.content: result = None else: result = response.json() @@ -109,7 +109,7 @@ def _base_request( except requests.exceptions.HTTPError as err: logger.error(f"{log_prefix} Request error: {err}. Response text: `{err.response.text}`") - if err.response.status_code == status.HTTP_401_UNAUTHORIZED: + if err.response.status_code == http_status.HTTP_401_UNAUTHORIZED: raise ProlificAuthenticationError message = err.args[0] diff --git a/mephisto/abstractions/providers/prolific/api/exceptions.py b/mephisto/abstractions/providers/prolific/api/exceptions.py index 7f747fc76..797e54a3d 100644 --- a/mephisto/abstractions/providers/prolific/api/exceptions.py +++ b/mephisto/abstractions/providers/prolific/api/exceptions.py @@ -6,7 +6,7 @@ from typing import Optional -from . import status +from mephisto.utils import http_status class ProlificException(Exception): @@ -27,7 +27,7 @@ class ProlificAPIKeyError(ProlificException): class ProlificRequestError(ProlificException): default_message = "Request error." - status_code = status.HTTP_400_BAD_REQUEST + status_code = http_status.HTTP_400_BAD_REQUEST def __init__(self, message: Optional[str] = None, status_code: Optional[int] = None): self.message = message or self.default_message @@ -36,4 +36,4 @@ def __init__(self, message: Optional[str] = None, status_code: Optional[int] = N class ProlificAuthenticationError(ProlificRequestError): default_message = "Authentication was failed." - status_code = status.HTTP_401_UNAUTHORIZED + status_code = http_status.HTTP_401_UNAUTHORIZED diff --git a/mephisto/client/cli.py b/mephisto/client/cli.py index c3b8c109b..9278ad5ae 100644 --- a/mephisto/client/cli.py +++ b/mephisto/client/cli.py @@ -119,7 +119,6 @@ def register_provider(args): provider_text += "\n* " + provider provider_text_markdown = Markdown(provider_text) console.print(provider_text_markdown) - logger.info("") return from mephisto.abstractions.databases.local_database import LocalMephistoDB diff --git a/mephisto/client/cli_scripts_commands.py b/mephisto/client/cli_scripts_commands.py index 21e405b4d..5629d352e 100644 --- a/mephisto/client/cli_scripts_commands.py +++ b/mephisto/client/cli_scripts_commands.py @@ -73,19 +73,21 @@ def print_non_markdown_list(items: List[str]): return res if script_type is None or script_type.strip() not in VALID_SCRIPT_TYPES: - logger.info("") raise click.UsageError( "You must specify a valid script_type from below. \n\nValid script types are:" + print_non_markdown_list(VALID_SCRIPT_TYPES) ) + script_type = script_type.strip() script_type_to_scripts_data = { "local_db": { "valid_script_names": LOCAL_DB_VALID_SCRIPTS_NAMES, "scripts": { + # TODO: `review_tips_local_db` is deprecated LOCAL_DB_VALID_SCRIPTS_NAMES[0]: review_tips_local_db.main, LOCAL_DB_VALID_SCRIPTS_NAMES[1]: remove_accepted_tip_local_db.main, + # TODO: `review_feedback_local_db` is deprecated LOCAL_DB_VALID_SCRIPTS_NAMES[2]: review_feedback_local_db.main, LOCAL_DB_VALID_SCRIPTS_NAMES[3]: load_data_local_db.main, LOCAL_DB_VALID_SCRIPTS_NAMES[4]: clear_worker_onboarding_local_db.main, @@ -124,7 +126,6 @@ def print_non_markdown_list(items: List[str]): if script_name is None or ( script_name not in script_type_to_scripts_data[script_type]["valid_script_names"] ): - logger.info("") raise click.UsageError( "You must specify a valid script_name from below. \n\nValid script names are:" + print_non_markdown_list( diff --git a/mephisto/review_app/client/src/App/App.tsx b/mephisto/review_app/client/src/App/App.tsx index 951750159..d7b56bc7b 100644 --- a/mephisto/review_app/client/src/App/App.tsx +++ b/mephisto/review_app/client/src/App/App.tsx @@ -16,7 +16,7 @@ import TaskWorkerOpinionsPage from "pages/TaskWorkerOpinionsPage/TaskWorkerOpini import * as React from "react"; import { Route, Routes } from "react-router-dom"; import urls from "urls"; -import css from "./App.css" +import css from "./App.css"; function App() { const [errors, setErrors] = React.useState([]); diff --git a/mephisto/review_app/client/src/components/CollapsableBlock/CollapsableBlock.css b/mephisto/review_app/client/src/components/CollapsableBlock/CollapsableBlock.css index b68b13a3b..60749464f 100644 --- a/mephisto/review_app/client/src/components/CollapsableBlock/CollapsableBlock.css +++ b/mephisto/review_app/client/src/components/CollapsableBlock/CollapsableBlock.css @@ -29,6 +29,10 @@ line-height: 0.5; } -.collapsable-block .collapsable-block-closed { +.collapsable-block .collapsable-block-separator { + margin: 5px 0 0; +} + +.collapsable-block .collapsable-block-content.collapsable-block-closed { display: none; } diff --git a/mephisto/review_app/client/src/components/CollapsableBlock/CollapsableBlock.tsx b/mephisto/review_app/client/src/components/CollapsableBlock/CollapsableBlock.tsx index 2aa5ce232..454d01cce 100644 --- a/mephisto/review_app/client/src/components/CollapsableBlock/CollapsableBlock.tsx +++ b/mephisto/review_app/client/src/components/CollapsableBlock/CollapsableBlock.tsx @@ -4,11 +4,10 @@ * LICENSE file in the root directory of this source tree. */ -import { useEffect } from 'react'; +import { useEffect } from "react"; import * as React from "react"; import "./CollapsableBlock.css"; - type CollapsableBlockPropsType = { children: any; className?: string; @@ -42,7 +41,14 @@ function CollapsableBlock(props: CollapsableBlockPropsType) { -
+ {openContent &&
} + +
{children}
diff --git a/mephisto/review_app/client/src/components/WorkerOpinion/WorkerOpinion.tsx b/mephisto/review_app/client/src/components/WorkerOpinion/WorkerOpinion.tsx deleted file mode 100644 index 200a62180..000000000 --- a/mephisto/review_app/client/src/components/WorkerOpinion/WorkerOpinion.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 CollapsableBlock from "components/CollapsableBlock/CollapsableBlock"; -import * as React from "react"; -import "./WorkerOpinion.css"; - -type WorkerOpinionPropsType = { - className?: string; - data: WorkerOpinionType; - onClickOnAttachment: (file: WorkerOpinionAttachmentType) => void; - open?: boolean; - title?: string | React.ReactElement; -}; - -function WorkerOpinion(props: WorkerOpinionPropsType) { - const { className, data, onClickOnAttachment, open, title } = props; - - const _title = title || "Worker Opinion"; - - return ( - - {/* Questions */} -
- {data.questions.map((question: WorkerOpinionQuestionType, index: number) => { - return ( -
-
- {question.question} -
-
-                {question.answer}
-              
-
- ); - })} -
- - {/* Attachments */} -
-
- Attachments -
- - {data.attachments.map((attachement: WorkerOpinionAttachmentType, index: number) => { - return ( -
onClickOnAttachment(attachement)} - key={`worker-opinion-attachment-${index}`} - > - {attachement.originalname} -
- ); - })} -
-
- ); -} - -export default WorkerOpinion; diff --git a/mephisto/review_app/client/src/components/WorkerOpinion/WorkerOpinion.css b/mephisto/review_app/client/src/components/WorkerOpinionCollapsable/WorkerOpinionCollapsable.css similarity index 96% rename from mephisto/review_app/client/src/components/WorkerOpinion/WorkerOpinion.css rename to mephisto/review_app/client/src/components/WorkerOpinionCollapsable/WorkerOpinionCollapsable.css index 553a360cf..330cee312 100644 --- a/mephisto/review_app/client/src/components/WorkerOpinion/WorkerOpinion.css +++ b/mephisto/review_app/client/src/components/WorkerOpinionCollapsable/WorkerOpinionCollapsable.css @@ -5,7 +5,6 @@ */ .worker-opinion .questions { - border-top: 1px solid #ccc; padding-top: 15px; white-space: pre !important; } diff --git a/mephisto/review_app/client/src/components/WorkerOpinionCollapsable/WorkerOpinionCollapsable.tsx b/mephisto/review_app/client/src/components/WorkerOpinionCollapsable/WorkerOpinionCollapsable.tsx new file mode 100644 index 000000000..3474f47a8 --- /dev/null +++ b/mephisto/review_app/client/src/components/WorkerOpinionCollapsable/WorkerOpinionCollapsable.tsx @@ -0,0 +1,70 @@ +/* + * 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 CollapsableBlock from "components/CollapsableBlock/CollapsableBlock"; +import * as React from "react"; +import "./WorkerOpinionCollapsable.css"; + +type WorkerOpinionCollapsablePropsType = { + className?: string; + data: WorkerOpinionType; + onClickOnAttachment: (file: WorkerOpinionAttachmentType) => void; + open?: boolean; + title?: string | React.ReactElement; +}; + +function WorkerOpinionCollapsable(props: WorkerOpinionCollapsablePropsType) { + const { className, data, onClickOnAttachment, open, title } = props; + + const _title = title || "Worker Opinion"; + + return ( + + {/* Questions */} +
+ {data.questions.map( + (question: WorkerOpinionQuestionType, index: number) => { + return ( +
+
{question.question}
+
{question.answer}
+
+ ); + } + )} +
+ + {/* Attachments */} +
+
Attachments
+ + {data.attachments.map( + (attachement: WorkerOpinionAttachmentType, index: number) => { + return ( +
onClickOnAttachment(attachement)} + key={`worker-opinion-attachment-${index}`} + > + {attachement.originalname} +
+ ); + } + )} +
+
+ ); +} + +export default WorkerOpinionCollapsable; diff --git a/mephisto/review_app/client/src/pages/TaskPage/InitialParameters/InitialParameters.css b/mephisto/review_app/client/src/pages/TaskPage/InitialParametersCollapsable/InitialParametersCollapsable.css similarity index 100% rename from mephisto/review_app/client/src/pages/TaskPage/InitialParameters/InitialParameters.css rename to mephisto/review_app/client/src/pages/TaskPage/InitialParametersCollapsable/InitialParametersCollapsable.css diff --git a/mephisto/review_app/client/src/pages/TaskPage/InitialParameters/InitialParameters.tsx b/mephisto/review_app/client/src/pages/TaskPage/InitialParametersCollapsable/InitialParametersCollapsable.tsx similarity index 66% rename from mephisto/review_app/client/src/pages/TaskPage/InitialParameters/InitialParameters.tsx rename to mephisto/review_app/client/src/pages/TaskPage/InitialParametersCollapsable/InitialParametersCollapsable.tsx index a6c73c952..11ed22276 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/InitialParameters/InitialParameters.tsx +++ b/mephisto/review_app/client/src/pages/TaskPage/InitialParametersCollapsable/InitialParametersCollapsable.tsx @@ -6,17 +6,18 @@ import CollapsableBlock from "components/CollapsableBlock/CollapsableBlock"; import * as React from "react"; -import JSONPretty from 'react-json-pretty'; -import "./InitialParameters.css"; +import JSONPretty from "react-json-pretty"; +import "./InitialParametersCollapsable.css"; - -type InitialParametersPropsType = { +type InitialParametersCollapsablePropsType = { data: object; open?: boolean; isJSON: boolean; }; -function InitialParameters(props: InitialParametersPropsType) { +function InitialParametersCollapsable( + props: InitialParametersCollapsablePropsType +) { const { data, open, isJSON } = props; return ( @@ -27,11 +28,7 @@ function InitialParameters(props: InitialParametersPropsType) { tooltip={"Toggle initial Unit parameters data"} > {isJSON ? ( - + ) : (
{JSON.stringify(data)}
)} @@ -39,4 +36,4 @@ function InitialParameters(props: InitialParametersPropsType) { ); } -export default InitialParameters; +export default InitialParametersCollapsable; diff --git a/mephisto/review_app/client/src/pages/TaskPage/Results/Results.css b/mephisto/review_app/client/src/pages/TaskPage/ResultsCollapsable/ResultsCollapsable.css similarity index 100% rename from mephisto/review_app/client/src/pages/TaskPage/Results/Results.css rename to mephisto/review_app/client/src/pages/TaskPage/ResultsCollapsable/ResultsCollapsable.css diff --git a/mephisto/review_app/client/src/pages/TaskPage/Results/Results.tsx b/mephisto/review_app/client/src/pages/TaskPage/ResultsCollapsable/ResultsCollapsable.tsx similarity index 69% rename from mephisto/review_app/client/src/pages/TaskPage/Results/Results.tsx rename to mephisto/review_app/client/src/pages/TaskPage/ResultsCollapsable/ResultsCollapsable.tsx index 70232afdb..394171b11 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/Results/Results.tsx +++ b/mephisto/review_app/client/src/pages/TaskPage/ResultsCollapsable/ResultsCollapsable.tsx @@ -6,17 +6,16 @@ import CollapsableBlock from "components/CollapsableBlock/CollapsableBlock"; import * as React from "react"; -import JSONPretty from 'react-json-pretty'; -import "./Results.css"; +import JSONPretty from "react-json-pretty"; +import "./ResultsCollapsable.css"; - -type ResultsPropsType = { +type ResultsCollapsablePropsType = { data: object; open?: boolean; isJSON: boolean; }; -function Results(props: ResultsPropsType) { +function ResultsCollapsable(props: ResultsCollapsablePropsType) { const { data, open, isJSON } = props; return ( @@ -27,11 +26,7 @@ function Results(props: ResultsPropsType) { tooltip={"Toggle Unit results data"} > {isJSON ? ( - + ) : (
{JSON.stringify(data)}
)} @@ -39,4 +34,4 @@ function Results(props: ResultsPropsType) { ); } -export default Results; +export default ResultsCollapsable; diff --git a/mephisto/review_app/client/src/pages/TaskPage/TaskPage.css b/mephisto/review_app/client/src/pages/TaskPage/TaskPage.css index 76b2b459f..1f6fad7cb 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/TaskPage.css +++ b/mephisto/review_app/client/src/pages/TaskPage/TaskPage.css @@ -80,7 +80,6 @@ .json-pretty .__json-pretty__ { white-space: pre !important; - border-top: 1px solid #cccccc; padding-top: 15px; } diff --git a/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx b/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx index 4bae5909c..9d5ea071a 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx +++ b/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx @@ -5,7 +5,7 @@ */ import { InReviewFileModal } from "components/InReviewFileModal/InReviewFileModal"; -import WorkerOpinion from "components/WorkerOpinion/WorkerOpinion"; +import WorkerOpinionCollapsable from "components/WorkerOpinionCollapsable/WorkerOpinionCollapsable"; import { MESSAGES_IFRAME_DATA_KEY, MESSAGES_IN_REVIEW_FILE_DATA_KEY, @@ -32,14 +32,14 @@ import { import { postWorkerBlock } from "requests/workers"; import urls from "urls"; import { setPageTitle, updateModalState } from "./helpers"; -import InitialParameters from "./InitialParameters/InitialParameters"; +import InitialParametersCollapsable from "./InitialParametersCollapsable/InitialParametersCollapsable"; import { APPROVE_MODAL_DATA_STATE, DEFAULT_MODAL_STATE_VALUE, REJECT_MODAL_DATA_STATE, SOFT_REJECT_MODAL_DATA_STATE, } from "./modalData"; -import Results from "./Results/Results"; +import ResultsCollapsable from "./ResultsCollapsable/ResultsCollapsable"; import ReviewModal from "./ReviewModal/ReviewModal"; import TaskHeader from "./TaskHeader/TaskHeader"; import "./TaskPage.css"; @@ -641,7 +641,7 @@ function TaskPage(props: PropsType) { {currentUnitDetails?.inputs && ( // Initial Unit parameters - @@ -659,7 +659,7 @@ function TaskPage(props: PropsType) { {currentUnitDetails?.outputs && ( <> {/* Results */} - (); - const [taskTimeline, setTaskTimeline] = React.useState(null); + const [taskTimeline, setTaskTimeline] = React.useState( + null + ); const [loading, setLoading] = React.useState(false); function onError(errorResponse: ErrorResponseType | null) { diff --git a/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.css b/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.css index 836669e01..6f887aa4d 100644 --- a/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.css +++ b/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.css @@ -38,16 +38,43 @@ font-size: 18px; } - .task-worker-opinions .content .task-worker-opinion .task-worker-opinion-title { + display: inline-flex; + flex-direction: row; + gap: 10px; +} + +.task-worker-opinions + .content + .task-worker-opinion + .task-worker-opinion-title + .task-worker-opinion-title-id { font-size: 18px; } +.task-worker-opinions + .content + .task-worker-opinion + .task-worker-opinion-title + .task-worker-opinion-title-id + .task-worker-opinion-title-id-value { + margin-left: 6px; + color: #aaaaaa; +} + .task-worker-opinions .content .task-worker-opinion .collapsable-block-icon { font-size: 32px; } -.task-worker-opinions .content .task-worker-opinion .questions { +.task-worker-opinions .content .task-worker-opinion .collapsable-block-content { + padding-left: 40px; +} + +.task-worker-opinions + .content + .task-worker-opinion + .collapsable-block-content + .questions { margin-top: 5px; } diff --git a/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.tsx b/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.tsx index b7b0a232e..32e73967b 100644 --- a/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.tsx +++ b/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.tsx @@ -4,9 +4,9 @@ * LICENSE file in the root directory of this source tree. */ -import { InReviewFileModal } from 'components/InReviewFileModal/InReviewFileModal'; +import { InReviewFileModal } from "components/InReviewFileModal/InReviewFileModal"; import TasksHeader from "components/TasksHeader/TasksHeader"; -import WorkerOpinion from 'components/WorkerOpinion/WorkerOpinion'; +import WorkerOpinionCollapsable from "components/WorkerOpinionCollapsable/WorkerOpinionCollapsable"; import { setPageTitle } from "pages/TaskPage/helpers"; import * as React from "react"; import { useEffect } from "react"; @@ -26,9 +26,13 @@ type TaskWorkerOpinionsPagePropsType = { function TaskWorkerOpinionsPage(props: TaskWorkerOpinionsPagePropsType) { const params = useParams(); - const [taskWorkerOpinions, setTaskWorkerOpinions] = React.useState(null); + const [taskWorkerOpinions, setTaskWorkerOpinions] = React.useState< + TaskWorkerOpinionsType + >(null); const [loading, setLoading] = React.useState(false); - const [opinionsVisibility, setOpinionsVisibility] = React.useState(true); + const [opinionsVisibility, setOpinionsVisibility] = React.useState( + true + ); const [inReviewFileModalShow, setInReviewFileModalShow] = React.useState< boolean >(false); @@ -43,10 +47,15 @@ function TaskWorkerOpinionsPage(props: TaskWorkerOpinionsPagePropsType) { } function onClickOnWorkerOpinionAttachment( - file: WorkerOpinionAttachmentType, workerOpinion: TaskWorkerOpinionType, - ) { - const unitDataFolderStartIndex = workerOpinion.unit_data_folder.indexOf("data/data"); - const unitDataFolder = workerOpinion.unit_data_folder.slice(unitDataFolderStartIndex); + file: WorkerOpinionAttachmentType, + workerOpinion: TaskWorkerOpinionType + ) { + const unitDataFolderStartIndex = workerOpinion.unit_data_folder.indexOf( + "data/data" + ); + const unitDataFolder = workerOpinion.unit_data_folder.slice( + unitDataFolderStartIndex + ); setInReviewFileModalData({ fieldname: file.fieldname, @@ -65,14 +74,22 @@ function TaskWorkerOpinionsPage(props: TaskWorkerOpinionsPagePropsType) { setPageTitle("Mephisto - Task Worker Opinions"); if (taskWorkerOpinions === null) { - getTaskWorkerOpinions(params.id, setTaskWorkerOpinions, setLoading, onError, null); + getTaskWorkerOpinions( + params.id, + setTaskWorkerOpinions, + setLoading, + onError, + null + ); } }, []); useEffect(() => { if (taskWorkerOpinions) { // Update title with current task name - setPageTitle(`Mephisto - Task Worker Opinions - ${taskWorkerOpinions.task_name}`); + setPageTitle( + `Mephisto - Task Worker Opinions - ${taskWorkerOpinions.task_name}` + ); } }, [taskWorkerOpinions]); @@ -84,19 +101,23 @@ function TaskWorkerOpinionsPage(props: TaskWorkerOpinionsPagePropsType) { {!loading && taskWorkerOpinions && ( // Task name
-
Task: {taskWorkerOpinions.task_name}
- - {taskWorkerOpinions?.worker_opinions && (<> -
{taskWorkerOpinions.worker_opinions.length} opinions
- - - )} +
+ Task: {taskWorkerOpinions.task_name} +
+ + {taskWorkerOpinions?.worker_opinions && ( + <> +
{taskWorkerOpinions.worker_opinions.length} opinions
+ + + + )}
)} @@ -114,12 +135,23 @@ function TaskWorkerOpinionsPage(props: TaskWorkerOpinionsPagePropsType) { (workerOpinion: TaskWorkerOpinionType, index: number) => { const title = ( - Worker ID: {workerOpinion.worker_id}{" "}Unit ID: {workerOpinion.unit_id} + + Worker ID: + + {workerOpinion.worker_id} + + + + Unit ID: + + {workerOpinion.unit_id} + + ); return ( - Timeline - Worker Opinions + Opinions Export results diff --git a/mephisto/review_app/server/__init__.py b/mephisto/review_app/server/__init__.py index 2b1817d8e..0a815228f 100644 --- a/mephisto/review_app/server/__init__.py +++ b/mephisto/review_app/server/__init__.py @@ -20,7 +20,7 @@ from werkzeug.utils import import_string from mephisto.abstractions.databases.local_database import LocalMephistoDB -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.abstractions.providers.prolific.api.exceptions import ProlificException from mephisto.tools.data_browser import DataBrowser from mephisto.utils.db import EntryDoesNotExistException @@ -96,7 +96,7 @@ def handle_not_flask_exception(e: Exception) -> Tuple[Response, int]: "error": e.message, } ), - status.HTTP_400_BAD_REQUEST, + http_status.HTTP_400_BAD_REQUEST, ) elif isinstance(e, EntryDoesNotExistException): @@ -106,7 +106,7 @@ def handle_not_flask_exception(e: Exception) -> Tuple[Response, int]: "error": "Not found", } ), - status.HTTP_404_NOT_FOUND, + http_status.HTTP_404_NOT_FOUND, ) # Other uncaught exceptions @@ -117,7 +117,7 @@ def handle_not_flask_exception(e: Exception) -> Tuple[Response, int]: "error": f"Server error: {e}", } ), - status.HTTP_500_INTERNAL_SERVER_ERROR, + http_status.HTTP_500_INTERNAL_SERVER_ERROR, ) return app diff --git a/mephisto/review_app/server/api/views/task_worker_opinions_view.py b/mephisto/review_app/server/api/views/task_worker_opinions_view.py index 90094dc55..a5b24b383 100644 --- a/mephisto/review_app/server/api/views/task_worker_opinions_view.py +++ b/mephisto/review_app/server/api/views/task_worker_opinions_view.py @@ -37,15 +37,17 @@ def get(self, task_id: str = None) -> dict: agent = unit.get_assigned_agent() unit_data_folder = agent.get_data_dir() if agent else None - worker_opinions.append({ - "worker_id": unit.worker_id, - "unit_data_folder": unit_data_folder, - "unit_id": unit.db_id, - "data": { - "attachments": worker_opinion["attachments"], - "questions": worker_opinion["questions"], - }, - }) + worker_opinions.append( + { + "worker_id": unit.worker_id, + "unit_data_folder": unit_data_folder, + "unit_id": unit.db_id, + "data": { + "attachments": worker_opinion["attachments"], + "questions": worker_opinion["questions"], + }, + } + ) return { "task_name": task.task_name, diff --git a/mephisto/review_app/server/api/views/unit_review_bundle_view.py b/mephisto/review_app/server/api/views/unit_review_bundle_view.py index 0a4b442ad..1c2687e39 100644 --- a/mephisto/review_app/server/api/views/unit_review_bundle_view.py +++ b/mephisto/review_app/server/api/views/unit_review_bundle_view.py @@ -14,7 +14,7 @@ from flask.views import MethodView from omegaconf.errors import ConfigKeyError -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.task_run import TaskRun from mephisto.data_model.unit import Unit @@ -44,7 +44,7 @@ def get(self, unit_id: str = None) -> Union[dict, Response]: ), } ), - status=status.HTTP_404_NOT_FOUND, + status=http_status.HTTP_404_NOT_FOUND, mimetype="application/json", ) diff --git a/mephisto/scripts/local_db/review_feedback_for_task.py b/mephisto/scripts/local_db/review_feedback_for_task.py index b51be26d5..2ffbc7781 100644 --- a/mephisto/scripts/local_db/review_feedback_for_task.py +++ b/mephisto/scripts/local_db/review_feedback_for_task.py @@ -17,19 +17,25 @@ """ import enum -from typing import Any, Dict, List +import warnings +from typing import Any +from typing import Dict +from typing import List + +from rich import box +from rich import print +from rich.markdown import Markdown +from rich.prompt import IntPrompt +from rich.prompt import Prompt +from rich.table import Table + from mephisto.abstractions.databases.local_database import LocalMephistoDB from mephisto.data_model.agent import Agent from mephisto.data_model.unit import Unit from mephisto.scripts.local_db.review_tips_for_task import get_index_of_value from mephisto.tools.data_browser import DataBrowser as MephistoDataBrowser -from rich import print -from rich.markdown import Markdown -from rich.prompt import Prompt, IntPrompt from mephisto.tools.scripts import print_out_task_names from mephisto.utils.rich import console -from rich.table import Table -from rich import box yes_response = set(["yes", "y", "YES", "Yes"]) @@ -41,7 +47,7 @@ class FeedbackReviewType(enum.Enum): UNREVIEWED = "u" -def set_feedback_as_reviewed(feedback: List, id: str, unit: Unit) -> None: +def _set_feedback_as_reviewed(feedback: List, id: str, unit: Unit) -> None: """Sets the reviewed property of a feedback to true""" assigned_agent = unit.get_assigned_agent() feedback_ids = [feedback_obj["id"] for feedback_obj in feedback] @@ -51,7 +57,7 @@ def set_feedback_as_reviewed(feedback: List, id: str, unit: Unit) -> None: assigned_agent.state.update_metadata(property_name="feedback", property_value=feedback) -def print_out_reviewed_feedback_elements( +def _print_out_reviewed_feedback_elements( filtered_feedback_list: List[Dict[str, Any]], agent: Agent ) -> None: if agent is not None: @@ -78,7 +84,7 @@ def print_out_reviewed_feedback_elements( console.print(feedback_table) -def print_out_unreviewed_feedback_elements( +def _print_out_unreviewed_feedback_elements( filtered_feedback_list: List[Dict[str, Any]], unit: Unit, feedback: List[Dict[str, Any]], @@ -90,7 +96,9 @@ def print_out_unreviewed_feedback_elements( feedback_table = Table( "Property", "Value", - title="Unreviewed Feedback {current_feedback} of {total_feedback} From Agent {agent_id}".format( + title=( + "Unreviewed Feedback {current_feedback} of {total_feedback} From Agent {agent_id}" + ).format( current_feedback=i + 1, total_feedback=len(filtered_feedback_list), agent_id=agent.get_agent_id() if agent is not None else "-1", @@ -112,7 +120,7 @@ def print_out_unreviewed_feedback_elements( show_default=False, ) if mark_feedback_as_reviewed == FeedbackReviewType.YES.value: - set_feedback_as_reviewed(feedback, feedback_obj["id"], unit) + _set_feedback_as_reviewed(feedback, feedback_obj["id"], unit) print("\nMarked the feedback as reviewed!") elif mark_feedback_as_reviewed == FeedbackReviewType.NO.value: @@ -123,6 +131,7 @@ def print_out_unreviewed_feedback_elements( def main(): + warnings.warn("No longer supported.", DeprecationWarning) db = LocalMephistoDB() mephisto_data_browser = MephistoDataBrowser(db) task_names = mephisto_data_browser.get_task_name_list() @@ -159,7 +168,11 @@ def main(): console.print(questions_markdown) # Making sure that the filter index is valid filter_by_question_index = IntPrompt.ask( - '\nIf you want to filter feedback by a question, then enter the question number to filter on.\nIf you want to see feedback to all questions, enter "-1" (Default: -1)', + ( + "\nIf you want to filter feedback by a question, " + "then enter the question number to filter on." + '\nIf you want to see feedback to all questions, enter "-1" (Default: -1)' + ), choices=[str(i) for i in range(-1, len(questions_list))], default=-1, show_default=False, @@ -222,11 +235,11 @@ def main(): # Filter the toxicity feedback to get reviewed feedback reviewed_feedback = list( filter( - lambda feedback_obj: feedback_obj["reviewed"] == True, + lambda feedback_obj: feedback_obj["reviewed"] is True, filtered_feedback, ) ) - print_out_reviewed_feedback_elements( + _print_out_reviewed_feedback_elements( filtered_feedback_list=reviewed_feedback, agent=unit.get_assigned_agent(), ) @@ -234,11 +247,11 @@ def main(): # Filter the toxicity feedback to get unreviewed feedback un_reviewed_feedback = list( filter( - lambda feedback_obj: feedback_obj["reviewed"] == False, + lambda feedback_obj: feedback_obj["reviewed"] is False, filtered_feedback, ) ) - print_out_unreviewed_feedback_elements( + _print_out_unreviewed_feedback_elements( filtered_feedback_list=un_reviewed_feedback, unit=unit, feedback=feedback, diff --git a/mephisto/scripts/local_db/review_tips_for_task.py b/mephisto/scripts/local_db/review_tips_for_task.py index 8c4571ff0..bfb95d9ff 100644 --- a/mephisto/scripts/local_db/review_tips_for_task.py +++ b/mephisto/scripts/local_db/review_tips_for_task.py @@ -16,23 +16,29 @@ It also removed the row in the assets/tips.csv file in your task's directory. """ import csv +import enum +import warnings from genericpath import exists from pathlib import Path -from typing import Any, List, Dict, Optional +from typing import Any +from typing import Dict +from typing import List +from typing import Optional + +from rich import box +from rich import print +from rich.prompt import FloatPrompt +from rich.prompt import Prompt +from rich.table import Table + from mephisto.abstractions.databases.local_database import LocalMephistoDB from mephisto.data_model.agent import Agent from mephisto.data_model.task_run import TaskRun from mephisto.data_model.unit import Unit from mephisto.data_model.worker import Worker from mephisto.tools.data_browser import DataBrowser as MephistoDataBrowser -from rich import print -from rich import box -from rich.prompt import Prompt -from rich.prompt import FloatPrompt -from rich.table import Table from mephisto.tools.scripts import print_out_task_names from mephisto.utils.rich import console -import enum class TipsReviewType(enum.Enum): @@ -41,14 +47,14 @@ class TipsReviewType(enum.Enum): SKIP = "s" -def get_index_of_value(lst: List[str], property: str) -> int: +def get_index_of_value(lst: List[str], _property: str) -> int: for i in range(len(lst)): - if lst[i] == property: + if lst[i] == _property: return i return 0 -def add_row_to_tips_file(task_run: TaskRun, item_to_add: Dict[str, Any]): +def _add_row_to_tips_file(task_run: TaskRun, item_to_add: Dict[str, Any]): """Adds a row the tips csv file""" blueprint_task_run_args = task_run.args["blueprint"] if "tips_location" in blueprint_task_run_args: @@ -89,7 +95,7 @@ def remove_tip_from_metadata( quit() -def accept_tip(tips: List, tips_copy: List, i: int, unit: Unit) -> None: +def _accept_tip(tips: List, tips_copy: List, i: int, unit: Unit) -> None: """Accepts a tip in metadata""" tips_id = [tip_obj["id"] for tip_obj in tips_copy] # gets the index of the tip in the tip_copy list @@ -98,11 +104,13 @@ def accept_tip(tips: List, tips_copy: List, i: int, unit: Unit) -> None: if assigned_agent is not None: tips_copy[index_to_update]["accepted"] = True - add_row_to_tips_file(unit.get_task_run(), tips_copy[index_to_update]) + _add_row_to_tips_file(unit.get_task_run(), tips_copy[index_to_update]) assigned_agent.state.update_metadata(property_name="tips", property_value=tips_copy) def main(): + warnings.warn("No longer supported.", DeprecationWarning) + db = LocalMephistoDB() mephisto_data_browser = MephistoDataBrowser(db) task_names = mephisto_data_browser.get_task_name_list() @@ -157,7 +165,7 @@ def main(): print("") if tip_response == TipsReviewType.ACCEPTED.value: # persists the tip in the db as it is accepted - accept_tip(tips, tips_copy, i, unit) + _accept_tip(tips, tips_copy, i, unit) print("[green]Tip Accepted[/green]") # given the option to pay a bonus to the worker who wrote the tip bonus = FloatPrompt.ask( diff --git a/mephisto/tools/data_browser.py b/mephisto/tools/data_browser.py index 1d4add732..85072acd2 100644 --- a/mephisto/tools/data_browser.py +++ b/mephisto/tools/data_browser.py @@ -82,7 +82,8 @@ def get_units_for_run_id(self, run_id: str) -> List[Unit]: task_run = TaskRun.get(self.db, run_id) return self._get_units_for_task_runs([task_run]) - def get_data_from_unit(self, unit: Unit) -> Dict[str, Any]: + @staticmethod + def get_data_from_unit(unit: Unit) -> Dict[str, Any]: """ Return a dict containing all data associated with the given unit, including its status, data, and start and end time. diff --git a/mephisto/tools/scripts.py b/mephisto/tools/scripts.py index 36582d05f..e84a7fd2d 100644 --- a/mephisto/tools/scripts.py +++ b/mephisto/tools/scripts.py @@ -8,37 +8,37 @@ Utilities that are useful for Mephisto-related scripts. """ +import functools +import os +import subprocess +from typing import Any +from typing import Callable +from typing import cast +from typing import List +from typing import Optional +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar + +import hydra +from omegaconf import DictConfig +from omegaconf import OmegaConf +from rich import print +from rich.markdown import Markdown + from mephisto.abstractions.databases.local_database import LocalMephistoDB +from mephisto.abstractions.databases.local_singleton_database import MephistoSingletonDB from mephisto.abstractions.providers.mturk.mturk_utils import try_prerun_cleanup +from mephisto.operations.hydra_config import build_default_task_config +from mephisto.operations.hydra_config import register_script_config +from mephisto.operations.hydra_config import TaskConfig from mephisto.operations.operator import Operator -from mephisto.abstractions.databases.local_singleton_database import MephistoSingletonDB +from mephisto.utils.dirs import get_root_data_dir +from mephisto.utils.dirs import get_run_file_dir from mephisto.utils.logger_core import format_loud -from mephisto.utils.testing import get_mock_requester -from mephisto.utils.dirs import get_root_data_dir, get_run_file_dir -from mephisto.operations.hydra_config import ( - build_default_task_config, - register_script_config, - TaskConfig, -) -from rich.markdown import Markdown from mephisto.utils.rich import console -from omegaconf import DictConfig, OmegaConf -import functools -import hydra -import subprocess -from typing import ( - List, - Tuple, - Any, - Type, - TypeVar, - Callable, - Optional, - cast, - TYPE_CHECKING, -) -import os -from rich import print +from mephisto.utils.testing import get_mock_requester if TYPE_CHECKING: from mephisto.abstractions.database import MephistoDB @@ -305,7 +305,8 @@ def print_out_task_names(header: str, task_names: List[str]) -> None: """Prints out task names and formats them nicely using rich""" if len(task_names) == 0: print( - "\n[red]There are no task names found[/red] \nYou should launch a task first and then run this script after the task is shut down\n" + "\n[red]There are no task names found[/red]\n" + "You should launch a task first and then run this script after the task is shut down\n" ) quit() task_names_text = """# {header} \n ## Task Names:""".format(header=header) diff --git a/mephisto/utils/agent_metadata.py b/mephisto/utils/agent_metadata.py index 25ac47108..eb6697228 100644 --- a/mephisto/utils/agent_metadata.py +++ b/mephisto/utils/agent_metadata.py @@ -4,6 +4,7 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +import warnings from uuid import uuid4 from mephisto.abstractions._subcomponents.agent_state import AgentState @@ -39,6 +40,7 @@ def _save_metadata_without_transforming(data: dict, state: AgentState): def _save_tips(data: dict, state: AgentState): """Handles the metadata submission of a tip""" + warnings.warn("No longer supported.", DeprecationWarning) tips_property_name = "tips" @@ -67,6 +69,7 @@ def _save_tips(data: dict, state: AgentState): def _save_feedback(data: dict, state: AgentState): """Handles the metadata submission of a feedback""" + warnings.warn("No longer supported.", DeprecationWarning) feedback_property_name = "feedback" diff --git a/mephisto/abstractions/providers/prolific/api/status.py b/mephisto/utils/http_status.py similarity index 100% rename from mephisto/abstractions/providers/prolific/api/status.py rename to mephisto/utils/http_status.py diff --git a/packages/mephisto-task-addons/src/WorkerOpinion/WorkerOpinion.js b/packages/mephisto-task-addons/src/WorkerOpinion/WorkerOpinion.js index aab50cbed..8e481be78 100644 --- a/packages/mephisto-task-addons/src/WorkerOpinion/WorkerOpinion.js +++ b/packages/mephisto-task-addons/src/WorkerOpinion/WorkerOpinion.js @@ -16,6 +16,8 @@ import TextArea from "./TextArea"; const DEFAULT_MAX_TEXT_LENGTH = 700; const DEFAULT_TEXTAREA_ROWS = 3; const DEFAULT_TEXTAREA_WIDTH = "100%"; +const DEFAULT_WIDGET_REQUIRED = false; +const DEFAULT_WIDGET_TITLE = "Your Feedback"; const defaultStateValue = { status: 0, text: "", @@ -27,6 +29,8 @@ function WorkerOpinion({ handleSubmit, maxTextLength, textAreaWidth, + title, + required, }) { // To make classNames more readable const stylePrefix = `mephisto-task-addons-worker-opinion__`; @@ -38,6 +42,8 @@ function WorkerOpinion({ const modifiedTextAreaWidth = textAreaWidth ? textAreaWidth : DEFAULT_TEXTAREA_WIDTH; + const widgetTitle = title || DEFAULT_WIDGET_TITLE; + const widgetRequired = required || DEFAULT_WIDGET_REQUIRED; // For when there are questions const [questionsTexts, setQuestionsTexts] = useState([]); @@ -78,8 +84,10 @@ function WorkerOpinion({

- Write opinion - (optional) + {widgetTitle} + {!widgetRequired && ( + (optional) + )}

diff --git a/packages/mephisto-worker-addons/README.md b/packages/mephisto-worker-addons/README.md index 4ee91d6f5..8616e849c 100644 --- a/packages/mephisto-worker-addons/README.md +++ b/packages/mephisto-worker-addons/README.md @@ -6,6 +6,8 @@ # mephisto-worker-addons +> NOTE: No longer supported package. Will be removed soon. See `mephisto-task-addons`. + The Tips component will allow task authors to set up a communication channel to solicit "tips" from workers to share with other workers, thus allowing for the "crowdsourcing" of the instructions for tasks as well. We find that workers sometimes will share these tips in third-party forums or via emails to the task authors. This feature creates a more vetted channel for such communication. ## Installation diff --git a/test/abstractions/providers/prolific/api/test_base_api_resourse.py b/test/abstractions/providers/prolific/api/test_base_api_resourse.py index cfd407ec7..1f7d56397 100644 --- a/test/abstractions/providers/prolific/api/test_base_api_resourse.py +++ b/test/abstractions/providers/prolific/api/test_base_api_resourse.py @@ -13,7 +13,7 @@ from requests import Response from requests.exceptions import HTTPError -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.abstractions.providers.prolific.api.base_api_resource import BaseAPIResource from mephisto.abstractions.providers.prolific.api.base_api_resource import HTTPMethod from mephisto.abstractions.providers.prolific.api.exceptions import ProlificAPIKeyError @@ -46,7 +46,7 @@ def test__base_request_success(self, mock_requests_get, *args): mock_response = Response() mock_response.raise_for_status = lambda: None - mock_response.status_code = status.HTTP_200_OK + mock_response.status_code = http_status.HTTP_200_OK mock_response._content = content mock_requests_get.return_value = mock_response @@ -80,7 +80,7 @@ def test__base_request_success_no_content(self, mock_requests_get, *args): mock_response = Response() mock_response.raise_for_status = lambda: None - mock_response.status_code = status.HTTP_204_NO_CONTENT + mock_response.status_code = http_status.HTTP_204_NO_CONTENT mock_response._content = content mock_requests_get.return_value = mock_response @@ -160,7 +160,7 @@ def test__base_request_request_httperror(self, mock_requests_get, *args): mock_response = Response() mock_response.raise_for_status = lambda: None - mock_response.status_code = status.HTTP_204_NO_CONTENT + mock_response.status_code = http_status.HTTP_204_NO_CONTENT mock_response._content = content error_message = "Error" @@ -199,7 +199,7 @@ def test__base_request_request_httperror_unauthorized(self, mock_requests_get, * mock_response = Response() mock_response.raise_for_status = lambda: None - mock_response.status_code = status.HTTP_401_UNAUTHORIZED + mock_response.status_code = http_status.HTTP_401_UNAUTHORIZED mock_response._content = content error_message = "Error" @@ -268,7 +268,7 @@ def test_get(self, mock_requests_get, *args): mock_response = Response() mock_response.raise_for_status = lambda: None - mock_response.status_code = status.HTTP_200_OK + mock_response.status_code = http_status.HTTP_200_OK mock_response._content = content mock_requests_get.return_value = mock_response @@ -293,7 +293,7 @@ def test_post(self, mock_requests_post, *args): mock_response = Response() mock_response.raise_for_status = lambda: None - mock_response.status_code = status.HTTP_200_OK + mock_response.status_code = http_status.HTTP_200_OK mock_response._content = content mock_requests_post.return_value = mock_response @@ -318,7 +318,7 @@ def test_patch(self, mock_requests_patch, *args): mock_response = Response() mock_response.raise_for_status = lambda: None - mock_response.status_code = status.HTTP_200_OK + mock_response.status_code = http_status.HTTP_200_OK mock_response._content = content mock_requests_patch.return_value = mock_response @@ -343,7 +343,7 @@ def test_delete(self, mock_requests_delete, *args): mock_response = Response() mock_response.raise_for_status = lambda: None - mock_response.status_code = status.HTTP_200_OK + mock_response.status_code = http_status.HTTP_200_OK mock_response._content = content mock_requests_delete.return_value = mock_response diff --git a/test/review_app/server/api/test_grant_workers_view.py b/test/review_app/server/api/test_grant_workers_view.py index 498171068..c906ea346 100644 --- a/test/review_app/server/api/test_grant_workers_view.py +++ b/test/review_app/server/api/test_grant_workers_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit from mephisto.utils.testing import find_unit_reviews @@ -48,7 +48,7 @@ def test_grant_success(self, *args, **kwargs): result = response.json unit_reviews = find_unit_reviews(self.db, qualification_id, worker_id, unit.task_id) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result, {}) self.assertEqual(len(unit_reviews), 1) self.assertEqual(unit_reviews[0]["updated_qualification_id"], qualification_id) @@ -81,7 +81,7 @@ def test_grant_no_unit_ids_error(self, *args, **kwargs): response = self.client.post(url, json={}) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual(result["error"], 'Field "unit_ids" is required.') diff --git a/test/review_app/server/api/test_home_view.py b/test/review_app/server/api/test_home_view.py index b8ab34b67..71bba8584 100644 --- a/test/review_app/server/api/test_home_view.py +++ b/test/review_app/server/api/test_home_view.py @@ -9,7 +9,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from test.review_app.server.api.base_test_api_view_case import BaseTestApiViewCase @@ -19,7 +19,7 @@ def test_redirect_success(self, *args, **kwargs): url = url_for("client-home") response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_302_FOUND) + self.assertEqual(response.status_code, http_status.HTTP_302_FOUND) @patch("os.path.join") def test_home_success(self, mock_join, *args, **kwargs): @@ -36,7 +36,7 @@ def test_home_success(self, mock_join, *args, **kwargs): url = url_for("client-tasks", path="tasks") response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(response.data, ui_html_file_data.encode()) self.assertEqual(response.mimetype, "text/html") @@ -48,7 +48,7 @@ def test_home__no_html_file_error(self, mock_join, *args, **kwargs): url = url_for("client-tasks", path="tasks") response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual( response.json["error"], "UI interface isn't ready to use. Build it or use separate address for dev UI server.", diff --git a/test/review_app/server/api/test_qualification_workers_view.py b/test/review_app/server/api/test_qualification_workers_view.py index ca7b9f61a..f27e0a93a 100644 --- a/test/review_app/server/api/test_qualification_workers_view.py +++ b/test/review_app/server/api/test_qualification_workers_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit from mephisto.utils.testing import get_test_qualification @@ -27,7 +27,7 @@ def test_qualification_list_no_workers_success(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["workers"]), 0) def test_qualification_list_one_worker_success(self, *args, **kwargs): @@ -56,7 +56,7 @@ def test_qualification_list_one_worker_success(self, *args, **kwargs): result = response.json first_worker = result["workers"][0] - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["workers"]), 1) self.assertEqual(first_worker["worker_id"], worker_id) self.assertEqual(first_worker["value"], qualification_value) diff --git a/test/review_app/server/api/test_qualifications_view.py b/test/review_app/server/api/test_qualifications_view.py index d0e0dcfb0..8ad73a705 100644 --- a/test/review_app/server/api/test_qualifications_view.py +++ b/test/review_app/server/api/test_qualifications_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.utils.testing import get_test_qualification from test.review_app.server.api.base_test_api_view_case import BaseTestApiViewCase @@ -22,7 +22,7 @@ def test_qualification_list_one_qualification_success(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["qualifications"]), 1) self.assertEqual(result["qualifications"][0]["id"], qualification_id) @@ -32,7 +32,7 @@ def test_qualification_list_empty_success(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["qualifications"]), 0) def test_qualification_create_success(self, *args, **kwargs): @@ -43,7 +43,7 @@ def test_qualification_create_success(self, *args, **kwargs): response = self.client.post(url, json={"name": qualification_name}) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result["name"], qualification_name) self.assertTrue("id" in result) @@ -53,7 +53,7 @@ def test_qualification_create_no_passed_name_error(self, *args, **kwargs): response = self.client.post(url, json={}) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual(result["error"], 'Field "name" is required.') def test_qualification_create_already_exists_error(self, *args, **kwargs): @@ -65,7 +65,7 @@ def test_qualification_create_already_exists_error(self, *args, **kwargs): response = self.client.post(url, json={"name": qualification_name}) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual( result["error"], f'Qualification with name "{qualification_name}" already exists.', diff --git a/test/review_app/server/api/test_revoke_workers_view.py b/test/review_app/server/api/test_revoke_workers_view.py index 849f9529b..e4cb42183 100644 --- a/test/review_app/server/api/test_revoke_workers_view.py +++ b/test/review_app/server/api/test_revoke_workers_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit from mephisto.utils.testing import find_unit_reviews @@ -48,7 +48,7 @@ def test_grant_success(self, *args, **kwargs): result = response.json unit_reviews = find_unit_reviews(self.db, qualification_id, worker_id, unit.task_id) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result, {}) self.assertEqual(len(unit_reviews), 1) self.assertEqual(unit_reviews[0]["revoked_qualification_id"], qualification_id) @@ -81,7 +81,7 @@ def test_grant_no_unit_ids_error(self, *args, **kwargs): response = self.client.post(url, json={}) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual(result["error"], 'Field "unit_ids" is required.') diff --git a/test/review_app/server/api/test_stats_view.py b/test/review_app/server/api/test_stats_view.py index 1cade74c2..bd66a67f7 100644 --- a/test/review_app/server/api/test_stats_view.py +++ b/test/review_app/server/api/test_stats_view.py @@ -9,7 +9,7 @@ from flask import url_for from mephisto.abstractions._subcomponents.agent_state import AgentState -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit from mephisto.utils.testing import get_test_qualification @@ -35,7 +35,7 @@ def test_stats_success(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual( result["stats"], { diff --git a/test/review_app/server/api/test_task_export_results_json_view.py b/test/review_app/server/api/test_task_export_results_json_view.py index d84355a83..f8c2be0ed 100644 --- a/test/review_app/server/api/test_task_export_results_json_view.py +++ b/test/review_app/server/api/test_task_export_results_json_view.py @@ -9,7 +9,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.review_app.server.api.views.task_export_results_view import get_result_file_path from test.review_app.server.api.base_test_api_view_case import BaseTestApiViewCase @@ -32,7 +32,7 @@ def test_task_export_result_json_success(self, mock_get_results_dir, *args, **kw url = url_for("task_export_results_json", task_id=task_id, n_units=n_units) response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(response.data, results_file_data.encode()) self.assertEqual(response.mimetype, "application/json") @@ -45,8 +45,8 @@ def test_task_export_result_json_not_found_error(self, *args, **kwargs): url = url_for("task_export_results_json", task_id=1, n_units=99) response2 = self.client.get(url) - self.assertEqual(response1.status_code, status.HTTP_404_NOT_FOUND) - self.assertEqual(response2.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(response1.status_code, http_status.HTTP_404_NOT_FOUND) + self.assertEqual(response2.status_code, http_status.HTTP_404_NOT_FOUND) if __name__ == "__main__": diff --git a/test/review_app/server/api/test_task_export_results_view.py b/test/review_app/server/api/test_task_export_results_view.py index 5d44bc8d2..3dc3f093a 100644 --- a/test/review_app/server/api/test_task_export_results_view.py +++ b/test/review_app/server/api/test_task_export_results_view.py @@ -9,7 +9,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.review_app.server.api.views.task_export_results_view import get_result_file_path from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit @@ -51,7 +51,7 @@ def test_task_export_result_success(self, mock_get_results_dir, *args, **kwargs) response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result, {"file_created": True}) self.assertEqual(response.mimetype, "application/json") @@ -60,7 +60,7 @@ def test_task_export_result_not_found_error(self, *args, **kwargs): url = url_for("task_export_results", task_id=999) response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, http_status.HTTP_404_NOT_FOUND) def test_task_export_result_not_reviews_error(self, *args, **kwargs): unit_id = get_test_unit(self.db) @@ -72,7 +72,7 @@ def test_task_export_result_not_reviews_error(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual( result["error"], ( diff --git a/test/review_app/server/api/test_task_stats_results_view.py b/test/review_app/server/api/test_task_stats_results_view.py index bed10b84d..b623466f6 100644 --- a/test/review_app/server/api/test_task_stats_results_view.py +++ b/test/review_app/server/api/test_task_stats_results_view.py @@ -10,7 +10,7 @@ from flask import url_for from omegaconf import OmegaConf -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.task import Task from mephisto.data_model.task_run import TaskRunArgs from mephisto.data_model.unit import Unit @@ -45,7 +45,7 @@ def test_task_stats_result_not_found_error(self, *args, **kwargs): url = url_for("task_stats_results", task_id=999) response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, http_status.HTTP_404_NOT_FOUND) def test_task_stats_result_not_reviews_error(self, *args, **kwargs): _, task_id = get_test_task(self.db) @@ -55,7 +55,7 @@ def test_task_stats_result_not_reviews_error(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual( result["error"], "This task has never been launched before.", @@ -72,7 +72,7 @@ def test_task_stats_result_not_form_composer(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual( result["error"], "Statistics for tasks of this type are not yet supported.", @@ -101,7 +101,7 @@ def test_task_stats_result_success(self, mock_collect_task_stats, *args, **kwarg response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result, expected_value) diff --git a/test/review_app/server/api/test_task_timeline_view.py b/test/review_app/server/api/test_task_timeline_view.py new file mode 100644 index 000000000..373639fe3 --- /dev/null +++ b/test/review_app/server/api/test_task_timeline_view.py @@ -0,0 +1,92 @@ +#!/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 unittest.mock import patch + +from flask import url_for +from requests import HTTPError +from requests import Response + +from mephisto.utils import http_status +from mephisto.utils.metrics import GRAFANA_PORT +from mephisto.utils.testing import get_test_task +from test.review_app.server.api.base_test_api_view_case import BaseTestApiViewCase + + +class TestTaskTimelineView(BaseTestApiViewCase): + def test_task_timeline_not_found_error(self, *args, **kwargs): + with self.app_context: + url = url_for("task_timeline", task_id=999) + response = self.client.get(url) + + self.assertEqual(response.status_code, http_status.HTTP_404_NOT_FOUND) + + @patch("requests.get") + def test_task_timeline_server_down(self, mock_requests_get, *args, **kwargs): + def mock_raise_for_status(*args, **kwargs): + raise HTTPError("Test") + + mock_response = Response() + mock_response.raise_for_status = mock_raise_for_status + mock_response.status_code = http_status.HTTP_400_BAD_REQUEST + mock_response._content = "" + + mock_requests_get.return_value = mock_response + + _, task_id = get_test_task(self.db) + + with self.app_context: + url = url_for("task_timeline", task_id=task_id) + response = self.client.get(url) + + self.assertEqual(response.status_code, http_status.HTTP_200_OK) + self.assertEqual( + response.json, + {"dashboard_url": None, "server_is_available": False, "task_name": "test_task"}, + ) + + @patch("mephisto.utils.metrics.get_default_dashboard_url") + @patch("requests.get") + def test_task_timeline_success( + self, + mock_requests_get, + mock_get_default_dashboard_url, + *args, + **kwargs, + ): + expected_dashboard_url = f"localhost:{GRAFANA_PORT}/test" + + def mock_raise_for_status(*args, **kwargs): + return + + mock_response = Response() + mock_response.raise_for_status = mock_raise_for_status + mock_response.status_code = http_status.HTTP_200_OK + mock_response._content = "" + + mock_requests_get.return_value = mock_response + mock_get_default_dashboard_url.return_value = expected_dashboard_url + + _, task_id = get_test_task(self.db) + + with self.app_context: + url = url_for("task_timeline", task_id=task_id) + response = self.client.get(url) + + self.assertEqual(response.status_code, http_status.HTTP_200_OK) + self.assertEqual( + response.json, + { + "dashboard_url": f"http://{expected_dashboard_url}", + "server_is_available": True, + "task_name": "test_task", + }, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/review_app/server/api/test_task_unit_ids_view.py b/test/review_app/server/api/test_task_unit_ids_view.py index 63d32fc0b..73859aa45 100644 --- a/test/review_app/server/api/test_task_unit_ids_view.py +++ b/test/review_app/server/api/test_task_unit_ids_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit from mephisto.utils.testing import get_test_task @@ -27,7 +27,7 @@ def test_no_units_success(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["worker_units_ids"]), 0) def test_one_unit_success(self, *args, **kwargs): @@ -43,7 +43,7 @@ def test_one_unit_success(self, *args, **kwargs): result = response.json first_worker_unit_ids = result["worker_units_ids"][0] - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["worker_units_ids"]), 1) self.assertEqual(first_worker_unit_ids["worker_id"], worker_id) self.assertEqual(first_worker_unit_ids["unit_id"], unit_id) diff --git a/test/review_app/server/api/test_task_view.py b/test/review_app/server/api/test_task_view.py index c580c4b71..7e4f679ed 100644 --- a/test/review_app/server/api/test_task_view.py +++ b/test/review_app/server/api/test_task_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.utils.testing import get_test_task from test.review_app.server.api.base_test_api_view_case import BaseTestApiViewCase @@ -20,7 +20,7 @@ def test_no_task_error(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, http_status.HTTP_404_NOT_FOUND) self.assertEqual(result, {"error": "Not found"}) def test_one_task_success(self, *args, **kwargs): @@ -31,7 +31,7 @@ def test_one_task_success(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result["id"], task_id) self.assertEqual(result["name"], task_name) self.assertTrue("created_at" in result) diff --git a/test/review_app/server/api/test_task_worker_opinions_view.py b/test/review_app/server/api/test_task_worker_opinions_view.py new file mode 100644 index 000000000..13b36e42d --- /dev/null +++ b/test/review_app/server/api/test_task_worker_opinions_view.py @@ -0,0 +1,176 @@ +#!/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 unittest.mock import patch + +from flask import url_for + +from mephisto.data_model.task import Task +from mephisto.data_model.unit import Unit +from mephisto.utils import http_status +from mephisto.utils.testing import get_test_task +from mephisto.utils.testing import get_test_task_run +from mephisto.utils.testing import get_test_worker +from mephisto.utils.testing import make_completed_unit +from test.review_app.server.api.base_test_api_view_case import BaseTestApiViewCase + + +class TestTaskWorkerOpinionsView(BaseTestApiViewCase): + def test_task_worker_opinions_not_found_error(self, *args, **kwargs): + with self.app_context: + url = url_for("task_worker_opinions", task_id=999) + response = self.client.get(url) + + self.assertEqual(response.status_code, http_status.HTTP_404_NOT_FOUND) + + def test_task_worker_opinions_no_units(self, *args, **kwargs): + task_name, task_id = get_test_task(self.db) + + with self.app_context: + url = url_for("task_worker_opinions", task_id=task_id) + response = self.client.get(url) + + self.assertEqual(response.status_code, http_status.HTTP_200_OK) + self.assertEqual( + response.json, + { + "task_name": task_name, + "worker_opinions": [], + }, + ) + + def test_task_worker_opinions_success_with_units_without_opinions(self, *args, **kwargs): + _, worker_id = get_test_worker(self.db) + get_test_task_run(self.db) + unit_id = make_completed_unit(self.db) + unit: Unit = Unit.get(self.db, unit_id) + task: Task = Task.get(self.db, unit.task_id) + + with self.app_context: + url = url_for("task_worker_opinions", task_id=task.db_id) + response = self.client.get(url) + + self.assertEqual(response.status_code, http_status.HTTP_200_OK) + self.assertEqual( + response.json, + { + "task_name": task.task_name, + "worker_opinions": [], + }, + ) + + @patch("mephisto.tools.data_browser.DataBrowser.get_data_from_unit") + def test_task_worker_opinions_success_with_units_with_opinions( + self, + mock_get_data_from_unit, + *args, + **kwargs, + ): + expected_attachment_1 = { + "destination": "/tmp/", + "encoding": "7bit", + "fieldname": "attachments", + "filename": "1-2-files-file-1.png", + "mimetype": "image/png", + "originalname": "file-1.png", + "path": "/tmp/1-2-files-file-1.png", + "size": 11111, + } + expected_attachment_2 = { + "destination": "/tmp/", + "encoding": "7bit", + "fieldname": "attachments", + "filename": "1-2-files-file-2.png", + "mimetype": "image/png", + "originalname": "file-2.png", + "path": "/tmp/1-2-files-file-2.png", + "size": 22222, + } + expected_question_1 = { + "answer": "yes", + "id": "7f352128-848d-4638-b465-3ff93142c01d", + "question": "Was this task hard?", + "reviewed": False, + "toxicity": None, + } + expected_question_2 = { + "answer": "great", + "id": "0ef341a8-b93a-4d65-8487-588f0c8fe0e5", + "question": "Is this a good example?", + "reviewed": False, + "toxicity": None, + } + + _, worker_id = get_test_worker(self.db) + get_test_task_run(self.db) + unit_id = make_completed_unit(self.db) + unit: Unit = Unit.get(self.db, unit_id) + task: Task = Task.get(self.db, unit.task_id) + + agent = unit.get_assigned_agent() + mock_get_data_from_unit.return_value = { + "assignment_id": unit.assignment_id, + "data": {}, + "metadata": { + "worker_opinion": { + "attachments": [ + expected_attachment_1, + expected_attachment_2, + ], + "attachments_by_fields": { + "attachments": [ + expected_attachment_1, + expected_attachment_2, + ], + }, + "questions": [ + expected_question_1, + expected_question_2, + ], + }, + }, + "status": agent.db_status, + "task_end": agent.state.get_task_end(), + "task_start": agent.state.get_task_start(), + "unit_id": unit.db_id, + "worker_id": agent.worker_id, + "tips": agent.state.get_tips(), + "feedback": agent.state.get_feedback(), + } + + with self.app_context: + url = url_for("task_worker_opinions", task_id=task.db_id) + response = self.client.get(url) + + self.assertEqual(response.status_code, http_status.HTTP_200_OK) + self.assertEqual( + response.json, + { + "task_name": task.task_name, + "worker_opinions": [ + { + "data": { + "attachments": [ + expected_attachment_1, + expected_attachment_2, + ], + "questions": [ + expected_question_1, + expected_question_2, + ], + }, + "unit_data_folder": agent.get_data_dir(), + "unit_id": unit.db_id, + "worker_id": unit.worker_id, + }, + ], + }, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/review_app/server/api/test_tasks_view.py b/test/review_app/server/api/test_tasks_view.py index e82b284ed..c6f22bcc5 100644 --- a/test/review_app/server/api/test_tasks_view.py +++ b/test/review_app/server/api/test_tasks_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.utils.testing import get_test_task from test.review_app.server.api.base_test_api_view_case import BaseTestApiViewCase @@ -20,7 +20,7 @@ def test_no_tasks_success(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result["tasks"], []) def test_one_task_success(self, *args, **kwargs): @@ -32,7 +32,7 @@ def test_one_task_success(self, *args, **kwargs): result = response.json first_response_task = result["tasks"][0] - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["tasks"]), 1) self.assertEqual(first_response_task["id"], task_id) self.assertEqual(first_response_task["name"], task_name) diff --git a/test/review_app/server/api/test_unit_bundle_js_view.py b/test/review_app/server/api/test_unit_bundle_js_view.py index 36f2daaec..9a5e8bef5 100644 --- a/test/review_app/server/api/test_unit_bundle_js_view.py +++ b/test/review_app/server/api/test_unit_bundle_js_view.py @@ -9,7 +9,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.operations.hydra_config import MephistoConfig from mephisto.utils.testing import get_test_unit from mephisto.utils.testing import MOCK_ARCHITECT_ARGS @@ -53,7 +53,7 @@ def test_bundle_js_success(self, *args, **kwargs): url = url_for("unit_review_bundle", unit_id=unit_id) response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(response.data, react_reviewapp_bundle_js_data.encode()) self.assertEqual(response.mimetype, "text/javascript") @@ -64,7 +64,7 @@ def test_bundle_js_not_found_error(self, *args, **kwargs): url = url_for("unit_review_bundle", unit_id=unit_id) response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, http_status.HTTP_404_NOT_FOUND) self.assertTrue("error" in response.json) diff --git a/test/review_app/server/api/test_unit_data_static_view.py b/test/review_app/server/api/test_unit_data_static_view.py index 71614a2d2..5742aef52 100644 --- a/test/review_app/server/api/test_unit_data_static_view.py +++ b/test/review_app/server/api/test_unit_data_static_view.py @@ -9,7 +9,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.agent import Agent from mephisto.utils.testing import get_test_agent from mephisto.utils.testing import get_test_unit @@ -24,7 +24,7 @@ def test_unit_data_static_no_agent_not_found_error(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, http_status.HTTP_404_NOT_FOUND) self.assertEqual(result["error"], "File not found") @patch("mephisto.data_model.unit.Unit.get_assigned_agent") @@ -44,7 +44,7 @@ def test_unit_data_static_with_agent_not_found_error( response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, http_status.HTTP_404_NOT_FOUND) self.assertEqual( result["error"], "The requested URL was not found on the server. " @@ -76,7 +76,7 @@ def test_unit_data_static_success_with_filename_from_fs( url = url_for("unit_data_static", unit_id=unit_id, filename=filename_from_fs) response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(response.mimetype, "text/plain") self.assertEqual(response.data, b"") @@ -108,12 +108,14 @@ def test_unit_data_static_success_with_filename_by_original_name( mock_get_parsed_data.return_value = { "inputs": {}, "outputs": { - "files": [ - { - "originalname": filename_original_name, - "filename": filename_from_fs, - }, - ], + "filesByFields": { + "files": [ + { + "originalname": filename_original_name, + "filename": filename_from_fs, + }, + ], + }, }, } @@ -121,7 +123,7 @@ def test_unit_data_static_success_with_filename_by_original_name( url = url_for("unit_data_static", unit_id=unit_id, filename=filename_original_name) response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(response.mimetype, "text/plain") self.assertEqual(response.data, b"") diff --git a/test/review_app/server/api/test_unit_html_view.py b/test/review_app/server/api/test_unit_html_view.py index 98f92f195..16a7487ba 100644 --- a/test/review_app/server/api/test_unit_html_view.py +++ b/test/review_app/server/api/test_unit_html_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.utils.testing import get_test_unit from test.review_app.server.api.base_test_api_view_case import BaseTestApiViewCase @@ -21,7 +21,7 @@ def test_html_success(self, *args, **kwargs): url = url_for("unit_review_html", unit_id=unit_id) response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertTrue( f'' in response.data.decode() ) diff --git a/test/review_app/server/api/test_units_approve_view.py b/test/review_app/server/api/test_units_approve_view.py index c6932158f..b3e39c2b1 100644 --- a/test/review_app/server/api/test_units_approve_view.py +++ b/test/review_app/server/api/test_units_approve_view.py @@ -9,7 +9,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit from mephisto.utils.testing import get_test_task_run @@ -25,7 +25,7 @@ def test_units_approve_no_unit_ids_passed_error(self, *args, **kwargs): response = self.client.post(url, json={}) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual(result["error"], "`unit_ids` parameter must be specified.") def test_units_approve_success(self, *args, **kwargs): @@ -45,7 +45,7 @@ def test_units_approve_success(self, *args, **kwargs): response = self.client.post(url, json={"unit_ids": [unit_id]}) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result, {}) self.assertEqual(unit.get_db_status(), AssignmentState.ACCEPTED) @@ -68,7 +68,7 @@ def test_units_approve_bonus_param_format_error(self, *args, **kwargs): with self.assertLogs(level="ERROR") as cm: response = self.client.post(url, json={"unit_ids": [unit_id], "bonus": bonus}) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertIn(f"Could not pay bonus. Passed value is invalid: {bonus}", cm.output[0]) @patch("mephisto.abstractions.providers.mock.mock_worker.MockWorker.bonus_worker") @@ -94,7 +94,7 @@ def test_units_approve_paying_bonus_error(self, mock_bonus_worker, *args, **kwar with self.assertLogs(level="ERROR") as cm: response = self.client.post(url, json={"unit_ids": [unit_id], "bonus": bonus}) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertIn(f"Could not pay bonus. Reason: {error_message}", cm.output[0]) @patch("mephisto.abstractions.providers.mock.mock_worker.MockWorker.bonus_worker") @@ -119,7 +119,7 @@ def test_units_approve_paying_bonus_exception_error(self, mock_bonus_worker, *ar with self.assertLogs(level="ERROR") as cm: response = self.client.post(url, json={"unit_ids": [unit_id], "bonus": bonus}) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertIn("Could not pay bonus. Unexpected error", cm.output[0]) diff --git a/test/review_app/server/api/test_units_details_view.py b/test/review_app/server/api/test_units_details_view.py index b22485551..29b5c6ad1 100644 --- a/test/review_app/server/api/test_units_details_view.py +++ b/test/review_app/server/api/test_units_details_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.utils.testing import get_test_unit from test.review_app.server.api.base_test_api_view_case import BaseTestApiViewCase @@ -20,7 +20,7 @@ def test_units_no_arguments_passed_error(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual(result["error"], "`unit_ids` parameter must be specified.") def test_units_parsing_arguments_error(self, *args, **kwargs): @@ -29,7 +29,7 @@ def test_units_parsing_arguments_error(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual(result["error"], "`unit_ids` must be a comma-separated list of integers.") def test_no_units_success(self, *args, **kwargs): @@ -38,7 +38,7 @@ def test_no_units_success(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["units"]), 0) def test_one_unit_success(self, *args, **kwargs): @@ -50,7 +50,7 @@ def test_one_unit_success(self, *args, **kwargs): result = response.json first_unit = result["units"][0] - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["units"]), 1) self.assertEqual(first_unit["id"], unit_id) diff --git a/test/review_app/server/api/test_units_reject_view.py b/test/review_app/server/api/test_units_reject_view.py index 8ce622ba2..ec1287ab2 100644 --- a/test/review_app/server/api/test_units_reject_view.py +++ b/test/review_app/server/api/test_units_reject_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit from mephisto.utils.testing import get_test_task_run @@ -24,7 +24,7 @@ def test_units_reject_no_unit_ids_passed_error(self, *args, **kwargs): response = self.client.post(url, json={}) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual(result["error"], "`unit_ids` parameter must be specified.") def test_units_reject_success(self, *args, **kwargs): @@ -44,7 +44,7 @@ def test_units_reject_success(self, *args, **kwargs): response = self.client.post(url, json={"unit_ids": [unit_id]}) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result, {}) self.assertEqual(unit.get_db_status(), AssignmentState.REJECTED) diff --git a/test/review_app/server/api/test_units_soft_reject_view.py b/test/review_app/server/api/test_units_soft_reject_view.py index 325c79131..6340a25f7 100644 --- a/test/review_app/server/api/test_units_soft_reject_view.py +++ b/test/review_app/server/api/test_units_soft_reject_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit from mephisto.utils.testing import get_test_task_run @@ -24,7 +24,7 @@ def test_units_soft_reject_no_unit_ids_passed_error(self, *args, **kwargs): response = self.client.post(url, json={}) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual(result["error"], "`unit_ids` parameter must be specified.") def test_units_soft_reject_success(self, *args, **kwargs): @@ -44,7 +44,7 @@ def test_units_soft_reject_success(self, *args, **kwargs): response = self.client.post(url, json={"unit_ids": [unit_id]}) result = response.json - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result, {}) self.assertEqual(unit.get_db_status(), AssignmentState.SOFT_REJECTED) diff --git a/test/review_app/server/api/test_units_view.py b/test/review_app/server/api/test_units_view.py index ee7ce4736..d007f6759 100644 --- a/test/review_app/server/api/test_units_view.py +++ b/test/review_app/server/api/test_units_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit from mephisto.utils.testing import get_test_unit @@ -22,7 +22,7 @@ def test_units_no_arguments_passed_error(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual( result["error"], "At least one of `task_id` or `unit_ids` parameters must be specified.", @@ -34,7 +34,7 @@ def test_no_units_success(self, *args, **kwargs): response = self.client.get(url) result = response.json - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, http_status.HTTP_404_NOT_FOUND) self.assertEqual(result["error"], "Not found") def test_one_unit_with_task_id_success(self, *args, **kwargs): @@ -50,7 +50,7 @@ def test_one_unit_with_task_id_success(self, *args, **kwargs): first_response_unit = result["units"][0] - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["units"]), 1) self.assertEqual(first_response_unit["id"], unit_id) @@ -95,7 +95,7 @@ def test_one_unit_with_unit_ids_success(self, *args, **kwargs): result = response.json first_response_unit = result["units"][0] - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["units"]), 1) self.assertEqual(first_response_unit["id"], unit_1_id) @@ -125,7 +125,7 @@ def test_two_units_with_unit_ids_success(self, *args, **kwargs): first_response_unit = result["units"][0] second_response_unit = result["units"][1] - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(len(result["units"]), 2) self.assertEqual(first_response_unit["id"], unit_1_id) self.assertEqual(second_response_unit["id"], unit_2_id) diff --git a/test/review_app/server/api/test_worker_block_view.py b/test/review_app/server/api/test_worker_block_view.py index 428825310..13de562b9 100644 --- a/test/review_app/server/api/test_worker_block_view.py +++ b/test/review_app/server/api/test_worker_block_view.py @@ -8,7 +8,7 @@ from flask import url_for -from mephisto.abstractions.providers.prolific.api import status +from mephisto.utils import http_status from mephisto.data_model.constants.assignment_state import AssignmentState from mephisto.data_model.unit import Unit from mephisto.data_model.worker import Worker @@ -28,7 +28,7 @@ def test_worker_block_no_review_note_passed_error(self, *args, **kwargs): response = self.client.post(url, json={}) result = response.json - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, http_status.HTTP_400_BAD_REQUEST) self.assertEqual(result["error"], "`review_note` parameter must be specified.") def test_worker_block_success(self, *args, **kwargs): @@ -58,7 +58,7 @@ def test_worker_block_success(self, *args, **kwargs): worker: Worker = Worker.get(self.db, worker_id) blocked_after = worker.is_blocked(requester) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, http_status.HTTP_200_OK) self.assertEqual(result, {}) self.assertFalse(blocked_before) self.assertTrue(blocked_after)