Skip to content

Commit

Permalink
Merge branch 'develop' into lforst-set-headers-instead-of-append
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst authored Oct 16, 2024
2 parents 28ace04 + 41515a6 commit ba16ef7
Show file tree
Hide file tree
Showing 17 changed files with 469 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"dependencies": {
"@sentry/vue": "latest || *",
"pinia": "^2.2.3",
"vue": "^3.4.15",
"vue-router": "^4.2.5"
},
Expand Down
14 changes: 14 additions & 0 deletions dev-packages/e2e-tests/test-applications/vue-3/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

import { createPinia } from 'pinia';

import * as Sentry from '@sentry/vue';
import { browserTracingIntegration } from '@sentry/vue';

const app = createApp(App);
const pinia = createPinia();

Sentry.init({
app,
Expand All @@ -22,5 +25,16 @@ Sentry.init({
trackComponents: ['ComponentMainView', '<ComponentOneView>'],
});

pinia.use(
Sentry.createSentryPiniaPlugin({
actionTransformer: action => `Transformed: ${action}`,
stateTransformer: state => ({
transformed: true,
...state,
}),
}),
);

app.use(pinia);
app.use(router);
app.mount('#app');
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const router = createRouter({
path: '/components',
component: () => import('../views/ComponentMainView.vue'),
},
{
path: '/cart',
component: () => import('../views/CartView.vue'),
},
],
});

Expand Down
43 changes: 43 additions & 0 deletions dev-packages/e2e-tests/test-applications/vue-3/src/stores/cart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { acceptHMRUpdate, defineStore } from 'pinia';

export const useCartStore = defineStore({
id: 'cart',
state: () => ({
rawItems: [] as string[],
}),
getters: {
items: (state): Array<{ name: string; amount: number }> =>
state.rawItems.reduce(
(items, item) => {
const existingItem = items.find(it => 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,88 @@
<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>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import { useCartStore } from '../stores/cart'
export default defineComponent({
setup() {
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 = []
}
}
// @ts-ignore
window.stores = { cart }
return {
itemName,
addItemToCart,
cart,
throwError,
clearCart,
}
},
})
</script>

<style scoped>
img {
width: 200px;
}
button,
input {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
</style>
35 changes: 35 additions & 0 deletions dev-packages/e2e-tests/test-applications/vue-3/tests/pinia.test.ts
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('/cart');

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

const errorPromise = waitForError('vue-3', 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'],
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ Sentry.init({
integrations: [
Sentry.httpIntegration({
ignoreOutgoingRequests: (url, request) => {
if (url.includes('example.com')) {
if (url === 'http://example.com/blockUrl') {
return true;
}
if (request.method === 'POST' && request.path === '/path') {

if (request.hostname === 'example.com' && request.path === '/blockRequest') {
return true;
}
return false;
Expand All @@ -32,28 +33,37 @@ const app = express();

app.use(cors());

app.get('/test', (_req, response) => {
http
.request('http://example.com/', res => {
res.on('data', () => {});
res.on('end', () => {
response.send({ response: 'done' });
});
})
.end();
app.get('/testUrl', (_req, response) => {
makeHttpRequest('http://example.com/blockUrl').then(() => {
makeHttpRequest('http://example.com/pass').then(() => {
response.send({ response: 'done' });
});
});
});

app.post('/testPath', (_req, response) => {
http
.request('http://example.com/path', res => {
res.on('data', () => {});
res.on('end', () => {
response.send({ response: 'done' });
});
})
.end();
app.get('/testRequest', (_req, response) => {
makeHttpRequest('http://example.com/blockRequest').then(() => {
makeHttpRequest('http://example.com/pass').then(() => {
response.send({ response: 'done' });
});
});
});

Sentry.setupExpressErrorHandler(app);

startExpressServerAndSendPortToRunner(app);

function makeHttpRequest(url) {
return new Promise((resolve, reject) => {
http
.get(url, res => {
res.on('data', () => {});
res.on('end', () => {
resolve();
});
})
.on('error', error => {
reject(error);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,65 +128,45 @@ describe('httpIntegration', () => {
});
});

describe("doesn't create child spans for outgoing requests ignored via `ignoreOutgoingRequests`", () => {
describe("doesn't create child spans or breadcrumbs for outgoing requests ignored via `ignoreOutgoingRequests`", () => {
test('via the url param', done => {
const runner = createRunner(__dirname, 'server-ignoreOutgoingRequests.js')
.expect({
transaction: {
contexts: {
trace: {
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
url: expect.stringMatching(/\/test$/),
'http.response.status_code': 200,
},
op: 'http.server',
status: 'ok',
},
},
transaction: 'GET /test',
spans: [
expect.objectContaining({ op: 'middleware.express', description: 'query' }),
expect.objectContaining({ op: 'middleware.express', description: 'expressInit' }),
expect.objectContaining({ op: 'middleware.express', description: 'corsMiddleware' }),
expect.objectContaining({ op: 'request_handler.express', description: '/test' }),
],
transaction: event => {
expect(event.transaction).toBe('GET /testUrl');

const requestSpans = event.spans?.filter(span => span.op === 'http.client');
expect(requestSpans).toHaveLength(1);
expect(requestSpans![0]?.description).toBe('GET http://example.com/pass');

const breadcrumbs = event.breadcrumbs?.filter(b => b.category === 'http');
expect(breadcrumbs).toHaveLength(1);
expect(breadcrumbs![0]?.data?.url).toEqual('http://example.com/pass');
},
})
.start(done);

runner.makeRequest('get', '/test');
runner.makeRequest('get', '/testUrl');
});

test('via the request param', done => {
const runner = createRunner(__dirname, 'server-ignoreOutgoingRequests.js')
.expect({
transaction: {
contexts: {
trace: {
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
url: expect.stringMatching(/\/testPath$/),
'http.response.status_code': 200,
},
op: 'http.server',
status: 'ok',
},
},
transaction: 'POST /testPath',
spans: [
expect.objectContaining({ op: 'middleware.express', description: 'query' }),
expect.objectContaining({ op: 'middleware.express', description: 'expressInit' }),
expect.objectContaining({ op: 'middleware.express', description: 'corsMiddleware' }),
expect.objectContaining({ op: 'request_handler.express', description: '/testPath' }),
],
transaction: event => {
expect(event.transaction).toBe('GET /testRequest');

const requestSpans = event.spans?.filter(span => span.op === 'http.client');
expect(requestSpans).toHaveLength(1);
expect(requestSpans![0]?.description).toBe('GET http://example.com/pass');

const breadcrumbs = event.breadcrumbs?.filter(b => b.category === 'http');
expect(breadcrumbs).toHaveLength(1);
expect(breadcrumbs![0]?.data?.url).toEqual('http://example.com/pass');
},
})
.start(done);

runner.makeRequest('post', '/testPath');
runner.makeRequest('get', '/testRequest');
});
});
});
Loading

0 comments on commit ba16ef7

Please sign in to comment.