Skip to content

Commit c152ee5

Browse files
committed
bringing in the screenshot integration and mock editor
1 parent dbcbfba commit c152ee5

File tree

20 files changed

+277
-132
lines changed

20 files changed

+277
-132
lines changed

packages/feedback-screenshot/src/screenshot.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
22
import type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
3-
import { ScreenshotButton } from './screenshotButton';
43
import { GLOBAL_OBJ } from '@sentry/utils';
54
import { h, render } from 'preact';
5+
import { ScreenshotButton } from './screenshotButton';
66

77
interface FeedbackScreenshotOptions {
88
buttonRef: HTMLDivElement;

packages/feedback/src/util/sendFeedbackRequest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createEventEnvelope, getClient, withScope, createAttachmentEnvelope } from '@sentry/core';
1+
import { createAttachmentEnvelope, createEventEnvelope, getClient, withScope } from '@sentry/core';
22
import type { Attachment, FeedbackEvent, TransportMakeRequestResponse } from '@sentry/types';
33

44
import { FEEDBACK_API_SOURCE, FEEDBACK_WIDGET_SOURCE } from '../constants';

packages/feedback/src/widget/Form.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import * as ScreenshotIntegration from '@sentry-internal/feedback-screenshot';
12
import type { Attachment } from '@sentry/types';
23
import type { FeedbackComponent, FeedbackFormData, FeedbackInternalOptions, FeedbackTextConfiguration } from '../types';
34
import { SubmitButton } from './SubmitButton';
45
import { createElement } from './util/createElement';
5-
import * as ScreenshotIntegration from '@sentry-internal/feedback-screenshot';
66

77
export interface FormComponentProps
88
extends Pick<

packages/feedback/src/widget/createWidget.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getCurrentScope } from '@sentry/core';
2-
import { logger } from '@sentry/utils';
32
import type { Attachment } from '@sentry/types';
3+
import { logger } from '@sentry/utils';
44
import type { FeedbackFormData, FeedbackInternalOptions, FeedbackWidget } from '../types';
55
import { handleFeedbackSubmit } from '../util/handleFeedbackSubmit';
66
import type { ActorComponent } from './Actor';

packages/feedback2/src/core/integration.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { BrowserClient } from '@sentry/browser';
21
import { getClient } from '@sentry/core';
32
import type { Integration, IntegrationFn } from '@sentry/types';
43
import { isBrowser, logger } from '@sentry/utils';
@@ -329,7 +328,7 @@ export class Feedback2 implements Integration {
329328
return this._dialog;
330329
}
331330

