Skip to content

Commit 76c6dbf

Browse files
author
Pascal Klesse
committed
feat: Implement user journey tracking and bug reporting enhancements
- Add UserJourneyTimeline component for visualizing user interactions. - Enhance useBugReport composable to include user journey data. - Integrate network request monitoring with detailed logging. - Introduce error boundary handling with automatic bug report modal opening. - Expand types to support user interactions and network requests. - Implement user interaction tracking with various event types (clicks, navigation, form submissions, etc.). - Add utility functions for managing network requests and user interactions. - Update bug report submission to include user journey and network request data.
1 parent 73e37a2 commit 76c6dbf

File tree

13 files changed

+18267
-13580
lines changed

13 files changed

+18267
-13580
lines changed

package-lock.json

Lines changed: 16733 additions & 13557 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -57,30 +57,30 @@
5757
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
5858
},
5959
"dependencies": {
60-
"@nuxt/kit": "4.1.3",
61-
"@nuxt/ui": "4.0.1",
62-
"puppeteer": "24.23.0",
63-
"zod": "4.1.12"
60+
"@nuxt/kit": "4.2.1",
61+
"@nuxt/ui": "4.2.1",
62+
"puppeteer": "24.31.0",
63+
"zod": "4.1.13"
6464
},
6565
"devDependencies": {
66-
"@nuxt/devtools": "2.6.5",
67-
"@nuxt/eslint-config": "1.9.0",
66+
"@nuxt/devtools": "3.1.0",
67+
"@nuxt/eslint-config": "1.10.0",
6868
"@nuxt/module-builder": "1.0.2",
69-
"@nuxt/schema": "4.1.3",
70-
"@nuxt/test-utils": "3.19.2",
69+
"@nuxt/schema": "4.2.1",
70+
"@nuxt/test-utils": "3.20.1",
7171
"@semantic-release/changelog": "6.0.3",
7272
"@semantic-release/git": "10.0.1",
73-
"@semantic-release/github": "11.0.6",
74-
"@semantic-release/npm": "12.0.2",
73+
"@semantic-release/github": "12.0.2",
74+
"@semantic-release/npm": "13.1.2",
7575
"@types/node": "latest",
7676
"changelogen": "0.6.2",
7777
"conventional-changelog-conventionalcommits": "9.1.0",
78-
"eslint": "9.37.0",
79-
"happy-dom": "18.0.1",
80-
"nuxt": "4.1.3",
81-
"semantic-release": "24.2.9",
82-
"typescript": "~5.9.3",
78+
"eslint": "9.39.1",
79+
"happy-dom": "20.0.10",
80+
"nuxt": "4.2.1",
81+
"semantic-release": "^25.0.2",
82+
"typescript": "5.9.3",
8383
"vitest": "3.2.4",
84-
"vue-tsc": "3.1.1"
84+
"vue-tsc": "3.1.5"
8585
}
86-
}
86+
}

