import {Fragment} from 'react';
import styled from '@emotion/styled';
import type {LocationDescriptor} from 'history';
import {LinkButton} from 'sentry/components/button';
import GroupList from 'sentry/components/issues/groupList';
import Link from 'sentry/components/links/link';
import LoadingError from 'sentry/components/loadingError';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {t, tct} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Group} from 'sentry/types/group';
import type {Organization} from 'sentry/types/organization';
import {useApiQuery} from 'sentry/utils/queryClient';
import useOrganization from 'sentry/utils/useOrganization';
type RelatedIssuesResponse = {
data: number[];
meta: {
event_id: string;
trace_id: string;
};
type: string;
};
function GroupRelatedIssues({group}: {group: Group}) {
return (
);
}
interface RelatedIssuesSectionProps {
group: Group;
relationType: string;
}
function RelatedIssuesSection({group, relationType}: RelatedIssuesSectionProps) {
const organization = useOrganization();
// Fetch the list of related issues
const hasGlobalViewsFeature = organization.features.includes('global-views');
const {
isPending,
isError,
data: relatedIssues,
refetch,
} = useApiQuery(
[
`/issues/${group.id}/related-issues/`,
{
query: {
...(hasGlobalViewsFeature ? undefined : {project: group.project.id}),
type: relationType,
},
},
],
{
staleTime: 0,
}
);
const traceMeta = relationType === 'trace_connected' ? relatedIssues?.meta : undefined;
const issues = relatedIssues?.data ?? [];
const query = `issue.id:[${issues}]`;
let title: React.ReactNode = null;
let extraInfo: React.ReactNode = null;
let openIssuesButton: React.ReactNode = null;
if (relationType === 'trace_connected' && traceMeta) {
({title, extraInfo, openIssuesButton} = getTraceConnectedContent(
traceMeta,
organization,
group
));
} else {
title = t('Issues with similar titles');
extraInfo = t(
'These issues have the same title and may have been caused by the same root cause.'
);
openIssuesButton = getLinkButton(
{
pathname: `/organizations/${organization.slug}/issues/`,
query: {
// project=-1 allows ensuring that the query will show issues from any projects for the org
// This is important for traces since issues can be for any project in the org
...(hasGlobalViewsFeature ? {project: '-1'} : {project: group.project.id}),
query: `issue.id:[${group.id},${issues}]`,
},
},
'Clicked Open Issues from same-root related issues',
'similar_issues.same_root_cause_clicked_open_issues'
);
}
return (
{isPending ? (
) : isError ? (
) : issues.length > 0 ? (
{title}{extraInfo}
{openIssuesButton}
) : null}
);
}
const getTraceConnectedContent = (
traceMeta: RelatedIssuesResponse['meta'],
organization: Organization,
group: Group
) => {
const hasGlobalViewsFeature = organization.features.includes('global-views');
const title = t('Issues in the same trace');
const url = `/organizations/${organization.slug}/performance/trace/${traceMeta.trace_id}/?node=error-${traceMeta.event_id}`;
const extraInfo = (
{tct('These issues were all found within [traceLink:this trace]', {
traceLink: {t('this trace')},
})}
);
const openIssuesButton = getLinkButton(
{
pathname: `/organizations/${organization.slug}/issues/`,
query: {
// project=-1 allows ensuring that the query will show issues from any projects for the org
// This is important for traces since issues can be for any project in the org
...(hasGlobalViewsFeature ? {project: '-1'} : {project: group.project.id}),
query: `trace:${traceMeta.trace_id}`,
},
},
'Clicked Open Issues from trace-connected related issues',
'similar_issues.trace_connected_issues_clicked_open_issues'
);
return {title, extraInfo, openIssuesButton};
};
const getLinkButton = (to: LocationDescriptor, eventName: string, eventKey: string) => {
return (
{t('Open in Issues')}
);
};
// Export the component without feature flag controls
export {GroupRelatedIssues};
const Title = styled('h4')`
font-size: ${p => p.theme.fontSizeLarge};
margin-bottom: ${space(0.75)};
`;
const HeaderWrapper = styled('div')`
margin-bottom: ${space(2)};
small {
color: ${p => p.theme.subText};
}
`;
const TextButtonWrapper = styled('div')`
align-items: center;
display: flex;
justify-content: space-between;
margin-bottom: ${space(1)};
width: 100%;
`;