Skip to content

Commit 811cf06

Browse files
Prevent matching items with null relationship item_ids
Re-add old comments Re-add old comments Fix test_graph.py with right numbers of nodes and edges for each test (for items and collections) Add test for item graph difference between admins and users (#1360) Re-add get_graph_cy_format modification Fix pytest Re-add user_only=False Re-add user_only=False
1 parent 63105fc commit 811cf06

File tree

3 files changed

+140
-31
lines changed

3 files changed

+140
-31
lines changed

pydatalab/src/pydatalab/routes/v0_1/graphs.py

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ def get_graph_cy_format(
1919
hide_collections: bool = True,
2020
):
2121
collection_id = request.args.get("collection_id", type=str)
22+
hide_collections = request.args.get(
23+
"hide_collections", default=False, type=lambda v: v.lower() == "true"
24+
)
2225

2326
if item_id is None:
2427
if collection_id is not None:
@@ -49,33 +52,56 @@ def get_graph_cy_format(
4952
all_documents.rewind()
5053

5154
else:
52-
all_documents = list(
55+
main_item = flask_mongo.db.items.find_one(
56+
{
57+
"item_id": item_id,
58+
**get_default_permissions(user_only=False),
59+
},
60+
projection={"item_id": 1, "name": 1, "type": 1, "relationships": 1},
61+
)
62+
63+
if not main_item:
64+
return (
65+
jsonify(status="error", message=f"Item {item_id} not found or no permission"),
66+
404,
67+
)
68+
69+
all_documents = [main_item]
70+
node_ids = {item_id}
71+
72+
for relationship in main_item.get("relationships", []) or []:
73+
if relationship.get("item_id"):
74+
node_ids.add(relationship["item_id"])
75+
76+
incoming_items = list(
5377
flask_mongo.db.items.find(
5478
{
55-
"$or": [{"item_id": item_id}, {"relationships.item_id": item_id}],
56-
**get_default_permissions(user_only=False),
79+
"$and": [
80+
{"relationships": {"$elemMatch": {"item_id": item_id}}},
81+
get_default_permissions(user_only=False),
82+
]
5783
},
5884
projection={"item_id": 1, "name": 1, "type": 1, "relationships": 1},
5985
)
6086
)
6187

62-
node_ids = {document["item_id"] for document in all_documents} | {
63-
relationship.get("item_id")
64-
for document in all_documents
65-
for relationship in document.get("relationships", [])
66-
}
67-
if len(node_ids) > 1:
68-
or_query = [{"item_id": id} for id in node_ids if id != item_id]
69-
next_shell = flask_mongo.db.items.find(
70-
{
71-
"$or": or_query,
72-
**get_default_permissions(user_only=False),
73-
},
74-
projection={"item_id": 1, "name": 1, "type": 1, "relationships": 1},
75-
)
88+
for doc in incoming_items:
89+
node_ids.add(doc["item_id"])
7690

77-
all_documents.extend(next_shell)
78-
node_ids = node_ids | {document["item_id"] for document in all_documents}
91+
all_documents.extend(incoming_items)
92+
93+
ids_to_fetch = node_ids - {doc["item_id"] for doc in all_documents}
94+
if ids_to_fetch:
95+
referenced_items = list(
96+
flask_mongo.db.items.find(
97+
{
98+
"item_id": {"$in": list(ids_to_fetch)},
99+
**get_default_permissions(user_only=False),
100+
},
101+
projection={"item_id": 1, "name": 1, "type": 1, "relationships": 1},
102+
)
103+
)
104+
all_documents.extend(referenced_items)
79105

80106
nodes = []
81107
edges = []
@@ -166,12 +192,18 @@ def get_graph_cy_format(
166192
}
167193
)
168194

169-
whitelist = {edge["data"]["source"] for edge in edges} | {item_id}
195+
whitelist = {edge["data"]["source"] for edge in edges} | {
196+
edge["data"]["target"] for edge in edges
197+
}
198+
if item_id:
199+
whitelist.add(item_id)
170200

171201
nodes = [
172202
node
173203
for node in nodes
174-
if node["data"]["type"] in ("samples", "cells") or node["data"]["id"] in whitelist
204+
if node["data"]["type"] in ("samples", "cells")
205+
or node["data"]["id"] in whitelist
206+
or node["data"]["id"].startswith("Collection:")
175207
]
176208

177209
return (jsonify(status="success", nodes=nodes, edges=edges), 200)

pydatalab/tests/server/test_graph.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ def test_simple_graph(admin_client):
129129
assert len(graph["edges"]) == 2
130130

131131
graph = admin_client.get("/item-graph/parent").json
132-
assert len(graph["nodes"]) == 6
133-
assert len(graph["edges"]) == 5
132+
assert len(graph["nodes"]) == 7
133+
assert len(graph["edges"]) == 8
134134

135135
samples = sample_list.json["responses"]
136136

@@ -156,5 +156,5 @@ def test_simple_graph(admin_client):
156156
assert len(graph["edges"]) == 4
157157

158158
graph = admin_client.get("/item-graph/parent").json
159-
assert len(graph["nodes"]) == 6
160-
assert len(graph["edges"]) == 5
159+
assert len(graph["nodes"]) == 7
160+
assert len(graph["edges"]) == 10

pydatalab/tests/server/test_item_graph.py

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,24 @@
88
from pydatalab.models import Sample, StartingMaterial
99

1010

11-
def test_single_starting_material(admin_client):
11+
def test_single_starting_material(admin_client, client):
1212
item_id = "material"
1313

1414
material = StartingMaterial(item_id=item_id)
1515

16-
creation = admin_client.post(
16+
creation = client.post(
1717
"/new-sample/",
1818
json={"new_sample_data": json.loads(material.json())},
1919
)
2020

2121
assert creation.status_code == 201
2222

2323
# A single material without connections should be ignored
24-
graph = admin_client.get("/item-graph").json
24+
graph = client.get("/item-graph").json
2525
assert len(graph["nodes"]) == 0
2626

2727
# Unless it is asked for directly
28-
graph = admin_client.get(f"/item-graph/{item_id}").json
28+
graph = client.get(f"/item-graph/{item_id}").json
2929
assert len(graph["nodes"]) == 1
3030

3131
# Now make a sample and connect it to the starting material; check that the
@@ -37,13 +37,90 @@ def test_single_starting_material(admin_client):
3737
],
3838
)
3939

40-
creation = admin_client.post(
40+
creation = client.post(
4141
"/new-sample/",
4242
json={"new_sample_data": json.loads(parent.json())},
4343
)
4444

4545
assert creation.status_code == 201
4646

47-
graph = admin_client.get("/item-graph").json
47+
graph = client.get("/item-graph").json
48+
assert len(graph["nodes"]) == 2
49+
assert len(graph["edges"]) == 1
50+
51+
# From both the starting material and the sample
52+
graph = client.get(f"/item-graph/{item_id}").json
53+
assert len(graph["nodes"]) == 2
54+
assert len(graph["edges"]) == 1
55+
56+
# From both the starting material and the sample
57+
graph = client.get("/item-graph/parent").json
58+
assert len(graph["nodes"]) == 2
59+
assert len(graph["edges"]) == 1
60+
61+
# Now add a few more samples in a chain and check that only the relevant ones are shown
62+
child = Sample(
63+
item_id="child",
64+
synthesis_constituents=[
65+
{"item": {"item_id": "parent", "type": "samples"}, "quantity": None}
66+
],
67+
)
68+
69+
creation = client.post(
70+
"/new-sample/",
71+
json={"new_sample_data": json.loads(child.json())},
72+
)
73+
74+
grandchild = Sample(
75+
item_id="grandchild",
76+
synthesis_constituents=[
77+
{"item": {"item_id": "child", "type": "samples"}, "quantity": None}
78+
],
79+
)
80+
81+
creation = client.post(
82+
"/new-sample/",
83+
json={"new_sample_data": json.loads(grandchild.json())},
84+
)
85+
86+
great_grandchild = Sample(
87+
item_id="great-grandchild",
88+
synthesis_constituents=[
89+
{"item": {"item_id": "grandchild", "type": "samples"}, "quantity": None}
90+
],
91+
)
92+
93+
creation = client.post(
94+
"/new-sample/",
95+
json={"new_sample_data": json.loads(great_grandchild.json())},
96+
)
97+
98+
graph = client.get("/item-graph").json
99+
assert len(graph["nodes"]) == 5
100+
assert len(graph["edges"]) == 4
101+
102+
# Check for bug where this behaviour was inconsistent between admin and non-admin users
103+
graph = admin_client.get("/item-graph/great-grandchild").json
104+
assert len(graph["nodes"]) == 2
105+
assert len(graph["edges"]) == 1
106+
107+
# Add an admin only item and check that the non-admin user still sees the same graph
108+
admin_great_great_grandchild = Sample(
109+
item_id="admin-great-great-grandchild",
110+
synthesis_constituents=[
111+
{"item": {"item_id": "great-grandchild", "type": "samples"}, "quantity": None}
112+
],
113+
)
114+
115+
creation = admin_client.post(
116+
"/new-sample/", json={"new_sample_data": json.loads(admin_great_great_grandchild.json())}
117+
)
118+
119+
graph = admin_client.get("/item-graph/great-grandchild").json
120+
assert len(graph["nodes"]) == 3
121+
assert len(graph["edges"]) == 2
122+
123+
# Current broken behaviour: non-admin users see the full graph, but not the things they don't have permission for
124+
graph = client.get("/item-graph/great-grandchild").json
48125
assert len(graph["nodes"]) == 2
49126
assert len(graph["edges"]) == 1

0 commit comments

Comments
 (0)