Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ai): Single source of truth for AI conditions #81158

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 14 additions & 24 deletions static/app/components/group/groupSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import styled from '@emotion/styled';

import {useAutofixSetup} from 'sentry/components/events/autofix/useAutofixSetup';
import Placeholder from 'sentry/components/placeholder';
import {IconFatal, IconFocus, IconSpan} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {IssueCategory} from 'sentry/types/group';
import type {Event} from 'sentry/types/event';
import type {Group} from 'sentry/types/group';
import type {Project} from 'sentry/types/project';
import marked from 'sentry/utils/marked';
import {type ApiQueryKey, useApiQuery} from 'sentry/utils/queryClient';
import useOrganization from 'sentry/utils/useOrganization';
import {useAiConfig} from 'sentry/views/issueDetails/streamline/useAiConfig';

interface GroupSummaryData {
groupId: string;
Expand All @@ -18,14 +20,6 @@ interface GroupSummaryData {
whatsWrong?: string | null;
}

const isSummaryEnabled = (
hasGenAIConsent: boolean,
hideAiFeatures: boolean,
groupCategory: IssueCategory
) => {
return hasGenAIConsent && !hideAiFeatures && groupCategory === IssueCategory.ERROR;
};

export const makeGroupSummaryQueryKey = (
organizationSlug: string,
groupId: string
Expand All @@ -34,30 +28,26 @@ export const makeGroupSummaryQueryKey = (
{method: 'POST'},
];

export function useGroupSummary(groupId: string, groupCategory: IssueCategory) {
export function useGroupSummary(
group: Group,
event: Event | null | undefined,
project: Project
) {
const organization = useOrganization();
// We piggyback and use autofix's genai consent check for now.
const {
data: autofixSetupData,
isPending: isAutofixSetupLoading,
isError: isAutofixSetupError,
} = useAutofixSetup({groupId});

const hasGenAIConsent = autofixSetupData?.genAIConsent.ok ?? false;
const hideAiFeatures = organization.hideAiFeatures;
const aiConfig = useAiConfig(group, event, project);

const queryData = useApiQuery<GroupSummaryData>(
makeGroupSummaryQueryKey(organization.slug, groupId),
makeGroupSummaryQueryKey(organization.slug, group.id),
{
staleTime: Infinity, // Cache the result indefinitely as it's unlikely to change if it's already computed
enabled: isSummaryEnabled(hasGenAIConsent, hideAiFeatures, groupCategory),
enabled: aiConfig.hasSummary,
}
);
return {
...queryData,
isPending: isAutofixSetupLoading || queryData.isPending,
isError: queryData.isError || isAutofixSetupError,
hasGenAIConsent,
isPending: aiConfig.isAutofixSetupLoading || queryData.isPending,
isError: queryData.isError,
};
}

Expand Down
66 changes: 9 additions & 57 deletions static/app/views/issueDetails/streamline/solutionsHubDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import AutofixFeedback from 'sentry/components/events/autofix/autofixFeedback';
import {AutofixSetupContent} from 'sentry/components/events/autofix/autofixSetupModal';
import {AutofixSteps} from 'sentry/components/events/autofix/autofixSteps';
import {useAiAutofix} from 'sentry/components/events/autofix/useAutofix';
import {useAutofixSetup} from 'sentry/components/events/autofix/useAutofixSetup';
import {DrawerBody, DrawerHeader} from 'sentry/components/globalDrawer/components';
import {GroupSummary, useGroupSummary} from 'sentry/components/group/groupSummary';
import HookOrDefault from 'sentry/components/hookOrDefault';
Expand All @@ -22,18 +21,15 @@ import LoadingIndicator from 'sentry/components/loadingIndicator';
import {IconDocs} from 'sentry/icons';
import {t, tct} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {EntryType, type Event} from 'sentry/types/event';
import type {Event} from 'sentry/types/event';
import type {Group} from 'sentry/types/group';
import type {Organization} from 'sentry/types/organization';
import type {Project} from 'sentry/types/project';
import {getShortEventId} from 'sentry/utils/events';
import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
import {getRegionDataFromOrganization} from 'sentry/utils/regions';
import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
import useOrganization from 'sentry/utils/useOrganization';
import {MIN_NAV_HEIGHT} from 'sentry/views/issueDetails/streamline/eventTitle';
import Resources from 'sentry/views/issueDetails/streamline/resources';
import {useIsSampleEvent} from 'sentry/views/issueDetails/utils';
import {useAiConfig} from 'sentry/views/issueDetails/streamline/useAiConfig';

interface AutofixStartBoxProps {
groupId: string;
Expand Down Expand Up @@ -102,34 +98,6 @@ function AutofixStartBox({onSend, groupId}: AutofixStartBoxProps) {
);
}

const shouldDisplayAiAutofixForOrganization = (organization: Organization) => {
return (
organization.features.includes('gen-ai-features') &&
organization.genAIConsent &&
!organization.hideAiFeatures &&
getRegionDataFromOrganization(organization)?.name !== 'de'
);
};

// Autofix requires the event to have stack trace frames in order to work correctly.
export function hasStacktraceWithFrames(event: Event) {
for (const entry of event.entries) {
if (entry.type === EntryType.EXCEPTION) {
if (entry.data.values?.some(value => value.stacktrace?.frames?.length)) {
return true;
}
}

if (entry.type === EntryType.THREADS) {
if (entry.data.values?.some(thread => thread.stacktrace?.frames?.length)) {
return true;
}
}
}

return false;
}

interface SolutionsHubDrawerProps {
event: Event;
group: Group;
Expand All @@ -147,31 +115,15 @@ export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerPr
data: summaryData,
isError,
isPending: isSummaryLoading,
} = useGroupSummary(group.id, group.issueCategory);
const {data: setupData, isPending: isSetupLoading} = useAutofixSetup({
groupId: group.id,
});
} = useGroupSummary(group, event, project);
const aiConfig = useAiConfig(group, event, project);

useRouteAnalyticsParams({
autofix_status: autofixData?.status ?? 'none',
});

const config = getConfigForIssueType(group, project);

const hasConsent = Boolean(setupData?.genAIConsent.ok);
const isAutofixSetupComplete = setupData?.integration.ok && hasConsent;

const hasSummary = (summaryData || isSummaryLoading) && !isError && hasConsent;

const organization = useOrganization();
const isSampleError = useIsSampleEvent();

const displayAiAutofix =
shouldDisplayAiAutofixForOrganization(organization) &&
config.autofix &&
hasStacktraceWithFrames(event) &&
!isSampleError;

return (
<SolutionsDrawerContainer className="solutions-drawer-container">
<SolutionsDrawerHeader>
Expand Down Expand Up @@ -240,15 +192,15 @@ export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerPr
</ButtonBar>
)}
</HeaderText>
{isSetupLoading ? (
{aiConfig.isAutofixSetupLoading ? (
<div data-test-id="ai-setup-loading-indicator">
<LoadingIndicator />
</div>
) : !hasConsent ? (
) : aiConfig.needsGenAIConsent ? (
<AiSetupDataConsent groupId={group.id} />
) : (
<Fragment>
{hasSummary && (
{aiConfig.hasSummary && (
<StyledCard>
<GroupSummary
data={summaryData}
Expand All @@ -257,9 +209,9 @@ export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerPr
/>
</StyledCard>
)}
{displayAiAutofix && (
{aiConfig.hasAutofix && (
<Fragment>
{!isAutofixSetupComplete ? (
{aiConfig.needsAutofixSetup ? (
<AutofixSetupContent groupId={group.id} projectId={project.id} />
) : !autofixData ? (
<AutofixStartBox onSend={triggerAutofix} groupId={group.id} />
Expand Down
68 changes: 20 additions & 48 deletions static/app/views/issueDetails/streamline/solutionsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import styled from '@emotion/styled';
import FeatureBadge from 'sentry/components/badge/featureBadge';
import {Button} from 'sentry/components/button';
import {Chevron} from 'sentry/components/chevron';
import {useAutofixSetup} from 'sentry/components/events/autofix/useAutofixSetup';
import useDrawer from 'sentry/components/globalDrawer';
import {GroupSummary, useGroupSummary} from 'sentry/components/group/groupSummary';
import Placeholder from 'sentry/components/placeholder';
Expand All @@ -15,15 +14,11 @@ import type {Group} from 'sentry/types/group';
import type {Project} from 'sentry/types/project';
import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
import {singleLineRenderer} from 'sentry/utils/marked';
import {getRegionDataFromOrganization} from 'sentry/utils/regions';
import useOrganization from 'sentry/utils/useOrganization';
import Resources from 'sentry/views/issueDetails/streamline/resources';
import {SidebarSectionTitle} from 'sentry/views/issueDetails/streamline/sidebar';
import {
hasStacktraceWithFrames,
SolutionsHubDrawer,
} from 'sentry/views/issueDetails/streamline/solutionsHubDrawer';
import {useHasStreamlinedUI, useIsSampleEvent} from 'sentry/views/issueDetails/utils';
import {SolutionsHubDrawer} from 'sentry/views/issueDetails/streamline/solutionsHubDrawer';
import {useAiConfig} from 'sentry/views/issueDetails/streamline/useAiConfig';
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';

export default function SolutionsSection({
group,
Expand All @@ -34,7 +29,6 @@ export default function SolutionsSection({
group: Group;
project: Project;
}) {
const organization = useOrganization();
const [isExpanded, setIsExpanded] = useState(false);
const openButtonRef = useRef<HTMLButtonElement>(null);
const {openDrawer} = useDrawer();
Expand Down Expand Up @@ -66,62 +60,39 @@ export default function SolutionsSection({
);
};

const hasGenAIConsent = organization.genAIConsent;
const {
data: summaryData,
isPending: isSummaryLoading,
isError: isSummaryError,
} = useGroupSummary(group.id, group.issueCategory);
const {data: autofixSetupData, isPending: isAutofixSetupLoading} = useAutofixSetup({
groupId: group.id,
});
} = useGroupSummary(group, event, project);

const isSampleError = useIsSampleEvent();
const hasStacktrace = event && hasStacktraceWithFrames(event);
const aiConfig = useAiConfig(group, event, project);

const issueTypeConfig = getConfigForIssueType(group, group.project);
const issueTypeConfig = getConfigForIssueType(group, project);

const areAiFeaturesAllowed =
!organization.hideAiFeatures &&
getRegionDataFromOrganization(organization)?.name !== 'de';

const isSummaryEnabled = issueTypeConfig.issueSummary.enabled;
const isAutofixEnabled = issueTypeConfig.autofix;
const hasResources = issueTypeConfig.resources;

const hasSummary = hasGenAIConsent && isSummaryEnabled && areAiFeaturesAllowed;
const hasAutofix =
isAutofixEnabled && areAiFeaturesAllowed && hasStacktrace && !isSampleError;

const needsGenAIConsent =
!hasGenAIConsent && (isSummaryEnabled || isAutofixEnabled) && areAiFeaturesAllowed;

const needsAutofixSetup =
isAutofixEnabled &&
!isAutofixSetupLoading &&
(!autofixSetupData?.genAIConsent.ok || !autofixSetupData?.integration.ok) &&
areAiFeaturesAllowed;

const showCtaButton = needsGenAIConsent || hasAutofix || (hasSummary && hasResources);
const isButtonLoading = isAutofixSetupLoading;
const showCtaButton =
aiConfig.needsGenAIConsent ||
aiConfig.hasAutofix ||
(aiConfig.hasSummary && aiConfig.hasResources);
const isButtonLoading = aiConfig.isAutofixSetupLoading;

const getButtonText = () => {
if (needsGenAIConsent) {
if (aiConfig.needsGenAIConsent) {
return t('Set up Sentry AI');
}

if (isAutofixEnabled) {
if (needsAutofixSetup) {
if (aiConfig.hasAutofix) {
if (aiConfig.needsAutofixSetup) {
return t('Set up Autofix');
}
return hasResources ? t('Open Resources & Autofix') : t('Open Autofix');
return aiConfig.hasResources ? t('Open Resources & Autofix') : t('Open Autofix');
}

return t('Open Resources');
};

const renderContent = () => {
if (needsGenAIConsent) {
if (aiConfig.needsGenAIConsent) {
return (
<Summary>
<HeadlineText
Expand All @@ -135,7 +106,8 @@ export default function SolutionsSection({
);
}

if (hasSummary) {
// Show the summary's loading state if we're still loading the autofix setup
if (aiConfig.hasSummary) {
return (
<Summary>
<GroupSummary
Expand All @@ -148,7 +120,7 @@ export default function SolutionsSection({
);
}

if (!hasSummary && hasResources) {
if (!aiConfig.hasSummary && issueTypeConfig.resources) {
return (
<ResourcesWrapper isExpanded={isExpanded}>
<ResourcesContent isExpanded={isExpanded}>
Expand All @@ -173,7 +145,7 @@ export default function SolutionsSection({
<SidebarSectionTitle style={{marginTop: 0}}>
<HeaderContainer>
{t('Solutions Hub')}
{hasSummary && (
{aiConfig.hasSummary && (
<StyledFeatureBadge
type="beta"
title={tct(
Expand Down
Loading