Skip to content

tech debt: Move the create-route derivation out of data() in Masthead.vue #18703

Description

@aalves08

Is your feature request related to a problem? Please describe.

shell/components/ResourceList/Masthead.vue does real work inside data() — it reads Vue globalProperties, calls into the extension manager, iterates the registered extensions and hits Vuex getters, all to decide a single route object:

data() {
  const params = { ...this.$route.params };

  let currPluginName = '';
  let formRoute;
  let overrideCreateLocationByExtension = false;
  const plugins = this.$extension.getPlugins();       // <- L102

  Object.keys(plugins).forEach((key) => {
    if (plugins[key].productNames.includes(this.$store.getters['productId'])) {
      currPluginName = key;
    }
  });

  if (currPluginName && plugins[currPluginName]?.topLevelProduct) {
    formRoute = { name: `${ this.$route.name }-create`, params: { ...params, product: this.$store.getters['productId'] } };
    overrideCreateLocationByExtension = true;
  } else {
    formRoute = { name: `${ this.$route.name }-create`, params };
  }
  ...
}

This bit us for real in #18634: on Rancher 2.11–2.13 the extension manager lives under $plugin rather than $extension, so this.$extension is undefined in an extension-bundled copy of this component and the dereference throws. Because the throw happens in data(), it doesn't degrade one button — it takes the entire route render down and the user gets a blank page with only a console error to go on. That failure mode is the subject of the epic in #11807: exceptions in data() are swallowed into an empty page, a regression from Vue 2 behaviour that we still live with.

#18664 fixes the immediate crash with a null-guard at the call site (this.$extension?.getPlugins?.() || {}), which is the right minimal fix to backport. But as raised in review (#18664 (comment)), the guard treats the symptom. The underlying problem is that data() is being used for derivation and dependency access instead of just declaring initial component state, so any new fragile dependency added there gets the same blast radius for free.

This is not isolated to Masthead.vue — roughly 474 .vue files under shell/ and pkg/ declare a data() block, and a meaningful number of them touch $store, $route or globalProperties inside it. Masthead.vue is just the highest-leverage instance: it's the header for every ResourceList, used by ~20 components directly and by every core and extension list page in the product. A crash here is a blank page on a very large slice of the UI.

Describe the solution you'd like

Move the create-route derivation out of data() in Masthead.vue:

  • formRoute, yamlRoute, overrideCreateLocationByExtension and hasEditComponent are all pure functions of props, $route and store state — they should be computed, not seeded state. Nothing mutates them after creation, so there's no reason for them to be reactive data at all.
  • data() should be left declaring plain initial state only.
  • Anything that genuinely needs to be seeded imperatively should be seeded from a lifecycle hook (created/fetch), where a throw is contained and can be surfaced properly instead of blanking the route.

Two secondary things worth folding in while the file is open:

  • Once the derivation lives in a computed, the extension-manager access is evaluated lazily and after mount, which means the $extension/$plugin compat shim in @shell/pkg/auto-import has actually had a chance to run. The null-guard from fix extensions issue with hard refresh on a list page route #18664 should stay regardless (the store seeds the manager keys with an empty object before swapping in the real one, so "present but not usable" is a genuine state), but it stops being the only thing standing between an extension and a white screen.
  • The component now has unit tests as of fix extensions issue with hard refresh on a list page route #18664 — the refactor should keep that coverage green and extend it to the computed form.

Describe alternatives you've considered

Additional context

Suggested labels: area/extensions, kind/enhancement (or your tech-debt equivalent), area/engineering.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions