Skip to content

Commit bec4db5

Browse files
committed
👔 Add in AskSeerConsent flow to askSeer.tsx
1 parent 67ec4ac commit bec4db5

File tree

1 file changed

+159
-7
lines changed

1 file changed

+159
-7
lines changed

static/app/components/searchQueryBuilder/askSeer.tsx

Lines changed: 159 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,120 @@
1-
import {useRef} from 'react';
1+
import {useEffect, useRef, useState} from 'react';
22
import styled from '@emotion/styled';
33
import {useOption} from '@react-aria/listbox';
44
import type {ComboBoxState} from '@react-stately/combobox';
55

6-
import { FeatureBadge } from 'sentry/components/core/badge/featureBadge';
6+
import {promptsUpdate} from 'sentry/actionCreators/prompts';
7+
import {FeatureBadge} from 'sentry/components/core/badge/featureBadge';
78
import InteractionStateLayer from 'sentry/components/core/interactionStateLayer';
9+
import {useOrganizationSeerSetup} from 'sentry/components/events/autofix/useOrganizationSeerSetup';
10+
import ExternalLink from 'sentry/components/links/externalLink';
11+
import LoadingIndicator from 'sentry/components/loadingIndicator';
812
import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context';
913
import {IconSeer} from 'sentry/icons';
10-
import {t} from 'sentry/locale';
14+
import {t, tct} from 'sentry/locale';
1115
import {space} from 'sentry/styles/space';
1216
import {trackAnalytics} from 'sentry/utils/analytics';
17+
import {useIsMutating, useMutation, useQueryClient} from 'sentry/utils/queryClient';
18+
import useApi from 'sentry/utils/useApi';
1319
import useOrganization from 'sentry/utils/useOrganization';
1420

1521
export const ASK_SEER_ITEM_KEY = 'ask_seer';
1622
export const ASK_SEER_CONSENT_ITEM_KEY = 'ask_seer_consent';
1723

