import {useCallback, useEffect, useState} from 'react'; import styled from '@emotion/styled'; import {promptsCheck, promptsUpdate} from 'sentry/actionCreators/prompts'; import {Button} from 'sentry/components/button'; import Card from 'sentry/components/card'; import Carousel from 'sentry/components/carousel'; import {openConfirmModal} from 'sentry/components/confirm'; import {DropdownMenu, MenuItemProps} from 'sentry/components/dropdownMenu'; import ExternalLink from 'sentry/components/links/externalLink'; import QuestionTooltip from 'sentry/components/questionTooltip'; import {IconCommit, IconEllipsis, IconGithub, IconMail} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {MissingMember, Organization} from 'sentry/types'; import {promptIsDismissed} from 'sentry/utils/promptIsDismissed'; import useApi from 'sentry/utils/useApi'; import withOrganization from 'sentry/utils/withOrganization'; type Props = { missingMembers: {integration: string; users: MissingMember[]}; onSendInvite: (email: string) => void; organization: Organization; }; export function InviteBanner({missingMembers, onSendInvite, organization}: Props) { // NOTE: this is currently used for Github only const hideBanner = !organization.features.includes('integrations-gh-invite') || !organization.access.includes('org:write') || !missingMembers?.users || missingMembers?.users.length === 0; const [sendingInvite, setSendingInvite] = useState(false); const [showBanner, setShowBanner] = useState(false); const api = useApi(); const integrationName = missingMembers?.integration; const promptsFeature = `${integrationName}_missing_members`; const snoozePrompt = useCallback(async () => { setShowBanner(false); await promptsUpdate(api, { organizationId: organization.id, feature: promptsFeature, status: 'snoozed', }); }, [api, organization, promptsFeature]); useEffect(() => { if (hideBanner) { return; } promptsCheck(api, { organizationId: organization.id, feature: promptsFeature, }).then(prompt => { setShowBanner(!promptIsDismissed(prompt)); }); }, [api, organization, promptsFeature, hideBanner]); if (hideBanner || !showBanner) { return null; } // TODO(cathy): include docs link const menuItems: MenuItemProps[] = [ { key: 'invite-banner-snooze', label: t('Hide Missing Members'), onAction: () => { openConfirmModal({ message: t('Are you sure you want to snooze this banner?'), onConfirm: snoozePrompt, }); }, }, ]; const handleSendInvite = async (email: string) => { if (sendingInvite) { return; } setSendingInvite(true); await onSendInvite(email); setSendingInvite(false); }; const users = missingMembers.users; const cards = users.slice(0, 5).map(member => ( {/* TODO: create mapping from integration to lambda external link function */} {tct('@[externalId]', {externalId: member.externalId})} {tct('[commitCount] Recent Commits', {commitCount: member.commitCount})} {member.email} )); cards.push(); return ( {t('Bring your full GitHub team on board in Sentry')} {tct('[missingMemberCount] missing members', { missingMemberCount: users.length, })} , 'aria-label': t('Actions'), }} /> {cards} ); } export default withOrganization(InviteBanner); type SeeMoreCardProps = { missingUsers: MissingMember[]; }; function SeeMoreCard({missingUsers}: SeeMoreCardProps) { return ( {tct('See all [missingMembersCount] missing members', { missingMembersCount: missingUsers.length, })} {tct('Accounting for [totalCommits] recent commits', { totalCommits: missingUsers.reduce((acc, curr) => acc + curr.commitCount, 0), })} ); } const StyledCard = styled(Card)` display: flex; padding: ${space(2)}; padding-bottom: ${space(1.5)}; overflow: hidden; `; const CardTitleContainer = styled('div')` display: flex; justify-content: space-between; margin-bottom: ${space(1)}; `; const CardTitleContent = styled('div')` display: flex; flex-direction: column; `; const CardTitle = styled('h6')` margin: 0; font-size: ${p => p.theme.fontSizeLarge}; font-weight: bold; color: ${p => p.theme.gray400}; `; const Subtitle = styled('div')` display: flex; align-items: center; font-size: ${p => p.theme.fontSizeSmall}; font-weight: 400; color: ${p => p.theme.gray300}; & > *:first-child { margin-left: ${space(0.5)}; display: flex; align-items: center; } `; const ButtonContainer = styled('div')` display: grid; grid-auto-flow: column; grid-column-gap: ${space(1)}; `; const MemberCard = styled(Card)` display: flex; flex-direction: row; flex-wrap: wrap; min-width: 30%; margin: ${space(1)} ${space(0.5)} 0 0; padding: ${space(2)} 18px; justify-content: center; align-items: center; `; const MemberCardContent = styled('div')` display: flex; flex-direction: column; flex: 1 1; width: 75%; `; const MemberCardContentRow = styled('div')` display: flex; align-items: center; margin-bottom: ${space(0.25)}; font-size: ${p => p.theme.fontSizeSmall}; & > *:first-child { margin-right: ${space(0.75)}; } `; const StyledExternalLink = styled(ExternalLink)` font-size: ${p => p.theme.fontSizeMedium}; `; const SeeMore = styled('div')` font-size: ${p => p.theme.fontSizeLarge}; `;