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

alkem-io / client-web / #10013

23 Jan 2025 06:59PM UTC coverage: 5.714%. First build
#10013

Pull #7423

travis-ci

Pull Request #7423: Role setsv2

188 of 11014 branches covered (1.71%)

Branch coverage included in aggregate %.

347 of 1593 new or added lines in 92 files covered. (21.78%)

1525 of 18967 relevant lines covered (8.04%)

0.18 hits per line

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

4.55
/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;
1✔
98
        if (actor) {
99
          newMap[actor.id] = actor;
NEW
100
        }
×
101
      }
NEW
102
      setActorDetailsMap(newMap);
×
103
    };
NEW
104
    fetchAll();
×
105
  }, [data, fetchActorDetails]);
NEW
106

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

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

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

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

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

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

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

201
  const [inviteForEntryRoleOnRoleSet] = useInviteForEntryRoleOnRoleSetMutation();
202
  const handleInviteContributorsOnRoleSet = async ({
203
    roleSetId,
204
    invitedContributorIds,
205
    invitedUserEmails,
206
    welcomeMessage,
207
    extraRoles,
208
  }: {
209
    roleSetId: string;
210
    invitedContributorIds: string[];
211
    invitedUserEmails: string[];
212
    welcomeMessage: string;
213
    extraRoles?: RoleName[];
214
  }) => {
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({
219
      variables: {
220
        roleSetId,
221
        invitedActorIds: invitedContributorIds,
222
        invitedUserEmails,
223
        welcomeMessage,
224
        extraRoles: filteredExtraRoles,
225
      },
226
      onCompleted: () => refetch(),
227
    });
228
    return result.data?.inviteForEntryRoleOnRoleSet ?? [];
229
  };
230

231
  return {
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,
243
    deletePlatformInvitation: handleDeletePlatformInvitation,
244
    isApplying,
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