Skip to content

Commit

Permalink
DEVPROD-3188: Attach additional data to ui.click breadcrumbs in Sentry (
Browse files Browse the repository at this point in the history
  • Loading branch information
minnakt authored May 8, 2024
1 parent 85438b7 commit 03f20fd
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,47 +31,59 @@ exports[`Snapshot Tests BookmarksBar.stories Default 1`] = `
class="css-1fmnfd"
data-cy="bookmark-4"
>
<span>
<span
data-bookmark="4"
>
4
</span>
</div>
<div
class="css-1fmnfd"
data-cy="bookmark-5"
>
<span>
<span
data-bookmark="5"
>
5
</span>
</div>
<div
class="css-1fmnfd"
data-cy="bookmark-6"
>
<span>
<span
data-bookmark="6"
>
6
</span>
</div>
<div
class="css-1fmnfd"
data-cy="bookmark-7"
>
<span>
<span
data-bookmark="7"
>
7
</span>
</div>
<div
class="css-zayph7"
data-cy="bookmark-10"
>
<span>
<span
data-bookmark="10"
>
10
</span>
</div>
<div
class="css-1fmnfd"
data-cy="bookmark-21"
>
<span>
<span
data-bookmark="21"
>
21
</span>
<svg
Expand All @@ -98,23 +110,29 @@ exports[`Snapshot Tests BookmarksBar.stories Default 1`] = `
class="css-1fmnfd"
data-cy="bookmark-24"
>
<span>
<span
data-bookmark="24"
>
24
</span>
</div>
<div
class="css-1fmnfd"
data-cy="bookmark-30"
>
<span>
<span
data-bookmark="30"
>
30
</span>
</div>
<div
class="css-1fmnfd"
data-cy="bookmark-85"
>
<span>
<span
data-bookmark="85"
>
85
</span>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/parsley/src/components/BookmarksBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const BookmarksBar: React.FC<BookmarksBarProps> = ({
scrollToIndex(l);
}}
>
<span>{l}</span>
<span data-bookmark={l}>{l}</span>
{l === shareLine && <StyledIcon glyph="Link" size="small" />}
</LogLineNumber>
))}
Expand Down
13 changes: 13 additions & 0 deletions apps/parsley/src/components/ErrorHandling/Sentry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,23 @@ import {
isProduction,
} from "utils/environmentVariables";
import ErrorFallback from "./ErrorFallback";
import { processHtmlAttributes } from "./utils";

