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 User Feedback buttons #2275

Merged
merged 29 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cb82319
Add Feedback dialog
christian-byrne Jan 16, 2025
56ce5cb
Add title prop
christian-byrne Jan 16, 2025
45c7b91
Add browser test
christian-byrne Jan 16, 2025
7f6ed90
Add FeedbackDialogContent import to dialogService
christian-byrne Jan 17, 2025
46f21c3
Add component test
christian-byrne Jan 17, 2025
a53b27c
Add feedback button to help topmenu dropdown
christian-byrne Jan 17, 2025
129f673
Add translation
christian-byrne Jan 17, 2025
39adbda
Update component test
christian-byrne Jan 17, 2025
5346e21
Convert ribbon-button to about panel badge
christian-byrne Jan 17, 2025
3c44f69
Update locales [skip ci]
invalid-email-address Jan 17, 2025
daf59d9
Add browser tests
christian-byrne Jan 17, 2025
f3618e3
Refactor tests
christian-byrne Jan 17, 2025
3401611
Format
christian-byrne Jan 17, 2025
82cd0c7
Change to more common icon and label
christian-byrne Jan 18, 2025
b90fa8a
Remove desktop give feedback menu command
christian-byrne Jan 18, 2025
6dd4eab
Update locales [skip ci]
invalid-email-address Jan 18, 2025
adc7585
Add optional props arg to dialog service function
christian-byrne Jan 19, 2025
30872f9
Revert about panel changes
christian-byrne Jan 19, 2025
905f5e1
Use path alias
christian-byrne Jan 19, 2025
9ad37d5
increment versionAdded in command def
christian-byrne Jan 19, 2025
8dc0975
Merge branch 'main' into feedback-button
christian-byrne Jan 19, 2025
3e31eab
Update locales [skip ci]
invalid-email-address Jan 19, 2025
c53e2b7
Add g.feedback
huchenlei Jan 19, 2025
9f0f096
Add min-w for dialog
huchenlei Jan 19, 2025
31de642
Update locales [skip ci]
invalid-email-address Jan 19, 2025
a2b4d09
Validate email format
huchenlei Jan 19, 2025
175b024
Remove rating input
huchenlei Jan 20, 2025
9506cb5
Remove rating related tests
huchenlei Jan 20, 2025
0142edb
Remove rating playwright test
huchenlei Jan 20, 2025
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
49 changes: 49 additions & 0 deletions browser_tests/dialog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,52 @@ test.describe('Settings', () => {
expect(request.postData()).toContain(JSON.stringify(expectedSetting))
})
})