332-
const client = getClient<BrowserClient>();
331+
const client = getClient(); // TODO: getClient<BrowserClient>()
333332
if (!client) {
334333
throw new Error('Sentry Client is not initialized correctly');
335334
}
@@ -367,18 +366,22 @@ export class Feedback2 implements Integration {
367366
const shadow = this._getShadow(options);
368367

369368
// TODO: some combination stuff when screenshots exists:
370-
const dialog = modalIntegration.createDialog(options, {
371-
onCreate: (dialog: DialogComponent) => {
372-
shadow.appendChild(dialog.style);
373-
shadow.appendChild(dialog.el);
369+
const dialog = modalIntegration.createDialog(
370+
options,
371+
{
372+
onCreate: (dialog: DialogComponent) => {
373+
shadow.appendChild(dialog.style);
374+
shadow.appendChild(dialog.el);
375+
},
376+
onSubmit: sendFeedback,
377+
onDone: (dialog: DialogComponent) => {
378+
shadow.removeChild(dialog.el);
379+
shadow.removeChild(dialog.style);
380+
this._dialog = null;
381+
},
374382
},
375-
onSubmit: sendFeedback,
376-
onDone: (dialog: DialogComponent) => {
377-
shadow.removeChild(dialog.el);
378-
shadow.removeChild(dialog.style);
379-
this._dialog = null;
380-
},
381-
});
383+
screenshotIntegration,
384+
);
382385
this._dialog = dialog;
383386
return dialog;
384387
}

packages/feedback2/src/core/sendFeedback.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createEventEnvelope, getClient, withScope } from '@sentry/core';
1+
import { createAttachmentEnvelope, createEventEnvelope, getClient, withScope } from '@sentry/core';
22
import type { FeedbackEvent, TransportMakeRequestResponse } from '@sentry/types';
33
import { getLocationHref } from '@sentry/utils';
44
import { FEEDBACK_API_SOURCE, FEEDBACK_WIDGET_SOURCE } from '../constants';
@@ -9,7 +9,7 @@ import { prepareFeedbackEvent } from '../util/prepareFeedbackEvent';
99
* Public API to send a Feedback item to Sentry
1010
*/
1111
export async function sendFeedback(
12-
{ name, email, message, source = FEEDBACK_API_SOURCE, url = getLocationHref() }: SendFeedbackParams,
12+
{ name, email, message, attachment, source = FEEDBACK_API_SOURCE, url = getLocationHref() }: SendFeedbackParams,
1313
{ includeReplay = true }: SendFeedbackOptions = {},
1414
): Promise<TransportMakeRequestResponse> {
1515
if (!message) {
@@ -55,12 +55,40 @@ export async function sendFeedback(
5555
client.emit('beforeSendFeedback', feedbackEvent, { includeReplay: Boolean(includeReplay) });
5656
}
5757

58-
const envelope = createEventEnvelope(feedbackEvent, dsn, client.getOptions()._metadata, client.getOptions().tunnel);
58+
try {
59+
const response = await transport.send(
60+
createEventEnvelope(feedbackEvent, dsn, client.getOptions()._metadata, client.getOptions().tunnel),
61+
);
62+
63+
if (attachment) {
64+
const formatted = {
65+
filename: attachment.name,
66+
data: new Uint8Array(await attachment.arrayBuffer()),
67+
contentType: attachment.type,
68+
};
69+
console.log({ formatted });
70+
await transport.send(
71+
createAttachmentEnvelope(
72+
feedbackEvent,
73+
[formatted],
74+
dsn,
75+
client.getOptions()._metadata,
76+
client.getOptions().tunnel,
77+
),
78+
);
79+
}
80+
81+
// TODO (v8): we can remove this guard once transport.send's type signature doesn't include void anymore
82+
if (!response) {
83+
throw new Error('Unable to send Feedback');
84+
}
5985

60-
let response: void | TransportMakeRequestResponse;
86+
// Require valid status codes, otherwise can assume feedback was not sent successfully
87+
if (typeof response.statusCode === 'number' && (response.statusCode < 200 || response.statusCode >= 300)) {
88+
throw new Error('Unable to send Feedback. Invalid response from server.');
89+
}
6190

62-
try {
63-
response = await transport.send(envelope);
91+
return response;
6492
} catch (err) {
6593
const error = new Error('Unable to send Feedback');
6694

@@ -73,18 +101,6 @@ export async function sendFeedback(
73101
}
74102
throw error;
75103
}
76-
77-
// TODO (v8): we can remove this guard once transport.send's type signature doesn't include void anymore
78-
if (!response) {
79-
throw new Error('Unable to send Feedback');
80-
}
81-
82-
// Require valid status codes, otherwise can assume feedback was not sent successfully
83-
if (typeof response.statusCode === 'number' && (response.statusCode < 200 || response.statusCode >= 300)) {
84-
throw new Error('Unable to send Feedback. Invalid response from server.');
85-
}
86-
87-
return response;
88104
});
89105
}
90106

packages/feedback2/src/modal/components/Dialog.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import { DOCUMENT, SUCCESS_MESSAGE_TIMEOUT } from '../../constants';
66
import type { FeedbackFormData } from '../../types';
77
import { createDialogStyles } from './Dialog.css';
88
import { DialogContent } from './DialogContent';
9-
import type { Props as DialogContentProps } from './DialogContent';
9+
import { DialogHeader } from './DialogHeader';
10+
import type { Props as HeaderProps } from './DialogHeader';
11+
import type { Props as FormProps } from './Form';
12+
import { Form } from './Form';
1013
import { SuccessIcon } from './SuccessIcon';
1114

