Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sentry Nuxt module #5279

Merged
merged 6 commits into from
Dec 17, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
@@ -191,6 +191,7 @@ jobs:
outputs: type=docker,dest=/tmp/${{ matrix.image }}.tar
build-contexts: ${{ matrix.build-contexts }}
build-args: |
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
SEMANTIC_VERSION=${{ needs.get-image-tag.outputs.image_tag }}
OV_PDM_VERSION=${{ steps.prepare-build-args.outputs.ov_pdm_version }}
CATALOG_PY_VERSION=${{ steps.prepare-build-args.outputs.catalog_py_version }}
3 changes: 3 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -43,3 +43,6 @@ src/locales/scripts/wp-locales.json
# To prevent accidentally adding a hardcoded robots.txt, see
# /src/server-middleware/robots.js for the robots.txt file.
src/static/robots.txt

# Sentry Config File
.env.sentry-build-plugin
Comment on lines +47 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for this being named .env.sentry-build-plugin? Could it be similar to the env setup for other other packages so that we can reuse the existing workflow (.env file created from env.template using the env recipe etc.)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added by the Sentry module wizard. I added the env variable to the GitHub repository, so it's not really necessary anymore.

10 changes: 10 additions & 0 deletions frontend/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -67,6 +67,7 @@ export default defineNuxtConfig({
"@nuxt/test-utils/module",
"@nuxtjs/sitemap",
"@nuxtjs/robots",
"@sentry/nuxt/module",
],
routeRules: {
"/photos/**": { redirect: { to: "/image/**", statusCode: 301 } },
@@ -126,4 +127,13 @@ export default defineNuxtConfig({
trailingSlash: false,
vueI18n: "./vue-i18n",
},
sentry: {
sourceMapsUploadOptions: {
org: "openverse",
project: "openverse-frontend",
},
},
sourcemap: {
client: "hidden",
},
})
3 changes: 1 addition & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -63,8 +63,7 @@
"@nuxtjs/sitemap": "^7.0.0",
"@nuxtjs/tailwindcss": "^6.12.1",
"@pinia/nuxt": "^0.9.0",
"@sentry/node": "^8.26.0",
"@sentry/vue": "^8.26.0",
"@sentry/nuxt": "^8.45.0",
"@tailwindcss/typography": "^0.5.13",
"@vueuse/core": "^12.0.0",
"@wordpress/is-shallow-equal": "^5.3.0",
16 changes: 16 additions & 0 deletions frontend/sentry.client.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useAppConfig, useRuntimeConfig } from "#imports"

import * as Sentry from "@sentry/nuxt"

Sentry.init({
dsn: useRuntimeConfig().public.sentry.dsn,
environment: useRuntimeConfig().public.sentry.environment,
release: useAppConfig().semanticVersion,
ignoreErrors: [
// Can be safely ignored, @see https://github.com/WICG/resize-observer/issues/38
/ResizeObserver loop limit exceeded/i,
],

tracesSampleRate: 1.0,
})
Sentry.setContext("render context", { platform: "client" })
15 changes: 15 additions & 0 deletions frontend/sentry.server.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as Sentry from "@sentry/nuxt"
import dotenv from "dotenv"

// Necessary for loading environment variables before Nuxt is loaded
// @see the section on server setup: https://nuxt.com/modules/sentry
dotenv.config()

Sentry.init({
dsn: process.env.NUXT_PUBLIC_SENTRY_DSN,
environment: process.env.NUXT_PUBLIC_SENTRY_ENVIRONMENT,
release: process.env.SEMANTIC_VERSION,

tracesSampleRate: 1.0,
})
Sentry.setContext("render context", { platform: "server" })
37 changes: 0 additions & 37 deletions frontend/server/plugins/sentry.ts

This file was deleted.

6 changes: 3 additions & 3 deletions frontend/src/components/VSketchFabViewer.vue
Original file line number Diff line number Diff line change
@@ -26,12 +26,12 @@ const emit = defineEmits<{ failure: [] }>()
const { t } = useI18n({ useScope: "global" })
const label = t("sketchfabIframeTitle", { sketchfab: "Sketchfab" })
const node = ref<Element | undefined>()
const { $sentry } = useNuxtApp()
const { $captureException, $captureMessage } = useNuxtApp()

