import {Fragment} from 'react'; import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator'; import {openModal} from 'app/actionCreators/modal'; import AsyncComponent from 'app/components/asyncComponent'; import IntegrationExternalMappingForm from 'app/components/integrationExternalMappingForm'; import IntegrationExternalMappings from 'app/components/integrationExternalMappings'; import {t} from 'app/locale'; import { ExternalActorMapping, ExternalUser, Integration, Member, Organization, } from 'app/types'; import withOrganization from 'app/utils/withOrganization'; type Props = AsyncComponent['props'] & { integration: Integration; organization: Organization; }; type State = AsyncComponent['state'] & { members: (Member & {externalUsers: ExternalUser[]})[]; }; class IntegrationExternalUserMappings extends AsyncComponent<Props, State> { getEndpoints(): ReturnType<AsyncComponent['getEndpoints']> { const {organization} = this.props; return [ [ 'members', `/organizations/${organization.slug}/members/`, {query: {query: 'hasExternalUsers:true', expand: 'externalUsers'}}, ], ]; } handleDelete = async (mapping: ExternalActorMapping) => { const {organization} = this.props; const endpoint = `/organizations/${organization.slug}/external-users/${mapping.id}/`; try { await this.api.requestPromise(endpoint, { method: 'DELETE', }); // remove config and update state addSuccessMessage(t('Deletion successful')); this.fetchData(); } catch { // no 4xx errors should happen on delete addErrorMessage(t('An error occurred')); } }; handleSubmitSuccess = () => { // Don't bother updating state. The info is in array of objects for each object in another array of objects. // Easier and less error-prone to re-fetch the data and re-calculate state. this.fetchData(); }; get mappings() { const {integration} = this.props; const {members} = this.state; const externalUserMappings = members.reduce((acc, member) => { const {externalUsers, user} = member; acc.push( ...externalUsers .filter(externalUser => externalUser.provider === integration.provider.key) .map(externalUser => ({...externalUser, sentryName: user.name})) ); return acc; }, [] as ExternalActorMapping[]); return externalUserMappings.sort((a, b) => parseInt(a.id, 10) - parseInt(b.id, 10)); } sentryNamesMapper(members: Member[]) { return members .filter(member => member.user) .map(({user: {id}, email, name}) => { const label = email !== name ? `${name} - ${email}` : `${email}`; return {id, name: label}; }); } openModal = (mapping?: ExternalActorMapping) => { const {organization, integration} = this.props; openModal(({Body, Header, closeModal}) => ( <Fragment> <Header closeButton>{t('Configure External User Mapping')}</Header> <Body> <IntegrationExternalMappingForm organization={organization} integration={integration} onSubmitSuccess={() => { this.handleSubmitSuccess(); closeModal(); }} mapping={mapping} sentryNamesMapper={this.sentryNamesMapper} type="user" url={`/organizations/${organization.slug}/members/`} onCancel={closeModal} baseEndpoint={`/organizations/${organization.slug}/external-users/`} /> </Body> </Fragment> )); }; renderBody() { const {integration} = this.props; return ( <Fragment> <IntegrationExternalMappings integration={integration} type="user" mappings={this.mappings} onCreateOrEdit={this.openModal} onDelete={this.handleDelete} /> </Fragment> ); } } export default withOrganization(IntegrationExternalUserMappings);