Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
pylipp committed Jan 3, 2025
1 parent b9abcd2 commit 58d0c37
Show file tree
Hide file tree
Showing 12 changed files with 106 additions and 34 deletions.
2 changes: 1 addition & 1 deletion back/boxtribute_server/authz.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ def authorize_cross_organisation_access(
MUTATIONS_FOR_BETA_LEVEL[3] = MUTATIONS_FOR_BETA_LEVEL[2] + ("deleteBoxes",)
MUTATIONS_FOR_BETA_LEVEL[4] = MUTATIONS_FOR_BETA_LEVEL[3] + (
"moveBoxesToLocation",
"assignTagToBoxes",
"assignTagsToBoxes",
"unassignTagFromBoxes",
)
MUTATIONS_FOR_BETA_LEVEL[5] = MUTATIONS_FOR_BETA_LEVEL[4] + (
Expand Down
7 changes: 4 additions & 3 deletions back/boxtribute_server/business_logic/warehouse/box/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,8 @@ def move_boxes_to_location(*, user_id, boxes, location):
return list(Box.select().where(Box.id << box_ids))


def assign_tag_to_boxes(*, user_id, boxes, tag):
"""Add TagsRelation entries for given boxes and tag. Update last_modified_* fields
def assign_tags_to_boxes(*, user_id, boxes, tag_ids):
"""Add TagsRelation entries for given boxes and tags. Update last_modified_* fields
of the affected boxes.
Return the list of updated boxes.
"""
Expand All @@ -453,11 +453,12 @@ def assign_tag_to_boxes(*, user_id, boxes, tag):
TagsRelation(
object_id=box.id,
object_type=TaggableObjectType.Box,
tag=tag.id,
tag=tag_id,
created_on=now,
created_by=user_id,
)
for box in boxes
for tag_id in tag_ids
]

box_ids = [box.id for box in boxes]
Expand Down
59 changes: 46 additions & 13 deletions back/boxtribute_server/business_logic/warehouse/box/mutations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from typing import Any

from ariadne import MutationType
from flask import g
Expand All @@ -19,7 +20,7 @@
from ....models.definitions.tag import Tag
from ....models.definitions.tags_relation import TagsRelation
from .crud import (
assign_tag_to_boxes,
assign_tags_to_boxes,
create_box,
delete_boxes,
move_boxes_to_location,
Expand All @@ -36,6 +37,11 @@ class BoxesResult:
invalid_box_label_identifiers: list[str]


@dataclass(kw_only=True)
class AssignTagsToBoxesResult(BoxesResult):
tag_error_info: list[dict[str, Any]]


@mutation.field("createBox")
def resolve_create_box(*_, creation_input):
requested_location = Location.get_by_id(creation_input["location_id"])
Expand Down Expand Up @@ -156,18 +162,40 @@ def resolve_move_boxes_to_location(*_, update_input):
)


@mutation.field("assignTagToBoxes")
@handle_unauthorized
def resolve_assign_tag_to_boxes(*_, update_input):
tag_id = update_input["tag_id"]
if (tag := Tag.get_or_none(tag_id)) is None:
return ResourceDoesNotExist(name="Tag", id=tag_id)
def authorize_tag(tag):
authorize(permission="tag_relation:assign")
authorize(permission="tag:read", base_id=tag.base_id)
if tag.deleted_on is not None:
return DeletedTag(name=tag.name)
if tag.type == TagType.Beneficiary:
return TagTypeMismatch(expected_type=TagType.Box)


def _validate_tags(tag_ids):
tag_errors = []
valid_tag_ids = []
for tag_id in tag_ids:
if (tag := Tag.get_or_none(tag_id)) is None:
tag_errors.append(
{"id": tag_id, "error": ResourceDoesNotExist(name="Tag", id=tag_id)}
)
continue
if (error := authorize_tag(tag)) is not None:
tag_errors.append({"id": tag_id, "error": error})
continue
if tag.deleted_on is not None:
tag_errors.append({"id": tag_id, "error": DeletedTag(name=tag.name)})
continue
if tag.type == TagType.Beneficiary:
tag_errors.append(
{"id": tag_id, "error": TagTypeMismatch(expected_type=TagType.Box)}
)
continue
valid_tag_ids.append(tag_id)
return valid_tag_ids, tag_errors


@mutation.field("assignTagsToBoxes")
def resolve_assign_tags_to_boxes(*_, update_input):
tag_ids = set(update_input["tag_ids"])
valid_tag_ids, tag_errors = _validate_tags(tag_ids)

label_identifiers = set(update_input["label_identifiers"])
boxes = (
Expand All @@ -179,7 +207,9 @@ def resolve_assign_tag_to_boxes(*_, update_input):
on=(
(TagsRelation.object_id == Box.id)
& (TagsRelation.object_type == TaggableObjectType.Box)
& (TagsRelation.tag == tag.id)
# TODO: this is not correct because it filters out a box if it has only
# one of the requested tags already
& (TagsRelation.tag << valid_tag_ids)
& TagsRelation.deleted_on.is_null()
),
)
Expand All @@ -194,11 +224,14 @@ def resolve_assign_tag_to_boxes(*_, update_input):
)
valid_box_label_identifiers = {box.label_identifier for box in boxes}

return BoxesResult(
updated_boxes=assign_tag_to_boxes(user_id=g.user.id, boxes=boxes, tag=tag),
return AssignTagsToBoxesResult(
updated_boxes=assign_tags_to_boxes(
user_id=g.user.id, boxes=boxes, tag_ids=valid_tag_ids
),
invalid_box_label_identifiers=sorted(
label_identifiers.difference(valid_box_label_identifiers)
),
tag_error_info=tag_errors,
)


Expand Down
1 change: 1 addition & 0 deletions back/boxtribute_server/graph_ql/bindables.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ def resolve_location_type(obj, *_):
UnionType("UnassignTagFromBoxesResult", resolve_type_by_class_name),
UnionType("QrCodeResult", resolve_type_by_class_name),
UnionType("BoxResult", resolve_type_by_class_name),
UnionType("TagError", resolve_type_by_class_name),
)
interface_types = (
InterfaceType("Location", resolve_location_type),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ input BoxMoveInput {

input BoxAssignTagInput {
labelIdentifiers: [String!]!
tagId: Int!
tagIds: [Int!]!
}

input CustomProductCreationInput {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Mutation {
" Any boxes that are non-existing, already inside the requested location, inside a different base other than the one of the requested location, and/or in a base that the user must not access are returned in the `BoxesResult.invalidBoxLabelIdentifiers` list. "
moveBoxesToLocation(updateInput: BoxMoveInput): MoveBoxesResult
" Any boxes that are non-existing, already assigned to the requested tag, and/or in a base that the user must not access are returned in the `BoxesResult.invalidBoxLabelIdentifiers` list. "
assignTagToBoxes(updateInput: BoxAssignTagInput): AssignTagToBoxesResult
assignTagsToBoxes(updateInput: BoxAssignTagInput): AssignTagsToBoxesResult
" Any boxes that are non-existing, don't have the requested tag assigned, and/or in a base that the user must not access are returned in the `BoxesResult.invalidBoxLabelIdentifiers` list. "
unassignTagFromBoxes(updateInput: BoxAssignTagInput): UnassignTagFromBoxesResult

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,23 @@ type ShipmentDetail {
receivedOn: Datetime
}

"""
Utility response type for box bulk tag-mutations, containing both updated boxes and invalid boxes (ignored due to e.g. being deleted, in prohibited base, and/or non-existing) as well as optional info about erroneous tags.
"""
type AssignTagsToBoxesResult {
updatedBoxes: [Box!]!
invalidBoxLabelIdentifiers: [String!]!
tagErrorInfo: [TagErrorInfo!]
}

""" Error info about tag with specified ID. """
type TagErrorInfo {
id: ID!
error: TagError!
}

union TagError = InsufficientPermissionError | ResourceDoesNotExistError | UnauthorizedForBaseError | TagTypeMismatchError | DeletedTagError

type InsufficientPermissionError {
" e.g. 'product:write' missing "
name: String!
Expand Down
2 changes: 1 addition & 1 deletion back/test/endpoint_tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ def test_update_non_existent_resource(
],
# Test case 8.2.23g
[
"assignTagToBoxes",
"assignTagsToBoxes",
'updateInput: { labelIdentifiers: ["12345678"], tagId: 0 }',
"...on ResourceDoesNotExistError { id name }",
{"id": "0", "name": "Tag"},
Expand Down
14 changes: 7 additions & 7 deletions back/test/endpoint_tests/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ def test_box_mutations(
assert response == {"name": f"{deleted_location['name']}"}

# Test case 8.2.23a, 8.2.23c
mutation = f"""mutation {{ assignTagToBoxes( updateInput: {{
mutation = f"""mutation {{ assignTagsToBoxes( updateInput: {{
labelIdentifiers: [{label_identifiers}], tagId: {tag_id} }} ) {{
...on BoxesResult {{
updatedBoxes {{ tags {{ id }} }}
Expand All @@ -579,7 +579,7 @@ def test_box_mutations(
"invalidBoxLabelIdentifiers": [another_created_box_label_identifier],
}

mutation = f"""mutation {{ assignTagToBoxes( updateInput: {{
mutation = f"""mutation {{ assignTagsToBoxes( updateInput: {{
labelIdentifiers: [{label_identifiers}], tagId: {tag_id} }} ) {{
...on BoxesResult {{
updatedBoxes {{ tags {{ id }} }}
Expand All @@ -592,7 +592,7 @@ def test_box_mutations(
}

generic_tag_id = str(tags[2]["id"])
mutation = f"""mutation {{ assignTagToBoxes( updateInput: {{
mutation = f"""mutation {{ assignTagsToBoxes( updateInput: {{
labelIdentifiers: [{label_identifiers}], tagId: {generic_tag_id} }} ) {{
...on BoxesResult {{
updatedBoxes {{ tags {{ id }} }}
Expand All @@ -607,7 +607,7 @@ def test_box_mutations(
}

another_generic_tag_id = str(tags[5]["id"])
mutation = f"""mutation {{ assignTagToBoxes( updateInput: {{
mutation = f"""mutation {{ assignTagsToBoxes( updateInput: {{
labelIdentifiers: [{label_identifiers}],
tagId: {another_generic_tag_id} }} ) {{
...on BoxesResult {{
Expand Down Expand Up @@ -672,7 +672,7 @@ def test_box_mutations(

# Test case 8.2.23h
beneficiary_tag_id = str(tags[0]["id"])
mutation = f"""mutation {{ assignTagToBoxes( updateInput: {{
mutation = f"""mutation {{ assignTagsToBoxes( updateInput: {{
labelIdentifiers: [{label_identifiers}], tagId: {beneficiary_tag_id} }} ) {{
...on TagTypeMismatchError {{ expectedType }} }} }}"""
response = assert_successful_request(client, mutation)
Expand All @@ -694,7 +694,7 @@ def test_box_mutations(
# Test case 8.2.23i
deleted_tag_id = str(tags[4]["id"])
tag_name = tags[4]["name"]
mutation = f"""mutation {{ assignTagToBoxes( updateInput: {{
mutation = f"""mutation {{ assignTagsToBoxes( updateInput: {{
labelIdentifiers: [{label_identifiers}], tagId: {deleted_tag_id} }} ) {{
...on DeletedTagError {{ name }} }} }}"""
response = assert_successful_request(client, mutation)
Expand Down Expand Up @@ -750,7 +750,7 @@ def test_box_mutations(
}

# Test case 8.2.23b, 8.2.23d, 8.2.23j
mutation = f"""mutation {{ assignTagToBoxes( updateInput: {{
mutation = f"""mutation {{ assignTagsToBoxes( updateInput: {{
labelIdentifiers: [{label_identifiers}], tagId: {tag_id} }} ) {{
...on BoxesResult {{
updatedBoxes {{ tags {{ id }} }}
Expand Down
6 changes: 3 additions & 3 deletions back/test/endpoint_tests/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ def test_invalid_permission_for_user_read(
],
# Test case 8.2.23e
[
"assignTagToBoxes",
"assignTagsToBoxes",
'updateInput: { labelIdentifiers: ["12345678"], tagId: 2 }',
"...on InsufficientPermissionError { name }",
{"name": "tag_relation:assign"},
Expand Down Expand Up @@ -653,8 +653,8 @@ def test_mutate_insufficient_permission(
],
# Test case 8.2.23f
[
"assignTagToBoxes",
'updateInput: { labelIdentifiers: ["12345678"], tagId: 4 }',
"assignTagsToBoxes",
'updateInput: { labelIdentifiers: ["12345678"], tagIds: [4] }',
"...on UnauthorizedForBaseError { id }",
{"id": "2"},
],
Expand Down
Loading

0 comments on commit 58d0c37

Please sign in to comment.