Browse Source

feat(issue-details): Add syntax highlighting to SQL code in breadcrumbs (#62590)

Closes https://github.com/getsentry/sentry/issues/52929

Before:
<img width="1015" alt="Screenshot 2024-01-05 at 9 08 51 AM"
src="https://github.com/getsentry/sentry/assets/22582037/f1c984c1-d0b7-478d-b522-77875e6a9dff">


After:
<img width="1015" alt="Screenshot 2024-01-05 at 9 08 16 AM"
src="https://github.com/getsentry/sentry/assets/22582037/afe50179-fa2e-4144-8cfe-7f7cad6fa29e">


After with Highlighting:
<img width="1015" alt="Screenshot 2024-01-05 at 9 09 43 AM"
src="https://github.com/getsentry/sentry/assets/22582037/1ef9941c-64f0-4b9b-a0d6-ce4d71e6422b">

NOTE: This PR does not include cases with annotated/filtered text:
![Screenshot 2024-01-09 at 9 24
01 AM](https://github.com/getsentry/sentry/assets/22582037/95315d06-9229-4d6b-8cdc-24b1dd51c423)
Julia Hoge 1 year ago
parent
commit
3d168b6f60

+ 2 - 17
static/app/components/events/interfaces/breadcrumbs/breadcrumb/data/default.tsx

@@ -1,10 +1,7 @@
-import styled from '@emotion/styled';
-
 import type {BreadcrumbTransactionEvent} from 'sentry/components/events/interfaces/breadcrumbs/types';
 import {AnnotatedText} from 'sentry/components/events/meta/annotatedText';
 import Highlight from 'sentry/components/highlight';
 import Link from 'sentry/components/links/link';
-import {space} from 'sentry/styles/space';
 import {Organization} from 'sentry/types';
 import {BreadcrumbTypeDefault, BreadcrumbTypeNavigation} from 'sentry/types/breadcrumbs';
 import {Event} from 'sentry/types/event';
@@ -111,18 +108,6 @@ function FormatMessage({
 
     return description;
   }
-  switch (breadcrumb.messageFormat) {
-    case 'sql':
-      return <FormattedCode>{content}</FormattedCode>;
-    default:
-      return content;
-  }
-}
 
-const FormattedCode = styled('div')`
-  padding: ${space(1)};
-  background: ${p => p.theme.backgroundSecondary};
-  border-radius: ${p => p.theme.borderRadius};
-  overflow-x: auto;
-  white-space: pre;
-`;
+  return content;
+}

+ 14 - 1
static/app/components/events/interfaces/breadcrumbs/breadcrumb/data/index.tsx

@@ -1,7 +1,12 @@
+import {Sql} from 'sentry/components/events/interfaces/breadcrumbs/breadcrumb/data/sql';
 import type {BreadcrumbTransactionEvent} from 'sentry/components/events/interfaces/breadcrumbs/types';
 import {BreadcrumbMeta} from 'sentry/components/events/interfaces/breadcrumbs/types';
 import {Organization} from 'sentry/types';
-import {BreadcrumbType, RawCrumb} from 'sentry/types/breadcrumbs';
+import {
+  BreadcrumbMessageFormat,
+  BreadcrumbType,
+  RawCrumb,
+} from 'sentry/types/breadcrumbs';
 import {Event} from 'sentry/types/event';
 
 import {Default} from './default';
@@ -31,6 +36,14 @@ export function Data({
     return <Http breadcrumb={breadcrumb} searchTerm={searchTerm} meta={meta} />;
   }
 
+  if (
+    !meta &&
+    breadcrumb.message &&
+    breadcrumb.messageFormat === BreadcrumbMessageFormat.SQL
+  ) {
+    return <Sql breadcrumb={breadcrumb} searchTerm={searchTerm} />;
+  }
+
   if (
     breadcrumb.type === BreadcrumbType.WARNING ||
     breadcrumb.type === BreadcrumbType.ERROR

+ 41 - 0
static/app/components/events/interfaces/breadcrumbs/breadcrumb/data/sql.spec.tsx

@@ -0,0 +1,41 @@
+import {initializeOrg} from 'sentry-test/initializeOrg';
+import {render, screen} from 'sentry-test/reactTestingLibrary';
+
+import {Sql} from 'sentry/components/events/interfaces/breadcrumbs/breadcrumb/data/sql';
+import {BreadcrumbLevelType, BreadcrumbType} from 'sentry/types/breadcrumbs';
+
+describe('Breadcrumb Data SQL', function () {
+  const {organization, router} = initializeOrg({
+    router: {
+      location: {query: {project: '0'}},
+    },
+  });
+
+  it('displays formatted SQL message', function () {
+    render(
+      <Sql
+        breadcrumb={{
+          type: BreadcrumbType.WARNING,
+          level: BreadcrumbLevelType.WARNING,
+          message: `
+SELECT db.id, db.project_id,
+       db.release_name, db.dist_name,
+       db.date_added
+FROM db
+WHERE (db.dist_name = %s
+       AND db.project_id = %s
+       AND db.release_name = %s)
+ORDER BY db.id ASC
+LIMIT 1
+FOR
+UPDATE NOWAIT`,
+        }}
+        searchTerm=""
+      />,
+      {organization, router}
+    );
+
+    expect(screen.getByText('SELECT db.id, db.project_id,')).toBeInTheDocument();
+    expect(screen.getByText('ORDER BY db.id ASC')).toBeInTheDocument();
+  });
+});

+ 35 - 0
static/app/components/events/interfaces/breadcrumbs/breadcrumb/data/sql.tsx

@@ -0,0 +1,35 @@
+import Highlight from 'sentry/components/highlight';
+import {BreadcrumbTypeDefault, BreadcrumbTypeNavigation} from 'sentry/types/breadcrumbs';
+import {usePrismTokens} from 'sentry/utils/usePrismTokens';
+
+import Summary from './summary';
+
+type Props = {
+  breadcrumb: BreadcrumbTypeNavigation | BreadcrumbTypeDefault;
+  searchTerm: string;
+};
+
+export function Sql({breadcrumb, searchTerm}: Props) {
+  const {data, message} = breadcrumb;
+  const tokens = usePrismTokens({code: message ?? '', language: 'sql'});
+
+  return (
+    <Summary kvData={data}>
+      <pre className="language-sql">
+        <code>
+          {tokens.map((line, i) => (
+            <div key={i}>
+              <div>
+                {line.map((token, j) => (
+                  <span key={j} className={token.className}>
+                    <Highlight text={searchTerm}>{token.children}</Highlight>
+                  </span>
+                ))}
+              </div>
+            </div>
+          ))}
+        </code>
+      </pre>
+    </Summary>
+  );
+}

+ 7 - 0
static/app/components/events/interfaces/breadcrumbs/breadcrumb/data/summary.tsx

@@ -52,6 +52,13 @@ const Wrapper = styled('div')`
   font-size: ${p => p.theme.fontSizeSmall};
   font-family: ${p => p.theme.text.familyMono};
   overflow: hidden;
+
+  pre,
+  code {
+    margin: 0;
+    padding: 0;
+    font-size: ${p => p.theme.fontSizeSmall};
+  }
 `;
 
 const ContextDataWrapper = styled('div')`

+ 5 - 1
static/app/types/breadcrumbs.tsx

@@ -29,13 +29,17 @@ export enum BreadcrumbType {
   INIT = 'init',
 }
 
+export enum BreadcrumbMessageFormat {
+  SQL = 'sql',
+}
+
 interface BreadcrumbTypeBase {
   level: BreadcrumbLevelType;
   // it's recommended
   category?: string | null;
   event_id?: string | null;
   message?: string;
-  messageFormat?: 'sql';
+  messageFormat?: BreadcrumbMessageFormat.SQL;
   messageRaw?: string;
   timestamp?: string;
 }