import {Fragment} from 'react'; import styled from '@emotion/styled'; import Access from 'sentry/components/acl/access'; import AsyncComponent from 'sentry/components/asyncComponent'; import Button from 'sentry/components/button'; import CircleIndicator from 'sentry/components/circleIndicator'; import SentryAppIcon from 'sentry/components/sentryAppIcon'; import Tag from 'sentry/components/tag'; import {IconFlag} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import space from 'sentry/styles/space'; import {IntegrationFeature, Organization, SentryApp} from 'sentry/types'; import {toPermissions} from 'sentry/utils/consolidatedScopes'; import { getIntegrationFeatureGate, trackIntegrationAnalytics, } from 'sentry/utils/integrationUtil'; import marked, {singleLineRenderer} from 'sentry/utils/marked'; import {recordInteraction} from 'sentry/utils/recordSentryAppInteraction'; type Props = { closeModal: () => void; isInstalled: boolean; onInstall: () => Promise; organization: Organization; sentryApp: SentryApp; } & AsyncComponent['props']; type State = { featureData: IntegrationFeature[]; } & AsyncComponent['state']; // No longer a modal anymore but yea :) export default class SentryAppDetailsModal extends AsyncComponent { componentDidUpdate(prevProps: Props) { // if the user changes org, count this as a fresh event to track if (this.props.organization.id !== prevProps.organization.id) { this.trackOpened(); } } componentDidMount() { this.trackOpened(); } trackOpened() { const {sentryApp, organization, isInstalled} = this.props; recordInteraction(sentryApp.slug, 'sentry_app_viewed'); trackIntegrationAnalytics( 'integrations.install_modal_opened', { integration_type: 'sentry_app', integration: sentryApp.slug, already_installed: isInstalled, view: 'external_install', integration_status: sentryApp.status, organization, }, {startSession: true} ); } getEndpoints(): ReturnType { const {sentryApp} = this.props; return [['featureData', `/sentry-apps/${sentryApp.slug}/features/`]]; } featureTags(features: Pick[]) { return features.map(feature => { const feat = feature.featureGate.replace(/integrations/g, ''); return {feat.replace(/-/g, ' ')}; }); } get permissions() { return toPermissions(this.props.sentryApp.scopes); } async onInstall() { const {onInstall} = this.props; // we want to make sure install finishes before we close the modal // and we should close the modal if there is an error as well try { await onInstall(); } catch (_err) { /* stylelint-disable-next-line no-empty-block */ } } renderPermissions() { const permissions = this.permissions; if ( Object.keys(permissions).filter(scope => permissions[scope].length > 0).length === 0 ) { return null; } return ( Permissions {permissions.read.length > 0 && ( {tct('[read] access to [resources] resources', { read: Read, resources: permissions.read.join(', '), })} )} {permissions.write.length > 0 && ( {tct('[read] and [write] access to [resources] resources', { read: Read, write: Write, resources: permissions.write.join(', '), })} )} {permissions.admin.length > 0 && ( {tct('[admin] access to [resources] resources', { admin: Admin, resources: permissions.admin.join(', '), })} )} ); } renderBody() { const {sentryApp, closeModal, isInstalled, organization} = this.props; const {featureData} = this.state; // Prepare the features list const features = (featureData || []).map(f => ({ featureGate: f.featureGate, description: ( ), })); const {FeatureList, IntegrationFeatures} = getIntegrationFeatureGate(); const overview = sentryApp.overview || ''; const featureProps = {organization, features}; return ( {sentryApp.name} {!!features.length && {this.featureTags(features)}} {({disabled, disabledReason}) => ( {!disabled && this.renderPermissions()}
{t('Authored By %s', sentryApp.author)}
{disabled && } {({hasAccess}) => hasAccess && ( ) }
)}
); } } const Heading = styled('div')` display: grid; grid-template-columns: max-content 1fr; gap: ${space(1)}; align-items: center; margin-bottom: ${space(2)}; `; const HeadingInfo = styled('div')` display: grid; grid-template-rows: max-content max-content; align-items: start; `; const Name = styled('div')` font-weight: bold; font-size: 1.4em; `; const Description = styled('div')` margin-bottom: ${space(2)}; li { margin-bottom: 6px; } `; const Author = styled('div')` color: ${p => p.theme.gray300}; `; const DisabledNotice = styled(({reason, ...p}: {reason: React.ReactNode}) => (
{reason}
))` display: grid; align-items: center; flex: 1; grid-template-columns: max-content 1fr; color: ${p => p.theme.red300}; font-size: 0.9em; `; const Text = styled('p')` margin: 0px 6px; `; const Permission = styled('div')` display: flex; `; const Footer = styled('div')` display: flex; padding: 20px 30px; border-top: 1px solid #e2dee6; margin: 20px -30px -30px; justify-content: space-between; `; const Title = styled('p')` margin-bottom: ${space(1)}; font-weight: bold; `; const Indicator = styled(p => )` margin-top: 7px; color: ${p => p.theme.success}; `; const Features = styled('div')` margin: -${space(0.5)}; `; const StyledTag = styled(Tag)` padding: ${space(0.5)}; `;