Skip to content

Commit

Permalink
membership-request [inveniosoftware#855]: implement decision flow
Browse files Browse the repository at this point in the history
- update role
- addressed TODOs
  • Loading branch information
fenekku committed Jul 30, 2024
1 parent 828ff70 commit d8a7f62
Show file tree
Hide file tree
Showing 16 changed files with 558 additions and 407 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
// Invenio-communities is free software; you can redistribute it and/or modify it
// under the terms of the MIT License; see LICENSE file for more details.

import { CommunityLinksExtractor } from "../CommunityLinksExtractor";
import { http } from "react-invenio-forms";

import { CommunityLinksExtractor } from "../CommunityLinksExtractor";
import { bulkMembersSerializer } from "../serializers";

/**
* API Client for community membership requests.
*
Expand All @@ -21,6 +23,14 @@ export class CommunityMembershipRequestsApi {
}

requestMembership = async (payload) => {
// assigned rather than defiend for ease of passing as callback
return await http.post(this.linksExtractor.url("membership_requests"), payload);
};

updateRole = async (membershipRequest, role) => {
// assigned rather than defiend for ease of passing as callback
const memberSerialized = bulkMembersSerializer([membershipRequest]);
const payload = { members: memberSerialized, role: role };
return await http.put(this.linksExtractor.url("membership_requests"), payload);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class MembershipRequestsEmptyResultsCmp extends Component {
<Segment placeholder textAlign="center">
<Header icon>
<Icon name="search" />
{i18next.t("No matching members found.")}
{i18next.t("No matching membership requests found.")}
</Header>
{queryString && (
<p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import React, { Component } from "react";
import { Image } from "react-invenio-forms";
import { Grid, Item, Table } from "semantic-ui-react";

import { MembershipRequestsContext } from "../../api/membershipRequests/MembershipRequestsContextProvider";
import { RoleDropdown } from "../components/dropdowns";
import { formattedTime } from "../utils";
import { buildRequest, formattedTime } from "../utils";

export class MembershipRequestsResultItem extends Component {
constructor(props) {
Expand All @@ -25,6 +26,8 @@ export class MembershipRequestsResultItem extends Component {
this.state = { membershipRequest: result };
}

static contextType = MembershipRequestsContext;

update = (data, value) => {
const { membershipRequest } = this.state;
this.setState({ membershipRequest: { ...membershipRequest, ...{ role: value } } });
Expand All @@ -39,13 +42,15 @@ export class MembershipRequestsResultItem extends Component {
} = this.props;

const {
membershipRequest: { member, request },
membershipRequest: { member },
membershipRequest,
} = this.state;
// TODO: Decision flow
// const { api: membershipRequestsApi } = this.context;
const rolesCanAssignByType = rolesCanAssign[member.type];
const membershipRequestExpiration = formattedTime(request.expires_at);

const request = buildRequest(membershipRequest, ["accept", "decline"]);
const { api: membershipRequestsApi } = this.context;
const roles = rolesCanAssign[member.type];
const expiration = formattedTime(request.expires_at);

return (
<Table.Row className="community-member-item">
<Table.Cell>
Expand Down Expand Up @@ -73,17 +78,16 @@ export class MembershipRequestsResultItem extends Component {
<RequestStatus status={request.status} />
</Table.Cell>
<Table.Cell
aria-label={i18next.t("Expires") + " " + membershipRequestExpiration}
aria-label={i18next.t("Expires") + " " + expiration}
data-label={i18next.t("Expires")}
>
{membershipRequestExpiration}
{expiration}
</Table.Cell>
<Table.Cell data-label={i18next.t("Role")}>
<RoleDropdown
roles={rolesCanAssignByType}
roles={roles}
action={membershipRequestsApi.updateRole}
successCallback={this.update}
// TODO: Decision flow
// action={membershipRequestsApi.updateRole}
disabled={!membershipRequest.permissions.can_update_role}
currentValue={membershipRequest.role}
resource={membershipRequest}
Expand All @@ -92,9 +96,10 @@ export class MembershipRequestsResultItem extends Component {
</Table.Cell>
<Table.Cell data-label={i18next.t("Actions")}>
<RequestActionController
request={membershipRequest}
// TODO: Decision flow
actionSuccessCallback={() => console.log("actionSuccessCallback called")}
request={request}
actionSuccessCallback={() => {
window.location.reload();
}}
/>
</Table.Cell>
</Table.Row>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ const dataAttr = document.getElementById(
const community = JSON.parse(dataAttr.community);
const communitiesAllRoles = JSON.parse(dataAttr.communitiesAllRoles);
const communitiesRolesCanAssign = JSON.parse(dataAttr.communitiesRolesCanAssign);
// TODO: Decision flow: do we need?
// const permissions = JSON.parse(dataAttr.permissions);

const appName = "InvenioCommunities.MembershipRequestsSearch";

Expand Down
5 changes: 4 additions & 1 deletion invenio_communities/members/records/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,10 @@ class Member(Record, MemberMixin):


class ArchivedInvitation(Record, MemberMixin):
"""An archived invitation record.
"""An archived invitation or membership request record.
The name is a historical legacy. It could be renamed in the future, but we don't
want to rename the index.
We are using a record without using the actual JSON document and
schema validation normally used in a record. The reason for using a record
Expand Down
2 changes: 1 addition & 1 deletion invenio_communities/members/records/dumpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ def dump(self, record, data):
def load(self, data, record_cls):
"""Load relations.request.type.
TODO: Works without it for now. Potentially revisit?
Works without implementation for now.
"""
pass
4 changes: 3 additions & 1 deletion invenio_communities/members/resources/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ class MemberResourceConfig(RecordResourceConfig):
AlreadyMemberError: create_error_handler(
HTTPJSONException(
code=400,
description="A member was already added or invited.",
description=_(
"A membership already exists or is already being assessed."
),
)
),
CommunityDeletedError: create_error_handler(
Expand Down
14 changes: 14 additions & 0 deletions invenio_communities/members/resources/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def create_url_rules(self):
route(
"GET", routes["membership_requests"], self.search_membership_requests
),
route(
"PUT", routes["membership_requests"], self.update_membership_requests
),
]

@request_view_args
Expand Down Expand Up @@ -160,3 +163,14 @@ def search_membership_requests(self):
search_preference=search_preference(),
)
return hits.to_dict(), 200

@request_view_args
@request_extra_args
@request_data
def update_membership_requests(self):
"""Update membership request.
From the outside, a membership request is its own resource.
From the inside, it's just a member when it comes to update.
"""
return self.update()
4 changes: 2 additions & 2 deletions invenio_communities/members/services/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ def _member_changed(self, member, community=None):
for user_id in user_ids:
on_user_membership_change(Identity(user_id))

def accept_invite(self, identity, record=None, data=None, **kwargs):
"""On accept invite."""
def accept_member_request(self, identity, record=None, data=None, **kwargs):
"""Upon acceptance of a member request (invitation or membership request)."""
self._member_changed(record)

def members_add(self, identity, record=None, community=None, data=None, **kwargs):
Expand Down
1 change: 1 addition & 0 deletions invenio_communities/members/services/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class MemberServiceConfig(RecordServiceConfig, ConfiguratorMixin):
search = MemberSearchOptions
search_public = PublicSearchOptions
search_invitations = InvitationsSearchOptions
search_membership_requests = InvitationsSearchOptions # Use same as invitations

links_item = {
"actions": LinksForActionsOfMember(
Expand Down
31 changes: 18 additions & 13 deletions invenio_communities/members/services/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class AcceptAction(actions.AcceptAction):

def execute(self, identity, uow):
"""Execute action."""
service().accept_invite(system_identity, self.request.id, uow=uow)
service().accept_member_request(system_identity, self.request.id, uow=uow)
uow.register(
NotificationOp(
CommunityInvitationAcceptNotificationBuilder.build(self.request)
Expand All @@ -61,7 +61,7 @@ class DeclineAction(actions.DeclineAction):

def execute(self, identity, uow):
"""Execute action."""
service().decline_invite(system_identity, self.request.id, uow=uow)
service().close_member_request(system_identity, self.request.id, uow=uow)
uow.register(
NotificationOp(
CommunityInvitationDeclineNotificationBuilder.build(self.request)
Expand All @@ -75,7 +75,7 @@ class CancelAction(actions.CancelAction):

def execute(self, identity, uow):
"""Execute action."""
service().decline_invite(system_identity, self.request.id, uow=uow)
service().close_member_request(system_identity, self.request.id, uow=uow)
uow.register(
NotificationOp(
CommunityInvitationCancelNotificationBuilder.build(self.request)
Expand All @@ -89,7 +89,7 @@ class ExpireAction(actions.ExpireAction):

def execute(self, identity, uow):
"""Execute action."""
service().decline_invite(system_identity, self.request.id, uow=uow)
service().close_member_request(system_identity, self.request.id, uow=uow)
uow.register(
NotificationOp(
CommunityInvitationExpireNotificationBuilder.build(self.request)
Expand Down Expand Up @@ -141,27 +141,32 @@ class CancelMembershipRequestAction(actions.CancelAction):

def execute(self, identity, uow):
"""Execute action."""
service().close_membership_request(system_identity, self.request.id, uow=uow)
service().close_member_request(system_identity, self.request.id, uow=uow)
# TODO: Notification flow: Investigate notifications
super().execute(identity, uow)


class AcceptMembershipRequestAction(actions.AcceptAction):
"""Accept membership request action."""
class DeclineMembershipRequestAction(actions.DeclineAction):
"""Decline membership request action."""

def execute(self, identity, uow):
"""Execute action."""
# TODO: Decision flow: Implement me
pass
service().close_member_request(system_identity, self.request.id, uow=uow)
# TODO: Notification flow: Investigate notifications
super().execute(identity, uow)


class DeclineMembershipRequestAction(actions.DeclineAction):
"""Decline membership request action."""
# TODO: Expiration flow: ExpireAction


class AcceptMembershipRequestAction(actions.AcceptAction):
"""Accept membership request action."""

def execute(self, identity, uow):
"""Execute action."""
# TODO: Decision flow: Implement me
pass
service().accept_member_request(system_identity, self.request.id, uow=uow)
# TODO: Notification flow: Investigate notifications
super().execute(identity, uow)


class MembershipRequestRequestType(RequestType):
Expand Down
23 changes: 19 additions & 4 deletions invenio_communities/members/services/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,24 @@ def get_permissions(self, obj):


class MembershipRequestDumpSchema(MemberDumpSchema):
"""Schema for dumping membership requests.
TODO: Decision flow: Investigate if can be merged with InvitationDumpSchema
"""
"""Schema for dumping membership requests."""

request = fields.Nested(RequestSchema)

def get_permissions(self, obj):
"""Get permission.
Only permission to see if current identity can_update role is needed.
:param obj: api.Member
"""
is_open = obj["request"]["is_open"]
permission_check = self.context["field_permission_check"]
can_update = permission_check(
"members_update",
community_id=obj.community_id,
member=obj,
)
return {
"can_update_role": is_open and can_update
}
Loading

0 comments on commit d8a7f62

Please sign in to comment.