Skip to content

Commit ea84bb2

Browse files
authored
Add "report a problem" UX to Devtools surfaces (#10596)
- Add several new "report a problem" links into areas of Devtools that users might encounter problems with - Add option to "_allow Replay support to view this recording_" (to auto-share the recording) - Change discard-pending-message UI to flow better - Rework the Support Form component to be more flexible (e.g. supporting extra context args) and more accessible to `replay-next` components (aka not routing it through Redux) - Removed a lot of unnecessary passing around of props to components that could already retrieve those props from context directly - Removed some unnecessary Redux `connect` functions that made code harder to read through <table> <tr> <td colspan="2"><strong>Source viewer</strong></td> </tr> <tr> <td>Source with source-maps</td> <td><img title="screenshot-source-has-source-map" src="https://github.com/replayio/devtools/assets/29597/442307b2-9a67-4641-ad20-0eb03f18030f" /></td> </tr> <tr> <td>Source with no source-maps</td> <td><img title="screenshot-source-no-source-map" src="https://github.com/replayio/devtools/assets/29597/09dffb3a-5d30-4e32-b51f-67cb667fcc9e" /></td><tr> <td colspan="2"><strong>Quick open modal</strong></td> </tr> <tr> <td>Lots of results</td> <td><img title="screenshot-typeahead-many-items" src="https://github.com/replayio/devtools/assets/29597/281117e1-7c6a-4bfa-9a8c-2f88bdff24d1" /></td> </tr> <tr> <td>Fewer than 5 results</td> <td><img title="screenshot-typeahead-3-items" src="https://github.com/replayio/devtools/assets/29597/2e183741-3a50-4352-bd03-bb13a9c0a75a" /></td> </tr> <tr> <td>No results</td> <td><img title="screenshot-typeahead-empty" src="https://github.com/replayio/devtools/assets/29597/af2cff72-b71c-4ecd-863c-6de203072030" /></td> </tr> <tr> <td colspan="2"><strong>Terminal expressions</strong></td> </tr> <tr> <td>Evaluates successfully</td> <td><img title="screenshot-terminal-result" src="https://github.com/replayio/devtools/assets/29597/a285b386-94a6-46b2-8d2e-4229afab58c6" /></td> </tr> <tr> <td>Throws an exception</td> <td><img title="screenshot-terminal-exception" src="https://github.com/replayio/devtools/assets/29597/ed317fcd-8abf-4557-ac1e-4ea29615c5dd" /></td> </tr> <tr> <td colspan="2"><strong>Video player</strong></td> </tr> <tr> <td>Context menu</td> <td><img src="https://github.com/replayio/devtools/assets/29597/4e5c581c-57bc-4da6-a974-79610293f778" /></td> </tr> <tr> <td colspan="2"><strong>Loading screen</strong></td> </tr> <tr> <td>When recording is still processing</td> <td><img src="https://github.com/replayio/devtools/assets/29597/25006c89-2192-4115-94c9-a6252887e010" /></td> </tr> </table> This PR also changes the discard confirmation screen to be more connected to the content you were editing. | Editing | Discard confirmation (before) | Discard confirmation (after) | | :--- | :--- | :--- | | ![Screenshot 2024-07-04 at 5 50 50 PM](https://github.com/replayio/devtools/assets/29597/a416fd35-2590-4ae4-a855-9464c3dcddd4) |![Screenshot 2024-07-04 at 5 52 37 PM](https://github.com/replayio/devtools/assets/29597/73891b08-9e1c-4bf8-911d-755765461cc7) | ![Screenshot 2024-07-04 at 5 51 06 PM](https://github.com/replayio/devtools/assets/29597/d0b3c0a8-dfab-49b3-8310-79949e3e494b) |
1 parent b9563de commit ea84bb2

37 files changed

+685
-426
lines changed

packages/replay-next/components/Icon.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export type IconType =
6666
| "react"
6767
| "remove"
6868
| "remove-alternate"
69+
| "report-problem"
6970
| "rewind"
7071
| "save"
7172
| "screenshot"
@@ -467,6 +468,10 @@ export default function Icon({
467468
path =
468469
"M14.59 8L12 10.59 9.41 8 8 9.41 10.59 12 8 14.59 9.41 16 12 13.41 14.59 16 16 14.59 13.41 12 16 9.41 14.59 8zM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z";
469470
break;
471+
case "report-problem":
472+
path =
473+
"M2.73 21h18.53c.77 0 1.25-.83.87-1.5l-9.27-16c-.39-.67-1.35-.67-1.73 0l-9.27 16c-.38.67.1 1.5.87 1.5zM13 18h-2v-2h2v2zm-1-4c-.55 0-1-.45-1-1v-2c0-.55.45-1 1-1s1 .45 1 1v2c0 .55-.45 1-1 1z";
474+
break;
470475
case "rewind":
471476
path =
472477
"M4.49875 3C5.32306 3 5.9975 3.675 5.9975 4.5V19.5C5.9975 20.325 5.32306 21 4.49875 21C3.67444 21 3 20.325 3 19.5V4.5C3 3.675 3.67444 3 4.49875 3ZM9.98418 13.23L18.6319 19.335C19.6212 20.04 21 19.32 21 18.105V5.895C21 4.68 19.6362 3.975 18.6319 4.665L9.98418 10.77C9.12988 11.37 9.12988 12.63 9.98418 13.23Z";
@@ -702,7 +707,8 @@ export default function Icon({
702707
);
703708
break;
704709
case "warning":
705-
path = "M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z";
710+
path =
711+
"M2.73 21h18.53c.77 0 1.25-.83.87-1.5l-9.27-16c-.39-.67-1.35-.67-1.73 0l-9.27 16c-.38.67.1 1.5.87 1.5zM13 18h-2v-2h2v2zm-1-4c-.55 0-1-.45-1-1v-2c0-.55.45-1 1-1s1 .45 1 1v2c0 .55-.45 1-1 1z";
706712
break;
707713
case "whole-word":
708714
path =

packages/replay-next/components/console/renderers/TerminalExpressionRenderer.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "react";
1111

1212
import useConsoleContextMenu from "replay-next/components/console/useConsoleContextMenu";
13+
import { ReportProblemLink } from "replay-next/components/errors/ReportProblemLink";
1314
import Icon from "replay-next/components/Icon";
1415
import Inspector from "replay-next/components/inspector";
1516
import ClientValueValueRenderer from "replay-next/components/inspector/values/ClientValueValueRenderer";
@@ -146,7 +147,23 @@ function EvaluatedContent({ terminalExpression }: { terminalExpression: Terminal
146147

147148
let children: ReactNode | null = null;
148149
if (exception) {
149-
children = <Inspector context="console" pauseId={pauseId} protocolValue={exception} />;
150+
children = (
151+
<>
152+
<Inspector context="console" pauseId={pauseId} protocolValue={exception} />
153+
<div className={styles.ReportProblem}>
154+
<ReportProblemLink
155+
context={{
156+
executionPoint: terminalExpression.point,
157+
expression: terminalExpression.expression,
158+
id: "terminal-expression",
159+
frameId,
160+
pauseId,
161+
}}
162+
/>{" "}
163+
if this is unexpected
164+
</div>
165+
</>
166+
);
150167
} else if (returned) {
151168
children = returned.value ? (
152169
<ClientValueValueRenderer

packages/replay-next/components/console/renderers/shared.module.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,12 @@
215215
box-shadow: 0 0 0 0 var(--pulse-color-off);
216216
}
217217
}
218+
219+
.ReportProblem {
220+
font-family: var(--font-family-default);
221+
font-size: var(--font-size-regular);
222+
margin-top: 0.25rem;
223+
display: flex;
224+
align-items: center;
225+
gap: 0.25rem;
226+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.Flex {
2+
display: inline-flex;
3+
flex-direction: row;
4+
align-items: center;
5+
gap: 0.25rem;
6+
}
7+
8+
.Icon {
9+
width: 1.75ch;
10+
height: 1.75ch;
11+
color: var(--color-link);
12+
}
13+
14+
.Link {
15+
text-decoration: underline;
16+
color: var(--color-link);
17+
cursor: pointer;
18+
}
19+
.Link:hover {
20+
text-decoration: none;
21+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { ReactNode, useContext } from "react";
2+
3+
import { SupportContext, SupportFormContext } from "replay-next/components/errors/SupportContext";
4+
import Icon from "replay-next/components/Icon";
5+
6+
import styles from "./ReportProblemLink.module.css";
7+
8+
export function ReportProblemLink({
9+
className = "",
10+
context,
11+
onClick: onClickProp = noop,
12+
promptText = "Please share any additional information that may be helpful",
13+
title = "Report a problem",
14+
}: {
15+
className?: string;
16+
context: SupportFormContext;
17+
onClick?: () => void;
18+
promptText?: string;
19+
title?: ReactNode;
20+
}) {
21+
const { showSupportForm } = useContext(SupportContext);
22+
23+
const onClick = () => {
24+
showSupportForm({
25+
context,
26+
promptText,
27+
title,
28+
});
29+
30+
onClickProp();
31+
};
32+
33+
return (
34+
<div className={`${styles.Flex} ${className}`} onClick={onClick}>
35+
<Icon className={styles.Icon} type="report-problem" />
36+
<div className={styles.Link}>Report a problem</div>
37+
</div>
38+
);
39+
}
40+
41+
function noop() {}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { PropsWithChildren, ReactNode, createContext, useCallback, useMemo, useState } from "react";
2+
3+
import { SupportForm } from "replay-next/components/errors/SupportForm";
4+
5+
export type SupportFormContext = {
6+
id: string;
7+
[key: string]: any;
8+
};
9+
10+
export type State = {
11+
context: SupportFormContext;
12+
promptText: string;
13+
title: ReactNode;
14+
};
15+
16+
export const SupportContext = createContext<{
17+
hideSupportForm: () => void;
18+
showSupportForm: (state: State) => void;
19+
state: State | null;
20+
}>(null as any);
21+
22+
export function SupportContextRoot({ children }: PropsWithChildren) {
23+
const [state, setState] = useState<State | null>(null);
24+
25+
const hideSupportForm = useCallback(() => setState(null), []);
26+
const showSupportForm = useCallback((state: State) => setState(state), []);
27+
28+
const value = useMemo(
29+
() => ({
30+
hideSupportForm,
31+
showSupportForm,
32+
state,
33+
}),
34+
[hideSupportForm, showSupportForm, state]
35+
);
36+
37+
return (
38+
<SupportContext.Provider value={value}>
39+
{children}
40+
{state && <SupportForm dismiss={hideSupportForm} state={state} />}
41+
</SupportContext.Provider>
42+
);
43+
}

packages/replay-next/components/errors/SupportForm.module.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
color: var(--support-form-color);
1414
caret-color: var(--support-form-color);
1515
}
16+
.TextArea:disabled {
17+
opacity: 0.5;
18+
}
1619
.TextArea:focus {
1720
box-shadow: none;
1821
outline-offset: 0;
@@ -28,6 +31,7 @@
2831
flex-direction: row;
2932
align-items: center;
3033
gap: 1ch;
34+
height: 2rem;
3135
}
3236

3337
.CancelCloseButton,
@@ -61,7 +65,34 @@
6165
.SubmitButton:hover {
6266
background-color: var(--primary-accent-hover);
6367
}
68+
.CancelCloseButton:disabled,
69+
.SubmitButton:disabled {
70+
opacity: 0.5;
71+
}
6472

6573
.ConfirmationMessage {
6674
text-align: center;
6775
}
76+
77+
.Form {
78+
width: 100%;
79+
display: flex;
80+
flex-direction: column;
81+
align-items: center;
82+
gap: 0.25rem;
83+
}
84+
85+
.ShareOption {
86+
width: 100%;
87+
display: flex;
88+
flex-direction: row;
89+
align-items: center;
90+
gap: 0.25ch;
91+
}
92+
.ShareOption[data-disabled] {
93+
opacity: 0.5;
94+
}
95+
96+
.Checkbox:disabled {
97+
opacity: 0.5;
98+
}

0 commit comments

Comments
 (0)