Skip to content

Commit

Permalink
feat: Add Source subject Details to endpoints that return individual …
Browse files Browse the repository at this point in the history
…answer submissions (M2-8140) (#1645)

This PR updates the following endpoints to add a nullable `sourceSubject` property to their `answer` objects

- Get activity answer: `GET /answers/applet/{applet_id}/activities/{activity_id}/answers/{answer_id}`
- Get flow submission: `GET /answers/applet/{applet_id}/flows/{flow_id}/submissions/{submit_id}`

The structure of the `sourceSubject` is the same `SubjectReadResponse` as that returned from get subject endpoint (`GET /subjects/{subject_id}`). The only exception being that the `lastSeen` property will always be null in these cases.
  • Loading branch information
sultanofcardio authored Nov 4, 2024
1 parent 6b158cc commit a7cfa5f
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/apps/answers/domain/answers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from apps.shared.domain.custom_validations import datetime_from_ms
from apps.shared.domain.types import _BaseModel
from apps.shared.locale import I18N
from apps.subjects.domain import SubjectReadResponse


class ClientMeta(InternalModel):
Expand Down Expand Up @@ -244,6 +245,7 @@ class ActivityAnswer(PublicModel):
migrated_data: dict | None = None
end_datetime: datetime.datetime
created_at: datetime.datetime
source_subject: SubjectReadResponse | None

@validator("activity_id", always=True)
def extract_activity_id(cls, value, values):
Expand Down
44 changes: 43 additions & 1 deletion src/apps/answers/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
from apps.subjects.constants import Relation
from apps.subjects.crud import SubjectsCrud
from apps.subjects.db.schemas import SubjectSchema
from apps.subjects.domain import SubjectReadResponse
from apps.users import User, UserSchema, UsersCRUD
from apps.workspaces.crud.applet_access import AppletAccessCRUD
from apps.workspaces.crud.user_applet_access import UserAppletAccessCRUD
Expand Down Expand Up @@ -627,6 +628,25 @@ async def get_activity_answer(
raise AnswerNotFoundError()

answer = answers[0]
source_subject = None

if answer.source_subject_id:
source_subject_schema = await SubjectsCrud(self.session).get_by_id(answer.source_subject_id)
source_subject = (
SubjectReadResponse(
id=source_subject_schema.id,
first_name=source_subject_schema.first_name,
last_name=source_subject_schema.last_name,
nickname=source_subject_schema.nickname,
secret_user_id=source_subject_schema.secret_user_id,
tag=source_subject_schema.tag,
applet_id=source_subject_schema.applet_id,
user_id=source_subject_schema.user_id,
)
if source_subject_schema
else None
)

answer_result = ActivityAnswer(
**answer.dict(exclude={"migrated_data"}),
**answer.answer_item.dict(
Expand All @@ -640,6 +660,7 @@ async def get_activity_answer(
"end_datetime",
}
),
source_subject=source_subject,
)

activities = await ActivityHistoriesCRUD(self.session).load_full([answer.activity_history_id])
Expand Down Expand Up @@ -744,8 +765,12 @@ async def get_flow_submission(

answer_result: list[ActivityAnswer] = []

source_subject_id_answer_index_map: dict[uuid.UUID, list[int]] = defaultdict(list)

is_flow_completed = False
for answer in answers:
for i, answer in enumerate(answers):
if answer.source_subject_id:
source_subject_id_answer_index_map[answer.source_subject_id].append(i)
if answer.flow_history_id and answer.is_flow_completed:
is_completed = True
answer_result.append(
Expand Down Expand Up @@ -777,6 +802,23 @@ async def get_flow_submission(
flows = await FlowsHistoryCRUD(self.session).load_full([flow_history_id])
assert flows

source_subject_ids = list(source_subject_id_answer_index_map.keys())
source_subjects = await SubjectsCrud(self.session).get_by_ids(source_subject_ids)
for source_subject_schema in source_subjects:
answer_indexes = source_subject_id_answer_index_map[source_subject_schema.id]
source_subject = SubjectReadResponse(
id=source_subject_schema.id,
first_name=source_subject_schema.first_name,
last_name=source_subject_schema.last_name,
nickname=source_subject_schema.nickname,
secret_user_id=source_subject_schema.secret_user_id,
tag=source_subject_schema.tag,
applet_id=source_subject_schema.applet_id,
user_id=source_subject_schema.user_id,
)
for answer_index in answer_indexes:
answer_result[answer_index].source_subject = source_subject

submission = FlowSubmissionDetails(
submission=FlowSubmission(
submit_id=submit_id,
Expand Down
102 changes: 98 additions & 4 deletions src/apps/answers/tests/test_answers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1471,7 +1471,9 @@ async def test_answered_applet_activities_1(
assert set(data["summary"]["identifier"]) == {"lastAnswerDate", "identifier", "userPublicKey"}
assert data["summary"]["identifier"]["identifier"] == "encrypted_identifier"

async def test_get_answer_activity(self, client: TestClient, tom: User, applet: AppletFull, answer: AnswerSchema):
async def test_get_answer_activity(
self, client: TestClient, tom: User, tom_applet_subject: SubjectSchema, applet: AppletFull, answer: AnswerSchema
):
client.login(tom)
response = await client.get(
self.activity_answer_url.format(
Expand All @@ -1480,7 +1482,79 @@ async def test_get_answer_activity(self, client: TestClient, tom: User, applet:
activity_id=applet.activities[0].id,
)
)
assert response.status_code == http.HTTPStatus.OK # TODO: Check response
assert response.status_code == http.HTTPStatus.OK
response_json = response.json()

result = response_json["result"]

assert set(result.keys()) == {"activity", "answer", "summary"}

result_answer = result["answer"]
assert set(result_answer.keys()) == {
"activityHistoryId",
"activityId",
"answer",
"createdAt",
"endDatetime",
"events",
"flowHistoryId",
"id",
"identifier",
"itemIds",
"migratedData",
"submitId",
"userPublicKey",
"version",
"sourceSubject",
}
assert result_answer["submitId"] == str(answer.submit_id)
assert (
result_answer["flowHistoryId"] == answer.flow_history_id
if answer.flow_history_id
else str(answer.flow_history_id)
)

assert set(result_answer.keys()) == {
"activityHistoryId",
"activityId",
"answer",
"createdAt",
"endDatetime",
"events",
"flowHistoryId",
"id",
"identifier",
"itemIds",
"migratedData",
"submitId",
"userPublicKey",
"version",
"sourceSubject",
}

assert result_answer["id"] == str(answer.id)

source_subject = result_answer["sourceSubject"]

assert set(source_subject.keys()) == {
"secretUserId",
"nickname",
"tag",
"id",
"lastSeen",
"appletId",
"userId",
"firstName",
"lastName",
}
assert source_subject["id"] == str(tom_applet_subject.id)
assert source_subject["userId"] == str(tom.id)
assert source_subject["secretUserId"] == tom_applet_subject.secret_user_id
assert source_subject["nickname"] == tom_applet_subject.nickname
assert source_subject["firstName"] == tom_applet_subject.first_name
assert source_subject["lastName"] == tom_applet_subject.last_name
assert source_subject["tag"] == tom_applet_subject.tag
assert source_subject["lastSeen"] is None

async def test_fail_answered_applet_not_existed_activities(
self, client: TestClient, tom: User, applet: AppletFull, uuid_zero: uuid.UUID, answer: AnswerSchema
Expand Down Expand Up @@ -2506,7 +2580,14 @@ async def test_review_flows_multiple_answers(
assert len(data[0]["answerDates"]) == 2
assert len(data[1]["answerDates"]) == 1

async def test_flow_submission(self, client, tom: User, applet_with_flow: AppletFull, tom_answer_activity_flow):
async def test_flow_submission(
self,
client: TestClient,
tom: User,
applet_with_flow: AppletFull,
tom_answer_activity_flow: AnswerSchema,
tom_applet_with_flow_subject: Subject,
):
client.login(tom)
url = self.flow_submission_url.format(
applet_id=applet_with_flow.id,
Expand All @@ -2525,11 +2606,24 @@ async def test_flow_submission(self, client, tom: User, applet_with_flow: Applet
# fmt: off
assert set(answer_data.keys()) == {
"activityHistoryId", "activityId", "answer", "createdAt", "endDatetime", "events", "flowHistoryId", "id",
"identifier", "itemIds", "migratedData", "submitId", "userPublicKey", "version"
"identifier", "itemIds", "migratedData", "submitId", "userPublicKey", "version", "sourceSubject"
}
assert answer_data["submitId"] == str(tom_answer_activity_flow.submit_id)
assert answer_data["flowHistoryId"] == str(tom_answer_activity_flow.flow_history_id)

source_subject = answer_data["sourceSubject"]

assert set(source_subject.keys()) == {"secretUserId", "nickname", "tag", "id", "lastSeen", "appletId", "userId",
"firstName", "lastName"}
assert source_subject["id"] == str(tom_applet_with_flow_subject.id)
assert source_subject["userId"] == str(tom.id)
assert source_subject["secretUserId"] == tom_applet_with_flow_subject.secret_user_id
assert source_subject["nickname"] == tom_applet_with_flow_subject.nickname
assert source_subject["firstName"] == tom_applet_with_flow_subject.first_name
assert source_subject["lastName"] == tom_applet_with_flow_subject.last_name
assert source_subject["tag"] == tom_applet_with_flow_subject.tag
assert source_subject["lastSeen"] is None

assert set(data["flow"].keys()) == {
"id", "activities", "autoAssign", "createdAt", "description", "hideBadge", "idVersion", "isHidden",
"isSingleReport",
Expand Down

0 comments on commit a7cfa5f

Please sign in to comment.