-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[WIP] Task review app - initial API commit
- Loading branch information
Showing
26 changed files
with
965 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
## Preview App Docs | ||
|
||
#### Run application | ||
|
||
```shell | ||
mephisto review_app --host 0.0.0.0 --port 5000 --debug True --provider prolific | ||
``` | ||
|
||
where | ||
|
||
- `-h`/`--host` - host address (optional, default: `"127.0.0.1"`) | ||
- `-p`/`--port` - port (optional, default: `5000`) | ||
- `-d`/`--debug` - debug (optional, default: `None`) | ||
- `-P`/`--provider` - provider (optional, default: `"prolific"`) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Copyright (c) Facebook, Inc. 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Copyright (c) Facebook, Inc. and its affiliates. | ||
# This source code is licensed under the MIT license found in the | ||
# LICENSE file in the root directory of this source tree. | ||
|
||
import json | ||
import os | ||
import traceback | ||
from logging.config import dictConfig | ||
from typing import Tuple | ||
|
||
from flask import Flask | ||
from werkzeug import Response | ||
from werkzeug.exceptions import HTTPException as WerkzeugHTTPException | ||
from werkzeug.utils import import_string | ||
|
||
from mephisto.abstractions.database import EntryDoesNotExistException | ||
from mephisto.abstractions.databases.local_database import LocalMephistoDB | ||
from mephisto.abstractions.providers.prolific.api import status | ||
from mephisto.abstractions.providers.prolific.api.exceptions import ProlificException | ||
from mephisto.tools.data_browser import DataBrowser | ||
from mephisto.utils.logger_core import get_logger | ||
from .urls import init_urls | ||
|
||
FLASK_SETTINGS_MODULE = os.environ.get( | ||
'FLASK_SETTINGS_MODULE', | ||
'mephisto.client.review_app.server.settings.base', | ||
) | ||
|
||
|
||
def create_app(provider: str) -> Flask: | ||
# Logging | ||
# TODO [Review APP]: Fix logging (it works in views only with `app.logger` somehow) | ||
flask_logger = get_logger('') | ||
settings = import_string(FLASK_SETTINGS_MODULE) | ||
dictConfig(settings.LOGGING) | ||
|
||
# Create and configure the app | ||
app = Flask(__name__) | ||
|
||
# Logger | ||
app.logger = flask_logger | ||
|
||
# Settings | ||
app.config.from_object(FLASK_SETTINGS_MODULE) | ||
|
||
# Databases | ||
app.db = LocalMephistoDB() | ||
app.data_browser = DataBrowser(db=app.db) | ||
app.datastore = app.db.get_datastore_for_provider(provider) | ||
|
||
# API URLS | ||
init_urls(app) | ||
|
||
# Logger for this module | ||
logger = get_logger(name=__name__) | ||
|
||
# Exceptions handlers | ||
@app.errorhandler(WerkzeugHTTPException) | ||
def handle_flask_exception(e: WerkzeugHTTPException) -> Response: | ||
logger.error(''.join(traceback.format_tb(e.__traceback__))) | ||
response = e.get_response() | ||
response.data = json.dumps({ | ||
'error': e.description, | ||
}) | ||
response.content_type = 'application/json' | ||
return response | ||
|
||
@app.errorhandler(Exception) | ||
def handle_not_flask_exception(e: Exception) -> Tuple[dict, int]: | ||
# Not to handle Flask exceptions here, pass it further to catch in `handle_flask_exception` | ||
if isinstance(e, WerkzeugHTTPException): | ||
return e | ||
|
||
elif isinstance(e, ProlificException): | ||
return { | ||
'error': e.message, | ||
}, status.HTTP_400_BAD_REQUEST | ||
|
||
elif isinstance(e, EntryDoesNotExistException): | ||
return { | ||
'error': 'Not found', | ||
}, status.HTTP_404_NOT_FOUND | ||
|
||
# Other uncaught exceptions | ||
logger.error(''.join(traceback.format_tb(e.__traceback__))) | ||
return { | ||
'error': str(e), | ||
}, status.HTTP_500_INTERNAL_SERVER_ERROR | ||
|
||
return app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Copyright (c) Facebook, Inc. 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Copyright (c) Facebook, Inc. and its affiliates. | ||
# This source code is licensed under the MIT license found in the | ||
# LICENSE file in the root directory of this source tree. | ||
|
||
from .qualification_workers_view import QualificationWorkersView | ||
from .qualifications_view import QualificationsView | ||
from .qualify_worker_view import QualifyWorkerView | ||
from .tasks_view import TasksView | ||
from .tasks_worker_units_view import TasksWorkerUnitsView | ||
from .units_approve_view import UnitsApproveView | ||
from .units_details_view import UnitsDetailsView | ||
from .units_reject_view import UnitsRejectView | ||
from .units_soft_reject_view import UnitsSoftRejectView | ||
from .units_view import UnitsView | ||
from .worker_block_view import WorkerBlockView |
110 changes: 110 additions & 0 deletions
110
mephisto/client/review_app/server/api/views/qualification_workers_view.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Copyright (c) Facebook, Inc. and its affiliates. | ||
# This source code is licensed under the MIT license found in the | ||
# LICENSE file in the root directory of this source tree. | ||
|
||
from typing import List | ||
from typing import Optional | ||
|
||
from flask import current_app as app | ||
from flask import request | ||
from flask.views import MethodView | ||
|
||
from mephisto.abstractions.databases.local_database import LocalMephistoDB | ||
from mephisto.abstractions.databases.local_database import nonesafe_int | ||
from mephisto.abstractions.databases.local_database import StringIDRow | ||
|
||
|
||
def _find_granted_qualifications(db: LocalMephistoDB, qualification_id: str) -> List[StringIDRow]: | ||
""" Return the granted qualifications in the database by the given qualification id """ | ||
|
||
with db.table_access_condition: | ||
conn = db._get_connection() | ||
c = conn.cursor() | ||
c.execute( | ||
f""" | ||
SELECT * FROM granted_qualifications | ||
WHERE (qualification_id = ?1); | ||
""", | ||
(nonesafe_int(qualification_id),), | ||
) | ||
|
||
results = c.fetchall() | ||
return results | ||
|
||
|
||
def _find_unit_reviews( | ||
datastore, | ||
qualification_id: str, | ||
worker_id: str, | ||
task_id: Optional[str] = None, | ||
) -> List[StringIDRow]: | ||
""" | ||
Return unit reviews in the datastore by the given Qualification ID, Worker ID and Task ID | ||
""" | ||
|
||
params = [nonesafe_int(qualification_id), nonesafe_int(worker_id)] | ||
task_query = "AND (task_id = ?3)" if task_id else "" | ||
if task_id: | ||
params.append(nonesafe_int(task_id)) | ||
|
||
with datastore.table_access_condition: | ||
conn = datastore._get_connection() | ||
conn.set_trace_callback(print) | ||
c = conn.cursor() | ||
c.execute( | ||
f""" | ||
SELECT * FROM unit_review | ||
WHERE (updated_qualification_id = ?1) AND (worker_id = ?2) {task_query} | ||
ORDER BY created_at ASC; | ||
""", | ||
params, | ||
) | ||
|
||
results = c.fetchall() | ||
return results | ||
|
||
|
||
class QualificationWorkersView(MethodView): | ||
def get(self, qualification_id) -> dict: | ||
""" Get list of all bearers of a qualification. """ | ||
|
||
task_id = request.args.get('task_id') | ||
|
||
db_qualification: StringIDRow = app.db.get_qualification(qualification_id) | ||
app.logger.debug(f"Found qualification in DB: {dict(db_qualification)}") | ||
|
||
db_granted_qualifications = _find_granted_qualifications(app.db, qualification_id) | ||
|
||
app.logger.debug( | ||
f"Found granted qualifications for this qualification in DB: " | ||
f"{db_granted_qualifications}" | ||
) | ||
|
||
workers = [] | ||
|
||
for gq in db_granted_qualifications: | ||
unit_reviews = _find_unit_reviews( | ||
app.datastore, qualification_id, gq["worker_id"], task_id, | ||
) | ||
|
||
if unit_reviews: | ||
latest_unit_review = unit_reviews[-1] | ||
unit_review_id = latest_unit_review["id"] | ||
granted_at = latest_unit_review["created_at"] | ||
else: | ||
continue | ||
|
||
workers.append( | ||
{ | ||
"worker_id": gq["worker_id"], | ||
"value": gq["value"], | ||
"unit_review_id": unit_review_id, # latest grant of this qualification | ||
"granted_at": granted_at, # maps to `unit_review.created_at` column | ||
} | ||
) | ||
|
||
return { | ||
"workers": workers, | ||
} |
Oops, something went wrong.