Skip to content

Commit 26a8e61

Browse files
authored
Update survey to use React/TS (github#19728)
* Update survey to use React/TS * Working survey form in react land * A little cleanup * Update Search.tsx * Update Survey.tsx * Remove token field from schema entirely, just let it fail validation * Update events.js * Update survey.html * use Link component * Use enum for state * Update old to match new
1 parent deccfca commit 26a8e61

8 files changed

Lines changed: 157 additions & 93 deletions

File tree

components/Search.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ export function Search({ isStandalone = false, updateSearchParams = true, childr
5959
function searchWithYourKeyboard(event: KeyboardEvent) {
6060
switch (event.key) {
6161
case '/':
62-
// when the input is focused, `/` should have no special behavior
63-
if ((event.target as HTMLInputElement)?.type === 'search') break
62+
// when an input is focused, `/` should have no special behavior
63+
if (['INPUT', 'TEXTAREA', 'SEARCH'].includes(document?.activeElement?.tagName || '')) break
6464
event.preventDefault() // prevent slash from being typed into input
6565
inputRef.current?.focus()
6666
break

components/Survey.tsx

Lines changed: 133 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,146 @@
1+
import { useState, useRef } from 'react'
12
import { ThumbsdownIcon, ThumbsupIcon } from '@primer/octicons-react'
23
import { useTranslation } from 'components/hooks/useTranslation'
4+
import { Link } from 'components/Link'
5+
import { sendEvent } from '../javascripts/events'
6+
7+
enum ViewState {
8+
START = 'START',
9+
YES = 'YES',
10+
NO = 'NO',
11+
END = 'END',
12+
}
313

414
export const Survey = () => {
515
const { t } = useTranslation('survey')
16+
const [state, setState] = useState<ViewState>(ViewState.START)
17+
const formRef = useRef<HTMLFormElement>(null)
18+
19+
function vote(state: ViewState) {
20+
return (evt: React.ChangeEvent<HTMLInputElement>) => {
21+
trackEvent(getFormData())
22+
setState(state)
23+
}
24+
}
25+
26+
function submit(evt: React.FormEvent) {
27+
evt.preventDefault()
28+
trackEvent(getFormData())
29+
setState(ViewState.END)
30+
}
31+
32+
function getFormData() {
33+
if (!formRef.current) return
34+
return new FormData(formRef.current)
35+
}
636

737
return (
8-
<form className="js-survey f5">
9-
<h2 data-help-start data-help-yes data-help-no className="mb-1 f4">
38+
<form className="f5" onSubmit={submit} ref={formRef}>
39+
<h2 className="mb-1 f4">
1040
{t`able_to_find`}
41+
42+
<Link
43+
className="f6 text-normal ml-3 color-text-link"
44+
href="/github/site-policy/github-privacy-statement"
45+
target="_blank"
46+
>
47+
{t`privacy_policy`}
48+
</Link>
1149
</h2>
12-
<p className="f6">
13-
<a href="/github/site-policy/github-privacy-statement">Privacy policy</a>
14-
</p>
15-
<p className="radio-group" data-help-start data-help-yes data-help-no>
16-
<input
17-
hidden
18-
id="survey-yes"
19-
type="radio"
20-
name="survey-vote"
21-
value="Yes"
22-
aria-label={t('yes')}
23-
/>
24-
<label className="btn x-radio-label mr-1" htmlFor="survey-yes">
25-
<ThumbsupIcon size={24} className="color-text-tertiary" />
26-
</label>
27-
<input
28-
hidden
29-
id="survey-no"
30-
type="radio"
31-
name="survey-vote"
32-
value="No"
33-
aria-label={t`no`}
34-
/>
35-
<label className="btn x-radio-label" htmlFor="survey-no">
36-
<ThumbsdownIcon size={24} className="color-text-tertiary" />
37-
</label>
38-
</p>
39-
<p className="color-text-secondary f6" hidden data-help-yes>
40-
{t('yes_feedback')}
41-
</p>
42-
<p className="color-text-secondary f6" hidden data-help-no>
43-
{t('no_feedback')}
44-
</p>
50+
51+
{/* Honeypot: token isn't a real field */}
4552
<input type="text" className="d-none" name="survey-token" aria-hidden="true" />
46-
<p hidden data-help-no>
47-
<label className="d-block mb-1 f6" htmlFor="survey-comment">
48-
<span>{t('comment_label')}</span>
49-
<span className="text-normal color-text-tertiary float-right ml-1">{t('optional')}</span>
50-
</label>
51-
<textarea
52-
className="form-control input-sm width-full"
53-
name="survey-comment"
54-
id="survey-comment"
55-
></textarea>
56-
</p>
57-
<p>
58-
<label className="d-block mb-1 f6" htmlFor="survey-email" hidden data-help-no>
59-
{t('email_label')}
60-
<span className="text-normal color-text-tertiary float-right ml-1">{t('optional')}</span>
61-
</label>
62-
<input
63-
type="email"
64-
className="form-control input-sm width-full"
65-
name="survey-email"
66-
id="survey-email"
67-
placeholder={t('email_placeholder')}
68-
hidden
69-
data-help-yes
70-
data-help-no
71-
/>
72-
</p>
73-
<p className="text-right" hidden data-help-yes data-help-no>
74-
<button type="submit" className="btn btn-sm">
75-
{t('send')}
76-
</button>
77-
</p>
78-
<p className="color-text-secondary f6" hidden data-help-end>
79-
{t('feedback')}
80-
</p>
53+
54+
{state !== ViewState.END && (
55+
<p className="radio-group">
56+
<input
57+
id="survey-yes"
58+
type="radio"
59+
name="survey-vote"
60+
value="Y"
61+
aria-label={t`yes`}
62+
hidden
63+
onChange={vote(ViewState.YES)}
64+
defaultChecked={state === ViewState.YES}
65+
/>
66+
<label className="btn x-radio-label mr-1" htmlFor="survey-yes">
67+
<ThumbsupIcon size={24} className="color-text-tertiary" />
68+
</label>
69+
<input
70+
id="survey-no"
71+
type="radio"
72+
name="survey-vote"
73+
value="N"
74+
aria-label={t`no`}
75+
hidden
76+
onChange={vote(ViewState.NO)}
77+
defaultChecked={state === ViewState.NO}
78+
/>
79+
<label className="btn x-radio-label" htmlFor="survey-no">
80+
<ThumbsdownIcon size={24} className="color-text-tertiary" />
81+
</label>
82+
</p>
83+
)}
84+
85+
{[ViewState.YES, ViewState.NO].includes(state) && (
86+
<>
87+
<p className="color-text-secondary f6">
88+
{state === ViewState.YES && t`yes_feedback`}
89+
{state === ViewState.NO && t`no_feedback`}
90+
</p>
91+
<p className="mb-3">
92+
<label className="d-block mb-1 f6" htmlFor="survey-comment">
93+
<span>
94+
{state === ViewState.YES && t`comment_yes_label`}
95+
{state === ViewState.NO && t`comment_no_label`}
96+
</span>
97+
<span className="text-normal color-text-tertiary float-right ml-1">
98+
{t`optional`}
99+
</span>
100+
</label>
101+
<textarea
102+
className="form-control input-sm width-full"
103+
name="survey-comment"
104+
id="survey-comment"
105+
></textarea>
106+
</p>
107+
<p>
108+
<label className="d-block mb-1 f6" htmlFor="survey-email">
109+
{t`email_label`}
110+
<span className="text-normal color-text-tertiary float-right ml-1">
111+
{t`optional`}
112+
</span>
113+
</label>
114+
<input
115+
type="email"
116+
className="form-control input-sm width-full"
117+
name="survey-email"
118+
id="survey-email"
119+
placeholder={t`email_placeholder`}
120+
/>
121+
<span className="f6 color-text-secondary">{t`not_support`}</span>
122+
</p>
123+
<p className="text-right">
124+
<button type="submit" className="btn btn-sm">
125+
{t`send`}
126+
</button>
127+
</p>
128+
</>
129+
)}
130+
131+
{state === ViewState.END && <p className="color-text-secondary f6">{t`feedback`}</p>}
81132
</form>
82133
)
83134
}
135+
136+
function trackEvent(formData: FormData | undefined) {
137+
if (!formData) return
138+
// Nota bene: convert empty strings to undefined
139+
return sendEvent({
140+
type: 'survey',
141+
survey_token: formData.get('survey-token') || undefined, // Honeypot
142+
survey_vote: formData.get('survey-vote') === 'Y',
143+
survey_comment: formData.get('survey-comment') || undefined,
144+
survey_email: formData.get('survey-email') || undefined,
145+
})
146+
}

data/ui.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,18 @@ survey:
5454
able_to_find: Did this doc help you?
5555
yes: Yes
5656
no: No
57-
yes_feedback: Want to learn about new docs features and updates? Sign up for updates!
58-
email_placeholder: email@example.com
57+
yes_feedback: We're continually improving our docs. We'd love to hear what we do well.
5958
no_feedback: We're continually improving our docs. We'd love to hear how we can do better.
60-
comment_label: Let us know what we can do better
59+
comment_yes_label: Let us know what we do well
60+
comment_no_label: Let us know what we can do better
6161
optional: Optional
6262
required: Required
63+
email_placeholder: email@example.com
6364
email_label: Can we contact you if we have more questions?
6465
send: Send
65-
feedback: Thank you! Your feedback has been submitted.
66+
feedback: Thank you! We received your feedback.
67+
not_support: If you need a reply, please contact support instead.
68+
privacy_policy: Privacy policy
6669
contribution_cta:
6770
title: Help us make these docs great!
6871
body: All GitHub docs are open source. See something that's wrong or unclear? Submit a pull request.

includes/survey.html

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
class="mb-1 f4"
88
>
99
{% data ui.survey.able_to_find %}
10+
<a
11+
class="f6 text-normal ml-3 color-text-link"
12+
href="/github/site-policy/github-privacy-statement"
13+
target="_blank"
14+
>{% data ui.survey.privacy_policy %}</a>
1015
</h2>
11-
<p class="f6">
12-
<a href="/github/site-policy/github-privacy-statement">Privacy policy</a>
13-
</p>
1416
<p
1517
class="radio-group"
1618
data-help-start
@@ -52,12 +54,13 @@
5254
name="survey-token"
5355
aria-hidden="true"
5456
/>
55-
<p hidden data-help-no>
57+
<p hidden data-help-yes data-help-no>
5658
<label
5759
class="d-block mb-1 f6"
5860
for="survey-comment"
5961
>
60-
<span>{% data ui.survey.comment_label %}</span>
62+
<span hidden data-help-yes>{% data ui.survey.comment_yes_label %}</span>
63+
<span hidden data-help-no>{% data ui.survey.comment_no_label %}</span>
6164
<span class="text-normal color-text-tertiary float-right ml-1">
6265
{% data ui.survey.optional %}
6366
</span>
@@ -68,12 +71,10 @@
6871
id="survey-comment"
6972
></textarea>
7073
</p>
71-
<p>
74+
<p hidden data-help-yes data-help-no>
7275
<label
7376
class="d-block mb-1 f6"
7477
for="survey-email"
75-
hidden
76-
data-help-no
7778
>
7879
{% data ui.survey.email_label %}
7980
<span class="text-normal color-text-tertiary float-right ml-1">
@@ -86,10 +87,8 @@
8687
name="survey-email"
8788
id="survey-email"
8889
placeholder="{% data ui.survey.email_placeholder %}"
89-
hidden
90-
data-help-yes
91-
data-help-no
9290
/>
91+
<span class="f6 color-text-secondary">{% data ui.survey.not_support %}</span>
9392
</p>
9493
<p
9594
class="text-right"

javascripts/events.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export function sendEvent ({
4141
search_query = undefined,
4242
search_context = undefined,
4343
navigate_label = undefined,
44+
survey_token = undefined, // Honeypot, doesn't exist in schema
4445
survey_vote = undefined,
4546
survey_comment = undefined,
4647
survey_email = undefined,
@@ -108,6 +109,7 @@ export function sendEvent ({
108109
navigate_label,
109110

110111
// Survey event
112+
survey_token, // Honeypot, doesn't exist in schema
111113
survey_vote,
112114
survey_comment,
113115
survey_email,

javascripts/survey.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ function trackEvent ({ token, vote, email, comment }) {
4646
}
4747

4848
export default function survey () {
49+
if (window.next) return
50+
4951
const form = document.querySelector('.js-survey')
5052
const texts = Array.from(document.querySelectorAll('.js-survey input, .js-survey textarea'))
5153
const votes = Array.from(document.querySelectorAll('.js-survey [type=radio]'))

lib/schema-event.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,6 @@ const surveySchema = {
272272
type: 'string',
273273
pattern: '^survey$'
274274
},
275-
token: {
276-
type: 'string',
277-
description: 'A honeypot.',
278-
maxLength: 0
279-
},
280275
survey_vote: {
281276
type: 'boolean',
282277
description: 'Whether the user found the page helpful.'

middleware/events.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const Ajv = require('ajv')
44
const addFormats = require('ajv-formats')
55
const schema = require('../lib/schema-event')
66

7-
const OMIT_FIELDS = ['type', 'token']
7+
const OMIT_FIELDS = ['type']
88

99
const ajv = new Ajv()
1010
addFormats(ajv)

0 commit comments

Comments
 (0)