Skip to content

Commit 5cc91e0

Browse files
feat(feedback): frontend to display summary (#93567)
1 parent 78d346e commit 5cc91e0

File tree

3 files changed

+145
-4
lines changed

3 files changed

+145
-4
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import styled from '@emotion/styled';
2+
3+
import useFeedbackSummary from 'sentry/components/feedback/list/useFeedbackSummary';
4+
import Placeholder from 'sentry/components/placeholder';
5+
import {IconSeer} from 'sentry/icons/iconSeer';
6+
import {t} from 'sentry/locale';
7+
import {space} from 'sentry/styles/space';
8+
import useOrganization from 'sentry/utils/useOrganization';
9+
10+
export default function FeedbackSummary() {
11+
const {isError, isPending, summary, tooFewFeedbacks} = useFeedbackSummary();
12+
13+
const organization = useOrganization();
14+
15+
if (
16+
!organization.features.includes('user-feedback-ai-summaries') ||
17+
tooFewFeedbacks ||
18+
isError
19+
) {
20+
return null;
21+
}
22+
23+
if (isPending) {
24+
return <Placeholder height="100px" />;
25+
}
26+
27+
return (
28+
<SummaryIconContainer>
29+
<IconSeer size="xs" />
30+
<SummaryContainer>
31+
<SummaryHeader>{t('Feedback Summary')}</SummaryHeader>
32+
<SummaryContent>{summary}</SummaryContent>
33+
</SummaryContainer>
34+
</SummaryIconContainer>
35+
);
36+
}
37+
38+
const SummaryContainer = styled('div')`
39+
display: flex;
40+
flex-direction: column;
41+
gap: ${space(1)};
42+
width: 100%;
43+
`;
44+
45+
const SummaryHeader = styled('p')`
46+
font-size: ${p => p.theme.fontSizeMedium};
47+
font-weight: ${p => p.theme.fontWeightBold};
48+
margin: 0;
49+
`;
50+
51+
const SummaryContent = styled('p')`
52+
font-size: ${p => p.theme.fontSizeSmall};
53+
color: ${p => p.theme.subText};
54+
margin: 0;
55+
`;
56+
57+
const SummaryIconContainer = styled('div')`
58+
display: flex;
59+
gap: ${space(1)};
60+
padding: ${space(2)};
61+
border: 1px solid ${p => p.theme.border};
62+
border-radius: ${p => p.theme.borderRadius};
63+
align-items: baseline;
64+
`;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
2+
import {useApiQuery} from 'sentry/utils/queryClient';
3+
import useOrganization from 'sentry/utils/useOrganization';
4+
import usePageFilters from 'sentry/utils/usePageFilters';
5+
6+
type FeedbackSummaryResponse = {
7+
numFeedbacksUsed: number;
8+
success: boolean;
9+
summary: string | null;
10+
};
11+
12+
export default function useFeedbackSummary(): {
13+
isError: boolean;
14+
isPending: boolean;
15+
summary: string | null;
16+
tooFewFeedbacks: boolean;
17+
} {
18+
const organization = useOrganization();
19+
20+
const {selection} = usePageFilters();
21+
22+
const normalizedDateRange = normalizeDateTimeParams(selection.datetime);
23+
24+
const {data, isPending, isError} = useApiQuery<FeedbackSummaryResponse>(
25+
[
26+
`/organizations/${organization.slug}/feedback-summary/`,
27+
{
28+
query: {
29+
...normalizedDateRange,
30+
project: selection.projects,
31+
},
32+
},
33+
],
34+
{
35+
staleTime: 5000,
36+
enabled:
37+
Boolean(normalizedDateRange) &&
38+
organization.features.includes('user-feedback-ai-summaries'),
39+
retry: 1,
40+
}
41+
);
42+
43+
if (isPending) {
44+
return {
45+
summary: null,
46+
isPending: true,
47+
isError: false,
48+
tooFewFeedbacks: false,
49+
};
50+
}
51+
52+
if (isError) {
53+
return {
54+
summary: null,
55+
isPending: false,
56+
isError: true,
57+
tooFewFeedbacks: false,
58+
};
59+
}
60+
61+
return {
62+
summary: data.summary,
63+
isPending: false,
64+
isError: false,
65+
tooFewFeedbacks: data.numFeedbacksUsed === 0 && !data.success,
66+
};
67+
}

static/app/views/feedback/feedbackListPage.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import FeedbackItemLoader from 'sentry/components/feedback/feedbackItem/feedback
77
import FeedbackWidgetBanner from 'sentry/components/feedback/feedbackOnboarding/feedbackWidgetBanner';
88
import FeedbackSearch from 'sentry/components/feedback/feedbackSearch';
99
import FeedbackSetupPanel from 'sentry/components/feedback/feedbackSetupPanel';
10+
import FeedbackSummary from 'sentry/components/feedback/feedbackSummary';
1011
import FeedbackWhatsNewBanner from 'sentry/components/feedback/feedbackWhatsNewBanner';
1112
import FeedbackList from 'sentry/components/feedback/list/feedbackList';
1213
import useCurrentFeedbackId from 'sentry/components/feedback/useCurrentFeedbackId';
@@ -84,9 +85,12 @@ export default function FeedbackListPage() {
8485
<FeedbackFilters style={{gridArea: 'filters'}} />
8586
{hasSetupOneFeedback || hasSlug ? (
8687
<Fragment>
87-
<Container style={{gridArea: 'list'}}>
88-
<FeedbackList />
89-
</Container>
88+
<SummaryListContainer style={{gridArea: 'list'}}>
89+
<FeedbackSummary />
90+
<Container>
91+
<FeedbackList />
92+
</Container>
93+
</SummaryListContainer>
9094
<SearchContainer>
9195
<FeedbackSearch />
9296
</SearchContainer>
@@ -118,6 +122,12 @@ const Background = styled('div')`
118122
gap: ${space(2)};
119123
`;
120124

125+
const SummaryListContainer = styled('div')`
126+
display: flex;
127+
flex-direction: column;
128+
gap: ${space(1)};
129+
`;
130+
121131
const LayoutGrid = styled('div')`
122132
overflow: hidden;
123133
flex-grow: 1;
@@ -153,7 +163,7 @@ const LayoutGrid = styled('div')`
153163
}
154164
155165
@media (min-width: ${p => p.theme.breakpoints.medium}) {
156-
grid-template-columns: minmax(1fr, 195px) 1fr;
166+
grid-template-columns: minmax(195px, 1fr) 1.5fr;
157167
}
158168
159169
@media (min-width: ${p => p.theme.breakpoints.large}) {

0 commit comments

Comments
 (0)