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

alkem-io / client-web / #10018

24 Jan 2025 08:24AM UTC coverage: 5.69%. First build
#10018

Pull #7423

travis-ci

Pull Request #7423: Role setsv2

188 of 11014 branches covered (1.71%)

Branch coverage included in aggregate %.

346 of 1594 new or added lines in 91 files covered. (21.71%)

1517 of 18952 relevant lines covered (8.0%)

0.18 hits per line

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

3.45
/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]) }
1✔
125
              : undefined,
126
          },
NEW
127
        })) ?? [],
×
128
      invitations:
NEW
129
        data?.lookup.roleSet?.invitations.map(inv => ({
×
130
          ...inv,
NEW
131
          contributorType: getContributorType(inv.actor.type),
×
132
          actor: {
NEW
133
            ...inv.actor,
×
134
            profile: inv.actor.profile
135
              ? { ...inv.actor.profile, email: getActorEmail(actorDetailsMap[inv.actor.id]) }
NEW
136
              : undefined,
×
NEW
137
          },
×
138
        })) ?? [],
139
      platformInvitations: data?.lookup.roleSet?.platformInvitations ?? [],
140
    };
141
  })();
142

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

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

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

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
      },
NEW
198
      onCompleted: () => refetch(),
×
199
    });
200

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

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

NEW
231
  return {
×
232
    applications,
NEW
233
    invitations,
×
234
    platformInvitations,
235
    authorizationPrivileges: data?.lookup.roleSet?.authorization?.myPrivileges ?? [],
236
    refetch,
237
    loading,
238
    applyForEntryRoleOnRoleSet: handleApplyForEntryRoleOnRoleSet,
239
    applicationStateChange: handleApplicationStateChange,
NEW
240
    inviteContributorsOnRoleSet: handleInviteContributorsOnRoleSet,
×
241
    invitationStateChange: handleInvitationStateChange,
242
    deleteInvitation: handleDeleteInvitation,
243
    deletePlatformInvitation: handleDeletePlatformInvitation,
NEW
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