Skip to content

Commit 1bcf5e2

Browse files
christian-byrnegithub-actions
andauthored
[API Node] Contact support button (#3571)
Co-authored-by: github-actions <[email protected]>
1 parent 9e24706 commit 1bcf5e2

File tree

22 files changed

+359
-87
lines changed

22 files changed

+359
-87
lines changed

src/components/dialog/content/ErrorDialogContent.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525
:label="$t('issueReport.helpFix')"
2626
@click="showSendReport"
2727
/>
28+
<Button
29+
v-if="authStore.currentUser"
30+
v-show="!reportOpen"
31+
text
32+
:label="$t('issueReport.contactSupportTitle')"
33+
@click="showContactSupport"
34+
/>
2835
</div>
2936
<template v-if="reportOpen">
3037
<Divider />
@@ -72,6 +79,8 @@ import FindIssueButton from '@/components/dialog/content/error/FindIssueButton.v
7279
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
7380
import { api } from '@/scripts/api'
7481
import { app } from '@/scripts/app'
82+
import { useCommandStore } from '@/stores/commandStore'
83+
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
7584
import { useSystemStatsStore } from '@/stores/systemStatsStore'
7685
import type { ReportField } from '@/types/issueReportTypes'
7786
import {
@@ -81,6 +90,8 @@ import {
8190
8291
import ReportIssuePanel from './error/ReportIssuePanel.vue'
8392
93+
const authStore = useFirebaseAuthStore()
94+
8495
const { error } = defineProps<{
8596
error: Omit<ErrorReportData, 'workflow' | 'systemStats' | 'serverLogs'> & {
8697
/**
@@ -123,6 +134,10 @@ const stackTraceField = computed<ReportField>(() => {
123134
}
124135
})
125136
137+
const showContactSupport = async () => {
138+
await useCommandStore().execute('Comfy.ContactSupport')
139+
}
140+
126141
onMounted(async () => {
127142
if (!systemStatsStore.systemStats) {
128143
await systemStatsStore.fetchSystemStats()

src/components/dialog/content/error/ReportIssuePanel.vue

Lines changed: 157 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -23,76 +23,137 @@
2323
</div>
2424
</template>
2525
<div class="p-4 mt-2 border border-round surface-border shadow-1">
26-
<div class="flex flex-row gap-3 mb-2">
27-
<div v-for="field in fields" :key="field.value">
28-
<FormField
29-
v-if="field.optIn"
30-
v-slot="$field"
31-
:name="field.value"
32-
class="flex space-x-1"
26+
<div class="flex flex-col gap-6">
27+
<FormField
28+
v-slot="$field"
29+
name="contactInfo"
30+
:initial-value="authStore.currentUser?.email"
31+
>
32+
<div class="self-stretch inline-flex justify-start items-center">
33+
<label for="contactInfo" class="pb-2 pt-0 opacity-80">{{
34+
$t('issueReport.email')
35+
}}</label>
36+
</div>
37+
<InputText
38+
id="contactInfo"
39+
v-bind="$field"
40+
class="w-full"
41+
:placeholder="$t('issueReport.provideEmail')"
42+
/>
43+
<Message
44+
v-if="$field?.error && $field.touched && $field.value !== ''"
45+
severity="error"
46+
size="small"
47+
variant="simple"
3348
>
34-
<Checkbox
49+
{{ t('issueReport.validation.invalidEmail') }}
50+
</Message>
51+
</FormField>
52+
53+
<FormField v-slot="$field" name="helpType">
54+
<div class="flex flex-col gap-2">
55+
<div
56+
class="self-stretch inline-flex justify-start items-center gap-2.5"
57+
>
58+
<label for="helpType" class="pb-2 pt-0 opacity-80">{{
59+
$t('issueReport.whatDoYouNeedHelpWith')
60+
}}</label>
61+
</div>
62+
<Dropdown
3563
v-bind="$field"
36-
v-model="selection"
37-
:input-id="field.value"
38-
:value="field.value"
64+
v-model="$field.value"
65+
:options="helpTypes"
66+
option-label="label"
67+
option-value="value"
68+
:placeholder="$t('issueReport.selectIssue')"
69+
class="w-full"
3970
/>
40-
<label :for="field.value">{{ field.label }}</label>
41-
</FormField>
42-
</div>
43-
</div>
44-
<FormField v-slot="$field" class="mb-4" name="details">
45-
<Textarea
46-
v-bind="$field"
47-
class="w-full"
48-
rows="5"
49-
:placeholder="$t('issueReport.provideAdditionalDetails')"
50-
:aria-label="$t('issueReport.provideAdditionalDetails')"
51-
/>
52-
<Message
53-
v-if="$field?.error && $field.touched && $field.value"
54-
severity="error"
55-
size="small"
56-
variant="simple"
57-
>
58-
{{ t('issueReport.validation.maxLength') }}
59-
</Message>
60-
</FormField>
61-
<FormField v-slot="$field" name="contactInfo">
62-
<InputText
63-
v-bind="$field"
64-
class="w-full"
65-
:placeholder="$t('issueReport.provideEmail')"
66-
/>
67-
<Message
68-
v-if="$field?.error && $field.touched && $field.value !== ''"
69-
severity="error"
70-
size="small"
71-
variant="simple"
72-
>
73-
{{ t('issueReport.validation.invalidEmail') }}
74-
</Message>
75-
</FormField>
76-
77-
<div class="flex flex-row gap-3 mt-2">
78-
<div v-for="checkbox in contactCheckboxes" :key="checkbox.value">
79-
<FormField
80-
v-slot="$field"
81-
:name="checkbox.value"
82-
class="flex space-x-1"
71+
<Message
72+
v-if="$field?.error"
73+
severity="error"
74+
size="small"
75+
variant="simple"
76+
>
77+
{{ t('issueReport.validation.selectIssueType') }}
78+
</Message>
79+
</div>
80+
</FormField>
81+
82+
<div class="flex flex-col gap-2">
83+
<div
84+
class="self-stretch inline-flex justify-start items-center gap-2.5"
8385
>
84-
<Checkbox
86+
<span class="pb-2 pt-0 opacity-80">{{
87+
$t('issueReport.whatCanWeInclude')
88+
}}</span>
89+
</div>
90+
<div class="flex flex-row gap-3">
91+
<div v-for="field in fields" :key="field.value">
92+
<FormField
93+
v-if="field.optIn"
94+
v-slot="$field"
95+
:name="field.value"
96+
class="flex space-x-1"
97+
>
98+
<Checkbox
99+
v-bind="$field"
100+
v-model="selection"
101+
:input-id="field.value"
102+
:value="field.value"
103+
/>
104+
<label :for="field.value">{{ field.label }}</label>
105+
</FormField>
106+
</div>
107+
</div>
108+
</div>
109+
<div class="flex flex-col gap-2">
110+
<FormField v-slot="$field" name="details">
111+
<div
112+
class="self-stretch inline-flex justify-start items-center gap-2.5"
113+
>
114+
<label for="details" class="pb-2 pt-0 opacity-80">{{
115+
$t('issueReport.describeTheProblem')
116+
}}</label>
117+
</div>
118+
<Textarea
85119
v-bind="$field"
86-
v-model="contactPrefs"
87-
:input-id="checkbox.value"
88-
:value="checkbox.value"
89-
:disabled="
90-
$form.contactInfo?.error || !$form.contactInfo?.value
91-
"
120+
id="details"
121+
class="w-full"
122+
rows="5"
123+
:placeholder="$t('issueReport.provideAdditionalDetails')"
124+
:aria-label="$t('issueReport.provideAdditionalDetails')"
92125
/>
93-
<label :for="checkbox.value">{{ checkbox.label }}</label>
126+
<Message
127+
v-if="$field?.error && $field.touched && $field.value"
128+
severity="error"
129+
size="small"
130+
variant="simple"
131+
>
132+
{{ t('issueReport.validation.maxLength') }}
133+
</Message>
94134
</FormField>
95135
</div>
136+
137+
<div class="flex flex-col gap-3 mt-2">
138+
<div v-for="checkbox in contactCheckboxes" :key="checkbox.value">
139+
<FormField
140+
v-slot="$field"
141+
:name="checkbox.value"
142+
class="flex space-x-1"
143+
>
144+
<Checkbox
145+
v-bind="$field"
146+
v-model="contactPrefs"
147+
:input-id="checkbox.value"
148+
:value="checkbox.value"
149+
:disabled="
150+
$form.contactInfo?.error || !$form.contactInfo?.value
151+
"
152+
/>
153+
<label :for="checkbox.value">{{ checkbox.label }}</label>
154+
</FormField>
155+
</div>
156+
</div>
96157
</div>
97158
</div>
98159
</Panel>
@@ -108,6 +169,7 @@ import _ from 'lodash'
108169
import cloneDeep from 'lodash/cloneDeep'
109170
import Button from 'primevue/button'
110171
import Checkbox from 'primevue/checkbox'
172+
import Dropdown from 'primevue/dropdown'
111173
import InputText from 'primevue/inputtext'
112174
import Message from 'primevue/message'
113175
import Panel from 'primevue/panel'
@@ -122,21 +184,24 @@ import {
122184
} from '@/schemas/issueReportSchema'
123185
import { api } from '@/scripts/api'
124186
import { app } from '@/scripts/app'
187+
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
125188
import type {
126189
DefaultField,
127190
IssueReportPanelProps,
128191
ReportField
129192
} from '@/types/issueReportTypes'
130193
import { isElectron } from '@/utils/envUtil'
194+
import { generateUUID } from '@/utils/formatUtil'
131195
132-
const ISSUE_NAME = 'User reported issue'
196+
const DEFAULT_ISSUE_NAME = 'User reported issue'
133197
134198
const props = defineProps<IssueReportPanelProps>()
135199
const { defaultFields = ['Workflow', 'Logs', 'SystemStats', 'Settings'] } =
136200
props
137201
138202
const { t } = useI18n()
139203
const toast = useToast()
204+
const authStore = useFirebaseAuthStore()
140205
141206
const selection = ref<string[]>([])
142207
const contactPrefs = ref<string[]>([])
@@ -147,6 +212,20 @@ const contactCheckboxes = [
147212
{ label: t('issueReport.notifyResolve'), value: 'notifyOnResolution' }
148213
]
149214
215+
const helpTypes = [
216+
{
217+
label: t('issueReport.helpTypes.billingPayments'),
218+
value: 'billingPayments'
219+
},
220+
{
221+
label: t('issueReport.helpTypes.loginAccessIssues'),
222+
value: 'loginAccessIssues'
223+
},
224+
{ label: t('issueReport.helpTypes.giveFeedback'), value: 'giveFeedback' },
225+
{ label: t('issueReport.helpTypes.bugReport'), value: 'bugReport' },
226+
{ label: t('issueReport.helpTypes.somethingElse'), value: 'somethingElse' }
227+
]
228+
150229
const defaultFieldsConfig: ReportField[] = [
151230
{
152231
label: t('issueReport.systemStats'),
@@ -213,6 +292,7 @@ const createCaptureContext = async (
213292
level: 'error',
214293
tags: {
215294
errorType: props.errorType,
295+
helpType: formData.helpType,
216296
followUp: formData.contactInfo ? formData.followUp : false,
217297
notifyOnResolution: formData.contactInfo
218298
? formData.notifyOnResolution
@@ -227,11 +307,24 @@ const createCaptureContext = async (
227307
}
228308
}
229309
310+
const generateUniqueTicketId = (type: string) => `${type}-${generateUUID()}`
311+
230312
const submit = async (event: FormSubmitEvent) => {
231313
if (event.valid) {
232314
try {
233315
const captureContext = await createCaptureContext(event.values)
234-
captureMessage(ISSUE_NAME, captureContext)
316+
317+
// If it's billing or access issue, generate unique id to be used by customer service ticketing
318+
const isValidContactInfo = event.values.contactInfo?.length
319+
const isCustomerServiceIssue =
320+
isValidContactInfo &&
321+
['billingPayments', 'loginAccessIssues'].includes(
322+
event.values.helpType || ''
323+
)
324+
const issueName = isCustomerServiceIssue
325+
? `ticket-${generateUniqueTicketId(event.values.helpType || '')}`
326+
: DEFAULT_ISSUE_NAME
327+
captureMessage(issueName, captureContext)
235328
submitted.value = true
236329
toast.add({
237330
severity: 'success',

src/components/dialog/content/error/__tests__/ReportIssuePanel.spec.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Form } from '@primevue/forms'
22
import { mount } from '@vue/test-utils'
3+
import { createPinia, setActivePinia } from 'pinia'
34
import Checkbox from 'primevue/checkbox'
45
import PrimeVue from 'primevue/config'
56
import InputText from 'primevue/inputtext'
@@ -65,7 +66,12 @@ vi.mock('@/scripts/api', () => ({
6566
api: {
6667
getLogs: vi.fn().mockResolvedValue('mock logs'),
6768
getSystemStats: vi.fn().mockResolvedValue('mock stats'),
68-
getSettings: vi.fn().mockResolvedValue('mock settings')
69+
getSettings: vi.fn().mockResolvedValue('mock settings'),
70+
fetchApi: vi.fn().mockResolvedValue({
71+
json: vi.fn().mockResolvedValue({}),
72+
text: vi.fn().mockResolvedValue('')
73+
}),
74+
apiURL: vi.fn().mockReturnValue('https://test.com')
6975
}
7076
}))
7177

@@ -139,12 +145,14 @@ vi.mock('@primevue/forms', () => ({
139145
describe('ReportIssuePanel', () => {
140146
beforeEach(() => {
141147
vi.clearAllMocks()
148+
const pinia = createPinia()
149+
setActivePinia(pinia)
142150
})
143151

144152
const mountComponent = (props: IssueReportPanelProps, options = {}): any => {
145153
return mount(ReportIssuePanel, {
146154
global: {
147-
plugins: [PrimeVue, i18n],
155+
plugins: [PrimeVue, i18n, createPinia()],
148156
directives: { tooltip: Tooltip }
149157
},
150158
props,

src/components/dialog/content/setting/CreditsPanel.vue

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ const dialogService = useDialogService()
134134
const authStore = useFirebaseAuthStore()
135135
const loading = computed(() => authStore.loading)
136136
137-
// Format balance from micros to dollars
138137
const formattedBalance = computed(() => {
139138
if (!authStore.balance) return '0.00'
140139
return formatMetronomeCurrency(authStore.balance.amount_micros, 'usd')
@@ -162,11 +161,11 @@ const handleCreditsHistoryClick = async () => {
162161
163162
const handleMessageSupport = () => {
164163
dialogService.showIssueReportDialog({
165-
title: t('credits.messageSupport'),
166-
subtitle: t('issueReport.feedbackTitle'),
164+
title: t('issueReport.contactSupportTitle'),
165+
subtitle: t('issueReport.contactSupportDescription'),
167166
panelProps: {
168167
errorType: 'BillingSupport',
169-
defaultFields: ['SystemStats', 'Settings']
168+
defaultFields: ['Workflow', 'Logs', 'SystemStats', 'Settings']
170169
}
171170
})
172171
}

0 commit comments

Comments
 (0)