Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
0815309
fix(nextjs): Await flush in api handlers (#14023)
chargome Oct 21, 2024
8b1f130
fix(solidstart): Use production server for e2e tests (#14033)
andreiborza Oct 21, 2024
7e39d04
Merge pull request #14036 from getsentry/master
github-actions[bot] Oct 21, 2024
022caf9
test(e2e): Create event dumps for all Next.js test apps (#14034)
Oct 22, 2024
6e2c0d1
fix(nuxt): Server-side setup in readme (#14049)
s1gr1d Oct 22, 2024
7b815bf
feat(nuxt): Add Sentry Pinia plugin (#14047)
s1gr1d Oct 22, 2024
5b22b26
fix(nextjs): Fix matching logic for file convention type for root lev…
Oct 22, 2024
d5756ea
deps: Bump bundler plugins and CLI to 2.22.6 and 2.37.0 respectively …
Oct 22, 2024
eb17d62
meta: Allow old Next.js version for dependency review (#14012)
mdtro Oct 22, 2024
6cee1bf
test(e2e): Add Next.js Turbopack E2E tests (#14031)
Oct 23, 2024
8a68fa9
fix(nuxt): Only wrap `.mjs` entry files in rollup (#14060)
s1gr1d Oct 23, 2024
c56d84d
test(node): Fix test runner (#14019)
timfish Oct 23, 2024
fe639f4
feat(nextjs/vercel-edge/cloudflare): Switch to OTEL for performance m…
Oct 24, 2024
e0820cf
fix(replay): Fix `onError` callback (#14002)
billyvg Oct 24, 2024
ff8e780
test(nextjs): Test for client trace propagation with Turbopack (#14059)
Oct 28, 2024
e68865a
fix(nextjs): Respect directives in value injection loader (#14083)
Oct 28, 2024
cc30c14
fix(core): Ensure standalone spans are not sent if SDK is disabled (#…
Lms24 Oct 28, 2024
974818a
chore(deps): bump rollup from 4.18.0 to 4.24.2 in /dev-packages/e2e-t…
dependabot[bot] Oct 28, 2024
765213b
ref: Remove overhead-metrics dev package (#14087)
mydea Oct 28, 2024
a70f797
ci: Update dependabot config (#14090)
mydea Oct 28, 2024
babfaed
chore(dev-deps): Bump rollup from 4.13.0 to 4.24.2 (#14093)
mydea Oct 28, 2024
e72d31d
feat(deps): bump @opentelemetry/instrumentation-fs from 0.15.0 to 0.1…
dependabot[bot] Oct 28, 2024
8e2d7d2
perf(otel): Only calculate current timestamp once (#14094)
AbhiPrasad Oct 28, 2024
5a2719b
feat(deps): bump @opentelemetry/instrumentation-express from 0.43.0 t…
dependabot[bot] Oct 28, 2024
bf1187e
ci(deps): bump denoland/setup-deno from 1.5.1 to 2.0.1 (#14096)
dependabot[bot] Oct 28, 2024
3dc4d08
feat(deps): bump @opentelemetry/instrumentation-aws-sdk from 0.44.0 t…
dependabot[bot] Oct 28, 2024
cfd411e
fix(profiling-node): Always warn when running on incompatible major v…
JonasBa Oct 28, 2024
5d2fc2c
feat(deps): bump @opentelemetry/instrumentation-kafkajs from 0.3.0 to…
dependabot[bot] Oct 28, 2024
dd0d7e0
feat(deps): bump @opentelemetry/instrumentation-connect from 0.39.0 t…
dependabot[bot] Oct 28, 2024
f32db11
chore(deps-dev): bump vite from 5.2.11 to 5.2.14 in /dev-packages/e2e…
dependabot[bot] Oct 28, 2024
d62a65c
meta: Bump E2E test timeouts (#14103)
Oct 28, 2024
6410841
chore(tests): Upgrade mysql2 package to 3.11.3 (#14105)
AbhiPrasad Oct 28, 2024
0982655
chore: Don't run dependabot on CodeQL PRs (#14109)
AbhiPrasad Oct 29, 2024
22c3865
chore: Unify nock versions (#14111)
AbhiPrasad Oct 29, 2024
c7378e6
fix(nuxt): Re-export all exported bindings (#14086)
s1gr1d Oct 29, 2024
9e676e0
test(browser-integration): Add sentry DSN route handler by default (#…
mydea Oct 29, 2024
a7193fb
ci: Use `list` reporter for playwright on CI instead of `line` (#14113)
mydea Oct 29, 2024
d396241
feat(node): Add breadcrumbs for `child_process` and `worker_thread` (…
timfish Oct 29, 2024
6fefd44
chore(dev-deps): Bump some (transitive) dev dependencies to latest (#…
mydea Oct 29, 2024
8f65bf7
ci: Fix CodeQL workflow config (#14117)
mydea Oct 29, 2024
f18d04f
chore(dev-deps): Bump size-limit to 11.1.6 (#14114)
mydea Oct 29, 2024
81b1f9f
ci(deps): bump github/codeql-action from 2 to 3 (#10004)
dependabot[bot] Oct 29, 2024
57ba5a7
fix(nextjs): Don't leak webpack types into exports (#14116)
Oct 29, 2024
06ef628
feat(nextjs): Add method and url to route handler request data (#14084)
onurtemizkan Oct 29, 2024
8d9322c
meta(changelog): Update changelog for 8.36.0
Oct 29, 2024
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
Prev Previous commit
Next Next commit
feat(nuxt): Add Sentry Pinia plugin (#14047)
closes #14039

By adding `trackPinia`, the Pinia store is monitored with Sentry.

```js
Sentry.init({
  dsn: useRuntimeConfig().public.sentry.dsn,
  trackPinia: true
});
```
or with custom options:
```js
Sentry.init({
  dsn: useRuntimeConfig().public.sentry.dsn,
  trackPinia: {
    actionTransformer: action => `Transformed: ${action}`,
  },
});
```
  • Loading branch information
s1gr1d authored Oct 22, 2024
commit 7b815bf481051a546e6078157b87b4c10ccd70d4
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script setup lang="ts">
import { ref } from '#imports'
import { useCartStore } from '~~/stores/cart'

const cart = useCartStore()

const itemName = ref('')

function addItemToCart() {
if (!itemName.value) return
cart.addItem(itemName.value)
itemName.value = ''
}

function throwError() {
throw new Error('This is an error')
}

function clearCart() {
if (window.confirm('Are you sure you want to clear the cart?')) {
cart.rawItems = []
}
}
</script>

<template>
<Layout>
<div>
<div style="margin: 1rem 0;">
<PiniaLogo />
</div>

<form @submit.prevent="addItemToCart" data-testid="add-items">
<input id="item-input" type="text" v-model="itemName" />
<button id="item-add">Add</button>
<button id="throw-error" @click="throwError">Throw error</button>
</form>

<form>
<ul data-testid="items">
<li v-for="item in cart.items" :key="item.name">
{{ item.name }} ({{ item.amount }})
<button
@click="cart.removeItem(item.name)"
type="button"
>X</button>
</li>
</ul>

<button
:disabled="!cart.items.length"
@click="clearCart"
type="button"
data-testid="clear"
>Clear the cart</button>
</form>
</div>
</Layout>
</template>



<style scoped>
img {
width: 200px;
}

button,
input {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
imports: { autoImport: false },

modules: ['@sentry/nuxt/module'],
modules: ['@pinia/nuxt', '@sentry/nuxt/module'],

runtimeConfig: {
public: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"test:assert": "pnpm test"
},
"dependencies": {
"@pinia/nuxt": "^0.5.5",
"@sentry/nuxt": "latest || *",
"nuxt": "^3.13.2"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,11 @@ Sentry.init({
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
trackComponents: true,
trackPinia: {
actionTransformer: action => `Transformed: ${action}`,
stateTransformer: state => ({
transformed: true,
...state,
}),
},
});
43 changes: 43 additions & 0 deletions dev-packages/e2e-tests/test-applications/nuxt-4/stores/cart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { acceptHMRUpdate, defineStore } from '#imports';

export const useCartStore = defineStore({
id: 'cart',
state: () => ({
rawItems: [] as string[],
}),
getters: {
items: (state): Array<{ name: string; amount: number }> =>
state.rawItems.reduce(
(items: any, item: any) => {
const existingItem = items.find((it: any) => it.name === item);

if (!existingItem) {
items.push({ name: item, amount: 1 });
} else {
existingItem.amount++;
}

return items;
},
[] as Array<{ name: string; amount: number }>,
),
},
actions: {
addItem(name: string) {
this.rawItems.push(name);
},

removeItem(name: string) {
const i = this.rawItems.lastIndexOf(name);
if (i > -1) this.rawItems.splice(i, 1);
},

throwError() {
throw new Error('error');
},
},
});

if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useCartStore, import.meta.hot));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { expect, test } from '@playwright/test';
import { waitForError } from '@sentry-internal/test-utils';

test('sends pinia action breadcrumbs and state context', async ({ page }) => {
await page.goto('/pinia-cart');

await page.locator('#item-input').fill('item');
await page.locator('#item-add').click();

const errorPromise = waitForError('nuxt-4', async errorEvent => {
return errorEvent?.exception?.values?.[0].value === 'This is an error';
});

await page.locator('#throw-error').click();

const error = await errorPromise;

expect(error).toBeTruthy();
expect(error.breadcrumbs?.length).toBeGreaterThan(0);

const actionBreadcrumb = error.breadcrumbs?.find(breadcrumb => breadcrumb.category === 'action');

expect(actionBreadcrumb).toBeDefined();
expect(actionBreadcrumb?.message).toBe('Transformed: addItem');
expect(actionBreadcrumb?.level).toBe('info');

const stateContext = error.contexts?.state?.state;

expect(stateContext).toBeDefined();
expect(stateContext?.type).toBe('pinia');
expect(stateContext?.value).toEqual({
transformed: true,
rawItems: ['item'],
});
});
15 changes: 13 additions & 2 deletions packages/nuxt/src/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import type { init as initNode } from '@sentry/node';
import type { SentryRollupPluginOptions } from '@sentry/rollup-plugin';
import type { SentryVitePluginOptions } from '@sentry/vite-plugin';
import type { init as initVue } from '@sentry/vue';
import type { createSentryPiniaPlugin, init as initVue } from '@sentry/vue';

// Omitting 'app' as the Nuxt SDK will add the app instance in the client plugin (users do not have to provide this)
export type SentryNuxtClientOptions = Omit<Parameters<typeof initVue>[0] & object, 'app'>;
export type SentryNuxtClientOptions = Omit<Parameters<typeof initVue>[0] & object, 'app'> & {
/**
* Control if an existing Pinia store should be monitored.
* Set this to `true` to track with default options or provide your custom Pinia plugin options.
*
* This only works if "@pinia/nuxt" is added to the `modules` array.
*
* @default false
*/
trackPinia?: true | Parameters<typeof createSentryPiniaPlugin>[0];
};

export type SentryNuxtServerOptions = Omit<Parameters<typeof initNode>[0] & object, 'app'>;

type SourceMapsOptions = {
Expand Down
25 changes: 22 additions & 3 deletions packages/nuxt/src/runtime/plugins/sentry.client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getClient } from '@sentry/core';
import { browserTracingIntegration, vueIntegration } from '@sentry/vue';
import { consoleSandbox } from '@sentry/utils';
import { browserTracingIntegration, createSentryPiniaPlugin, vueIntegration } from '@sentry/vue';
import { defineNuxtPlugin } from 'nuxt/app';
import { reportNuxtError } from '../utils';

Expand Down Expand Up @@ -34,18 +35,36 @@ export default defineNuxtPlugin({
name: 'sentry-client-integrations',
dependsOn: ['sentry-client-config'],
async setup(nuxtApp) {
const sentryClient = getClient();
const clientOptions = sentryClient && sentryClient.getOptions();

// This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false", in which case everything inside
// will get tree-shaken away
if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) {
const sentryClient = getClient();

if (sentryClient && '$router' in nuxtApp) {
sentryClient.addIntegration(
browserTracingIntegration({ router: nuxtApp.$router as VueRouter, routeLabel: 'path' }),
);
}
}

if (clientOptions && 'trackPinia' in clientOptions && clientOptions.trackPinia) {
if ('$pinia' in nuxtApp) {
(nuxtApp.$pinia as { use: (plugin: unknown) => void }).use(
// `trackPinia` is an object with custom options or `true` (pass `undefined` to use default options)
createSentryPiniaPlugin(clientOptions.trackPinia === true ? undefined : clientOptions.trackPinia),
);
} else {
clientOptions.debug &&
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] You set `trackPinia`, but the Pinia module was not found. Make sure to add `"@pinia/nuxt"` to your modules array.',
);
});
}
}

nuxtApp.hook('app:created', vueApp => {
const sentryClient = getClient();

Expand Down