Skip to content

Commit

Permalink
Implement new resource coverage tools
Browse files Browse the repository at this point in the history
Closes #107, #49
  • Loading branch information
bkis committed Jan 8, 2024
1 parent 35ff22f commit 45610e3
Show file tree
Hide file tree
Showing 13 changed files with 426 additions and 102 deletions.
85 changes: 80 additions & 5 deletions Tekst-API/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,60 @@
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResourceCoverage"
}
}
}
},
"404": {
"description": "Not found"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/browse/resources/{id}/coverage-details": {
"get": {
"tags": [
"browse"
],
"summary": "Get detailed resource coverage data",
"operationId": "getDetailedResourceCoverageData",
"security": [
{
"APIKeyCookie": []
},
{
"OAuth2PasswordBearer": []
}
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"example": "5eb7cf5a86d9755df3a6c593",
"title": "Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
Expand All @@ -391,9 +445,12 @@
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ResourceNodeCoverage"
"type": "array",
"items": {
"$ref": "#/components/schemas/ResourceNodeCoverage"
}
},
"title": "Response Get Resource Coverage Data Browse Resources Id Coverage Get"
"title": "Response Get Detailed Resource Coverage Data Browse Resources Id Coverage Details Get"
}
}
}
Expand Down Expand Up @@ -6722,6 +6779,24 @@
],
"title": "ResourceCommentTranslation"
},
"ResourceCoverage": {
"properties": {
"covered": {
"type": "integer",
"title": "Covered"
},
"total": {
"type": "integer",
"title": "Total"
}
},
"type": "object",
"required": [
"covered",
"total"
],
"title": "ResourceCoverage"
},
"ResourceDescriptionTranslation": {
"properties": {
"locale": {
Expand Down Expand Up @@ -6753,14 +6828,14 @@
},
"covered": {
"type": "boolean",
"title": "Covered"
"title": "Covered",
"default": false
}
},
"type": "object",
"required": [
"label",
"position",
"covered"
"position"
],
"title": "ResourceNodeCoverage"
},
Expand Down
7 changes: 6 additions & 1 deletion Tekst-API/tekst/models/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,12 @@ class ResourceReadExtras(ModelBase):
ResourceBaseUpdate = ResourceBase.update_model()


class ResourceCoverage(ModelBase):
covered: int
total: int


class ResourceNodeCoverage(ModelBase):
label: str
position: int
covered: bool
covered: bool = False
59 changes: 54 additions & 5 deletions Tekst-API/tekst/routers/browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
NodeDocument,
NodeRead,
)
from tekst.models.resource import ResourceBaseDocument, ResourceNodeCoverage
from tekst.models.resource import (
ResourceBaseDocument,
ResourceCoverage,
ResourceNodeCoverage,
)
from tekst.models.unit import UnitBaseDocument
from tekst.resource_types import AnyUnitRead, AnyUnitReadBody, resource_types_mgr

