Skip to content

Commit 7ad67f8

Browse files
committed
membership-request [inveniosoftware#855]: address TODOs
1 parent a2428f5 commit 7ad67f8

File tree

11 files changed

+77
-51
lines changed

11 files changed

+77
-51
lines changed

Diff for: invenio_communities/assets/semantic-ui/js/invenio_communities/members/membership_requests/MembershipRequestsEmptyResults.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class MembershipRequestsEmptyResultsCmp extends Component {
1313
<Segment placeholder textAlign="center">
1414
<Header icon>
1515
<Icon name="search" />
16-
{i18next.t("No matching members found.")}
16+
{i18next.t("No matching membership requests found.")}
1717
</Header>
1818
{queryString && (
1919
<p>

Diff for: invenio_communities/assets/semantic-ui/js/invenio_communities/members/membership_requests/MembershipRequestsResultItem.js

+5-12
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,12 @@ export class MembershipRequestsResultItem extends Component {
4545
membershipRequest: { member },
4646
membershipRequest,
4747
} = this.state;
48-
console.log("********************");
49-
console.log("membershipRequest");
50-
console.dir(membershipRequest);
51-
console.log("********************");
5248

5349
const request = buildRequest(membershipRequest, ["accept", "decline"]);
54-
console.log("********************");
55-
console.log("request");
56-
console.dir(request);
57-
console.log("********************");
58-
5950
const { api: membershipRequestsApi } = this.context;
6051
const roles = rolesCanAssign[member.type];
6152
const expiration = formattedTime(request.expires_at);
53+
6254
return (
6355
<Table.Row className="community-member-item">
6456
<Table.Cell>
@@ -104,9 +96,10 @@ export class MembershipRequestsResultItem extends Component {
10496
</Table.Cell>
10597
<Table.Cell data-label={i18next.t("Actions")}>
10698
<RequestActionController
107-
request={membershipRequest}
108-
// TODO: Decision flow
109-
actionSuccessCallback={() => console.log("actionSuccessCallback called")}
99+
request={request}
100+
actionSuccessCallback={() => {
101+
window.location.reload();
102+
}}
110103
/>
111104
</Table.Cell>
112105
</Table.Row>

Diff for: invenio_communities/assets/semantic-ui/js/invenio_communities/members/membership_requests/index.js

-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ const dataAttr = document.getElementById(
3939
const community = JSON.parse(dataAttr.community);
4040
const communitiesAllRoles = JSON.parse(dataAttr.communitiesAllRoles);
4141
const communitiesRolesCanAssign = JSON.parse(dataAttr.communitiesRolesCanAssign);
42-
// TODO: Decision flow: do we need?
43-
// const permissions = JSON.parse(dataAttr.permissions);
4442

4543
const appName = "InvenioCommunities.MembershipRequestsSearch";
4644

Diff for: invenio_communities/members/records/api.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,10 @@ class Member(Record, MemberMixin):
204204

205205

206206
class ArchivedInvitation(Record, MemberMixin):
207-
"""An archived invitation record.
207+
"""An archived invitation or membership request record.
208+
209+
The name is a historical legacy. It could be renamed in the future, but we don't
210+
want to rename the index.
208211
209212
We are using a record without using the actual JSON document and
210213
schema validation normally used in a record. The reason for using a record

Diff for: invenio_communities/members/records/dumpers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ def dump(self, record, data):
3030
def load(self, data, record_cls):
3131
"""Load relations.request.type.
3232
33-
TODO: Works without it for now. Potentially revisit?
33+
Works without implementation for now.
3434
"""
3535
pass

Diff for: invenio_communities/members/resources/config.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ class MemberResourceConfig(RecordResourceConfig):
4646
AlreadyMemberError: create_error_handler(
4747
HTTPJSONException(
4848
code=400,
49-
description="A member was already added or invited.",
49+
description=_(
50+
"A membership already exists or is already being assessed."
51+
),
5052
)
5153
),
5254
CommunityDeletedError: create_error_handler(

Diff for: invenio_communities/members/services/config.py

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ class MemberServiceConfig(RecordServiceConfig, ConfiguratorMixin):
182182
search = MemberSearchOptions
183183
search_public = PublicSearchOptions
184184
search_invitations = InvitationsSearchOptions
185+
search_membership_requests = InvitationsSearchOptions # Use same as invitations
185186

186187
links_item = {
187188
"actions": LinksForActionsOfMember(

Diff for: invenio_communities/members/services/schemas.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,24 @@ def get_permissions(self, obj):
229229

230230

231231
class MembershipRequestDumpSchema(MemberDumpSchema):
232-
"""Schema for dumping membership requests.
233-
234-
TODO: Decision flow: Investigate if can be merged with InvitationDumpSchema
235-
"""
232+
"""Schema for dumping membership requests."""
236233

237234
request = fields.Nested(RequestSchema)
235+
236+
def get_permissions(self, obj):
237+
"""Get permission.
238+
239+
Only permission to see if current identity can_update role is needed.
240+
241+
:param obj: api.Member
242+
"""
243+
is_open = obj["request"]["is_open"]
244+
permission_check = self.context["field_permission_check"]
245+
can_update = permission_check(
246+
"members_update",
247+
community_id=obj.community_id,
248+
member=obj,
249+
)
250+
return {
251+
"can_update_role": is_open and can_update
252+
}

Diff for: invenio_communities/members/services/service.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def invitation_dump_schema(self):
8787

8888
@property
8989
def membership_request_dump_schema(self):
90-
"""Schema for dumping a membership request to JSON."""
90+
"""Schema for dumping a membership request to REST API JSON."""
9191
return ServiceSchemaWrapper(self, schema=MembershipRequestDumpSchema)
9292

9393
# Loading schemas
@@ -873,9 +873,8 @@ def search_membership_requests(
873873
community_id,
874874
"search_membership_requests",
875875
self.membership_request_dump_schema,
876-
# Use same as invitations
877-
self.config.search_invitations, # TODO: Decision flow: Rename/merge ?
878-
record_cls=ArchivedInvitation, # TODO: Decision flow: merge or new?
876+
self.config.search_membership_requests,
877+
record_cls=ArchivedInvitation,
879878
extra_filter=(
880879
dsl.Q("term", **{"active": False})
881880
& dsl.Q(

Diff for: invenio_communities/views/communities.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,8 @@ def membership_requests(pid_value, community, community_ui):
466466
"invenio_communities/details/members/membership_requests.html",
467467
theme=community_ui.get("theme", {}),
468468
community=community_ui,
469-
roles_can_assign=_get_roles_can_invite(community.id), # TODO: Decision flow
469+
# We use the same roles as the ones that can be invited
470+
roles_can_assign=_get_roles_can_invite(community.id),
470471
permissions=permissions,
471472
)
472473

Diff for: tests/members/test_members_resource.py

+38-24
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ def test_search(
238238
assert r.status_code == 200
239239
data = r.json
240240
assert data["sortBy"] == "name"
241+
expected_links = {
242+
"self": f"https://127.0.0.1:5000/api/communities/{community_id}/members?page=1&size=25&sort=name" # noqa
243+
}
244+
assert expected_links == data["links"]
241245
assert data["hits"]["total"] == 3
242246
assert "role" in data["aggregations"]
243247
assert "visibility" in data["aggregations"]
@@ -300,6 +304,10 @@ def test_search_public(
300304
assert r.status_code == 200
301305
data = r.json
302306
assert data["sortBy"] == "name"
307+
expected_links = {
308+
"self": f"https://127.0.0.1:5000/api/communities/{community_id}/members?page=1&size=25&sort=name" # noqa
309+
}
310+
assert expected_links == data["links"]
303311
assert data["hits"]["total"] == 1
304312
hit = data["hits"]["hits"][0]
305313
# Public view has no facets (because that would leak information on
@@ -350,9 +358,15 @@ def test_search_invitation(
350358
assert hit["permissions"]["can_update_role"] is True
351359
assert hit["permissions"]["can_cancel"] is True
352360

361+
request_id = hit["request"]["id"]
362+
expected_links = {
363+
"actions": {
364+
"cancel": f"https://127.0.0.1:5000/api/requests/{request_id}/actions/cancel",
365+
},
366+
}
367+
assert expected_links == hit["links"]
368+
353369

354-
# TODO: member serialization/links
355-
# TODO: request serialization/links
356370
# TODO: community member can see info
357371
# TODO: community non-member can't see info
358372
# TODO: facet by role, facet by visibility, define sorts.
@@ -375,9 +389,8 @@ def test_post_membership_requests(app, client, headers, community_id, create_use
375389
headers=headers,
376390
json={"message": "Can I join the club?"},
377391
)
378-
assert 201 == r.status_code
379-
380392
RequestEvent.index.refresh()
393+
assert 201 == r.status_code
381394

382395
# Get links to check
383396
url_of_request = r.json["links"]["self"].replace(app.config["SITE_API_URL"], "")
@@ -399,22 +412,25 @@ def test_post_membership_requests(app, client, headers, community_id, create_use
399412
assert "Can I join the club?" == msg
400413

401414

402-
def test_put_membership_requests(
403-
client, headers, community_id, owner, new_user_data, db
404-
):
405-
# update membership request
406-
# TODO: Implement me!
407-
assert True
408-
409-
410415
def test_error_handling_for_membership_requests(
411-
client, headers, community_id, owner, new_user_data, db
416+
client, create_user, headers, community_id, owner, new_user_data, db
412417
):
413-
# TODO: Implement me!
414-
# error handling registered
415-
# - permission handling registered
416-
# - duplicate handling registered
417-
assert True
418+
user = create_user({"email": "[email protected]", "username": "user_foo"})
419+
client = user.login(client)
420+
r = client.post(
421+
f"/communities/{community_id}/membership-requests",
422+
headers=headers,
423+
json={"message": "Can I join the club?"},
424+
)
425+
assert 201 == r.status_code
426+
427+
# Error if duplicate posting
428+
r = client.post(
429+
f"/communities/{community_id}/membership-requests",
430+
headers=headers,
431+
json={"message": "Can I join the club again?"},
432+
)
433+
assert 400 == r.status_code
418434

419435

420436
def test_get_membership_requests(
@@ -451,6 +467,7 @@ def test_get_membership_requests(
451467
assert "id" in hit["request"]
452468
assert "status" in hit["request"]
453469
assert "expires_at" in hit["request"]
470+
# hits > hit > links
454471
request_id = hit["request"]["id"]
455472
expected_links = {
456473
"actions": {
@@ -459,11 +476,8 @@ def test_get_membership_requests(
459476
},
460477
}
461478
assert expected_links == hit["links"]
479+
# hits > hit > permissions
480+
assert hit["permissions"]["can_update_role"] is True
481+
462482
# TODO: Expiration flow : assess if expiration makes sense for membership requests.
463483
# assert hit["request"]["expires_at"] is not None
464-
# TODO: Decision flow
465-
# hits > hit > permissions
466-
# assert "permissions" in hit
467-
# assert hit["permissions"]["can_update_role"] is True
468-
# assert hit["permissions"]["can_cancel"] is True
469-
# hits > hit > links

0 commit comments

Comments
 (0)