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 users

When 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.vue
  • ee/app/assets/javascripts/registrations/components/trial_welcome_form.vue
  • ee/app/assets/javascripts/registrations/components/company_form.vue
  • ee/app/assets/javascripts/trials/components/create_trial_form.vue
  • ee/app/assets/javascripts/pages/gitlab_subscriptions/self_managed/trials/components/create_trial_form.vue
  • ee/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

Users Sentry
21 https://new-sentry.gitlab.net/organizations/gitlab/issues/3344056/
5 https://new-sentry.gitlab.net/organizations/gitlab/issues/3345333/
2 https://new-sentry.gitlab.net/organizations/gitlab/issues/3358695/
1 https://new-sentry.gitlab.net/organizations/gitlab/issues/3393470/
1 https://new-sentry.gitlab.net/organizations/gitlab/issues/3973990/

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

  1. Block or fail the /-/countries request (offline, devtools request blocking, or a 500).
  2. Visit /users/sign_up/welcome.
  3. The country dropdown does not render; the console shows TypeError: e.countries.map is not a function.