const initializeSentry = () => {
try {
init({
beforeBreadcrumb: (breadcrumb, hint) => {
if (breadcrumb?.category?.startsWith("ui")) {
const { target } = hint?.event ?? {};
if (target?.dataset?.cy) {
// eslint-disable-next-line no-param-reassign
breadcrumb.message = `${target.tagName.toLowerCase()}[data-cy="${target.dataset.cy}"]`;
}
// eslint-disable-next-line no-param-reassign
breadcrumb.data = processHtmlAttributes(target);
}
return breadcrumb;
},
debug: !isProduction(),
dsn: getSentryDSN(),
environment: getReleaseStage() || "development",
Expand Down
3 changes: 3 additions & 0 deletions apps/parsley/src/components/ErrorHandling/initialize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe("should initialize error handlers according to release stage", () => {
initializeErrorHandling();

expect(Sentry.init).toHaveBeenCalledWith({
beforeBreadcrumb: expect.any(Function),
debug: false,
dsn: "fake-sentry-key",
environment: "production",
Expand All @@ -43,6 +44,7 @@ describe("should initialize error handlers according to release stage", () => {
initializeErrorHandling();

expect(Sentry.init).toHaveBeenCalledWith({
beforeBreadcrumb: expect.any(Function),
debug: true,
dsn: "fake-sentry-key",
environment: "beta",
Expand All @@ -57,6 +59,7 @@ describe("should initialize error handlers according to release stage", () => {
initializeErrorHandling();

expect(Sentry.init).toHaveBeenCalledWith({
beforeBreadcrumb: expect.any(Function),
debug: true,
dsn: "fake-sentry-key",
environment: "staging",
Expand Down
27 changes: 27 additions & 0 deletions apps/parsley/src/components/ErrorHandling/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { processHtmlAttributes } from "./utils";

describe("processHtmlAttributes", () => {
it("properly extracts attributes", () => {
const htmlElement = document.createElement("input");
htmlElement.setAttribute("aria-label", "custom-aria-label");
htmlElement.setAttribute("title", "custom-title");
htmlElement.setAttribute("data-cy", "custom-data-cy");
htmlElement.setAttribute("data-loading", "false");

const htmlAttributes = processHtmlAttributes(htmlElement);
expect(htmlAttributes.ariaLabel).toBe("custom-aria-label");
expect(htmlAttributes?.dataset?.cy).toBe("custom-data-cy");
expect(htmlAttributes?.dataset?.loading).toBe("false");
expect(htmlAttributes.title).toBe("custom-title");
});

it("omits attributes that are unset", () => {
const htmlElement = document.createElement("div");
htmlElement.setAttribute("data-cy", "custom-data-cy");

const htmlAttributes = processHtmlAttributes(htmlElement);
expect(htmlAttributes?.dataset?.cy).toBe("custom-data-cy");
expect(htmlAttributes.ariaLabel).toBeUndefined();
expect(htmlAttributes.title).toBeUndefined();
});
});
19 changes: 19 additions & 0 deletions apps/parsley/src/components/ErrorHandling/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* `processHtmlAttributes` extracts useful attributes to attach as
* Sentry breadcrumb data.
* @param htmlElement - HTML element detected by Sentry
* @returns an object containing attributes of the HTML element
*/
const processHtmlAttributes = (htmlElement: HTMLElement) => {
const ariaLabel = htmlElement.getAttribute("aria-label");
const title = htmlElement.getAttribute("title");
// Get dataset directly to handle all data-* attributes.
const { dataset } = htmlElement ?? {};
return {
...(ariaLabel && { ariaLabel }),
...(dataset && Object.keys(dataset).length > 0 && { dataset }),
...(title && { title }),
};
};

export { processHtmlAttributes };
13 changes: 0 additions & 13 deletions apps/parsley/src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,17 +853,6 @@ export type JiraConfig = {
host?: Maybe<Scalars["String"]["output"]>;
};

export type JiraField = {
__typename?: "JiraField";
displayText: Scalars["String"]["output"];
field: Scalars["String"]["output"];
};

export type JiraFieldInput = {
displayText: Scalars["String"]["input"];
field: Scalars["String"]["input"];
};

export type JiraIssueSubscriber = {
__typename?: "JiraIssueSubscriber";
issueType: Scalars["String"]["output"];
Expand Down Expand Up @@ -2600,12 +2589,10 @@ export type TaskTestsArgs = {
export type TaskAnnotationSettings = {
__typename?: "TaskAnnotationSettings";
fileTicketWebhook: Webhook;
jiraCustomFields?: Maybe<Array<JiraField>>;
};

export type TaskAnnotationSettingsInput = {
fileTicketWebhook?: InputMaybe<WebhookInput>;
jiraCustomFields?: InputMaybe<Array<JiraFieldInput>>;
};

export type TaskContainerCreationOpts = {
Expand Down
13 changes: 13 additions & 0 deletions apps/spruce/src/components/ErrorHandling/Sentry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,25 @@ import type { Scope, SeverityLevel } from "@sentry/react";
import type { Context, Primitive } from "@sentry/types";
import { environmentVariables } from "utils";
import ErrorFallback from "./ErrorFallback";
import { processHtmlAttributes } from "./utils";

const { getReleaseStage, getSentryDSN, isProduction } = environmentVariables;

const initializeSentry = () => {
try {
init({
beforeBreadcrumb: (breadcrumb, hint) => {
if (breadcrumb?.category?.startsWith("ui")) {
const { target } = hint?.event ?? {};
if (target?.dataset?.cy) {
// eslint-disable-next-line no-param-reassign
breadcrumb.message = `${target.tagName.toLowerCase()}[data-cy="${target.dataset.cy}"]`;
}
// eslint-disable-next-line no-param-reassign
breadcrumb.data = processHtmlAttributes(target);
}
return breadcrumb;
},
dsn: getSentryDSN(),
debug: !isProduction(),
normalizeDepth: 5,
Expand Down
3 changes: 3 additions & 0 deletions apps/spruce/src/components/ErrorHandling/initialize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe("should initialize error handlers according to release stage", () => {
initializeErrorHandling();

expect(Sentry.init).toHaveBeenCalledWith({
beforeBreadcrumb: expect.any(Function),
dsn: "fake-sentry-key",
debug: false,
normalizeDepth: 5,
Expand All @@ -46,6 +47,7 @@ describe("should initialize error handlers according to release stage", () => {
initializeErrorHandling();

expect(Sentry.init).toHaveBeenCalledWith({
beforeBreadcrumb: expect.any(Function),
dsn: "fake-sentry-key",
debug: true,
normalizeDepth: 5,
Expand All @@ -61,6 +63,7 @@ describe("should initialize error handlers according to release stage", () => {
initializeErrorHandling();

expect(Sentry.init).toHaveBeenCalledWith({
beforeBreadcrumb: expect.any(Function),
dsn: "fake-sentry-key",
debug: true,
normalizeDepth: 5,
Expand Down
27 changes: 27 additions & 0 deletions apps/spruce/src/components/ErrorHandling/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { processHtmlAttributes } from "./utils";

describe("processHtmlAttributes", () => {
it("properly extracts attributes", () => {
const htmlElement = document.createElement("input");
htmlElement.setAttribute("aria-label", "custom-aria-label");
htmlElement.setAttribute("title", "custom-title");
htmlElement.setAttribute("data-cy", "custom-data-cy");
htmlElement.setAttribute("data-loading", "false");

const htmlAttributes = processHtmlAttributes(htmlElement);
expect(htmlAttributes.ariaLabel).toBe("custom-aria-label");
expect(htmlAttributes?.dataset?.cy).toBe("custom-data-cy");
expect(htmlAttributes?.dataset?.loading).toBe("false");
expect(htmlAttributes.title).toBe("custom-title");
});

it("omits attributes that are unset", () => {
const htmlElement = document.createElement("div");
htmlElement.setAttribute("data-cy", "custom-data-cy");

const htmlAttributes = processHtmlAttributes(htmlElement);
expect(htmlAttributes?.dataset?.cy).toBe("custom-data-cy");
expect(htmlAttributes.ariaLabel).toBeUndefined();
expect(htmlAttributes.title).toBeUndefined();
});
});
19 changes: 19 additions & 0 deletions apps/spruce/src/components/ErrorHandling/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* `processHtmlAttributes` extracts useful attributes to attach as
* Sentry breadcrumb data.
* @param htmlElement - HTML element detected by Sentry
* @returns an object containing attributes of the HTML element
*/
const processHtmlAttributes = (htmlElement: HTMLElement) => {
const ariaLabel = htmlElement.getAttribute("aria-label");
const title = htmlElement.getAttribute("title");
// Get dataset directly to handle all data-* attributes.
const { dataset } = htmlElement ?? {};
return {
...(ariaLabel && { ariaLabel }),
...(dataset && Object.keys(dataset).length > 0 && { dataset }),
...(title && { title }),
};
};

export { processHtmlAttributes };
13 changes: 0 additions & 13 deletions apps/spruce/src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,17 +853,6 @@ export type JiraConfig = {
host?: Maybe<Scalars["String"]["output"]>;
};

export type JiraField = {
__typename?: "JiraField";
displayText: Scalars["String"]["output"];
field: Scalars["String"]["output"];
};

export type JiraFieldInput = {
displayText: Scalars["String"]["input"];
field: Scalars["String"]["input"];
};

export type JiraIssueSubscriber = {
__typename?: "JiraIssueSubscriber";
issueType: Scalars["String"]["output"];
Expand Down Expand Up @@ -2600,12 +2589,10 @@ export type TaskTestsArgs = {
export type TaskAnnotationSettings = {
__typename?: "TaskAnnotationSettings";
fileTicketWebhook: Webhook;
jiraCustomFields?: Maybe<Array<JiraField>>;
};

export type TaskAnnotationSettingsInput = {
fileTicketWebhook?: InputMaybe<WebhookInput>;
jiraCustomFields?: InputMaybe<Array<JiraFieldInput>>;
};

export type TaskContainerCreationOpts = {
Expand Down

0 comments on commit 03f20fd

Please sign in to comment.