test.describe('Feedback dialog', () => {
test('Should open from topmenu help command', async ({ comfyPage }) => {
// Open feedback dialog from top menu
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Feedback'])

// Verify feedback dialog content is visible
const feedbackHeader = comfyPage.page.getByRole('heading', {
name: 'Feedback'
})
await expect(feedbackHeader).toBeVisible()
})

test('Should close when close button clicked', async ({ comfyPage }) => {
// Open feedback dialog
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Feedback'])

const feedbackHeader = comfyPage.page.getByRole('heading', {
name: 'Feedback'
})

// Close feedback dialog
await comfyPage.page
.getByLabel('', { exact: true })
.getByLabel('Close')
.click()
await feedbackHeader.waitFor({ state: 'hidden' })

// Verify dialog is closed
await expect(feedbackHeader).not.toBeVisible()
})

test('Should update rating icons when selecting rating', async ({
comfyPage
}) => {
// Open feedback dialog
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Feedback'])

// Test rating interaction
const stars = comfyPage.page.locator('.pi-star')
await stars.nth(3).click()
await expect(
comfyPage.page.getByLabel('Rating').locator('i').nth(3)
).toHaveClass(/pi-star-fill/)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
</template>
<ReportIssuePanel
v-if="sendReportOpen"
:title="$t('issueReport.submitErrorReport')"
error-type="graphExecutionError"
:extra-fields="[stackTraceField]"
:tags="{ exceptionMessage: props.error.exception_message }"
Expand Down
53 changes: 53 additions & 0 deletions src/components/dialog/content/FeedbackDialogContent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<template>
<div class="p-8 h-full">
<Card>
<template #content>
<div class="flex flex-col items-center">
<h3 class="text-4xl">{{ $t('menuLabels.Feedback') }}</h3>
<Rating
v-model="rating"
class="flex justify-center"
:aria-label="$t('issueReport.rating')"
>
<template #onicon>
<i class="pi pi-star-fill text-4xl"></i>
</template>
<template #officon>
<i class="pi pi-star text-4xl hover:text-highlight" />
</template>
</Rating>
</div>
</template>
</Card>
</div>
<div>
<ReportIssuePanel
error-type="Feedback"
:title="$t('issueReport.feedbackTitle')"
:extra-fields="[ratingField]"
:default-fields="['SystemStats', 'Settings']"
/>
</div>
</template>

<script setup lang="ts">
import Card from 'primevue/card'
import Rating from 'primevue/rating'
import { computed, ref } from 'vue'

import ReportIssuePanel from '@/components/dialog/content/error/ReportIssuePanel.vue'
import type { ReportField } from '@/types/issueReportTypes'

const rating = ref(null)

const ratingField = computed<ReportField>(() => {
return {
label: 'rating',
value: 'Rating',
optIn: false,
data: {
rating: rating.value
}
}
})
</script>
154 changes: 154 additions & 0 deletions src/components/dialog/content/__tests__/FeedbackDialogContent.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// @ts-strict-ignore
import { mount } from '@vue/test-utils'
import PrimeVue from 'primevue/config'
import Tooltip from 'primevue/tooltip'
import { beforeAll, describe, expect, it, vi } from 'vitest'
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'

import enMessages from '@/locales/en/main.json'
import type { ReportField } from '@/types/issueReportTypes'

import FeedbackDialogContent from '../FeedbackDialogContent.vue'

const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: enMessages
}
})

vi.mock('@/components/error/ReportIssuePanel.vue', () => ({
default: {
name: 'ReportIssuePanel',
props: ['errorType', 'title', 'extraFields', 'defaultFields'],
template: '<div><slot /></div>'
}
}))

vi.mock('primevue/usetoast', () => ({
useToast: vi.fn(() => ({
add: vi.fn()
}))
}))

describe('FeedbackDialogContent', () => {
beforeAll(() => {
const app = createApp({})
app.use(PrimeVue)
app.use(i18n)
})

const mountComponent = (): any =>
mount(FeedbackDialogContent, {
global: {
plugins: [PrimeVue, i18n],
directives: { tooltip: Tooltip }
}
})

describe('Rating functionality', () => {
it('initializes rating to null', () => {
const wrapper = mountComponent()
expect(wrapper.vm.rating).toBeNull()
})

it('updates rating when a star is clicked', async () => {
const wrapper = mountComponent()
const ratingComponent = wrapper.findComponent({ name: 'Rating' })

// Click the 4th start (out of 5)
await ratingComponent.findAll('i').at(3)?.trigger('click')

// Verify the rating has been updated
expect(wrapper.vm.rating).toBe(4)
})
})

describe('ReportIssuePanel integration', () => {
it('passes correct props to ReportIssuePanel', () => {
const wrapper = mountComponent()
const reportIssuePanel = wrapper.findComponent({
name: 'ReportIssuePanel'
})

expect(reportIssuePanel.props()).toMatchObject({
errorType: 'Feedback',
title: enMessages['issueReport']['feedbackTitle'],
defaultFields: ['SystemStats', 'Settings']
})
})

it('includes rating in extraFields when updated', async () => {
const wrapper = mountComponent()
const reportIssuePanel = wrapper.findComponent({
name: 'ReportIssuePanel'
})

// Click the 5th star (out of 5)
const ratingComponent = wrapper.findComponent({ name: 'Rating' })
await ratingComponent.findAll('i').at(4)?.trigger('click')

const expectedExtraFields: ReportField[] = [
{
label: 'rating',
value: 'Rating',
optIn: false,
data: { rating: 5 }
}
]
expect(reportIssuePanel.props('extraFields')).toEqual(expectedExtraFields)
})

it('includes rating in extraFields as null when no rating is selected', () => {
const wrapper = mountComponent()
const reportIssuePanel = wrapper.findComponent({
name: 'ReportIssuePanel'
})

const expectedExtraFields: ReportField[] = [
{
label: 'rating',
value: 'Rating',
optIn: false,
data: { rating: null }
}
]
expect(reportIssuePanel.props('extraFields')).toEqual(expectedExtraFields)
})

it('resets the rating to null on re-render', async () => {
const wrapper = mountComponent()

// Simulate rating selection
wrapper.vm.rating = 4
expect(wrapper.vm.rating).toBe(4)

// Re-mount the component
wrapper.unmount()
const newWrapper = mountComponent()

// Verify rating is reset
expect(newWrapper.vm.rating).toBeNull()
})

it('passes all expected extraFields to ReportIssuePanel', () => {
const wrapper = mountComponent()
const reportIssuePanel = wrapper.findComponent({
name: 'ReportIssuePanel'
})

const extraFields = reportIssuePanel.props('extraFields') as ReportField[]

expect(extraFields).toContainEqual(
expect.objectContaining({
label: 'rating',
value: 'Rating',
optIn: false,
data: { rating: null }
})
)
})
})
})
8 changes: 6 additions & 2 deletions src/components/dialog/content/error/ReportIssuePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Panel>
<template #header>
<div class="flex items-center gap-2">
<span class="font-bold">{{ $t('issueReport.submitErrorReport') }}</span>
<span class="font-bold">{{ title }}</span>
</div>
</template>
<template #footer>
Expand All @@ -19,6 +19,7 @@
</template>
<div class="p-4 mt-4 border border-round surface-border shadow-1">
<CheckboxGroup
v-if="reportCheckboxes.length"
v-model="selection"
class="gap-4 mb-4"
:checkboxes="reportCheckboxes"
Expand Down Expand Up @@ -76,6 +77,7 @@ const props = defineProps<{
defaultFields?: DefaultField[]
extraFields?: ReportField[]
tags?: Record<string, string>
title?: string
}>()
const {
defaultFields = ['Workflow', 'Logs', 'SystemStats', 'Settings'],
Expand Down Expand Up @@ -116,7 +118,9 @@ const defaultReportCheckboxes = [
{ label: t('g.settings'), value: 'Settings' }
]
const reportCheckboxes = computed(() => [
...(props.extraFields?.map(({ label, value }) => ({ label, value })) ?? []),
...(props.extraFields
?.filter(({ optIn }) => optIn)
.map(({ label, value }) => ({ label, value })) ?? []),
...defaultReportCheckboxes.filter(({ value }) =>
defaultFields.includes(value as DefaultField)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type ReportIssuePanelProps = {
errorType: string
defaultFields?: DefaultField[]
extraFields?: ReportField[]
tags?: Record<string, string>
title?: string
}

const i18n = createI18n({
Expand Down
2 changes: 1 addition & 1 deletion src/constants/coreMenuCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ export const CORE_MENU_COMMANDS = [
'Comfy.Help.OpenComfyOrgDiscord'
]
],
[['Help'], ['Comfy.Help.AboutComfyUI']]
[['Help'], ['Comfy.Help.AboutComfyUI', 'Comfy.Feedback']]
]
13 changes: 1 addition & 12 deletions src/extensions/core/electronAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,6 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
electronAPI.openDevTools()
}
},
{
id: 'Comfy-Desktop.OpenFeedbackPage',
label: 'Feedback',
icon: 'pi pi-envelope',
function() {
window.open('https://forum.comfy.org/c/v1-feedback/', '_blank')
}
},
{
id: 'Comfy-Desktop.OpenUserGuide',
label: 'Desktop User Guide',
Expand Down Expand Up @@ -176,10 +168,7 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
menuCommands: [
{
path: ['Help'],
commands: [
'Comfy-Desktop.OpenUserGuide',
'Comfy-Desktop.OpenFeedbackPage'
]
commands: ['Comfy-Desktop.OpenUserGuide']
},
{
path: ['Help'],
Expand Down
9 changes: 9 additions & 0 deletions src/hooks/coreCommandHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,15 @@ export function useCoreCommands(): ComfyCommand[] {
if (workflowStore.activeWorkflow)
workflowService.closeWorkflow(workflowStore.activeWorkflow)
}
},
{
id: 'Comfy.Feedback',
icon: 'pi pi-megaphone',
label: 'Give Feedback',
versionAdded: '1.7.15',
function: () => {
dialogService.showFeedbackDialog()
}
}
]
}
6 changes: 3 additions & 3 deletions src/locales/en/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
"Comfy-Desktop_OpenDevTools": {
"label": "Open DevTools"
},
"Comfy-Desktop_OpenFeedbackPage": {
"label": "Feedback"
},
"Comfy-Desktop_OpenUserGuide": {
"label": "Desktop User Guide"
},
Expand Down Expand Up @@ -86,6 +83,9 @@
"Comfy_ExportWorkflowAPI": {
"label": "Export Workflow (API Format)"
},
"Comfy_Feedback": {
"label": "Give Feedback"
},
"Comfy_Graph_FitGroupToContents": {
"label": "Fit Group To Contents"
},
Expand Down
Loading