Country/state dropdowns throw when the countries/states fetch fails
Summary
Sentry shows ~30 users hitting a TypeError on the signup welcome page
(/users/sign_up/welcome) because the country and state dropdowns receive a
non-array value:
TypeError: e.states.map is not a function 21 users
TypeError: e.countries.map is not a function 5 usersWhen this fires the dropdown never renders, on a page in the middle of the signup funnel.
Root cause
The Apollo local resolvers in
ee/app/assets/javascripts/subscriptions/graphql/resolvers.js
swallow a fetch failure into an alert and return the alert's return value:
countries: () => {
return Api.fetchCountries()
.then(({ data }) => data.map(/* ... */))
.catch(() => createAlert({ message: ERROR_FETCHING_COUNTRIES }));
},createAlert() returns an alert object, not an array. So when
Api.fetchCountries() rejects, the resolver resolves successfully with a
non-array value rather than rejecting.
The consumer then assumes an array
(country_state_mixin.js#L78):
update(data) {
return data.countries.map((country) => ({ value: country.id, text: country.name }));
},.map is not a function on the alert object, and the component throws. The
states resolver and its update callback have the same shape.
Blast radius
The mixin is used by six components, so any of them can hit this when the countries/states request fails:
ee/app/assets/javascripts/registrations/components/free_welcome_form.vueee/app/assets/javascripts/registrations/components/trial_welcome_form.vueee/app/assets/javascripts/registrations/components/company_form.vueee/app/assets/javascripts/trials/components/create_trial_form.vueee/app/assets/javascripts/pages/gitlab_subscriptions/self_managed/trials/components/create_trial_form.vueee/app/assets/javascripts/hand_raise_leads/hand_raise_lead/components/hand_raise_lead_modal.vue
Sentry attributes the current reports to /users/sign_up/welcome, but the
same failure mode exists on the trial and hand-raise forms.
Proposed fix
Fix it at the resolver rather than guarding each consumer, so all six benefit and the contract ("this query returns an array") holds:
.catch(() => {
createAlert({ message: ERROR_FETCHING_COUNTRIES });
return [];
});The user still sees the alert, and the dropdown renders empty instead of the
component throwing. A defensive data.countries ?? [] in the mixin would stop
the throw too, but it would mask the failure rather than surface it.
Worth deciding separately whether an empty dropdown is the right UX, or whether the form should show a retry.
Sentry issues
Counts are from a 7-day window. Browsers include Chrome, Edge and Safari, so this is not browser-specific. Found via a dry run of the Sentry Signal Loop.
Steps to reproduce
- Block or fail the
/-/countriesrequest (offline, devtools request blocking, or a 500). - Visit
/users/sign_up/welcome. - The country dropdown does not render; the console shows
TypeError: e.countries.map is not a function.