diff --git a/static/app/components/feedback/feedbackSummary.tsx b/static/app/components/feedback/feedbackSummary.tsx new file mode 100644 index 00000000000000..8cf3c23dcb88e5 --- /dev/null +++ b/static/app/components/feedback/feedbackSummary.tsx @@ -0,0 +1,64 @@ +import styled from '@emotion/styled'; + +import useFeedbackSummary from 'sentry/components/feedback/list/useFeedbackSummary'; +import Placeholder from 'sentry/components/placeholder'; +import {IconSeer} from 'sentry/icons/iconSeer'; +import {t} from 'sentry/locale'; +import {space} from 'sentry/styles/space'; +import useOrganization from 'sentry/utils/useOrganization'; + +export default function FeedbackSummary() { + const {isError, isPending, summary, tooFewFeedbacks} = useFeedbackSummary(); + + const organization = useOrganization(); + + if ( + !organization.features.includes('user-feedback-ai-summaries') || + tooFewFeedbacks || + isError + ) { + return null; + } + + if (isPending) { + return ; + } + + return ( + + + + {t('Feedback Summary')} + {summary} + + + ); +} + +const SummaryContainer = styled('div')` + display: flex; + flex-direction: column; + gap: ${space(1)}; + width: 100%; +`; + +const SummaryHeader = styled('p')` + font-size: ${p => p.theme.fontSizeMedium}; + font-weight: ${p => p.theme.fontWeightBold}; + margin: 0; +`; + +const SummaryContent = styled('p')` + font-size: ${p => p.theme.fontSizeSmall}; + color: ${p => p.theme.subText}; + margin: 0; +`; + +const SummaryIconContainer = styled('div')` + display: flex; + gap: ${space(1)}; + padding: ${space(2)}; + border: 1px solid ${p => p.theme.border}; + border-radius: ${p => p.theme.borderRadius}; + align-items: baseline; +`; diff --git a/static/app/components/feedback/list/useFeedbackSummary.tsx b/static/app/components/feedback/list/useFeedbackSummary.tsx new file mode 100644 index 00000000000000..e593e29b0cc93f --- /dev/null +++ b/static/app/components/feedback/list/useFeedbackSummary.tsx @@ -0,0 +1,67 @@ +import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse'; +import {useApiQuery} from 'sentry/utils/queryClient'; +import useOrganization from 'sentry/utils/useOrganization'; +import usePageFilters from 'sentry/utils/usePageFilters'; + +type FeedbackSummaryResponse = { + numFeedbacksUsed: number; + success: boolean; + summary: string | null; +}; + +export default function useFeedbackSummary(): { + isError: boolean; + isPending: boolean; + summary: string | null; + tooFewFeedbacks: boolean; +} { + const organization = useOrganization(); + + const {selection} = usePageFilters(); + + const normalizedDateRange = normalizeDateTimeParams(selection.datetime); + + const {data, isPending, isError} = useApiQuery( + [ + `/organizations/${organization.slug}/feedback-summary/`, + { + query: { + ...normalizedDateRange, + project: selection.projects, + }, + }, + ], + { + staleTime: 5000, + enabled: + Boolean(normalizedDateRange) && + organization.features.includes('user-feedback-ai-summaries'), + retry: 1, + } + ); + + if (isPending) { + return { + summary: null, + isPending: true, + isError: false, + tooFewFeedbacks: false, + }; + } + + if (isError) { + return { + summary: null, + isPending: false, + isError: true, + tooFewFeedbacks: false, + }; + } + + return { + summary: data.summary, + isPending: false, + isError: false, + tooFewFeedbacks: data.numFeedbacksUsed === 0 && !data.success, + }; +} diff --git a/static/app/views/feedback/feedbackListPage.tsx b/static/app/views/feedback/feedbackListPage.tsx index 2ca5c65d4dae1f..ca4f173b99c38f 100644 --- a/static/app/views/feedback/feedbackListPage.tsx +++ b/static/app/views/feedback/feedbackListPage.tsx @@ -7,6 +7,7 @@ import FeedbackItemLoader from 'sentry/components/feedback/feedbackItem/feedback import FeedbackWidgetBanner from 'sentry/components/feedback/feedbackOnboarding/feedbackWidgetBanner'; import FeedbackSearch from 'sentry/components/feedback/feedbackSearch'; import FeedbackSetupPanel from 'sentry/components/feedback/feedbackSetupPanel'; +import FeedbackSummary from 'sentry/components/feedback/feedbackSummary'; import FeedbackWhatsNewBanner from 'sentry/components/feedback/feedbackWhatsNewBanner'; import FeedbackList from 'sentry/components/feedback/list/feedbackList'; import useCurrentFeedbackId from 'sentry/components/feedback/useCurrentFeedbackId'; @@ -84,9 +85,12 @@ export default function FeedbackListPage() { {hasSetupOneFeedback || hasSlug ? ( - - - + + + + + + @@ -118,6 +122,12 @@ const Background = styled('div')` gap: ${space(2)}; `; +const SummaryListContainer = styled('div')` + display: flex; + flex-direction: column; + gap: ${space(1)}; +`; + const LayoutGrid = styled('div')` overflow: hidden; flex-grow: 1; @@ -153,7 +163,7 @@ const LayoutGrid = styled('div')` } @media (min-width: ${p => p.theme.breakpoints.medium}) { - grid-template-columns: minmax(1fr, 195px) 1fr; + grid-template-columns: minmax(195px, 1fr) 1.5fr; } @media (min-width: ${p => p.theme.breakpoints.large}) {