src/module.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,37 @@ export interface ModuleOptions {
3838
enableScreenshot?: boolean
3939
enableBrowserInfo?: boolean
4040
enableConsoleLogs?: boolean
41+
enableNetworkRequests?: boolean
42+
enableErrorBoundary?: boolean
43+
enableUserJourney?: boolean
44+
autoOpenOnError?: boolean
4145

4246
// Styling
4347
theme?: 'light' | 'dark' | 'auto'
4448

4549
// Console Logs
4650
maxConsoleLogs?: number
4751

52+
// Network Requests
53+
maxNetworkRequests?: number
54+
55+
// User Journey Tracking
56+
userJourney?: {
57+
enabled?: boolean
58+
maxEvents?: number
59+
captureClicks?: boolean
60+
captureNavigation?: boolean
61+
captureFormInteractions?: boolean
62+
captureHover?: boolean
63+
captureScroll?: boolean
64+
captureInputChanges?: boolean
65+
captureInputValues?: boolean
66+
captureKeyboard?: boolean
67+
captureErrors?: boolean
68+
captureModalEvents?: boolean
69+
throttleRate?: number
70+
}
71+
4872
// HTTP Basic Authentication
4973
httpAuth?: {
5074
username: string
@@ -70,8 +94,28 @@ export default defineNuxtModule<ModuleOptions>({
7094
enableScreenshot: true,
7195
enableBrowserInfo: true,
7296
enableConsoleLogs: true,
97+
enableNetworkRequests: true,
98+
enableErrorBoundary: true,
99+
enableUserJourney: true,
100+
autoOpenOnError: false,
73101
theme: 'auto',
74102
maxConsoleLogs: 50,
103+
maxNetworkRequests: 50,
104+
userJourney: {
105+
enabled: true,
106+
maxEvents: 50,
107+
captureClicks: true,
108+
captureNavigation: true,
109+
captureFormInteractions: true,
110+
captureHover: true,
111+
captureScroll: true,
112+
captureInputChanges: true,
113+
captureInputValues: false,
114+
captureKeyboard: true,
115+
captureErrors: true,
116+
captureModalEvents: true,
117+
throttleRate: 100,
118+
},
75119
},
76120
async setup(options, nuxt) {
77121
// Early exit if module is disabled
@@ -106,8 +150,14 @@ export default defineNuxtModule<ModuleOptions>({
106150
enableScreenshot: options.enableScreenshot,
107151
enableBrowserInfo: options.enableBrowserInfo,
108152
enableConsoleLogs: options.enableConsoleLogs,
153+
enableNetworkRequests: options.enableNetworkRequests,
154+
enableErrorBoundary: options.enableErrorBoundary,
155+
enableUserJourney: options.enableUserJourney,
156+
autoOpenOnError: options.autoOpenOnError,
109157
theme: options.theme,
110158
maxConsoleLogs: options.maxConsoleLogs,
159+
maxNetworkRequests: options.maxNetworkRequests,
160+
userJourney: options.userJourney,
111161
// Don't expose sensitive data to client: linearApiKey, linearTeamName, linearProjectName, httpAuth
112162
}
113163

@@ -141,6 +191,16 @@ export default defineNuxtModule<ModuleOptions>({
141191
filePath: resolver.resolve('./runtime/components/BugIcon.vue'),
142192
})
143193

194+
addComponent({
195+
name: 'ErrorBoundary',
196+
filePath: resolver.resolve('./runtime/components/ErrorBoundary.vue'),
197+
})
198+
199+
addComponent({
200+
name: 'UserJourneyTimeline',
201+
filePath: resolver.resolve('./runtime/components/UserJourneyTimeline.vue'),
202+
})
203+
144204
// Add composables
145205
addImports({
146206
name: 'useBugReport',

src/runtime/components/BugReportForm.vue

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import type { AttachmentFile, BugReportConfig, BugReportData, BugReportType } fr
66
import { captureScreenshot as captureScreenshotUtil } from '../utils/screenshot'
77
import { getBrowserInfo } from '../utils/browserInfo'
88
import { getConsoleLogs } from '../utils/consoleLogs'
9+
import { getNetworkRequests } from '../utils/networkRequests'
910
import { useBugReport } from '../composables/useBugReport'
1011
import AttachmentsList from './AttachmentsList.vue'
12+
import UserJourneyTimeline from './UserJourneyTimeline.vue'
1113
1214
interface Props {
1315
isSubmitting?: boolean
@@ -26,7 +28,7 @@ withDefaults(defineProps<Props>(), {
2628
const emit = defineEmits<Emits>()
2729
2830
const runtimeConfig = useRuntimeConfig()
29-
const { previewScreenshot, capturingScreenshot } = useBugReport()
31+
const { previewScreenshot, capturingScreenshot, lastError, getUserJourney } = useBugReport()
3032
3133
const config = computed((): BugReportConfig => runtimeConfig.public.bugLt as BugReportConfig)
3234
@@ -46,6 +48,15 @@ const state = reactive({
4648
stepsToReproduce: '',
4749
})
4850
51+
// Pre-fill form if error info is available
52+
watch(lastError, (errorInfo) => {
53+
if (errorInfo) {
54+
state.title = errorInfo.message.substring(0, 100)
55+
state.type = 'bug'
56+
state.description = `**Error Message:**\n${errorInfo.message}\n\n**Component:** ${errorInfo.componentName || 'Unknown'}\n\n**Stack Trace:**\n\`\`\`\n${errorInfo.stack || 'No stack trace available'}\n\`\`\``
57+
}
58+
}, { immediate: true })
59+
4960
const typeOptions = [
5061
{ label: 'Bug', value: 'bug' },
5162
{ label: 'Feature', value: 'feature' },
@@ -65,6 +76,10 @@ const attachments = ref<AttachmentFile[]>([])
6576
const localCapturingScreenshot = ref<boolean>(false)
6677
const includeBrowserInfo = ref<boolean>(true)
6778
const includeConsoleLogs = ref<boolean>(true)
79+
const includeNetworkRequests = ref<boolean>(true)
80+
const includeUserJourney = ref<boolean>(true)
81+
82+
const userJourneyEvents = computed(() => getUserJourney())
6883
6984
// Watch for pre-captured screenshot changes
7085
watch(previewScreenshot, (newScreenshot) => {
@@ -153,6 +168,14 @@ const onSubmit = async () => {
153168
data.consoleLogs = getConsoleLogs(config.value?.maxConsoleLogs)
154169
}
155170
171+
if (includeNetworkRequests.value && config.value?.enableNetworkRequests) {
172+
data.networkRequests = getNetworkRequests(config.value?.maxNetworkRequests)
173+
}
174+
175+
if (includeUserJourney.value && config.value?.enableUserJourney) {
176+
data.userInteractions = getUserJourney()
177+
}
178+
156179
emit('submit', data)
157180
}
158181
</script>
@@ -311,10 +334,59 @@ const onSubmit = async () => {
311334
:disabled="isSubmitting"
312335
/>
313336
</div>
337+
338+
<!-- Network Requests Toggle -->
339+
<div
340+
v-if="config?.enableNetworkRequests"
341+
class="flex items-center justify-between"
342+
>
343+
<div>
344+
<p class="text-sm font-medium text-gray-700 dark:text-gray-200">
345+
Netzwerk-Anfragen auslesen
346+
</p>
347+
<p class="text-xs text-gray-500 dark:text-gray-400">
348+
API-Calls und HTTP-Requests der letzten Minuten
349+
</p>
350+
</div>
351+
<USwitch
352+
v-model="includeNetworkRequests"
353+
:disabled="isSubmitting"
354+
/>
355+
</div>
356+
357+
<!-- User Journey Toggle -->
358+
<div
359+
v-if="config?.enableUserJourney"
360+
class="flex items-center justify-between"
361+
>
362+
<div>
363+
<p class="text-sm font-medium text-gray-700 dark:text-gray-200">
364+
User Journey auslesen
365+
</p>
366+
<p class="text-xs text-gray-500 dark:text-gray-400">
367+
Benutzer-Interaktionen und Navigation ({{ userJourneyEvents.length }} Events)
368+
</p>
369+
</div>
370+
<USwitch
371+
v-model="includeUserJourney"
372+
:disabled="isSubmitting"
373+
/>
374+
</div>
314375
</div>
315376
</template>
316377
</UAccordion>
317378

379+
<!-- User Journey Timeline Preview -->
380+
<div
381+
v-if="config?.enableUserJourney && includeUserJourney && userJourneyEvents.length > 0"
382+
class="space-y-2"
383+
>
384+
<label class="text-sm font-medium text-gray-700 dark:text-gray-200">
385+
User Journey Preview ({{ userJourneyEvents.length }} Events)
386+
</label>
387+
<UserJourneyTimeline :events="userJourneyEvents" />
388+
</div>
389+
318390
<!-- Action Buttons -->
319391
<div class="flex justify-end gap-3 pt-4">
320392
<UButton

0 commit comments

Comments
 (0)