12-
export interface Props extends DialogContentProps {
15+
export interface Props extends HeaderProps, FormProps {
1316
successMessageText: string;
1417
onFormSubmitted: () => void;
1518
}
@@ -98,7 +101,12 @@ function DialogContainer({ open, onFormSubmitted, ...props }: Props & { open: bo
98101
</div>
99102
) : (
100103
<dialog class="dialog" onClick={props.onFormClose} open={open}>
101-
<DialogContent {...props} onSubmitSuccess={onSubmitSuccess} />
104+
<DialogContent {...props}>
105+
<DialogHeader {...props} />
106+
<div style={{ display: 'flex', flexDirection: 'row', gap: '8px' }}>
107+
<Form {...props} onSubmitSuccess={onSubmitSuccess} />
108+
</div>
109+
</DialogContent>
102110
</dialog>
103111
)}
104112
</Fragment>
Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,12 @@
11
// biome-ignore lint/nursery/noUnusedImports: reason
22
import { h } from 'preact'; // eslint-disable-line @typescript-eslint/no-unused-vars
33
import type { ComponentChildren, VNode } from 'preact';
4-
import { useMemo } from 'preact/hooks';
5-
import { Form } from './Form';
6-
import type { Props as FormProps } from './Form';
7-
import type { Props as LogoProps } from './SentryLogo';
8-
import { SentryLogo } from './SentryLogo';
94

10-
export interface Props extends FormProps, LogoProps {
11-
children?: ComponentChildren;
12-
formTitle: string;
13-
showBranding: boolean;
5+
export interface Props {
6+
children: ComponentChildren;
147
}
158

16-
export function DialogContent({ children, colorScheme, formTitle, showBranding, ...props }: Props): VNode {
17-
const logoHtml = useMemo(() => {
18-
const logo = SentryLogo({ colorScheme });
19-
return { __html: logo.outerHTML };
20-
}, [colorScheme]);
21-
9+
export function DialogContent({ children }: Props): VNode {
2210
return (
2311
<div
2412
class="dialog__content"
@@ -27,23 +15,7 @@ export function DialogContent({ children, colorScheme, formTitle, showBranding,
2715
e.stopPropagation();
2816
}}
2917
>
30-
<h2 class="dialog__header">
31-
{formTitle}
32-
{showBranding ? (
33-
<a
34-
class="brand-link"
35-
target="_blank"
36-
href="https://sentry.io/welcome/"
37-
title="Powered by Sentry"
38-
rel="noopener noreferrer"
39-
dangerouslySetInnerHTML={logoHtml}
40-
/>
41-
) : null}
42-
</h2>
43-
<div style={{ display: 'flex', flexDirection: 'row', gap: '8px' }}>
44-
{children}
45-
<Form {...props} />
46-
</div>
18+
{children}
4719
</div>
4820
);
4921
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// biome-ignore lint/nursery/noUnusedImports: reason
2+
import { h } from 'preact'; // eslint-disable-line @typescript-eslint/no-unused-vars
3+
import type { VNode } from 'preact';
4+
import { useMemo } from 'preact/hooks';
5+
import type { FeedbackInternalOptions } from '../../types';
6+
import type { Props as LogoProps } from './SentryLogo';
7+
import { SentryLogo } from './SentryLogo';
8+
9+
export interface Props extends LogoProps {
10+
formTitle: FeedbackInternalOptions['formTitle'];
11+
showBranding: FeedbackInternalOptions['showBranding'];
12+
}
13+
14+
export function DialogHeader({ colorScheme, formTitle, showBranding }: Props): VNode {
15+
const logoHtml = useMemo(() => {
16+
const logo = SentryLogo({ colorScheme });
17+
return { __html: logo.outerHTML };
18+
}, [colorScheme]);
19+
20+
return (
21+
<h2 class="dialog__header">
22+
{formTitle}
23+
{showBranding ? (
24+
<a
25+
class="brand-link"
26+
target="_blank"
27+
href="https://sentry.io/welcome/"
28+
title="Powered by Sentry"
29+
rel="noopener noreferrer"
30+
dangerouslySetInnerHTML={logoHtml}
31+
/>
32+
) : null}
33+
</h2>
34+
);
35+
}

0 commit comments

Comments
 (0)