24+
const setupCheckQueryKey = (orgSlug: string) =>
25+
`/organizations/${orgSlug}/seer/setup-check/`;
26+
27+
function AskSeerConsentOption<T>({state}: {state: ComboBoxState<T>}) {
28+
const api = useApi();
29+
const queryClient = useQueryClient();
30+
const organization = useOrganization();
31+
const itemRef = useRef<HTMLDivElement>(null);
32+
const linkRef = useRef<HTMLAnchorElement>(null);
33+
const [optionDisableOverride, setOptionDisableOverride] = useState(false);
34+
35+
useEffect(() => {
36+
const link = linkRef.current;
37+
if (!link) return undefined;
38+
39+
const disableOption = () => setOptionDisableOverride(true);
40+
const enableOption = () => setOptionDisableOverride(false);
41+
42+
link.addEventListener('mouseover', disableOption);
43+
link.addEventListener('mouseout', enableOption);
44+
45+
return () => {
46+
link.removeEventListener('mouseover', disableOption);
47+
link.removeEventListener('mouseout', enableOption);
48+
};
49+
}, []);
50+
51+
const seerAcknowledgeMutation = useMutation({
52+
mutationKey: [setupCheckQueryKey(organization.slug)],
53+
mutationFn: () => {
54+
return promptsUpdate(api, {
55+
organization,
56+
feature: 'seer_autofix_setup_acknowledged',
57+
status: 'dismissed',
58+
});
59+
},
60+
onSuccess: () => {
61+
queryClient.invalidateQueries({
62+
queryKey: [setupCheckQueryKey(organization.slug)],
63+
});
64+
},
65+
});
66+
67+
const {optionProps, labelProps, isFocused, isPressed} = useOption(
68+
{
69+
key: ASK_SEER_CONSENT_ITEM_KEY,
70+
'aria-label': 'Enable Gen AI',
71+
shouldFocusOnHover: true,
72+
shouldSelectOnPressUp: true,
73+
isDisabled: optionDisableOverride,
74+
},
75+
state,
76+
itemRef
77+
);
78+
79+
const handleClick = () => {
80+
trackAnalytics('trace.explorer.ai_query_interface', {
81+
organization,
82+
action: 'consent_accepted',
83+
});
84+
seerAcknowledgeMutation.mutate();
85+
};
86+
87+
return (
88+
<AskSeerListItem
89+
ref={itemRef}
90+
onClick={handleClick}
91+
{...optionProps}
92+
justifyContent="space-between"
93+
>
94+
<InteractionStateLayer isHovered={isFocused} isPressed={isPressed} />
95+
<div style={{display: 'flex', alignItems: 'center', gap: space(1)}}>
96+
<IconSeer />
97+
<AskSeerLabel {...labelProps}>
98+
{t('Enable Gen AI')} <FeatureBadge type="beta" />
99+
</AskSeerLabel>
100+
</div>
101+
<SeerConsentText>
102+
{tct(
103+
'Query assistant requires Generative AI which is subject to our [dataProcessingPolicy:data processing policy].',
104+
{
105+
dataProcessingPolicy: (
106+
<TooltipSubExternalLink
107+
ref={linkRef}
108+
href="https://docs.sentry.io/product/security/ai-ml-policy/#use-of-identifying-data-for-generative-ai-features"
109+
/>
110+
),
111+
}
112+
)}
113+
</SeerConsentText>
114+
</AskSeerListItem>
115+
);
116+
}
117+
18118
function AskSeerOption<T>({state}: {state: ComboBoxState<T>}) {
19119
const ref = useRef<HTMLDivElement>(null);
20120
const {setDisplaySeerResults} = useSearchQueryBuilder();
@@ -26,6 +126,7 @@ function AskSeerOption<T>({state}: {state: ComboBoxState<T>}) {
26126
'aria-label': 'Ask Seer',
27127
shouldFocusOnHover: true,
28128
shouldSelectOnPressUp: true,
129+
isDisabled: false,
29130
},
30131
state,
31132
ref
@@ -51,13 +152,63 @@ function AskSeerOption<T>({state}: {state: ComboBoxState<T>}) {
51152
}
52153

53154
export function AskSeer<T>({state}: {state: ComboBoxState<T>}) {
155+
const organization = useOrganization();
156+
const {gaveSeerConsentRef} = useSearchQueryBuilder();
157+
const isMutating = useIsMutating({
158+
mutationKey: [setupCheckQueryKey(organization.slug)],
159+
});
160+
161+
const {setupAcknowledgement, isPending: isPendingSetupCheck} =
162+
useOrganizationSeerSetup();
163+
const orgHasAcknowledged = setupAcknowledgement.orgHasAcknowledged;
164+
165+
if (!gaveSeerConsentRef.current && orgHasAcknowledged && !isPendingSetupCheck) {
166+
gaveSeerConsentRef.current = true;
167+
}
168+
169+
if (isPendingSetupCheck || isMutating) {
170+
return (
171+
<AskSeerPane>
172+
<AskSeerListItem>
173+
<AskSeerLabel width="auto">{t('Loading Seer')}</AskSeerLabel>
174+
<LoadingIndicator size={16} style={{margin: 0}} />
175+
</AskSeerListItem>
176+
</AskSeerPane>
177+
);
178+
}
179+
180+
if (orgHasAcknowledged) {
181+
return (
182+
<AskSeerPane>
183+
<AskSeerOption state={state} />
184+
</AskSeerPane>
185+
);
186+
}
187+
54188
return (
55189
<AskSeerPane>
56-
<AskSeerOption state={state} />
190+
<AskSeerConsentOption state={state} />
57191
</AskSeerPane>
58192
);
59193
}
60194

195+
const TooltipSubExternalLink = styled(ExternalLink)`
196+
color: ${p => p.theme.purple400};
197+
198+
:hover {
199+
color: ${p => p.theme.purple400};
200+
text-decoration: underline;
201+
}
202+
`;
203+
204+
const SeerConsentText = styled('p')`
205+
color: ${p => p.theme.subText};
206+
font-size: ${p => p.theme.fontSize.xs};
207+
font-weight: ${p => p.theme.fontWeight.normal};
208+
margin: 0;
209+
background-color: none;
210+
`;
211+
61212
const AskSeerPane = styled('div')`
62213
grid-area: seer;
63214
display: flex;
@@ -69,7 +220,7 @@ const AskSeerPane = styled('div')`
69220
width: 100%;
70221
`;
71222

72-
const AskSeerListItem = styled('div')`
223+
const AskSeerListItem = styled('div')<{justifyContent?: 'flex-start' | 'space-between'}>`
73224
position: relative;
74225
display: flex;
75226
align-items: center;
@@ -83,7 +234,7 @@ const AskSeerListItem = styled('div')`
83234
font-size: ${p => p.theme.fontSize.md};
84235
font-weight: ${p => p.theme.fontWeight.bold};
85236
text-align: left;
86-
justify-content: flex-start;
237+
justify-content: ${p => p.justifyContent ?? 'flex-start'};
87238
gap: ${space(1)};
88239
list-style: none;
89240
margin: 0;
@@ -99,12 +250,13 @@ const AskSeerListItem = styled('div')`
99250
}
100251
`;
101252

102-
const AskSeerLabel = styled('span')`
253+
const AskSeerLabel = styled('span')<{width?: 'auto'}>`
103254
${p => p.theme.overflowEllipsis};
104255
color: ${p => p.theme.purple400};
105256
font-size: ${p => p.theme.fontSize.md};
106257
font-weight: ${p => p.theme.fontWeight.bold};
107258
display: flex;
108259
align-items: center;
109260
gap: ${space(1)};
261+
width: ${p => p.width};
110262
`;

0 commit comments

Comments
 (0)