autofixInsightCards.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. import {useCallback, useEffect, useState} from 'react';
  2. import {createPortal} from 'react-dom';
  3. import {usePopper} from 'react-popper';
  4. import styled from '@emotion/styled';
  5. import {AnimatePresence, type AnimationProps, motion} from 'framer-motion';
  6. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  7. import {Button} from 'sentry/components/button';
  8. import {
  9. replaceHeadersWithBold,
  10. SuggestedFixSnippet,
  11. } from 'sentry/components/events/autofix/autofixRootCause';
  12. import type {
  13. AutofixInsight,
  14. AutofixRepository,
  15. BreadcrumbContext,
  16. } from 'sentry/components/events/autofix/types';
  17. import {makeAutofixQueryKey} from 'sentry/components/events/autofix/useAutofix';
  18. import BreadcrumbItemContent from 'sentry/components/events/breadcrumbs/breadcrumbItemContent';
  19. import {
  20. BreadcrumbIcon,
  21. BreadcrumbLevel,
  22. getBreadcrumbColorConfig,
  23. getBreadcrumbTitle,
  24. } from 'sentry/components/events/breadcrumbs/utils';
  25. import Input from 'sentry/components/input';
  26. import StructuredEventData from 'sentry/components/structuredEventData';
  27. import Timeline from 'sentry/components/timeline';
  28. import {
  29. IconArrow,
  30. IconChevron,
  31. IconCode,
  32. IconFire,
  33. IconRefresh,
  34. IconSpan,
  35. IconUser,
  36. } from 'sentry/icons';
  37. import {t} from 'sentry/locale';
  38. import {space} from 'sentry/styles/space';
  39. import {BreadcrumbLevelType, BreadcrumbType} from 'sentry/types/breadcrumbs';
  40. import {singleLineRenderer} from 'sentry/utils/marked';
  41. import {useMutation, useQueryClient} from 'sentry/utils/queryClient';
  42. import testableTransition from 'sentry/utils/testableTransition';
  43. import useApi from 'sentry/utils/useApi';
  44. interface AutofixBreadcrumbSnippetProps {
  45. breadcrumb: BreadcrumbContext;
  46. }
  47. function AutofixBreadcrumbSnippet({breadcrumb}: AutofixBreadcrumbSnippetProps) {
  48. const type = BreadcrumbType[breadcrumb.category.toUpperCase()];
  49. const level = BreadcrumbLevelType[breadcrumb.level.toUpperCase()];
  50. const rawCrumb = {
  51. message: breadcrumb.body,
  52. category: breadcrumb.category,
  53. type,
  54. level,
  55. };
  56. return (
  57. <BackgroundPanel>
  58. <BreadcrumbItem
  59. title={
  60. <Header>
  61. <div>
  62. <TextBreak>{getBreadcrumbTitle(rawCrumb)}</TextBreak>
  63. </div>
  64. <BreadcrumbLevel level={level}>{level}</BreadcrumbLevel>
  65. </Header>
  66. }
  67. colorConfig={getBreadcrumbColorConfig(type)}
  68. icon={<BreadcrumbIcon type={type} />}
  69. isActive
  70. showLastLine
  71. >
  72. <ContentWrapper>
  73. <BreadcrumbItemContent breadcrumb={rawCrumb} meta={{}} fullyExpanded />
  74. </ContentWrapper>
  75. </BreadcrumbItem>
  76. </BackgroundPanel>
  77. );
  78. }
  79. export function ExpandableInsightContext({
  80. children,
  81. title,
  82. icon,
  83. rounded,
  84. expandByDefault = false,
  85. }: {
  86. children: React.ReactNode;
  87. title: string;
  88. expandByDefault?: boolean;
  89. icon?: React.ReactNode;
  90. rounded?: boolean;
  91. }) {
  92. const [expanded, setExpanded] = useState(expandByDefault);
  93. const toggleExpand = () => {
  94. setExpanded(oldState => !oldState);
  95. };
  96. return (
  97. <ExpandableContext isRounded={rounded}>
  98. <ContextHeader
  99. onClick={toggleExpand}
  100. name={title}
  101. isRounded={rounded}
  102. isExpanded={expanded}
  103. size="sm"
  104. >
  105. <ContextHeaderWrapper>
  106. <ContextHeaderLeftAlign>
  107. {icon}
  108. <ContextHeaderText>{title}</ContextHeaderText>
  109. </ContextHeaderLeftAlign>
  110. <IconChevron size="xs" direction={expanded ? 'down' : 'right'} />
  111. </ContextHeaderWrapper>
  112. </ContextHeader>
  113. {expanded && <ContextBody>{children}</ContextBody>}
  114. </ExpandableContext>
  115. );
  116. }
  117. const animationProps: AnimationProps = {
  118. exit: {opacity: 0, height: 0, scale: 0.8, y: -20},
  119. initial: {opacity: 0, height: 0, scale: 0.8},
  120. animate: {opacity: 1, height: 'auto', scale: 1},
  121. transition: testableTransition({
  122. duration: 1.0,
  123. height: {
  124. type: 'spring',
  125. bounce: 0.2,
  126. },
  127. scale: {
  128. type: 'spring',
  129. bounce: 0.2,
  130. },
  131. y: {
  132. type: 'tween',
  133. ease: 'easeOut',
  134. },
  135. }),
  136. };
  137. interface AutofixInsightCardProps {
  138. groupId: string;
  139. hasCardAbove: boolean;
  140. hasCardBelow: boolean;
  141. index: number;
  142. insight: AutofixInsight;
  143. repos: AutofixRepository[];
  144. runId: string;
  145. stepIndex: number;
  146. isLastInsightInStep?: boolean;
  147. shouldHighlightRethink?: boolean;
  148. }
  149. function AutofixInsightCard({
  150. insight,
  151. hasCardBelow,
  152. hasCardAbove,
  153. repos,
  154. index,
  155. stepIndex,
  156. groupId,
  157. runId,
  158. shouldHighlightRethink,
  159. isLastInsightInStep,
  160. }: AutofixInsightCardProps) {
  161. const isUserMessage = insight.justification === 'USER';
  162. const [expanded, setExpanded] = useState(false);
  163. const toggleExpand = () => {
  164. setExpanded(oldState => !oldState);
  165. };
  166. return (
  167. <ContentWrapper>
  168. <AnimatePresence initial>
  169. <AnimationWrapper key="content" {...animationProps}>
  170. {hasCardAbove && (
  171. <ChainLink
  172. insightCardAboveIndex={index - 1}
  173. stepIndex={stepIndex}
  174. groupId={groupId}
  175. runId={runId}
  176. isHighlighted={shouldHighlightRethink}
  177. />
  178. )}
  179. {!isUserMessage && (
  180. <InsightContainer>
  181. <InsightCardRow onClick={toggleExpand}>
  182. <MiniHeader
  183. dangerouslySetInnerHTML={{
  184. __html: singleLineRenderer(insight.insight),
  185. }}
  186. />
  187. <StyledIconChevron direction={expanded ? 'down' : 'right'} size="xs" />
  188. </InsightCardRow>
  189. {expanded && (
  190. <ContextBody>
  191. <p
  192. dangerouslySetInnerHTML={{
  193. __html: singleLineRenderer(
  194. replaceHeadersWithBold(insight.justification)
  195. ),
  196. }}
  197. />
  198. {insight.stacktrace_context &&
  199. insight.stacktrace_context.length > 0 && (
  200. <div>
  201. <ContextSectionTitle>
  202. <IconFire color="red400" />
  203. {t(
  204. 'Stacktrace%s and Variables:',
  205. insight.stacktrace_context.length > 1 ? 's' : ''
  206. )}
  207. </ContextSectionTitle>
  208. {insight.stacktrace_context
  209. .map((stacktrace, i) => {
  210. let vars: any = {};
  211. try {
  212. vars = JSON.parse(stacktrace.vars_as_json);
  213. } catch {
  214. vars = {vars: stacktrace.vars_as_json};
  215. }
  216. return (
  217. <div key={i}>
  218. <SuggestedFixSnippet
  219. snippet={{
  220. snippet: stacktrace.code_snippet,
  221. repo_name: stacktrace.repo_name,
  222. file_path: stacktrace.file_name,
  223. }}
  224. linesToHighlight={[]}
  225. repos={repos}
  226. />
  227. <StyledStructuredEventData
  228. data={vars}
  229. maxDefaultDepth={1}
  230. />
  231. </div>
  232. );
  233. })
  234. .reverse()}
  235. </div>
  236. )}
  237. {insight.breadcrumb_context &&
  238. insight.breadcrumb_context.length > 0 && (
  239. <div>
  240. <ContextSectionTitle>
  241. <IconSpan color="green400" />
  242. {t(
  243. 'Breadcrumb%s:',
  244. insight.breadcrumb_context.length > 1 ? 's' : ''
  245. )}
  246. </ContextSectionTitle>
  247. {insight.breadcrumb_context
  248. .map((breadcrumb, i) => {
  249. return (
  250. <AutofixBreadcrumbSnippet key={i} breadcrumb={breadcrumb} />
  251. );
  252. })
  253. .reverse()}
  254. </div>
  255. )}
  256. {insight.codebase_context && insight.codebase_context.length > 0 && (
  257. <div>
  258. <ContextSectionTitle>
  259. <IconCode color="purple400" />
  260. {t(
  261. 'Code Snippet%s:',
  262. insight.codebase_context.length > 1 ? 's' : ''
  263. )}
  264. </ContextSectionTitle>
  265. {insight.codebase_context
  266. .map((code, i) => {
  267. return (
  268. <SuggestedFixSnippet
  269. key={i}
  270. snippet={code}
  271. linesToHighlight={[]}
  272. repos={repos}
  273. />
  274. );
  275. })
  276. .reverse()}
  277. </div>
  278. )}
  279. </ContextBody>
  280. )}
  281. </InsightContainer>
  282. )}
  283. {isUserMessage && (
  284. <UserMessageContainer>
  285. <IconUser />
  286. <UserMessage
  287. dangerouslySetInnerHTML={{
  288. __html: singleLineRenderer(insight.insight),
  289. }}
  290. />
  291. </UserMessageContainer>
  292. )}
  293. {hasCardBelow && (
  294. <ChainLink
  295. insightCardAboveIndex={index}
  296. stepIndex={stepIndex}
  297. groupId={groupId}
  298. runId={runId}
  299. isHighlighted={shouldHighlightRethink}
  300. isLastCard={isLastInsightInStep}
  301. />
  302. )}
  303. </AnimationWrapper>
  304. </AnimatePresence>
  305. </ContentWrapper>
  306. );
  307. }
  308. interface AutofixInsightCardsProps {
  309. groupId: string;
  310. hasStepAbove: boolean;
  311. hasStepBelow: boolean;
  312. insights: AutofixInsight[];
  313. repos: AutofixRepository[];
  314. runId: string;
  315. stepIndex: number;
  316. shouldHighlightRethink?: boolean;
  317. }
  318. function AutofixInsightCards({
  319. insights,
  320. repos,
  321. hasStepBelow,
  322. hasStepAbove,
  323. stepIndex,
  324. groupId,
  325. runId,
  326. shouldHighlightRethink,
  327. }: AutofixInsightCardsProps) {
  328. return (
  329. <InsightsContainer>
  330. {insights.length > 0 ? (
  331. insights.map((insight, index) =>
  332. !insight ? null : (
  333. <AutofixInsightCard
  334. key={index}
  335. insight={insight}
  336. hasCardBelow={index < insights.length - 1 || hasStepBelow}
  337. hasCardAbove={hasStepAbove && index === 0}
  338. repos={repos}
  339. index={index}
  340. stepIndex={stepIndex}
  341. groupId={groupId}
  342. runId={runId}
  343. isLastInsightInStep={index === insights.length - 1}
  344. shouldHighlightRethink={shouldHighlightRethink}
  345. />
  346. )
  347. )
  348. ) : stepIndex === 0 && !hasStepBelow ? (
  349. <NoInsightsYet />
  350. ) : hasStepBelow ? (
  351. <EmptyResultsContainer>
  352. <ChainLink
  353. insightCardAboveIndex={null}
  354. stepIndex={stepIndex}
  355. groupId={groupId}
  356. runId={runId}
  357. isHighlighted={shouldHighlightRethink}
  358. isLastCard
  359. />
  360. </EmptyResultsContainer>
  361. ) : null}
  362. </InsightsContainer>
  363. );
  364. }
  365. export function useUpdateInsightCard({groupId, runId}: {groupId: string; runId: string}) {
  366. const api = useApi({persistInFlight: true});
  367. const queryClient = useQueryClient();
  368. return useMutation({
  369. mutationFn: (params: {
  370. message: string;
  371. retain_insight_card_index: number | null;
  372. step_index: number;
  373. }) => {
  374. return api.requestPromise(`/issues/${groupId}/autofix/update/`, {
  375. method: 'POST',
  376. data: {
  377. run_id: runId,
  378. payload: {
  379. type: 'restart_from_point_with_feedback',
  380. message: params.message,
  381. step_index: params.step_index,
  382. retain_insight_card_index: params.retain_insight_card_index,
  383. },
  384. },
  385. });
  386. },
  387. onSuccess: _ => {
  388. queryClient.invalidateQueries({queryKey: makeAutofixQueryKey(groupId)});
  389. addSuccessMessage(t('Thanks, rethinking this...'));
  390. },
  391. onError: () => {
  392. addErrorMessage(t('Something went wrong when sending Autofix your message.'));
  393. },
  394. });
  395. }
  396. function ChainLink({
  397. groupId,
  398. runId,
  399. stepIndex,
  400. insightCardAboveIndex,
  401. isHighlighted,
  402. isLastCard,
  403. }: {
  404. groupId: string;
  405. insightCardAboveIndex: number | null;
  406. runId: string;
  407. stepIndex: number;
  408. isHighlighted?: boolean;
  409. isLastCard?: boolean;
  410. }) {
  411. const [showOverlay, setShowOverlay] = useState(false);
  412. const [referenceElement, setReferenceElement] = useState<
  413. HTMLAnchorElement | HTMLButtonElement | null
  414. >(null);
  415. const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
  416. const [comment, setComment] = useState('');
  417. const {mutate: send} = useUpdateInsightCard({groupId, runId});
  418. const {styles, attributes} = usePopper(referenceElement, popperElement, {
  419. placement: 'left-start',
  420. modifiers: [
  421. {
  422. name: 'offset',
  423. options: {
  424. offset: [-16, 8],
  425. },
  426. },
  427. {
  428. name: 'flip',
  429. options: {
  430. fallbackPlacements: ['right-start', 'bottom-start'],
  431. },
  432. },
  433. ],
  434. });
  435. const handleClickOutside = useCallback(
  436. (event: MouseEvent) => {
  437. if (
  438. referenceElement?.contains(event.target as Node) ||
  439. popperElement?.contains(event.target as Node)
  440. ) {
  441. return;
  442. }
  443. setShowOverlay(false);
  444. },
  445. [popperElement, referenceElement]
  446. );
  447. useEffect(() => {
  448. if (showOverlay) {
  449. document.addEventListener('mousedown', handleClickOutside);
  450. } else {
  451. document.removeEventListener('mousedown', handleClickOutside);
  452. }
  453. return () => {
  454. document.removeEventListener('mousedown', handleClickOutside);
  455. };
  456. }, [showOverlay, handleClickOutside]);
  457. return (
  458. <ArrowContainer>
  459. <IconArrow direction={'down'} className="arrow-icon" />
  460. <RethinkButtonContainer className="rethink-button-container">
  461. <AnimatePresence>
  462. {isLastCard && isHighlighted && (
  463. <RethinkMessage
  464. initial={{opacity: 0, x: 20}}
  465. animate={{opacity: 1, x: 0}}
  466. exit={{opacity: 0, x: 20}}
  467. transition={{duration: 0.4}}
  468. >
  469. Not satisfied?
  470. </RethinkMessage>
  471. )}
  472. </AnimatePresence>
  473. <RethinkButton
  474. ref={setReferenceElement}
  475. icon={<IconRefresh size="xs" />}
  476. size="zero"
  477. className="rethink-button"
  478. title={t('Rethink from here')}
  479. aria-label={t('Rethink from here')}
  480. onClick={() => setShowOverlay(true)}
  481. isHighlighted={isHighlighted}
  482. />
  483. </RethinkButtonContainer>
  484. {showOverlay &&
  485. createPortal(
  486. <RethinkInput
  487. ref={setPopperElement}
  488. style={styles.popper}
  489. {...attributes.popper}
  490. id="autofix-rethink-input"
  491. >
  492. <form
  493. onSubmit={e => {
  494. e.preventDefault();
  495. e.stopPropagation();
  496. setShowOverlay(false);
  497. setComment('');
  498. send({
  499. message: comment,
  500. step_index: stepIndex,
  501. retain_insight_card_index: insightCardAboveIndex,
  502. });
  503. }}
  504. className="row-form"
  505. onClick={e => e.stopPropagation()}
  506. id="autofix-rethink-input"
  507. >
  508. <Input
  509. type="text"
  510. placeholder="You should know X... Dive deeper into Y... Look at Z..."
  511. value={comment}
  512. onChange={e => setComment(e.target.value)}
  513. size="md"
  514. autoFocus
  515. id="autofix-rethink-input"
  516. />
  517. <Button
  518. type="submit"
  519. icon={<IconRefresh />}
  520. title="Restart analysis from this point in the chain"
  521. aria-label="Restart analysis from this point in the chain"
  522. priority="primary"
  523. size="md"
  524. id="autofix-rethink-input"
  525. />
  526. </form>
  527. </RethinkInput>,
  528. document.querySelector('.solutions-drawer-container') ?? document.body
  529. )}
  530. </ArrowContainer>
  531. );
  532. }
  533. const ContextSectionTitle = styled('p')`
  534. font-weight: bold;
  535. margin-bottom: 0;
  536. display: flex;
  537. align-items: center;
  538. gap: ${space(1)};
  539. `;
  540. const InsightCardRow = styled('div')`
  541. display: flex;
  542. justify-content: space-between;
  543. align-items: center;
  544. cursor: pointer;
  545. &:hover {
  546. background-color: ${p => p.theme.backgroundSecondary};
  547. }
  548. `;
  549. const UserMessageContainer = styled('div')`
  550. color: ${p => p.theme.subText};
  551. display: flex;
  552. padding: ${space(2)};
  553. align-items: center;
  554. border: 1px solid ${p => p.theme.innerBorder};
  555. border-radius: ${p => p.theme.borderRadius};
  556. overflow: hidden;
  557. box-shadow: ${p => p.theme.dropShadowMedium};
  558. margin-left: ${space(4)};
  559. margin-right: ${space(4)};
  560. `;
  561. const UserMessage = styled('div')`
  562. margin-left: ${space(2)};
  563. flex-shrink: 100;
  564. word-break: break-word;
  565. `;
  566. const NoInsightsYet = styled('div')`
  567. display: flex;
  568. justify-content: center;
  569. flex-direction: column;
  570. color: ${p => p.theme.subText};
  571. `;
  572. const EmptyResultsContainer = styled('div')`
  573. position: relative;
  574. bottom: -${space(1)};
  575. `;
  576. const InsightsContainer = styled('div')``;
  577. const InsightContainer = styled(motion.div)`
  578. border: 1px solid ${p => p.theme.innerBorder};
  579. border-radius: ${p => p.theme.borderRadius};
  580. overflow: hidden;
  581. box-shadow: ${p => p.theme.dropShadowMedium};
  582. margin-left: ${space(2)};
  583. margin-right: ${space(2)};
  584. animation: fadeFromActive 1.2s ease-out;
  585. @keyframes fadeFromActive {
  586. from {
  587. background-color: ${p => p.theme.active};
  588. border-color: ${p => p.theme.active};
  589. }
  590. to {
  591. background-color: ${p => p.theme.background};
  592. border-color: ${p => p.theme.innerBorder};
  593. }
  594. }
  595. `;
  596. const ArrowContainer = styled('div')`
  597. display: grid;
  598. grid-template-columns: 1fr auto 1fr;
  599. color: ${p => p.theme.subText};
  600. align-items: center;
  601. position: relative;
  602. z-index: 0;
  603. padding-top: ${space(1)};
  604. padding-bottom: ${space(1)};
  605. .arrow-icon {
  606. grid-column: 2 / 3;
  607. justify-self: center;
  608. align-self: center;
  609. }
  610. .rethink-button-container {
  611. grid-column: 3 / 4;
  612. justify-self: end;
  613. align-self: center;
  614. position: relative;
  615. }
  616. `;
  617. const RethinkButtonContainer = styled('div')`
  618. position: relative;
  619. `;
  620. const RethinkMessage = styled(motion.div)`
  621. color: ${p => p.theme.active};
  622. font-size: ${p => p.theme.fontSizeSmall};
  623. position: absolute;
  624. right: calc(100% + ${space(1)});
  625. margin-top: 1px;
  626. white-space: nowrap;
  627. `;
  628. const RethinkButton = styled(Button)<{isHighlighted?: boolean}>`
  629. font-weight: normal;
  630. font-size: small;
  631. border: none;
  632. color: ${p => p.theme.subText};
  633. transition: all 0.4s ease-in-out;
  634. position: relative;
  635. ${p =>
  636. p.isHighlighted &&
  637. `
  638. color: ${p.theme.button.primary.backgroundActive};
  639. background: ${p.theme.purple100};
  640. border-radius: ${p.theme.borderRadius};
  641. &:hover {
  642. color: ${p.theme.activeHover};
  643. background: ${p.theme.purple200};
  644. }
  645. `}
  646. &:hover {
  647. transform: scale(1.05);
  648. }
  649. &:active {
  650. transform: scale(0.95);
  651. }
  652. `;
  653. const RethinkInput = styled('div')`
  654. position: fixed;
  655. box-shadow: ${p => p.theme.dropShadowHeavy};
  656. border: 1px solid ${p => p.theme.border};
  657. width: 90%;
  658. background: ${p => p.theme.backgroundElevated};
  659. padding: ${space(0.5)};
  660. border-radius: ${p => p.theme.borderRadius};
  661. z-index: ${p => p.theme.zIndex.tooltip};
  662. .row-form {
  663. display: flex;
  664. gap: ${space(1)};
  665. }
  666. `;
  667. const BreadcrumbItem = styled(Timeline.Item)`
  668. border-bottom: 1px solid transparent;
  669. &:not(:last-child) {
  670. border-image: linear-gradient(
  671. to right,
  672. transparent 20px,
  673. ${p => p.theme.translucentInnerBorder} 20px
  674. )
  675. 100% 1;
  676. }
  677. `;
  678. const ContentWrapper = styled('div')``;
  679. const Header = styled('div')`
  680. display: grid;
  681. grid-template-columns: 1fr auto;
  682. `;
  683. const TextBreak = styled('span')`
  684. word-wrap: break-word;
  685. word-break: break-all;
  686. `;
  687. const BackgroundPanel = styled('div')`
  688. padding: ${space(1)};
  689. margin-top: ${space(2)};
  690. margin-bottom: ${space(2)};
  691. background: ${p => p.theme.backgroundSecondary};
  692. border-radius: ${p => p.theme.borderRadius};
  693. `;
  694. const MiniHeader = styled('p')`
  695. padding-top: ${space(2)};
  696. padding-right: ${space(2)};
  697. padding-left: ${space(2)};
  698. width: 95%;
  699. word-break: break-word;
  700. `;
  701. const ExpandableContext = styled('div')<{isRounded?: boolean}>`
  702. width: 100%;
  703. border-radius: ${p => (p.isRounded ? p.theme.borderRadius : 0)};
  704. `;
  705. const ContextHeader = styled(Button)<{isExpanded?: boolean; isRounded?: boolean}>`
  706. width: 100%;
  707. box-shadow: none;
  708. margin: 0;
  709. border: none;
  710. font-weight: normal;
  711. background: ${p => p.theme.backgroundSecondary};
  712. border-radius: ${p => {
  713. if (!p.isRounded) {
  714. return 0;
  715. }
  716. if (p.isExpanded) {
  717. return `${p.theme.borderRadius} ${p.theme.borderRadius} 0 0`;
  718. }
  719. return p.theme.borderRadius;
  720. }};
  721. `;
  722. const ContextHeaderLeftAlign = styled('div')`
  723. display: flex;
  724. gap: ${space(1)};
  725. align-items: center;
  726. `;
  727. const ContextHeaderWrapper = styled('div')`
  728. display: flex;
  729. justify-content: space-between;
  730. align-items: center;
  731. width: 100%;
  732. `;
  733. const ContextHeaderText = styled('p')`
  734. height: 0;
  735. `;
  736. const ContextBody = styled('div')`
  737. padding: ${space(2)};
  738. background: ${p => p.theme.alert.info.backgroundLight};
  739. border-radius: 0 0 ${p => p.theme.borderRadius} ${p => p.theme.borderRadius};
  740. overflow: hidden;
  741. `;
  742. const StyledStructuredEventData = styled(StructuredEventData)`
  743. border-top: solid 1px ${p => p.theme.border};
  744. border-top-left-radius: 0;
  745. border-top-right-radius: 0;
  746. `;
  747. const AnimationWrapper = styled(motion.div)`
  748. transform-origin: top center;
  749. &.new-insight {
  750. animation: textFadeFromActive 1.2s ease-out;
  751. }
  752. @keyframes textFadeFromActive {
  753. from {
  754. color: ${p => p.theme.white};
  755. }
  756. to {
  757. color: inherit;
  758. }
  759. }
  760. `;
  761. const StyledIconChevron = styled(IconChevron)`
  762. width: 5%;
  763. flex-shrink: 0;
  764. display: flex;
  765. justify-content: center;
  766. align-items: center;
  767. color: ${p => p.theme.subText};
  768. `;
  769. export default AutofixInsightCards;