Expand Down Expand Up @@ -178,10 +182,14 @@ async def get_path_options_by_root(
return options


@router.get("/resources/{id}/coverage", status_code=status.HTTP_200_OK)
@router.get(
"/resources/{id}/coverage",
status_code=status.HTTP_200_OK,
response_model=ResourceCoverage,
)
async def get_resource_coverage_data(
resource_id: Annotated[PydanticObjectId, Path(alias="id")], user: OptionalUserDep
) -> list[ResourceNodeCoverage]:
) -> dict:
resource_doc = await ResourceBaseDocument.find_one(
ResourceBaseDocument.id == resource_id,
await ResourceBaseDocument.access_conditions_read(user),
Expand All @@ -191,7 +199,36 @@ async def get_resource_coverage_data(
raise HTTPException(
status.HTTP_404_NOT_FOUND, detail=f"No resource with ID {resource_id}"
)
return (
return {
"covered": await UnitBaseDocument.find(
UnitBaseDocument.resource_id == resource_id,
with_children=True,
).count(),
"total": await NodeDocument.find(
NodeDocument.text_id == resource_doc.text_id,
NodeDocument.level == resource_doc.level,
).count(),
}


@router.get(
"/resources/{id}/coverage-details",
status_code=status.HTTP_200_OK,
response_model=list[list[ResourceNodeCoverage]],
)
async def get_detailed_resource_coverage_data(
resource_id: Annotated[PydanticObjectId, Path(alias="id")], user: OptionalUserDep
) -> list[list[dict]]:
resource_doc = await ResourceBaseDocument.find_one(
ResourceBaseDocument.id == resource_id,
await ResourceBaseDocument.access_conditions_read(user),
with_children=True,
)
if not resource_doc:
raise HTTPException(
status.HTTP_404_NOT_FOUND, detail=f"No resource with ID {resource_id}"
)
data = (
await NodeDocument.find(
NodeDocument.text_id == resource_doc.text_id,
NodeDocument.level == resource_doc.level,
Expand Down Expand Up @@ -225,11 +262,23 @@ async def get_resource_coverage_data(
"$project": {
"label": 1,
"position": 1,
"parent_id": 1,
"covered": {"$gt": [{"$size": "$units"}, 0]},
}
},
],
projection_model=ResourceNodeCoverage,
)
.to_list()
)

# group nodes by parent
out = []
prev_parent_id = "init"
for node in data:
if node["parent_id"] == prev_parent_id:
out[-1].append(node)
else:
out.append([node])
prev_parent_id = node["parent_id"]

return out
43 changes: 27 additions & 16 deletions Tekst-API/tests/test_api_browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ async def test_get_unit_siblings(

@pytest.mark.anyio
async def test_get_node_path(
test_client: AsyncClient,
insert_sample_data,
status_fail_msg,
test_client: AsyncClient, insert_sample_data, status_fail_msg, wrong_id
):
inserted_ids = await insert_sample_data("texts", "nodes")
text_id = inserted_ids["texts"][0]
Expand All @@ -64,7 +62,7 @@ async def test_get_node_path(
# invalid node data
resp = await test_client.get(
"/browse/nodes/path",
params={"textId": "658c163106aa5002b5b90e33", "level": 0, "position": 0},
params={"textId": {wrong_id}, "level": 0, "position": 0},
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), list)
Expand All @@ -73,9 +71,7 @@ async def test_get_node_path(

@pytest.mark.anyio
async def test_get_path_options_by_head(
test_client: AsyncClient,
insert_sample_data,
status_fail_msg,
test_client: AsyncClient, insert_sample_data, status_fail_msg, wrong_id
):
await insert_sample_data("texts", "nodes")
text = await TextDocument.find_one(TextDocument.slug == "fdhdgg")
Expand All @@ -91,7 +87,7 @@ async def test_get_path_options_by_head(

# invalid node data
resp = await test_client.get(
"/browse/nodes/658c163106aa5002b5b90e33/path/options-by-head",
f"/browse/nodes/{wrong_id}/path/options-by-head",
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), list)
Expand All @@ -100,9 +96,7 @@ async def test_get_path_options_by_head(

@pytest.mark.anyio
async def test_get_path_options_by_root(
test_client: AsyncClient,
insert_sample_data,
status_fail_msg,
test_client: AsyncClient, insert_sample_data, status_fail_msg, wrong_id
):
await insert_sample_data("texts", "nodes")
text = await TextDocument.find_one(TextDocument.slug == "fdhdgg")
Expand All @@ -118,7 +112,7 @@ async def test_get_path_options_by_root(

# invalid node data
resp = await test_client.get(
"/browse/nodes/658c163106aa5002b5b90e33/path/options-by-root",
f"/browse/nodes/{wrong_id}/path/options-by-root",
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), list)
Expand All @@ -127,20 +121,37 @@ async def test_get_path_options_by_root(

@pytest.mark.anyio
async def test_get_resource_coverage_data(
test_client: AsyncClient,
insert_sample_data,
status_fail_msg,
test_client: AsyncClient, insert_sample_data, status_fail_msg, wrong_id
):
inserted_ids = await insert_sample_data("texts", "nodes", "resources", "units")
resource_id = inserted_ids["resources"][0]
resp = await test_client.get(
f"/browse/resources/{resource_id}/coverage",
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), dict)

# invalid node data
resp = await test_client.get(
f"/browse/resources/{wrong_id}/coverage",
)
assert resp.status_code == 404, status_fail_msg(404, resp)


@pytest.mark.anyio
async def test_get_detailed_resource_coverage_data(
test_client: AsyncClient, insert_sample_data, status_fail_msg, wrong_id
):
inserted_ids = await insert_sample_data("texts", "nodes", "resources", "units")
resource_id = inserted_ids["resources"][0]
resp = await test_client.get(
f"/browse/resources/{resource_id}/coverage-details",
)
assert resp.status_code == 200, status_fail_msg(200, resp)
assert isinstance(resp.json(), list)

# invalid node data
resp = await test_client.get(
"/browse/resources/658c163106aa5002b5b90e33/coverage",
f"/browse/resources/{wrong_id}/coverage-details",
)
assert resp.status_code == 404, status_fail_msg(404, resp)
2 changes: 2 additions & 0 deletions Tekst-Web/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export type PlatformStats = components['schemas']['PlatformStats'];
export type PlatformData = components['schemas']['PlatformData'];
export type PlatformSettingsRead = components['schemas']['PlatformSettingsRead'];
export type PlatformSettingsUpdate = components['schemas']['PlatformSettingsUpdate'];
export type ResourceCoverage = components['schemas']['ResourceCoverage'];
export type ResourceNodeCoverage = components['schemas']['ResourceNodeCoverage'];

// client segments
Expand Down Expand Up @@ -151,6 +152,7 @@ export type AnyResourceCreate = PlaintextResourceCreate | DebugResourceCreate;
export type AnyResourceRead = (PlaintextResourceRead | DebugResourceRead) & {
active?: boolean;
units?: AnyUnitRead[];
coverage?: ResourceCoverage;
};
export type AnyResourceUpdate = PlaintextResourceUpdate | DebugResourceUpdate;

Expand Down
Loading

0 comments on commit 45610e3

Please sign in to comment.