/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ import * as POLLING from '$config/polling'; import { AccessRequestAction } from '@ada/common'; import { AccessRequestEntity, AccessRequestIdentifier } from '@ada/api'; import { apiHooks, useApiInvalidation } from '$api'; import { useHistory } from 'react-router-dom'; import { useI18nContext } from '$strings'; import { useIsAdmin, useUserId } from '$core/provider/UserProvider'; import { useNotificationContext } from '$northstar-plus'; import React, { useCallback, useEffect, useState } from 'react'; const NOTIFICATION_UUID = Symbol('ACCESS_REQUEST_NOTIFICATION').toString(); type AcknowledgeMap = Record; export interface IAccessRequestContext { accessRequests?: AccessRequestEntity[]; hasPendingAccessRequests: boolean; approveRequest: (accessRequest: AccessRequestEntity, reason: string) => Promise; denyRequest: (accessRequest: AccessRequestEntity, reason: string) => Promise; getAccessRequestAcknowledgement: ( accessRequest: AccessRequestIdentifier, ) => [action: AccessRequestAction, pending: boolean] | null; } export const AccessRequestContext = React.createContext(undefined); //NOSONAR (S2814:Duplicate) - false positive - type vs value export const useAccessRequestContext = () => { const context = React.useContext(AccessRequestContext); if (context == null) throw new Error('Must wrap with AccessRequestContext.Provider'); return context; }; export const AccessRequestProvider: React.FC = ({ children }) => { const { LL } = useI18nContext(); const history = useHistory(); const { addError, addFlashbar, dismissNotification } = useNotificationContext(); const { invalidateEntityLists } = useApiInvalidation(); const userId = useUserId(); const isAdmin = useIsAdmin(); const [acknowledged, setAcknowledge] = useState({}); const getAcknowledgement = useCallback( ({ groupId, userId }) => { //NOSONAR (S1117:ShadowVar) - ignore for readability return acknowledged[`${groupId}::${userId}`]; }, [acknowledged], ); const select = useCallback( (items: AccessRequestEntity[]): AccessRequestEntity[] => { // filter acknowledge and prevent non-admins from approving themselves return items.filter((item) => { return !(getAcknowledgement(item) != null || (isAdmin === false && item.userId === userId)); }); }, [userId, isAdmin, acknowledged], ); const [accessRequests, queryInfo] = apiHooks.useAllIdentityRequests( {}, { select, refetchInterval: POLLING.ACCESS_REQUESTS, }, ); const refetchRequests = useCallback(() => { queryInfo.refetch({ force: true }); invalidateEntityLists('IdentityGroup'); }, [queryInfo.refetch, invalidateEntityLists]); // eslint-disable-line react-hooks/exhaustive-deps const [sendDenyOrApprove] = apiHooks.usePutIdentityRequestActionAsync(); const handleAction = useCallback( async ({ groupId, userId }: AccessRequestEntity, action: AccessRequestAction, reason: string) => { //NOSONAR (S1117:ShadowVar) - ignore for readability try { // update UI with processing setAcknowledge((current) => ({ ...current, [`${groupId}::${userId}`]: [action, true], })); await sendDenyOrApprove({ groupId, userId, action, putIdentityRequestActionRequest: { reason, }, }); setAcknowledge((current) => ({ ...current, [`${groupId}::${userId}`]: [action, false], })); addFlashbar({ header: LL.ENTITY.AccessRequest_.notify.header(groupId), content: reason, type: action === AccessRequestAction.DENY ? 'warning' : 'success', dismissible: 'auto', }); refetchRequests(); } catch (error: any) { addError({ header: LL.VIEW.GROUP.AccessRequest.notify.failed({ userId, action }), content: error.message, }); } }, [], ); const denyRequest = useCallback( async (accessRequest: AccessRequestEntity, reason: string) => { return handleAction(accessRequest, AccessRequestAction.DENY, reason); }, [handleAction], ); const approveRequest = useCallback( async (accessRequest: AccessRequestEntity, reason: string) => { return handleAction(accessRequest, AccessRequestAction.APPROVE, reason); }, [handleAction], ); const [notified, setNotified] = useState(false); const pendingCount = accessRequests?.length || 0; const hasPendingAccessRequests = pendingCount > 0; useEffect(() => { if (hasPendingAccessRequests === true && notified === false) { addFlashbar({ id: NOTIFICATION_UUID, type: 'info', header: LL.VIEW.GROUP.AccessRequest.notify.persistent.header(pendingCount), content: LL.VIEW.GROUP.AccessRequest.notify.persistent.content(), dismissible: false, buttonText: LL.VIEW.GROUP.AccessRequest.notify.persistent.buttonText(), onButtonClick: () => history.push('/groups'), }); setNotified(true); } if (hasPendingAccessRequests === false && notified === true) { dismissNotification(NOTIFICATION_UUID); setNotified(false); } }, [hasPendingAccessRequests, notified]); const context: IAccessRequestContext = { accessRequests, hasPendingAccessRequests, denyRequest, approveRequest, getAccessRequestAcknowledgement: getAcknowledgement, }; return {children}; };