Skip to content

Commit

Permalink
Add qualifications management functionality to TaskReview app
Browse files Browse the repository at this point in the history
  • Loading branch information
meta-paul committed Oct 9, 2024
1 parent f6037c8 commit 05f4335
Show file tree
Hide file tree
Showing 78 changed files with 4,153 additions and 772 deletions.
98 changes: 97 additions & 1 deletion docs/web/docs/guides/how_to_use/review_app/server_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ Get all available qualifications (to select "approve" and "reject" qualification
{
"qualifications": [
{
"creation_date": <str>,
"description": <str>,
"id": <int>,
"name": <str>,
},
Expand All @@ -223,6 +225,52 @@ Create a new qualification

---

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

Get metadata for a qualificaition

```json
{
"creation_date": <str>,
"description": <str>,
"id": <int>,
"name": <str>,
}
```

---

### `PATCH /api/qualifications/{id}`

Update a qualification

```json
{
"name": <str>,
"description": <str>,
}
```

---

### `GET /api/qualifications/{id}/details`

Get additional data about a qualification

```json
{
"granted_qualifications_count": <int>,
}
```

---

### `DELETE /api/qualifications/{id}`

Delete a qualificaition

---

### `GET /api/qualifications/{id}/workers?{task_id=}`

Get list of all bearers of a qualification.
Expand Down Expand Up @@ -256,6 +304,18 @@ Grant qualification to a worker

---

### `PATCH /api/qualifications/{id}/workers/{id}/grant`

Update value of existing granted qualification

```json
{
"value": <int>,
}
```

---

### `POST /api/qualifications/{id}/workers/{id}/revoke`

Revoke qualification from a worker
Expand All @@ -268,6 +328,42 @@ Revoke qualification from a worker

---

### `PATCH /api/qualifications/{id}/workers/{id}/revoke`

Revoke qualification from a worker (see the difference from `POST` in the code)

---

### `GET /api/granted-qualifications`

Get list of all granted queslifications

```json
{
"granted_qualifications": [
{
"granted_at": <str>,
"qualification_id": <str>,
"qualification_name": <str>,
"units": [
{
"task_id": <str>,
"task_name": <str>,
"unit_id": <str>,
},
... // more units
],
"value_current": <int>,
"worker_id": <str>,
"worker_name": <str>,
},
... // more granted qualifications
],
}
```

---

### `GET /api/units?{task_id=}{unit_ids=}`

Get workers' results (filtered by task_id and/or unit_ids, etc) - without full details of input/output. At least one filtering parameter must be specified
Expand Down Expand Up @@ -312,7 +408,7 @@ Get full input for specified workers results (`units_ids` parameter is mandatory
"has_task_source_review": <bool>,
"id": <int>,
"inputs": <json object>, // instructions for worker
"metadata": <json object>, // any metadata (e.g. Worker Opinion)
"metadata": <json object>, // any metadata (e.g. Worker Opinion, Unit Reviews, etc)
"outputs": <json object>, // response from worker
"prepared_inputs": <json object>, // prepared instructions from worker
"unit_data_folder": <str>}, // path to data dir in file system
Expand Down
52 changes: 43 additions & 9 deletions mephisto/abstractions/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
GET_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="get_qualification")
FIND_QUALIFICATIONS_LATENCY = DATABASE_LATENCY.labels(method="find_qualifications")
DELETE_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="delete_qualification")
UPDATE_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="update_qualification")
GRANT_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="grant_qualification")
FIND_GRANT_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="find_granted_qualification")
CHECK_GRANTED_QUALIFICATIONS_LATENCY = DATABASE_LATENCY.labels(
Expand Down Expand Up @@ -600,7 +601,8 @@ def update_unit(
self, unit_id: str, agent_id: Optional[str] = None, status: Optional[str] = None
) -> None:
"""
Update the given task with the given parameters if possible, raise appropriate exception otherwise.
Update the given unit with the given parameters if possible,
raise appropriate exception otherwise.
"""
return self._update_unit(unit_id=unit_id, status=status)

Expand Down Expand Up @@ -901,17 +903,21 @@ def find_onboarding_agents(
)

@abstractmethod
def _make_qualification(self, qualification_name: str) -> str:
def _make_qualification(
self, qualification_name: str, description: Optional[str] = None
) -> str:
"""make_qualification implementation"""
raise NotImplementedError()

@MAKE_QUALIFICATION_LATENCY.time()
def make_qualification(self, qualification_name: str) -> str:
def make_qualification(self, qualification_name: str, description: Optional[str] = None) -> str:
"""
Make a new qualification, throws an error if a qualification by the given name
already exists. Return the id for the qualification.
"""
return self._make_qualification(qualification_name=qualification_name)
return self._make_qualification(
qualification_name=qualification_name, description=description
)

@abstractmethod
def _find_qualifications(self, qualification_name: Optional[str] = None) -> List[Qualification]:
Expand Down Expand Up @@ -942,9 +948,7 @@ def get_qualification(self, qualification_id: str) -> Mapping[str, Any]:

@abstractmethod
def _delete_qualification(self, qualification_name: str) -> None:
"""
Remove this qualification from all workers that have it, then delete the qualification
"""
"""delete_qualification implementation"""
raise NotImplementedError()

@DELETE_QUALIFICATION_LATENCY.time()
Expand All @@ -958,16 +962,46 @@ def delete_qualification(self, qualification_name: str) -> None:
provider = ProviderClass(self)
provider.cleanup_qualification(qualification_name)

@abstractmethod
def _update_qualification(
self,
qualification_id: str,
name: str,
description: Optional[str] = None,
) -> None:
"""update_qualification implementation"""
raise NotImplementedError()

@UPDATE_QUALIFICATION_LATENCY.time()
def update_qualification(
self,
qualification_id: str,
name: str,
description: Optional[str] = None,
) -> None:
"""
Update the given qualification with the given parameters if possible,
raise appropriate exception otherwise.
"""
return self._update_qualification(
qualification_id=qualification_id,
name=name,
description=description,
)

@FIND_GRANT_QUALIFICATION_LATENCY.time()
def find_granted_qualifications(
self,
worker_id: Optional[str] = None,
qualification_id: Optional[str] = None,
) -> List[GrantedQualification]:
"""
Find granted qualifications.
If `worker_id` is not supplied, returns all granted qualifications.
If nothing supplied, returns all granted qualifications.
"""
return self._check_granted_qualifications(worker_id=worker_id)
return self._check_granted_qualifications(
worker_id=worker_id, qualification_id=qualification_id
)

@abstractmethod
def _grant_qualification(self, qualification_id: str, worker_id: str, value: int = 1) -> None:
Expand Down
42 changes: 38 additions & 4 deletions mephisto/abstractions/databases/local_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ def _update_unit(
self, unit_id: str, agent_id: Optional[str] = None, status: Optional[str] = None
) -> None:
"""
Update the given task with the given parameters if possible,
Update the given unit with the given parameters if possible,
raise appropriate exception otherwise.
"""
if status not in AssignmentState.valid_unit():
Expand Down Expand Up @@ -1117,7 +1117,9 @@ def _find_agents(
rows = c.fetchall()
return [Agent(self, str(r["agent_id"]), row=r, _used_new_call=True) for r in rows]

def _make_qualification(self, qualification_name: str) -> str:
def _make_qualification(
self, qualification_name: str, description: Optional[str] = None
) -> str:
"""
Make a new qualification, throws an error if a qualification by the given name
already exists. Return the id for the qualification.
Expand All @@ -1128,8 +1130,8 @@ def _make_qualification(self, qualification_name: str) -> str:
c = conn.cursor()
try:
c.execute(
"INSERT INTO qualifications(qualification_name) VALUES (?);",
(qualification_name,),
"INSERT INTO qualifications(qualification_name, description) VALUES (?, ?);",
(qualification_name, description),
)
qualification_id = str(c.lastrowid)
return qualification_id
Expand Down Expand Up @@ -1195,6 +1197,38 @@ def _delete_qualification(self, qualification_name: str) -> None:
(qualification_name,),
)

def _update_qualification(
self,
qualification_id: str,
name: str,
description: Optional[str] = None,
) -> None:
"""
Update the given qualification with the given parameters if possible,
raise appropriate exception otherwise.
"""
with self.table_access_condition, self.get_connection() as conn:
c = conn.cursor()
try:
c.execute(
"""
UPDATE qualifications
SET qualification_name = ?2, description = ?3
WHERE qualification_id = ?1;
""",
[
nonesafe_int(qualification_id),
name,
description,
],
)
except sqlite3.IntegrityError as e:
if is_key_failure(e):
raise EntryDoesNotExistException(
f"Given qualification_id {qualification_id} not found in the database"
)
raise MephistoDBException(e)

def _grant_qualification(self, qualification_id: str, worker_id: str, value: int = 1) -> None:
"""
Grant a worker the given qualification. Update the qualification value if it
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/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.

"""
List of changes:
1. Add `description` field into `qualifications` table
"""


ADD_QUALIFICATION_DESCRIPTION = """
ALTER TABLE qualifications ADD COLUMN description CHAR(500);
"""
2 changes: 2 additions & 0 deletions mephisto/abstractions/databases/migrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
# LICENSE file in the root directory of this source tree.

from ._001_20240325_data_porter_feature import *
from ._002_20241002_add_qualification_description import *


migrations = {
"20240418_data_porter_feature": MODIFICATIONS_FOR_DATA_PORTER,
"20241002_add_qualification_description": ADD_QUALIFICATION_DESCRIPTION,
}
24 changes: 17 additions & 7 deletions mephisto/data_model/qualification.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from mephisto.data_model._db_backed_meta import (
MephistoDBBackedMeta,
MephistoDataModelComponentMixin,
)

from typing import Optional, Mapping, TYPE_CHECKING, Any
from typing import Any
from typing import Mapping
from typing import Optional
from typing import TYPE_CHECKING

from mephisto.data_model._db_backed_meta import MephistoDataModelComponentMixin
from mephisto.data_model._db_backed_meta import MephistoDBBackedMeta
from mephisto.utils.logger_core import get_logger

if TYPE_CHECKING:
from mephisto.abstractions.database import MephistoDB

from mephisto.utils.logger_core import get_logger

logger = get_logger(name=__name__)

Expand Down Expand Up @@ -72,11 +72,16 @@ def __init__(
"now deprecated in favor of calling Qualification.get(db, id). "
)
self.db: "MephistoDB" = db

if row is None:
row = db.get_qualification(db_id)

assert row is not None, f"Given db_id {db_id} did not exist in given db"

self.db_id: str = row["qualification_id"]
self.qualification_name: str = row["qualification_name"]
self.description: str = row["description"]
self.creation_date: str = row["creation_date"]


class GrantedQualification:
Expand All @@ -90,9 +95,14 @@ def __init__(
row: Optional[Mapping[str, Any]] = None,
):
self.db: "MephistoDB" = db

if row is None:
row = db.get_granted_qualification(qualification_id, worker_id)

assert row is not None, f"Granted qualification did not exist in given db"

self.worker_id: str = row["worker_id"]
self.qualification_id: str = row["qualification_id"]
self.value: str = row["value"]
self.creation_date: str = row["creation_date"]
self.update_date: str = row["update_date"]
Loading

0 comments on commit 05f4335

Please sign in to comment.