Skip to content

Commit 2c53de8

Browse files
authored
734 view and modify list of metadata definitions in UI (#758)
* add endpoint and test * codegen and black * add action * add search and get endpoint * add search and get endpoint to action * add to reducer * basic page for metadata definition ready * add delete confirmation * add create metadata modal * ordering in decending order * update message * black * fix the pytest * codegen * fix codegen
1 parent f83957a commit 2c53de8

File tree

17 files changed

+835
-115
lines changed

17 files changed

+835
-115
lines changed

backend/app/models/metadata.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class MetadataDefinitionBase(BaseModel):
8989

9090
name: str
9191
description: Optional[str]
92+
created: datetime = Field(default_factory=datetime.utcnow)
9293
context: Optional[
9394
List[Union[dict, AnyUrl]]
9495
] # https://json-ld.org/spec/latest/json-ld/#the-context
@@ -121,7 +122,8 @@ class Settings:
121122

122123

123124
class MetadataDefinitionOut(MetadataDefinitionDB):
124-
pass
125+
class Config:
126+
fields = {"id": "id"}
125127

126128

127129
def validate_definition(content: dict, metadata_def: MetadataDefinitionOut):

backend/app/routers/groups.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from beanie.operators import Or, Push, RegEx
66
from bson.objectid import ObjectId
77
from fastapi import HTTPException, Depends, APIRouter
8-
from pymongo import DESCENDING
98

109
from app.deps.authorization_deps import AuthorizationDB, GroupAuthorization
1110
from app.keycloak_auth import get_current_user, get_user
@@ -38,7 +37,7 @@ async def get_groups(
3837
"""Get a list of all Groups in the db the user is a member/owner of.
3938
4039
Arguments:
41-
skip -- number of initial records to skip (i.e. for pagination)
40+
skip -- number of initial recoto_list()rds to skip (i.e. for pagination)
4241
limit -- restrict number of records to be returned (i.e. for pagination)
4342
4443

backend/app/routers/metadata.py

Lines changed: 95 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from typing import Optional, List
22

3+
from beanie import PydanticObjectId
4+
from beanie.odm.operators.find.evaluation import RegEx
5+
from beanie.odm.operators.find.logical import Or
36
from elasticsearch import Elasticsearch
47
from fastapi import (
58
APIRouter,
@@ -44,24 +47,108 @@ async def save_metadata_definition(
4447

4548

4649
@router.get("/definition", response_model=List[MetadataDefinitionOut])
47-
async def get_metadata_definition(
50+
async def get_metadata_definition_list(
4851
name: Optional[str] = None,
4952
user=Depends(get_current_user),
5053
skip: int = 0,
5154
limit: int = 2,
5255
):
5356
if name is None:
54-
defs = await MetadataDefinitionDB.find().skip(skip).limit(limit).to_list()
57+
defs = await MetadataDefinitionDB.find(
58+
sort=(-MetadataDefinitionDB.created), skip=skip, limit=limit
59+
).to_list()
5560
else:
56-
defs = (
57-
await MetadataDefinitionDB.find(MetadataDefinitionDB.name == name)
58-
.skip(skip)
59-
.limit(limit)
60-
.to_list()
61-
)
61+
defs = await MetadataDefinitionDB.find(
62+
MetadataDefinitionDB.name == name,
63+
sort=(-MetadataDefinitionDB.created),
64+
skip=skip,
65+
limit=limit,
66+
).to_list()
6267
return [mddef.dict() for mddef in defs]
6368

6469

70+
@router.get(
71+
"/definition/{metadata_definition_id}", response_model=MetadataDefinitionOut
72+
)
73+
async def get_metadata_definition(
74+
metadata_definition_id: str,
75+
user=Depends(get_current_user),
76+
):
77+
if (
78+
mdd := await MetadataDefinitionDB.get(PydanticObjectId(metadata_definition_id))
79+
) is not None:
80+
return mdd.dict()
81+
raise HTTPException(
82+
status_code=404,
83+
detail=f"Metadata definition {metadata_definition_id} not found",
84+
)
85+
86+
87+
@router.delete(
88+
"/definition/{metadata_definition_id}", response_model=MetadataDefinitionOut
89+
)
90+
async def delete_metadata_definition(
91+
metadata_definition_id: str,
92+
user=Depends(get_current_user),
93+
):
94+
"""Delete metadata definition by specific ID."""
95+
mdd = await MetadataDefinitionDB.find_one(
96+
MetadataDefinitionDB.id == PyObjectId(metadata_definition_id)
97+
)
98+
if mdd:
99+
# Check if metadata using this definition exists
100+
metadata_using_definition = await MetadataDB.find(
101+
MetadataDB.definition == mdd.name
102+
).to_list()
103+
104+
if len(metadata_using_definition) > 0:
105+
raise HTTPException(
106+
status_code=400,
107+
detail=f"Metadata definition: {mdd.name} ({metadata_definition_id}) in use. "
108+
f"You cannot delete it until all metadata records using it are deleted.",
109+
)
110+
111+
# TODO: Refactor this with permissions checks etc.
112+
await mdd.delete()
113+
return mdd.dict()
114+
else:
115+
raise HTTPException(
116+
status_code=404,
117+
detail=f"Metadata definition {metadata_definition_id} not found",
118+
)
119+
120+
121+
@router.get(
122+
"/definition/search/{search_term}", response_model=List[MetadataDefinitionOut]
123+
)
124+
async def search_metadata_definition(
125+
search_term: str,
126+
skip: int = 0,
127+
limit: int = 10,
128+
user=Depends(get_current_user),
129+
):
130+
"""Search all metadata definition in the db based on text.
131+
132+
Arguments:
133+
text -- any text matching name or description
134+
skip -- number of initial records to skip (i.e. for pagination)
135+
limit -- restrict number of records to be returned (i.e. for pagination)
136+
"""
137+
138+
mdds = await MetadataDefinitionDB.find(
139+
Or(
140+
RegEx(field=MetadataDefinitionDB.name, pattern=search_term),
141+
RegEx(field=MetadataDefinitionDB.description, pattern=search_term),
142+
RegEx(field=MetadataDefinitionDB.context, pattern=search_term),
143+
),
144+
sort=(-MetadataDefinitionDB.created),
145+
skip=skip,
146+
limit=limit,
147+
).to_list()
148+
149+
return [mdd.dict() for mdd in mdds]
150+
151+
65152
@router.patch("/{metadata_id}", response_model=MetadataOut)
66153
async def update_metadata(
67154
metadata_in: MetadataPatch,

backend/app/tests/test_metadata.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,32 @@ def test_dataset_create_metadata_no_context(client: TestClient, headers: dict):
7474
assert response.status_code == 400
7575

7676

77-
def test_dataset_create_metadata_definition(client: TestClient, headers: dict):
77+
def test_dataset_create_and_delete_metadata_definition_success(
78+
client: TestClient, headers: dict
79+
):
80+
# Post the definition itself
81+
response = client.post(
82+
f"{settings.API_V2_STR}/metadata/definition",
83+
json=metadata_definition2,
84+
headers=headers,
85+
)
86+
assert (
87+
response.status_code == 200 or response.status_code == 409
88+
) # 409 = definition already exists
89+
metadata_definition_id = response.json().get("id")
90+
91+
# Delete metadata definition
92+
response = client.delete(
93+
f"{settings.API_V2_STR}/metadata/definition/{metadata_definition_id}",
94+
headers=headers,
95+
)
96+
assert response.status_code == 200
97+
assert response.json().get("id") == metadata_definition_id
98+
99+
100+
def test_dataset_create_and_delete_metadata_definition_fail(
101+
client: TestClient, headers: dict
102+
):
78103
# Post the definition itself
79104
response = client.post(
80105
f"{settings.API_V2_STR}/metadata/definition",
@@ -84,6 +109,7 @@ def test_dataset_create_metadata_definition(client: TestClient, headers: dict):
84109
assert (
85110
response.status_code == 200 or response.status_code == 409
86111
) # 409 = definition already exists
112+
metadata_definition_id = response.json().get("id")
87113

88114
# check if @context is injected correctly
89115
assert response.json().get("@context") is not None
@@ -108,6 +134,13 @@ def test_dataset_create_metadata_definition(client: TestClient, headers: dict):
108134
)
109135
assert response.status_code == 409
110136

137+
# Delete metadata definition
138+
response = client.delete(
139+
f"{settings.API_V2_STR}/metadata/definition/{metadata_definition_id}",
140+
headers=headers,
141+
)
142+
assert response.status_code == 400
143+
111144

112145
@pytest.mark.asyncio
113146
async def test_dataset_patch_metadata_definition(client: TestClient, headers: dict):

0 commit comments

Comments
 (0)