• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alkem-io / client-web / #10022

24 Jan 2025 09:50AM UTC coverage: 5.689%. First build
#10022

Pull #7423

travis-ci

Pull Request #7423: Role setsv2

188 of 11016 branches covered (1.71%)

Branch coverage included in aggregate %.

346 of 1599 new or added lines in 92 files covered. (21.64%)

1517 of 18956 relevant lines covered (8.0%)

0.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

3.13
/src/domain/access/ApplicationsAndInvitations/useRoleSetApplicationsAndInvitations.ts
1
import { useEffect, useState } from 'react';
2
import {
3
  refetchUserPendingMembershipsQuery,
4
  useActorDetailsLazyQuery,
5
  useApplyForEntryRoleOnRoleSetMutation,
6
  useCommunityApplicationsInvitationsQuery,
7
  useDeleteInvitationMutation,
8
  useDeletePlatformInvitationMutation,
9
  useEventOnApplicationMutation,
10
  useInvitationStateEventMutation,
11
  useInviteForEntryRoleOnRoleSetMutation,
12
} from '@/core/apollo/generated/apollo-hooks';
13
import {
14
  type ActorDetailsQuery,
15
  ActorType,
16
  type AuthorizationPrivilege,
17
  RoleName,
18
} from '@/core/apollo/generated/graphql-schema';
19
import { evictFromCache } from '@/core/apollo/utils/evictFromCache';
20
import type { ApplicationModel } from '../model/ApplicationModel';
21
import type { InvitationModel } from '../model/InvitationModel';
22
import type InvitationResultModel from '../model/InvitationResultModel';
23
import type { PlatformInvitationModel } from '../model/PlatformInvitationModel';
24

25
type useRoleSetApplicationsAndInvitationsParams = {
26
  roleSetId: string | undefined;
27
};
28

29
type useRoleSetApplicationsAndInvitationsProvided = {
30
  applications: ApplicationModel[];
31
  invitations: InvitationModel[];
32
  platformInvitations: PlatformInvitationModel[];
33
  authorizationPrivileges: AuthorizationPrivilege[];
34
  applyForEntryRoleOnRoleSet: (
35
    roleSetId: string,
36
    questions: { name: string; value: string; sortOrder: number }[]
37
  ) => Promise<unknown>;
38
  applicationStateChange: (applicationId: string, eventName: string) => Promise<unknown>;
39
  inviteContributorsOnRoleSet: (inviteData: {
40
    roleSetId: string;
41
    invitedContributorIds: string[];
42
    invitedUserEmails: string[];
43
    welcomeMessage: string;
44
    extraRoles?: RoleName[];
45
  }) => Promise<InvitationResultModel[]>;
46
  invitationStateChange: (invitationId: string, eventName: string) => Promise<unknown>;
47
  deleteInvitation: (invitationId: string) => Promise<unknown>;
48
  deletePlatformInvitation: (invitationId: string) => Promise<unknown>;
49
  refetch: () => Promise<unknown>;
50
  loading: boolean;
51
  isApplying: boolean;
52
};
53

54
const getContributorType = (type: ActorType | undefined): ActorType => {
55
  return type ?? ActorType.User;
56
};
57

