Skip to content

Commit 820a1ff

Browse files
committed
👔 Add in AskSeerConsent flow to askSeer.tsx
1 parent 3d9da9f commit 820a1ff

File tree

1 file changed

+156
-6
lines changed

1 file changed

+156
-6
lines changed

static/app/components/searchQueryBuilder/askSeer.tsx

Lines changed: 156 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,117 @@
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 {promptsUpdate} from 'sentry/actionCreators/prompts';
67
import InteractionStateLayer from 'sentry/components/core/interactionStateLayer';
8+
import {useOrganizationSeerSetup} from 'sentry/components/events/autofix/useOrganizationSeerSetup';
9+
import ExternalLink from 'sentry/components/links/externalLink';
10+
import LoadingIndicator from 'sentry/components/loadingIndicator';
711
import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context';
812
import {IconSeer} from 'sentry/icons';
9-
import {t} from 'sentry/locale';
13+
import {t, tct} from 'sentry/locale';
1014
import {space} from 'sentry/styles/space';
1115
import {trackAnalytics} from 'sentry/utils/analytics';
16+
import {useIsMutating, useMutation, useQueryClient} from 'sentry/utils/queryClient';
17+
import useApi from 'sentry/utils/useApi';
1218
import useOrganization from 'sentry/utils/useOrganization';
1319

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

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

50149
export function AskSeer<T>({state}: {state: ComboBoxState<T>}) {
150+
const organization = useOrganization();
151+
const {gaveSeerConsentRef} = useSearchQueryBuilder();
152+
const isMutating = useIsMutating({
153+
mutationKey: [setupCheckQueryKey(organization.slug)],
154+
});
155+
156+
const {setupAcknowledgement, isPending: isPendingSetupCheck} =
157+
useOrganizationSeerSetup();
158+
const orgHasAcknowledged = setupAcknowledgement.orgHasAcknowledged;
159+
160+
if (!gaveSeerConsentRef.current && orgHasAcknowledged && !isPendingSetupCheck) {
161+
gaveSeerConsentRef.current = true;
162+
}
163+
164+
if (isPendingSetupCheck || isMutating) {
165+
return (
166+
<AskSeerPane>
167+
<AskSeerListItem>
168+
<AskSeerLabel width="auto">{t('Loading Seer')}</AskSeerLabel>
169+
<LoadingIndicator size={16} style={{margin: 0}} />
170+
</AskSeerListItem>
171+
</AskSeerPane>
172+
);
173+
}
174+
175+
if (orgHasAcknowledged) {
176+
return (
177+
<AskSeerPane>
178+
<AskSeerOption state={state} />
179+
</AskSeerPane>
180+
);
181+
}
182+
51183
return (
52184
<AskSeerPane>
53-
<AskSeerOption state={state} />
185+
<AskSeerConsentOption state={state} />
54186
</AskSeerPane>
55187
);
56188
}
57189

190+
const TooltipSubExternalLink = styled(ExternalLink)`
191+
color: ${p => p.theme.purple400};
192+
193+
:hover {
194+
color: ${p => p.theme.purple400};
195+
text-decoration: underline;
196+
}
197+
`;
198+
199+
const SeerConsentText = styled('p')`
200+
color: ${p => p.theme.subText};
201+
font-size: ${p => p.theme.fontSize.xs};
202+
font-weight: ${p => p.theme.fontWeight.normal};
203+
margin: 0;
204+
background-color: none;
205+
`;
206+
58207
const AskSeerPane = styled('div')`
59208
grid-area: seer;
60209
display: flex;
@@ -66,7 +215,7 @@ const AskSeerPane = styled('div')`
66215
width: 100%;
67216
`;
68217

69-
const AskSeerListItem = styled('div')`
218+
const AskSeerListItem = styled('div')<{justifyContent?: 'flex-start' | 'space-between'}>`
70219
position: relative;
71220
display: flex;
72221
align-items: center;
@@ -80,7 +229,7 @@ const AskSeerListItem = styled('div')`
80229
font-size: ${p => p.theme.fontSize.md};
81230
font-weight: ${p => p.theme.fontWeight.bold};
82231
text-align: left;
83-
justify-content: flex-start;
232+
justify-content: ${p => p.justifyContent ?? 'flex-start'};
84233
gap: ${space(1)};
85234
list-style: none;
86235
margin: 0;
@@ -98,9 +247,10 @@ const AskSeerListItem = styled('div')`
98247
}
99248
`;
100249

101-
const AskSeerLabel = styled('span')`
250+
const AskSeerLabel = styled('span')<{width?: 'auto'}>`
102251
${p => p.theme.overflowEllipsis};
103252
color: ${p => p.theme.purple400};
104253
font-size: ${p => p.theme.fontSize.md};
105254
font-weight: ${p => p.theme.fontWeight.bold};
255+
width: ${p => p.width};
106256
`;

0 commit comments

Comments
 (0)