-
Notifications
You must be signed in to change notification settings - Fork 27.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: connect error rating buttons to telemetry API (#74496)
We're adding a new feature to the error overlay that allows developers to rate the current error message. Developers can rate the error message by clicking on the thumb up or down icon in the error overlay footer. This pull request adds the click handler for the error rating buttons. When the buttons are clicked, the click handler calls a previously implemented internal API endpoint. The endpoint is connected to the usual Next.js Telemetry. To test the UI, run `pnpm storybook` and go to http://localhost:6006/?path=/story/erroroverlaylayout--default. https://github.com/user-attachments/assets/6daf6e64-e8bb-4e5e-b282-082086f1e529
- Loading branch information
Showing
13 changed files
with
166 additions
and
117 deletions.
There are no files selected for viewing
17 changes: 0 additions & 17 deletions
17
...al/components/Errors/error-overlay-footer/error-feedback/error-feedback-toast.stories.tsx
This file was deleted.
Oops, something went wrong.
29 changes: 0 additions & 29 deletions
29
...l/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback-toast.tsx
This file was deleted.
Oops, something went wrong.
80 changes: 49 additions & 31 deletions
80
...imental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,62 @@ | ||
import { useState } from 'react' | ||
|
||
import { useState, useCallback } from 'react' | ||
import { ThumbsUp } from '../../../../icons/thumbs/thumbs-up' | ||
import { ThumbsDown } from '../../../../icons/thumbs/thumbs-down' | ||
import { ErrorFeedbackToast } from './error-feedback-toast' | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
export function ErrorFeedback({ errorCode }: { errorCode: string }) { | ||
const [voted, setVoted] = useState<'good' | 'bad' | null>(null) | ||
const [isToastVisible, setIsToastVisible] = useState(false) | ||
interface ErrorFeedbackProps { | ||
errorCode: string | ||
} | ||
|
||
export function ErrorFeedback({ errorCode }: ErrorFeedbackProps) { | ||
const [voted, setVoted] = useState<boolean | null>(null) | ||
const hasVoted = voted !== null | ||
|
||
// TODO: make API call to /__nextjs_error_feedback | ||
const handleFeedback = (value: 'good' | 'bad') => { | ||
setVoted(value) | ||
setIsToastVisible(true) | ||
} | ||
const handleFeedback = useCallback( | ||
async (wasHelpful: boolean) => { | ||
try { | ||
const response = await fetch( | ||
`${process.env.__NEXT_ROUTER_BASEPATH || ''}/__nextjs_error_feedback?errorCode=${errorCode}&wasHelpful=${wasHelpful}` | ||
) | ||
|
||
if (!response.ok) { | ||
// Handle non-2xx HTTP responses here if needed | ||
console.error('Failed to record feedback on the server.') | ||
} | ||
|
||
setVoted(wasHelpful) | ||
} catch (error) { | ||
console.error('Failed to record feedback:', error) | ||
} | ||
}, | ||
[errorCode] | ||
) | ||
|
||
return ( | ||
<> | ||
<div className="error-feedback"> | ||
<p>Was this helpful?</p> | ||
<button | ||
onClick={() => handleFeedback('good')} | ||
disabled={hasVoted} | ||
className={`feedback-button ${voted === 'good' ? 'voted' : ''}`} | ||
> | ||
<ThumbsUp /> | ||
</button> | ||
<button | ||
onClick={() => handleFeedback('bad')} | ||
disabled={hasVoted} | ||
className={`feedback-button ${voted === 'bad' ? 'voted' : ''}`} | ||
> | ||
<ThumbsDown /> | ||
</button> | ||
{hasVoted ? ( | ||
<p className="error-feedback-thanks">Thanks for your feedback!</p> | ||
) : ( | ||
<> | ||
<p>Was this helpful?</p> | ||
<button | ||
aria-label="Mark as helpful" | ||
onClick={() => handleFeedback(true)} | ||
disabled={hasVoted} | ||
className={`feedback-button ${voted === true ? 'voted' : ''}`} | ||
> | ||
<ThumbsUp /> | ||
</button> | ||
<button | ||
aria-label="Mark as not helpful" | ||
onClick={() => handleFeedback(false)} | ||
disabled={hasVoted} | ||
className={`feedback-button ${voted === false ? 'voted' : ''}`} | ||
> | ||
<ThumbsDown /> | ||
</button> | ||
</> | ||
)} | ||
</div> | ||
<ErrorFeedbackToast | ||
isVisible={isToastVisible} | ||
setIsVisible={setIsToastVisible} | ||
/> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
...xperimental/internal/components/Errors/error-overlay-layout/error-overlay-layout.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/** | ||
* @jest-environment jsdom | ||
*/ | ||
/* eslint-disable import/no-extraneous-dependencies */ | ||
import { render, screen, fireEvent, act } from '@testing-library/react' | ||
import { ErrorOverlayLayout } from './error-overlay-layout' | ||
import '@testing-library/jest-dom' | ||
|
||
// Mock maintain--tab-focus module | ||
jest.mock('../../../components/Overlay/maintain--tab-focus', () => ({ | ||
__esModule: true, | ||
default: jest.fn(() => ({ | ||
disengage: jest.fn(), | ||
})), | ||
})) | ||
|
||
const renderTestComponent = () => { | ||
return render( | ||
<ErrorOverlayLayout | ||
errorType="Build Error" | ||
errorMessage="Failed to compile" | ||
errorCode="E001" | ||
error={new Error('Sample error')} | ||
isBuildError={true} | ||
onClose={() => {}} | ||
> | ||
Module not found: Cannot find module './missing-module' | ||
</ErrorOverlayLayout> | ||
) | ||
} | ||
|
||
describe('ErrorOverlayLayout Component', () => { | ||
beforeEach(() => { | ||
// Mock fetch | ||
global.fetch = jest.fn(() => { | ||
return Promise.resolve({ | ||
ok: true, | ||
headers: new Headers(), | ||
redirected: false, | ||
status: 200, | ||
statusText: 'OK', | ||
type: 'basic', | ||
url: '', | ||
json: () => Promise.resolve({}), | ||
text: () => Promise.resolve(''), | ||
blob: () => Promise.resolve(new Blob()), | ||
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)), | ||
formData: () => Promise.resolve(new FormData()), | ||
clone: () => new Response(), | ||
} as Response) | ||
}) as jest.Mock | ||
}) | ||
|
||
test('renders ErrorOverlayLayout with provided props', () => { | ||
renderTestComponent() | ||
expect(screen.getByText('Failed to compile')).toBeInTheDocument() | ||
expect( | ||
screen.getByText( | ||
"Module not found: Cannot find module './missing-module'" | ||
) | ||
).toBeInTheDocument() | ||
}) | ||
|
||
test('sends feedback when clicking helpful button', async () => { | ||
renderTestComponent() | ||
|
||
expect( | ||
screen.queryByText('Thanks for your feedback!') | ||
).not.toBeInTheDocument() | ||
|
||
// Click helpful button | ||
await act(async () => { | ||
fireEvent.click(screen.getByLabelText('Mark as helpful')) | ||
}) | ||
|
||
expect(fetch).toHaveBeenCalledWith( | ||
'/__nextjs_error_feedback?errorCode=E001&wasHelpful=true' | ||
) | ||
|
||
expect(screen.getByText('Thanks for your feedback!')).toBeInTheDocument() | ||
}) | ||
|
||
test('sends feedback when clicking not helpful button', async () => { | ||
renderTestComponent() | ||
|
||
expect( | ||
screen.queryByText('Thanks for your feedback!') | ||
).not.toBeInTheDocument() | ||
|
||
await act(async () => { | ||
fireEvent.click(screen.getByLabelText('Mark as not helpful')) | ||
}) | ||
|
||
expect(fetch).toHaveBeenCalledWith( | ||
'/__nextjs_error_feedback?errorCode=E001&wasHelpful=false' | ||
) | ||
|
||
expect(screen.getByText('Thanks for your feedback!')).toBeInTheDocument() | ||
}) | ||
}) |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...ponents/react-dev-overlay/_experimental/internal/container/RootLayoutMissingTagsError.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...src/client/components/react-dev-overlay/_experimental/internal/styles/ComponentStyles.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters