import type {ExternalIssueComponent} from 'sentry/components/group/externalIssuesList/types'; import useIssueTrackingFilter from 'sentry/components/group/externalIssuesList/useIssueTrackingFilter'; import SentryAppInstallationStore from 'sentry/stores/sentryAppInstallationsStore'; import {useLegacyStore} from 'sentry/stores/useLegacyStore'; import type {Event} from 'sentry/types/event'; import type {Group} from 'sentry/types/group'; import type {Project} from 'sentry/types/project'; import useOrganization from 'sentry/utils/useOrganization'; import useSentryAppComponentsStore from 'sentry/utils/useSentryAppComponentsStore'; type Props = { event: Event; group: Group; project: Project; }; export default function useExternalIssueData({group, event, project}: Props) { const organization = useOrganization(); const issueTrackingFilter = useIssueTrackingFilter(); const components = useSentryAppComponentsStore({componentType: 'issue-link'}); const sentryAppInstallations = useLegacyStore(SentryAppInstallationStore); const renderSentryAppIssues = (): ExternalIssueComponent[] => { return components .map(component => { const {sentryApp, error: disabled} = component; const installation = sentryAppInstallations.find( i => i.app.uuid === sentryApp.uuid ); // should always find a match but TS complains if we don't handle this case if (!installation) { return null; } const issue = (group.sentryAppIssues || []).find( i => i.serviceType === sentryApp.slug ); return { type: 'sentry-app-issue', key: sentryApp.slug, disabled, hasLinkedIssue: !!issue, props: { sentryApp, group, organization, event, sentryAppComponent: component, sentryAppInstallation: installation, externalIssue: issue, disabled, }, }; }) .filter((x): x is ExternalIssueComponent => x !== null); }; const renderIntegrationIssues = (): ExternalIssueComponent[] => { return ( group.integrationIssues?.map(issue => ({ type: 'integration-issue', key: issue.key, disabled: false, hasLinkedIssue: true, props: { configurations: [], externalIssue: issue, group, onChange: () => {}, }, })) ?? [] ); }; const renderPluginIssues = (): ExternalIssueComponent[] => { return group.pluginIssues?.map((plugin, i) => ({ type: 'plugin-issue', key: `plugin-issue-${i}`, disabled: false, hasLinkedIssue: true, props: { group, project, plugin, }, })); }; const renderPluginActions = (): ExternalIssueComponent[] => { return ( group.pluginActions?.map((plugin, i) => ({ type: 'plugin-action', key: `plugin-action-${i}`, disabled: false, hasLinkedIssue: false, props: {plugin}, })) ?? [] ); }; const linkedIssues = [ ...renderSentryAppIssues(), ...renderIntegrationIssues(), ...renderPluginIssues(), ...renderPluginActions(), ].filter(issue => !issueTrackingFilter || issue.key === issueTrackingFilter); // Plugins: need to do some extra logic to detect if the issue is linked, // by checking if there exists an issue object const plugins = linkedIssues.filter( a => (a.type === 'plugin-issue' || a.type === 'plugin-action') && 'issue' in a.props.plugin ); // Sentry app issues: read from `hasLinkedIssue` property const sentryAppIssues = linkedIssues.filter( a => a.hasLinkedIssue && a.type === 'sentry-app-issue' && a.props.externalIssue?.issueId === group.id ); // Integration issues const integrationIssues = linkedIssues.filter(a => a.type === 'integration-issue'); return {linkedIssues: plugins.concat(integrationIssues).concat(sentryAppIssues)}; }