58
const useRoleSetApplicationsAndInvitations = ({
59
  roleSetId,
60
}: useRoleSetApplicationsAndInvitationsParams): useRoleSetApplicationsAndInvitationsProvided => {
61
  const [fetchActorDetails] = useActorDetailsLazyQuery();
62

63
  const {
64
    data,
65
    loading,
66
    refetch: refetchCommunityApplicationsInvitations,
67
  } = useCommunityApplicationsInvitationsQuery({
68
    // biome-ignore lint/style/noNonNullAssertion: guarded by skip
69
    variables: { roleSetId: roleSetId! },
70
    skip: !roleSetId,
71
  });
72

73
  const refetch = async () => {
74
    if (roleSetId) {
75
      await refetchCommunityApplicationsInvitations();
76
      await refetchUserPendingMembershipsQuery();
77
    }
78
  };
79

80
  // Fetch actor-specific details for application/invitation contributors
81
  type ActorDetail = NonNullable<ActorDetailsQuery['actor']>;
82
  const [actorDetailsMap, setActorDetailsMap] = useState<Record<string, ActorDetail>>({});
83

84
  useEffect(() => {
85
    const appIds = data?.lookup.roleSet?.applications.map(app => app.actor.id) ?? [];
86
    const invIds = data?.lookup.roleSet?.invitations.map(inv => inv.actor.id) ?? [];
87
    const contributorIds = [...new Set([...appIds, ...invIds])];
88

89
    if (contributorIds.length === 0) {
90
      return;
91
    }
92

93
    const fetchAll = async () => {
94
      const results = await Promise.all(contributorIds.map(actorId => fetchActorDetails({ variables: { actorId } })));
95
      const newMap: Record<string, ActorDetail> = {};
96
      for (const result of results) {
97
        const actor = result.data?.actor;
98
        if (actor) {
99
          newMap[actor.id] = actor;
100
        }
101
      }
102
      setActorDetailsMap(newMap);
103
    };
104
    fetchAll();
105
  }, [data, fetchActorDetails]);
106

107
  const getActorEmail = (actorDetail: ActorDetail | undefined): string | undefined => {
108
    if (!actorDetail) return undefined;
109
    if (actorDetail.type === ActorType.User && 'email' in actorDetail) return actorDetail.email;
110
    if (actorDetail.type === ActorType.Organization && 'contactEmail' in actorDetail)
111
      return actorDetail.contactEmail ?? undefined;
112
    return undefined;
113
  };
114

115
  const { applications, invitations, platformInvitations } = (() => {
116
    return {
117
      applications:
118
        data?.lookup.roleSet?.applications.map(app => ({
119
          ...app,
120
          contributorType: getContributorType(app.actor.type),
121
          actor: {
122
            ...app.actor,
123
            profile: app.actor.profile
124
              ? { ...app.actor.profile, email: getActorEmail(actorDetailsMap[app.actor.id]) }
125
              : undefined,
1✔
126
          },
127
        })) ?? [],
NEW
128
      invitations:
×
129
        data?.lookup.roleSet?.invitations.map(inv => ({
NEW
130
          ...inv,
×
131
          contributorType: getContributorType(inv.actor.type),
NEW
132
          actor: {
×
133
            ...inv.actor,
NEW
134
            profile: inv.actor.profile
×
135
              ? { ...inv.actor.profile, email: getActorEmail(actorDetailsMap[inv.actor.id]) }
136
              : undefined,
NEW
137
          },
×
NEW
138
        })) ?? [],
×
139
      platformInvitations: data?.lookup.roleSet?.platformInvitations ?? [],
140
    };
141
  })();
142

143
  const [applyForEntryRoleOnRoleSet, { loading: isApplying }] = useApplyForEntryRoleOnRoleSetMutation();
1✔
144
  const handleApplyForEntryRoleOnRoleSet = (
145
    roleSetId: string,
146
    questions: { name: string; sortOrder: number; value: string }[]
147
  ) =>
148
    applyForEntryRoleOnRoleSet({
149
      variables: {
NEW
150
        roleSetId,
×
151
        questions,
152
      },
153
      onCompleted: () => refetch(),
154
    });
NEW
155

×
NEW
156
  const [eventOnApplication] = useEventOnApplicationMutation();
×
NEW
157
  const handleApplicationStateChange = (applicationId: string, newState: string) =>
×
NEW
158
    eventOnApplication({
×
159
      variables: {
160
        input: {
161
          applicationID: applicationId,
NEW
162
          eventName: newState,
×
NEW
163
        },
×
164
      },
NEW
165
      update: cache => {
×
166
        if (roleSetId) {
167
          evictFromCache(cache, roleSetId, 'RoleSet');
168
        }
169
      },
NEW
170
      onCompleted: () => refetch(),
×
171
    });
172

173
  const [invitationStateEvent] = useInvitationStateEventMutation();
174
  const handleInvitationStateChange = (invitationId: string, eventName: string) =>
×
175
    invitationStateEvent({
176
      variables: {
177
        invitationId,
NEW
178
        eventName,
×
NEW
179
      },
×
180
      onCompleted: () => refetch(),
181
    });
182

NEW
183
  const [deleteInvitation] = useDeleteInvitationMutation();
×
184
  const handleDeleteInvitation = (invitationId: string) =>
185
    deleteInvitation({
186
      variables: {
187
        invitationId,
NEW
188
      },
×
189
      onCompleted: () => refetch(),
190
    });
NEW
191

×
NEW
192
  const [deletePlatformInvitation] = useDeletePlatformInvitationMutation();
×
NEW
193
  const handleDeletePlatformInvitation = (invitationId: string) =>
×
194
    deletePlatformInvitation({
195
      variables: {
196
        invitationId,
197
      },
198
      onCompleted: () => refetch(),
199
    });
NEW
200

×
201
  const [inviteForEntryRoleOnRoleSet] = useInviteForEntryRoleOnRoleSetMutation();
202
  const handleInviteContributorsOnRoleSet = async ({
NEW
203
    roleSetId,
×
NEW
204
    invitedContributorIds,
×
NEW
205
    invitedUserEmails,
×
206
    welcomeMessage,
207
    extraRoles,
208
  }: {
209
    roleSetId: string;
NEW
210
    invitedContributorIds: string[];
×
211
    invitedUserEmails: string[];
212
    welcomeMessage: string;
NEW
213
    extraRoles?: RoleName[];
×
NEW
214
  }) => {
×
NEW
215
    // Filter out the Member role as it's not an extra role
×
216
    const filteredExtraRoles = (extraRoles ?? []).filter(role => role !== RoleName.Member);
217

218
    const result = await inviteForEntryRoleOnRoleSet({
NEW
219
      variables: {
×
220
        roleSetId,
221
        invitedActorIds: invitedContributorIds,
NEW
222
        invitedUserEmails,
×
NEW
223
        welcomeMessage,
×
NEW
224
        extraRoles: filteredExtraRoles,
×
225
      },
226
      onCompleted: () => refetch(),
227
    });
NEW
228
    return result.data?.inviteForEntryRoleOnRoleSet ?? [];
×
229
  };
230

NEW
231
  return {
×
NEW
232
    applications,
×
233
    invitations,
234
    platformInvitations,
235
    authorizationPrivileges: data?.lookup.roleSet?.authorization?.myPrivileges ?? [],
236
    refetch,
237
    loading,
238
    applyForEntryRoleOnRoleSet: handleApplyForEntryRoleOnRoleSet,
239
    applicationStateChange: handleApplicationStateChange,
240
    inviteContributorsOnRoleSet: handleInviteContributorsOnRoleSet,
241
    invitationStateChange: handleInvitationStateChange,
242
    deleteInvitation: handleDeleteInvitation,
NEW
243
    deletePlatformInvitation: handleDeletePlatformInvitation,
×
244
    isApplying,
NEW
245
  };
×
246
};
247

248
export default useRoleSetApplicationsAndInvitations;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc