Skip to content

Commit

Permalink
Display qualifications granted to a worker in TaskReview app
Browse files Browse the repository at this point in the history
  • Loading branch information
meta-paul committed Mar 19, 2024
1 parent cd04708 commit a4b4a98
Show file tree
Hide file tree
Showing 12 changed files with 205 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import { useEffect } from "react";
import { Button, Col, Form, Row } from "react-bootstrap";
import { getQualifications, postQualification } from "requests/qualifications";
import "./ModalForm.css";
import { getWorkerGrantedQualifications } from "requests/workers";

const BONUS_FOR_WORKER_ENABLED = true;
const FEEDBACK_FOR_WORKER_ENABLED = true;
const QUALIFICATION_VALUE_MIN = 1;
const QUALIFICATION_VALUE_MAX = 10;

const range = (start, end) => Array.from(Array(end + 1).keys()).slice(start);

Expand All @@ -24,13 +27,14 @@ type ModalFormProps = {
};

function ModalForm(props: ModalFormProps) {
const [
workerGrantedQualifications,
setWorkerGrantedQualifications,
] = React.useState<WorkerGrantedQualificationsType>({});
const [qualifications, setQualifications] = React.useState<
Array<QualificationType>
QualificationType[]
>(null);
const [
getQualificationsloading,
setGetQualificationsloading,
] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const [_, setCreateQualificationLoading] = React.useState(false);

const onChangeAssign = (value: boolean) => {
Expand All @@ -53,6 +57,17 @@ function ModalForm(props: ModalFormProps) {
prevFormData.newQualificationValue = "";
} else {
prevFormData.qualification = Number(value);

const prevGrantedQualification =
workerGrantedQualifications[Number(value)];
const prevGrantedQualificationValue = prevGrantedQualification?.value;
if (prevGrantedQualificationValue !== undefined) {
// Set to previous granted value for selected qualification
prevFormData.qualificationValue = prevGrantedQualificationValue;
} else {
// Set to default value
prevFormData.qualificationValue = QUALIFICATION_VALUE_MIN;
}
}

props.setData({ ...props.data, form: prevFormData });
Expand Down Expand Up @@ -131,17 +146,32 @@ function ModalForm(props: ModalFormProps) {
requestQualifications();
};

const onGetWorkerGrantedQualificationsSuccess = (
grantedQualifications: GrantedQualificationType[]
) => {
const _workerGrantedQualifications = {};

grantedQualifications.forEach((gq: GrantedQualificationType) => {
_workerGrantedQualifications[gq.qualification_id] = gq;
});
setWorkerGrantedQualifications(_workerGrantedQualifications);
};

const requestQualifications = () => {
let params;
if (props.data.type === ReviewType.REJECT) {
params = { worker_id: props.workerId };
}

getQualifications(
setQualifications,
setGetQualificationsloading,
onError,
params
getQualifications(setQualifications, setLoading, onError, params);
};

const requestWorkerGrantedQualifications = () => {
getWorkerGrantedQualifications(
props.workerId,
onGetWorkerGrantedQualificationsSuccess,
setLoading,
onError
);
};

Expand All @@ -156,12 +186,14 @@ function ModalForm(props: ModalFormProps) {

// Effiects
useEffect(() => {
requestWorkerGrantedQualifications();

if (qualifications === null) {
requestQualifications();
}
}, []);

if (getQualificationsloading) {
if (loading) {
return;
}

Expand Down Expand Up @@ -201,9 +233,20 @@ function ModalForm(props: ModalFormProps) {
<option value={"+"}>+ Add new qualification</option>
{qualifications &&
qualifications.map((q: QualificationType) => {
const prevGrantedQualification =
workerGrantedQualifications[q.id];
const prevGrantedQualificationValue =
prevGrantedQualification?.value;

let nameSuffix = "";
if (prevGrantedQualificationValue !== undefined) {
nameSuffix = ` (granted value: ${prevGrantedQualificationValue})`;
}
const qualificationName = `${q.name}${nameSuffix}`;

return (
<option key={"qual" + q.id} value={q.id}>
{q.name}
{qualificationName}
</option>
);
})}
Expand All @@ -218,7 +261,10 @@ function ModalForm(props: ModalFormProps) {
onChangeAssignQualificationValue(e.target.value)
}
>
{range(1, 10).map((i) => {
{range(
QUALIFICATION_VALUE_MIN,
QUALIFICATION_VALUE_MAX
).map((i) => {
return <option key={"qualVal" + i}>{i}</option>;
})}
</Form.Select>
Expand Down
23 changes: 22 additions & 1 deletion mephisto/review_app/client/src/requests/workers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function postWorkerBlock(
data: { [key: string]: string | number | number[] },
abortController?: AbortController
) {
const url = generateURL(urls.server.workersBlock(id), null, null);
const url = generateURL(urls.server.workersBlock, [id], null);

makeRequest(
"POST",
Expand All @@ -29,3 +29,24 @@ export function postWorkerBlock(
abortController
);
}

export function getWorkerGrantedQualifications(
id: number,
setDataAction: SetRequestDataActionType,
setLoadingAction: SetRequestLoadingActionType,
setErrorsAction: SetRequestErrorsActionType,
abortController?: AbortController
) {
const url = generateURL(urls.server.workerGrantedQualifications, [id]);

makeRequest(
"GET",
url,
null,
(data) => setDataAction(data.granted_qualifications),
setLoadingAction,
setErrorsAction,
"getWorkerGrantedQualifications error:",
abortController
);
}
11 changes: 11 additions & 0 deletions mephisto/review_app/client/src/types/qualifications.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,14 @@ declare type QualificationType = {
id: number;
name: string;
};

declare type GrantedQualificationType = {
worker_id: number;
qualification_id: number;
value: number;
granted_at: string;
};

declare type WorkerGrantedQualificationsType = {
[key: number]: GrantedQualificationType;
};
2 changes: 2 additions & 0 deletions mephisto/review_app/client/src/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const urls = {
API_URL + `/api/units/${id}/static/${filename}`,
unitsOutputsFileByFieldname: (id, fieldname) =>
API_URL + `/api/units/${id}/static/fieldname/${fieldname}`,
workerGrantedQualifications: (id) =>
API_URL + `/api/workers/${id}/qualifications`,
workersBlock: (id) => API_URL + `/api/workers/${id}/block`,
},
};
Expand Down
20 changes: 20 additions & 0 deletions mephisto/review_app/server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,26 @@ Permanently block a worker

---

`GET /api/workers/{id}/qualifications`

Get list of all granted qualifications for a worker

```
{
"granted_qualifications": [
{
"worker_id": <int>,
"qualification_id": <int>,
"value": <int>,
"granted_at": <int>, // maps to `unit_review.created_at` column
}
],
... // more granted qualifications
}
```

---

`GET /api/stats?{task_id=}{worker_id=}{since=}{limit=}`

Get stats of (recent) approvals. Either `task_id` or `worker_id` (or both) must be present.
Expand Down
1 change: 1 addition & 0 deletions mephisto/review_app/server/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
from .units_soft_reject_view import UnitsSoftRejectView
from .units_view import UnitsView
from .worker_block_view import WorkerBlockView
from .worker_granted_qualifications_view import WorkerGrantedQualificationsView
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def _find_unit_reviews(


class QualificationWorkersView(MethodView):
def get(self, qualification_id) -> dict:
def get(self, qualification_id: int) -> dict:
"""Get list of all bearers of a qualification."""

task_id = request.args.get("task_id")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class UnitDataStaticByFieldNameView(MethodView):
@staticmethod
def _get_filename_by_fieldname(agent: Agent, fieldname: str) -> Union[str, None]:
unit_parsed_data = agent.state.get_parsed_data()
outputs = unit_parsed_data.get("outputs", {})
outputs = unit_parsed_data.get("outputs") or {}
# In case if there is outdated code that returns `final_submission`
# under `outputs` key, we should use the value in side `final_submission`
if "final_submission" in outputs:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def _get_filename_by_original_name(unit: Unit, filename: str) -> Union[str, None
agent: Agent = unit.get_assigned_agent()
if agent:
unit_parsed_data = agent.state.get_parsed_data()
outputs = unit_parsed_data.get("outputs", {})
outputs = unit_parsed_data.get("outputs") or {}
# In case if there is outdated code that returns `final_submission`
# under `outputs` key, we should use the value in side `final_submission`
if "final_submission" in outputs:
Expand Down
4 changes: 2 additions & 2 deletions mephisto/review_app/server/api/views/units_details_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ def get(self) -> dict:
task_run: TaskRun = unit.get_task_run()
has_task_source_review = bool(task_run.args.get("blueprint").get("task_source_review"))

inputs = unit_data.get("data", {}).get("inputs", {})
outputs = unit_data.get("data", {}).get("outputs", {})
inputs = unit_data.get("data", {}).get("inputs") or {}
outputs = unit_data.get("data", {}).get("outputs") or {}

# In case if there is outdated code that returns `final_submission`
# under `inputs` and `outputs` keys, we should use the value in side `final_submission`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python3

# Copyright (c) Meta Platforms and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from typing import List

from flask import current_app as app
from flask.views import MethodView

from mephisto.abstractions.databases.local_database import LocalMephistoDB
from mephisto.abstractions.databases.local_database import StringIDRow
from mephisto.data_model.worker import Worker


def _find_granted_qualifications(db: LocalMephistoDB, worker_id: str) -> List[StringIDRow]:
"""Return the granted qualifications in the database by the given worker id"""

with db.table_access_condition:
conn = db._get_connection()
c = conn.cursor()
c.execute(
"""
SELECT
gq.value,
gq.worker_id,
gq.qualification_id,
gq.granted_qualification_id,
ur.created_at AS granted_at
FROM granted_qualifications AS gq
LEFT JOIN (
SELECT
updated_qualification_id,
created_at
FROM unit_review
ORDER BY created_at DESC
/*
We’re retrieving unit_review data only
for the latest update of the worker-qualification pair.
*/
LIMIT 1
) AS ur ON ur.updated_qualification_id = gq.qualification_id
WHERE gq.worker_id = ?1
""",
(worker_id,),
)
rows = c.fetchall()
return rows


class WorkerGrantedQualificationsView(MethodView):
def get(self, worker_id: int) -> dict:
"""Get list of all granted queslifications for a worker."""

worker: Worker = Worker.get(app.db, str(worker_id))
app.logger.debug(f"Found Worker in DB: {worker}")

db_granted_qualifications = _find_granted_qualifications(app.db, worker.db_id)

app.logger.debug(
f"Found granted qualifications for worker {worker_id} in DB: "
f"{list(db_granted_qualifications)}"
)

granted_qualifications = []
for gq in db_granted_qualifications:
granted_qualifications.append(
{
"worker_id": gq["worker_id"],
"qualification_id": gq["qualification_id"],
"value": int(gq["value"]),
"granted_at": gq["worker_id"], # maps to `unit_review.created_at` column
},
)

return {
"granted_qualifications": granted_qualifications,
}
6 changes: 6 additions & 0 deletions mephisto/review_app/server/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ def init_urls(app: Flask):
"/api/workers/<int:worker_id>/block",
view_func=api_views.WorkerBlockView.as_view("worker_block"),
)
app.add_url_rule(
"/api/workers/<int:worker_id>/qualifications",
view_func=api_views.WorkerGrantedQualificationsView.as_view(
"worker_granted_qualifications",
),
)
app.add_url_rule(
"/api/stats",
view_func=api_views.StatsView.as_view("stats"),
Expand Down

0 comments on commit a4b4a98

Please sign in to comment.