const initSketchfab = async () => {
await loadScript(sketchfabUrl)
if (typeof window.Sketchfab === "undefined") {
$sentry.captureMessage("Unable to find window.Sketchfab after loading")
$captureMessage("Unable to find window.Sketchfab after loading")
return
}

@@ -44,7 +44,7 @@ const initSketchfab = async () => {
const sf = new window.Sketchfab(node.value)
sf.init(props.uid, {
error: (e: unknown) => {
$sentry.captureException(e)
$captureException(e)
emit("failure")
},
})
8 changes: 3 additions & 5 deletions frontend/src/plugins/01.api-token.server.ts
Original file line number Diff line number Diff line change
@@ -2,11 +2,11 @@ import { defineNuxtPlugin, useRuntimeConfig } from "#imports"

import { Mutex, MutexInterface } from "async-mutex"
import axios from "axios"
import * as Sentry from "@sentry/nuxt"

import { debug, warn } from "~/utils/console"

import type { AxiosError } from "axios"
import type { NuxtApp } from "#app"

/* Process level state */

@@ -161,14 +161,12 @@ export const getApiAccessToken = async (): Promise<string | undefined> => {
return process.tokenData.accessToken
}

export default defineNuxtPlugin(async (app) => {
export default defineNuxtPlugin(async () => {
let openverseApiToken: string | undefined
try {
openverseApiToken = await getApiAccessToken()
} catch (e) {
const sentry =
app.ssrContext?.event.context.$sentry ?? (app as NuxtApp).$sentry
sentry.captureException(e)
Sentry.captureException(e)
}
return {
provide: {
4 changes: 2 additions & 2 deletions frontend/src/plugins/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { defineNuxtPlugin } from "#imports"

import { isAxiosError } from "axios"
import * as Sentry from "@sentry/nuxt"

import { ERR_UNKNOWN, ErrorCode, errorCodes } from "#shared/constants/errors"
import type { SupportedSearchType } from "#shared/constants/media"
@@ -122,8 +123,7 @@ export function recordError(
searchType: fetchingError.searchType,
})
} else {
const sentry = nuxtApp.ssrContext?.event.context.$sentry ?? nuxtApp.$sentry
sentry.captureException(originalError, { extra: { fetchingError } })
Sentry.captureException(originalError, { extra: { fetchingError } })
}
}

32 changes: 6 additions & 26 deletions frontend/src/plugins/sentry.client.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,13 @@
import { defineNuxtPlugin, useRuntimeConfig, useAppConfig } from "#imports"
import { defineNuxtPlugin } from "#imports"

import * as Sentry from "@sentry/vue"

export default defineNuxtPlugin((nuxtApp) => {
const {
public: { sentry },
} = useRuntimeConfig()

const { semanticVersion } = useAppConfig()

if (!sentry.dsn) {
console.warn("Sentry DSN wasn't provided")
}

Sentry.init({
dsn: sentry.dsn,
environment: sentry.environment,
release: semanticVersion,
app: nuxtApp.vueApp,
ignoreErrors: [
// Can be safely ignored, @see https://github.com/WICG/resize-observer/issues/38
/ResizeObserver loop limit exceeded/i,
],
})
Sentry.setContext("render context", { platform: "client" })
import * as Sentry from "@sentry/nuxt"

export default defineNuxtPlugin(() => {
const { captureException, captureMessage } = Sentry
return {
provide: {
sentry: Sentry,
captureException,
captureMessage,
},
}
})
7 changes: 4 additions & 3 deletions frontend/src/stores/active-media.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useNuxtApp } from "#imports"

import { defineStore } from "pinia"
import { useNuxtApp } from "#app"

import type { SupportedMediaType } from "#shared/constants/media"
import { audioErrorMessages } from "#shared/constants/audio"
@@ -85,8 +86,8 @@ export const useActiveMediaStore = defineStore(ACTIVE_MEDIA, {
? audioErrorMessages[err.name as keyof typeof audioErrorMessages]
: "err_unknown"
if (message === "err_unknown") {
const { $sentry } = useNuxtApp()
$sentry.captureException(err)
const { $captureException } = useNuxtApp()
$captureException(err)
}
this.setMessage({ message })
audio?.pause()
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { expect } from "@playwright/test"
import { test } from "~~/test/playwright/utils/test"
import breakpoints from "~~/test/playwright/utils/breakpoints"
import {
@@ -6,7 +7,8 @@ import {
sleep,
} from "~~/test/playwright/utils/navigation"
import { setViewportToFullHeight } from "~~/test/playwright/utils/viewport"
import { languageDirections } from "~~/test/playwright/utils/i18n"
import { languageDirections, t } from "~~/test/playwright/utils/i18n"
import { getH1 } from "~~/test/playwright/utils/components"

import { ALL_MEDIA, supportedSearchTypes } from "#shared/constants/media"

@@ -56,6 +58,7 @@ breakpoints.describeXl(({ breakpoint, expectSnapshot }) => {
// eslint-disable-next-line playwright/no-networkidle
await page.waitForLoadState("networkidle")

await expect(getH1(page, t("404.title"))).toBeVisible()
await expectSnapshot(page, "generic-error-ltr", page, {
screenshotOptions: { fullPage: true },
})
@@ -77,6 +80,7 @@ for (const searchType of supportedSearchTypes) {
await preparePageForTests(page, breakpoint)
await goToSearchTerm(page, `SearchPage500error`, { searchType })

await expect(getH1(page, t("404.title"))).toBeVisible()
await expectSnapshot(page, "generic-error-ltr", page, {
screenshotOptions: { fullPage: true },
})
@@ -101,6 +105,7 @@ for (const searchType of supportedSearchTypes) {
searchType,
})

await expect(getH1(page, t("404.title"))).toBeVisible()
await expectSnapshot(page, "generic-error", page, {
dir,
screenshotOptions: {
@@ -136,6 +141,8 @@ for (const searchType of supportedSearchTypes) {
})
await goToSearchTerm(page, "cat", { dir, searchType, mode: "CSR" })

await expect(getH1(page, t("serverTimeout.heading", dir))).toBeVisible()

await setViewportToFullHeight(page)

await page.mouse.move(0, 82)
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import { expect } from "@playwright/test"
import { test } from "~~/test/playwright/utils/test"
import breakpoints from "~~/test/playwright/utils/breakpoints"
import {
isPageDesktop,
pathWithDir,
preparePageForTests,
} from "~~/test/playwright/utils/navigation"
@@ -11,6 +12,7 @@ import {
getHomepageSearchButton,
getLanguageSelect,
getLoadMoreButton,
getMenuButton,
} from "~~/test/playwright/utils/components"

test.describe.configure({ mode: "parallel" })
@@ -32,6 +34,11 @@ for (const contentPage of contentPages) {

await page.goto(pathWithDir(contentPage, dir))
// Ensure the page is hydrated
// eslint-disable-next-line playwright/no-conditional-in-test
if (!isPageDesktop(page)) {
// eslint-disable-next-line playwright/no-conditional-expect
await expect(getMenuButton(page, dir)).toBeEnabled()
}
await expect(page.locator("#language")).toHaveValue(
dir === "ltr" ? "en" : "ar"
)
Original file line number Diff line number Diff line change
@@ -29,20 +29,6 @@ const stubs = {
RouterLink: RouterLinkStub,
}

const captureExceptionMock = vi.fn()

vi.mock("#app", async () => {
const original = await import("#app")
return {
...original,
useNuxtApp: vi.fn(() => ({
$sentry: {
captureException: captureExceptionMock,
},
})),
}
})

describe("AudioTrack", () => {
let options = null
let props = null
@@ -109,7 +95,6 @@ describe("AudioTrack", () => {
${"NotAllowedError"} | ${/Reproduction not allowed./i}
${"NotSupportedError"} | ${/This audio format is not supported by your browser./i}
${"AbortError"} | ${/You aborted playback./i}
${"UnknownError"} | ${/An unexpected error has occurred./i}
`(
"on play error displays a message instead of the waveform",
async ({ errorType, errorText }) => {
@@ -139,15 +124,6 @@ describe("AudioTrack", () => {
expect(playStub).toHaveBeenCalledTimes(1)
expect(pauseStub).toHaveBeenCalledTimes(1)
expect(getByText(errorText)).toBeVisible()

// Only the UnknownError should be sent to Sentry.
if (errorType === "UnknownError") {
// eslint-disable-next-line vitest/no-conditional-expect
expect(captureExceptionMock).toHaveBeenCalledWith(playError)
} else {
// eslint-disable-next-line vitest/no-conditional-expect
expect(captureExceptionMock).not.toHaveBeenCalled()
}
}
)

Loading
Loading