Keep the recent searches dropdown reactive under Vue 3

Problem

On Vue 3 the recent searches dropdown of the legacy filtered search always reads "You don't have any recent searches", even when local storage holds searches. The page is /dashboard/merge_requests/search, and the same bar is used by the issue analytics and productivity analytics pages.

On Vue 2 the same page works.

Root cause

app/assets/javascripts/filtered_search/recent_searches_root.js mounts the dropdown with the state object of RecentSearchesStore as its data:

const { state } = this.store;
this.vm = new Vue({ el: this.wrapperElement, data() { return state; }, ... });

RecentSearchesStore keeps its own reference to that object and writes to it directly, for example this.state.recentSearches = .... The searches arrive after mount, because FilteredSearchManager#setup reads local storage in a promise.

  • On Vue 2, data() makes the returned object reactive in place, so a write on the store reference is seen by the component.
  • On Vue 3, the returned object is wrapped in a reactive proxy. The store still holds the raw object, so its writes never notify the component.

Measured in the browser on master with Vue 3, with two searches in local storage:

store.state.recentSearches  ["roadmap","backend"]
vm.$data.recentSearches     []
vm.$data === store.state    false
entries in the dropdown     0

The same measurement with Vue 2 gives vm.$data === store.state and two entries.

History

The pattern is old and was correct while GitLab ran Vue 2. data() { return state; } came in with 7fc090b7 in 2018, when the component moved into its own file. The file was last touched by 3fc5ee9c, which only added the mounting point name. No commit broke the dropdown. The Vue 3 rollout exposes it.

Fix

Point the store at the reactive data of the mounted component, so later writes go through the proxy. On Vue 2 the assignment is a no operation, because vm.$data is the same object.

The spec now covers the case that failed: a write to the store after mount must reach the rendered list. It fails on master with VUE_VERSION=3 and passes with this change.

Verification

  • VUE_VERSION=3 jest spec/frontend/filtered_search/recent_searches_root_spec.js: fails on master, 2 tests pass here.
  • jest spec/frontend/filtered_search: 3 suites pass, 45 tests pass on Vue 2.
  • ESLint and Prettier report no change on both files.

Verification video

Two runs at 1.5x speed, both with the GDK on Vue 3 and two searches in local storage.

  1. master. The overlay reads the live objects: the store holds the two searches, the component data holds none. The dropdown lists nothing.
  2. This branch. The store and the component data hold the same two searches, and the dropdown lists them with the "Clear recent searches" button.

Found while recording the verification video for !251465 (merged).

Changelog: fixed

Merge request reports

Loading
Loading