# Kubernetes Resources Data Load Optimizations In order to improve performance of Rancher Dashboard especially for systems with a large number of resources, several changes have been introduced to the codebase such as incremental loading of list views, manual refresh of list views and optimisation for "secondary" data load. ## The `resource-manager` mixin for secondary data load This is the mixin responsible for optimizing the loading of "secondary" data (data that is used to complete information on a given page, ex: populating a select input) which doesn't need to be watched (updated via websocket) or stored in the Vue store (Vuex). It fetches namespaced data directly from our API, avoiding the need to fetch all items and then filtering by a given namespace. If you specify a namespace but any of the given resources you configured for your data fetch is not namespaced, the mixin will take care of that. It will only return the data for successful network requests, throwing a `console.error` for any requests that have failed. It provides a way to fetch data only for namespaced resources, which is very useful on a CREATE screen scenario where if a user toggles the namespace selector, data will need to be fetched again, but only for those namespace-dependent resources. **NOTE:** by default, data fetched using this mixin will **NOT** have a model applied to it. There's an option called classify that will apply models if needed. ### Quick usage guide Just import the `resource-manager` mixin to the given page, create the configuration variable that tells which data it should load and use the method `resourceManagerFetchSecondaryResources` to load the given data. If you want a code example on Rancher Dashoard, check for a full implementation of the "secondary data load" on `mixins/workloads.js`. Example: **Importing mixin** ``` import ResourceManager from '@shell/mixins/resource-manager'; ``` **Setting up a configuration variable** Allowed params for the configuration object: * @param \{**String**\} `namespace` - Namespace identifier * @param \{**Object**\} `data` - Object containing info about the data needed to be fetched and how it should be parsed. **NOTE: The object KEY NEEDS to be the resource TYPE!** * @param \{**Array**\} `data[TYPE].applyTo` - The array of operations needed to be performed for the specific data TYPE * @param \{**String**\} `data[TYPE].applyTo[x].var` - The 'this' property name that should be populated with the data fetched * @param \{**Boolean**\} `data[TYPE].applyTo[x].classify` - Whether the data fetched should have a model applied to it * @param \{**Function**\} `data[TYPE].applyTo[x].parsingFunc` - Optional parsing function if the fetched data needs to be parsed Example of a configuration object to be used with the `resource-manager`: ``` this.secondaryResourceDataConfig = { namespace: 'fleet-default', data: { 'secrets': { applyTo: [ { var: 'namespacedSecrets' }, { var: 'imagePullNamespacedSecrets', parsingFunc: (data) => { return data.filter(secret => (secret._type === 'docker' || secret._type === 'docker_json')); }, classify: true } ] } } } ``` Looking at the above example, we configuring the secondary data load to get data for the namespace `fleet-default` and with fetch `secrets`from the API and apply it to two diferent variables: 1. Will apply the full set of results fetched for the resource type `secrets` to variable `this.namespacedSecrets` 2. Will apply the parsed set of results (`parsingFunc` defines how data should be parsed) to the variable `this.imagePullNamespacedSecrets` which will apply the correct model to that parsed dataset because of the flag `classify` **Initialize secondary data load** ``` async fetch() { this.resourceManagerFetchSecondaryResources(this.secondaryResourceDataConfig); } ``` ### Methods available #### **resourceManagerFetchSecondaryResources** Function used to initialize the data loading procedure * @param \{**Object**\} `dataConfig` - Configuration object * @param \{**Boolean**\} `onlyNamespaced` - Flag to enable the fetch from API *ONLY* for namespaced resources (will ignore requests for non-namespaced resources you have defined on the configuration object). Defaults to `false` Example: ``` async fetch() { this.resourceManagerFetchSecondaryResources(dataConfig, onlyNamespaced); } ``` #### **resourceManagerClearSecondaryResources** Function used to clear the results for the secondary resource data fetch. It's a very useful method in a CREATE screen scenario where a user can "create" a namespace on the UI, operation which will make all our previous namespaced results invalid and in need to be cleared. * @param \{**Object**\} `dataConfig` - Configuration object * @param \{**Boolean**\} `onlyNamespaced` - Flag to enable the fetch from API *ONLY* for namespaced resources (will ignore requests for non-namespaced resources you have defined on the configuration object). Defaults to `false` Example: ``` async fetch() { this.resourceManagerClearSecondaryResources(dataConfig, onlyNamespaced); } ``` ## The `resource-fetch` mixin for incremental loading and manual refresh The `resource-fetch` mixin is the controller for all operations on a list view regarding the incremental loading and manual refresh features. Both incremental loading and manual refresh are features that affect LIST views and are activated on the `Performance` section under `Global Settings` on Rancher Dashboard UI via a flag and also a configurable threshold of number of items for which is they are triggerable. To understand better how the mixin works, one should understand first how list views are managed and rendered. A list view of resources (table with a list of items of a given resource) can be of two types: 1. a *custom* list, which are defined on `/shell/list` folder on the Rancher Dashboard project 2. a *default* list, which will apply to all resources that **aren't** defined on `/shell/list` folder with a dedicated file Both have a common entry point: the `/shell/components/ResourceList/index.vue` file. This is where all lists start their rendering process. This is where it will decide to load a custom view (follow `this.hasListComponent`) or render a simple `ResourceTable` (check template part of the file). For a default list, we should check for the last couple lines on the `async fetch` method for the function called `this.$fetchType(resource)` which is a method exposed by the mixin (where you can pass the resource type as argument) which will handle all data loading for that given resource type. Under the hood it performs a `findAll` but it append some specific flags for incremental loading (`incremental`) and manual refresh (`hasManualRefresh` && `watch`) which will tap into the normal flow of data request from our API. For a **default** list view, all should be covered with defaults by the `/shell/components/ResourceList/index.vue` file. Two quick important notes: - There is a `loading` computed prop in the mixin that is the default flag for the loading state of a `ResourceTable` - There is a `rows` computed prop in the mixin that is the default list of items loaded on the mixin If any of these two are referenced on a custom list template part but aren't defined on the JS part of the same file, it's because it uses the defaults which come from the mixin itself. Another important configuration part is about the incremental loader options (component name `ResourceLoadingIndicator`). For custom lists the `ResourceTable` component is on the lookout for a method called `$loadingResources` which is defined on your custom list methods. If it exists, it should return an object with two properties: `loadIndeterminate` - by default the incremental loader is of a **determinate** type, which means that it will show the current count of items already loaded / total items to be loaded. `loadResources` - by default the incremental loader will load the counts for the resource passed, but there are custom list views (ex: workloads) are comprised of multiple resources. You'll need to pass an array of all resource types for that list if you want to show the correct numbers on the incremental loader. `Incremental loader` component (`ResourceLoadingIndicator`) is rendered inside the `Masthead` component (ResourceList... there are two Mastheads). Generally, custom lists don't have a `Masthead` specified on it's template, but there's a Masthead rendered. That comes from `/shell/components/ResourceList/index.vue`, ### Implementation examples - simple custom list implementation, with it's own `async fetch` check `catalog.cattle.io.app` custom list - custom list implementation, **without** `async fetch` check `catalog.cattle.io.clusterrepo` custom list - custom list implementation, **with** `async fetch` and `$loadingResources` check `fleet.cattle.io.bundle` custom list - custom list implementation, **with** `async fetch` and it's own Masthead instance check `fleet.cattle.io.gitrepo` custom list - multiple resources implementation check `workload` custom list