Merge pull request #10435 from richard-cox/optimise-branding-apps-fetch

Fetch only specific csp adapter helm apps... instead of all helm apps
This commit is contained in:
Richard Cox 2025-06-23 09:13:31 +01:00 committed by GitHub
commit fd92fbe93c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 254 additions and 36 deletions

View File

@ -0,0 +1,170 @@
import { mount } from '@vue/test-utils';
import { CATALOG, MANAGEMENT } from '@shell/config/types';
import Brand from '@shell/mixins/brand';
describe('brandMixin', () => {
const createWrapper = (vaiOn = false) => {
const Component = {
template: '<div></div>',
mixins: [Brand],
};
const data = {
apps: null, haveAppsAndSettings: null, canPaginate: false
};
const store = {
dispatch: (action, ...args) => {
switch (action) {
case 'management/findAll':
if (args[0] === MANAGEMENT.SETTING) {
return [];
}
if (args[0] === CATALOG.APP) {
return [];
}
break;
}
},
getters: {
'auth/loggedIn': () => true,
'auth/fromHeader': () => false,
'management/byId': () => undefined,
'management/canList': () => () => true,
'management/schemaFor': (type: string) => {
switch (type) {
case MANAGEMENT.SETTING:
return { linkFor: () => undefined };
}
},
'management/generation': () => undefined,
'management/paginationEnabled': () => vaiOn,
'management/all': (type: string) => {
switch (type) {
case MANAGEMENT.SETTING:
return [];
}
},
}
};
const wrapper = mount(
Component,
{
data: () => data,
global: { mocks: { $store: store } }
});
const spyManagementFindAll = jest.spyOn(store, 'dispatch');
return {
wrapper,
store,
spyManagementFindAll,
};
};
describe('should make correct requests', () => {
it('vai off', async() => {
const { wrapper, spyManagementFindAll } = createWrapper(false);
// NOTE - wrapper.vm.$options.fetch() doesn't work
await wrapper.vm.$options.fetch.apply(wrapper.vm);
// wrapper.vm.$nextTick();
expect(spyManagementFindAll).toHaveBeenNthCalledWith(1, 'management/findAll', {
type: MANAGEMENT.SETTING,
opt: {
load: 'multi', redirectUnauthorized: false, url: `/v1/${ MANAGEMENT.SETTING }s`
}
});
expect(spyManagementFindAll).toHaveBeenNthCalledWith(2, 'management/findAll', { type: CATALOG.APP });
});
it('vai on', async() => {
const { wrapper, spyManagementFindAll } = createWrapper(true);
// NOTE - wrapper.vm.$options.fetch() doesn't work
await wrapper.vm.$options.fetch.apply(wrapper.vm);
expect(spyManagementFindAll).toHaveBeenNthCalledWith(1, 'management/findAll', {
type: MANAGEMENT.SETTING,
opt: {
load: 'multi', url: `/v1/${ MANAGEMENT.SETTING }s`, redirectUnauthorized: false
}
});
expect(spyManagementFindAll).toHaveBeenNthCalledWith(2, 'management/findPage', {
type: CATALOG.APP,
opt: {
pagination: {
filters: [{
equals: true,
fields: [{
equals: true, exact: true, field: 'metadata.name', value: 'rancher-csp-adapter'
}, {
equals: true, exact: true, field: 'metadata.name', value: 'rancher-csp-billing-adapter'
}],
param: 'filter'
}],
labelSelector: undefined,
page: null,
pageSize: null,
projectsOrNamespaces: [],
sort: []
}
}
});
});
});
describe('cspAdapter', () => {
it('should have correct csp values (off)', async() => {
const { wrapper, store } = createWrapper();
const spyManagementFindAll = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
const { type } = options as any;
if (type === MANAGEMENT.SETTING) {
return Promise.resolve([]);
}
if (type === CATALOG.APP) {
return Promise.resolve([]);
}
return Promise.reject(new Error('reason'));
});
// NOTE - wrapper.vm.$options.fetch() doesn't work
await wrapper.vm.$options.fetch.apply(wrapper.vm, []);
expect(spyManagementFindAll).toHaveBeenCalledTimes(2);
expect(wrapper.vm.canCalcCspAdapter).toBeTruthy();
expect(wrapper.vm.cspAdapter).toBeFalsy();
});
it.each(['rancher-csp-adapter', 'rancher-csp-billing-adapter'])('should have correct csp values (on - %p )', async(chartName) => {
const { wrapper, store } = createWrapper();
const spyManagementFindAll = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
const { type } = options as any;
if (type === MANAGEMENT.SETTING) {
return Promise.resolve([]);
}
if (type === CATALOG.APP) {
return Promise.resolve([{ metadata: { name: chartName } }]);
}
return Promise.reject(new Error('reason'));
});
// NOTE - wrapper.vm.$options.fetch() doesn't work
await wrapper.vm.$options.fetch.apply(wrapper.vm, []);
expect(spyManagementFindAll).toHaveBeenCalledTimes(2);
expect(wrapper.vm.canCalcCspAdapter).toBeTruthy();
expect(wrapper.vm.cspAdapter).toBeTruthy();
});
});
});

View File

@ -1,39 +1,38 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { CATALOG, MANAGEMENT } from '@shell/config/types'; import { MANAGEMENT } from '@shell/config/types';
import { SETTING } from '@shell/config/settings'; import { SETTING } from '@shell/config/settings';
import { createCssVars } from '@shell/utils/color'; import { createCssVars } from '@shell/utils/color';
import { setTitle } from '@shell/config/private-label'; import { setTitle } from '@shell/config/private-label';
import { setFavIcon, haveSetFavIcon } from '@shell/utils/favicon'; import { setFavIcon, haveSetFavIcon } from '@shell/utils/favicon';
import { allHash } from '@shell/utils/promise';
const cspAdaptorApp = ['rancher-csp-adapter', 'rancher-csp-billing-adapter']; import { fetchInitialSettings } from '@shell/utils/settings';
import CspAdapterUtils from '@shell/utils/cspAdaptor';
export const hasCspAdapter = (apps) => {
return apps?.find((a) => cspAdaptorApp.includes(a.metadata?.name));
};
export default { export default {
async fetch() { async fetch() {
// For the login page, the schemas won't be loaded - we don't need the apps in this case
try { try {
if (this.$store.getters['management/canList'](CATALOG.APP)) { const res = await allHash({
this.apps = await this.$store.dispatch('management/findAll', { type: CATALOG.APP }); // Ensure we read the settings even when we are not authenticated
} globalSettings: fetchInitialSettings(this.$store),
} catch (e) {} apps: CspAdapterUtils.fetchCspAdaptorApp(this.$store),
});
// Ensure we read the settings even when we are not authenticated
try {
// The favicon is implicitly dependent on the initial settings having already been fetched // The favicon is implicitly dependent on the initial settings having already been fetched
if (!haveSetFavIcon()) { if (!haveSetFavIcon()) {
setFavIcon(this.$store); setFavIcon(this.$store);
} }
} catch (e) {}
this.apps = res.apps;
} catch (e) { }
// Setting this up front will remove `computed` churn, and we only care that we've initialised them // Setting this up front will remove `computed` churn, and we only care that we've initialised them
this.haveAppsAndSettings = !!this.apps && !!this.globalSettings; this.haveAppsAndSettings = !!this.apps && !!this.globalSettings;
}, },
data() { data() {
return { apps: null, haveAppsAndSettings: null }; return {
apps: null, haveAppsAndSettings: null, canPaginate: false
};
}, },
computed: { computed: {
@ -87,7 +86,7 @@ export default {
// Note! this used to be `findBy(this.app)` however for that case we lost reactivity on the collection // Note! this used to be `findBy(this.app)` however for that case we lost reactivity on the collection
// (computed fires before fetch, fetch happens and update apps, computed would not fire again - even with vue.set) // (computed fires before fetch, fetch happens and update apps, computed would not fire again - even with vue.set)
// So use `.find` in method instead // So use `.find` in method instead
return hasCspAdapter(this.apps); return CspAdapterUtils.hasCspAdapter({ $store: this.$store, apps: this.apps });
}, },
canCalcCspAdapter() { canCalcCspAdapter() {

View File

@ -2,13 +2,13 @@
import BannerGraphic from '@shell/components/BannerGraphic'; import BannerGraphic from '@shell/components/BannerGraphic';
import IndentedPanel from '@shell/components/IndentedPanel'; import IndentedPanel from '@shell/components/IndentedPanel';
import CommunityLinks from '@shell/components/CommunityLinks'; import CommunityLinks from '@shell/components/CommunityLinks';
import { CATALOG, MANAGEMENT } from '@shell/config/types'; import { MANAGEMENT } from '@shell/config/types';
import { getVendor } from '@shell/config/private-label'; import { getVendor } from '@shell/config/private-label';
import { SETTING } from '@shell/config/settings'; import { SETTING } from '@shell/config/settings';
import { addParam } from '@shell/utils/url'; import { addParam } from '@shell/utils/url';
import { isRancherPrime } from '@shell/config/version'; import { isRancherPrime } from '@shell/config/version';
import { hasCspAdapter } from '@shell/mixins/brand';
import TabTitle from '@shell/components/TabTitle'; import TabTitle from '@shell/components/TabTitle';
import CspAdapterUtils from '@shell/utils/cspAdaptor';
export default { export default {
@ -42,9 +42,7 @@ export default {
return setting; return setting;
}; };
if ( this.$store.getters['management/canList'](CATALOG.APP) ) { this.apps = await CspAdapterUtils.fetchCspAdaptorApp(this.$store);
this.apps = await this.$store.dispatch('management/findAll', { type: CATALOG.APP });
}
this.brandSetting = await fetchOrCreateSetting(SETTING.BRAND, ''); this.brandSetting = await fetchOrCreateSetting(SETTING.BRAND, '');
this.serverUrlSetting = await fetchOrCreateSetting(SETTING.SERVER_URL, ''); this.serverUrlSetting = await fetchOrCreateSetting(SETTING.SERVER_URL, '');
this.uiIssuesSetting = await this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.ISSUES }); this.uiIssuesSetting = await this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.ISSUES });
@ -75,7 +73,7 @@ export default {
computed: { computed: {
cspAdapter() { cspAdapter() {
return hasCspAdapter(this.apps); return CspAdapterUtils.hasCspAdapter({ $store: this.$store, apps: this.apps });
}, },
hasSupport() { hasSupport() {

View File

@ -667,19 +667,19 @@ export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStore = {
} }
} }
}, },
// Disabled due to https://github.com/rancher/dashboard/issues/14493 management: {
// management: { resources: {
// resources: { enableAll: false,
// enableAll: false, enableSome: {
// enableSome: { enabled: [
// enabled: [ // { resource: CAPI.RANCHER_CLUSTER, context: ['home', 'side-bar'] }, // Disabled due to https://github.com/rancher/dashboard/issues/14493
// { resource: CAPI.RANCHER_CLUSTER, context: ['home', 'side-bar'] }, // { resource: MANAGEMENT.CLUSTER, context: ['side-bar'] }, // Disabled due to https://github.com/rancher/dashboard/issues/14493
// { resource: MANAGEMENT.CLUSTER, context: ['side-bar'] }, { resource: CATALOG.APP, context: ['branding'] },
// ], ],
// generic: false, generic: false,
// } }
// } }
// } }
}; };
export default new StevePaginationUtils(); export default new StevePaginationUtils();

51
shell/utils/cspAdaptor.ts Normal file
View File

@ -0,0 +1,51 @@
// For testing these could be changed to something like...
import { CATALOG } from '@shell/config/types';
import { FilterArgs, PaginationFilterField, PaginationParamFilter } from '@shell/types/store/pagination.types';
import { VuexStore } from '@shell/types/store/vuex';
const CSP_ADAPTER_APPS = ['rancher-csp-adapter', 'rancher-csp-billing-adapter'];
// For testing above line could be replaced with below line...
// const cspAdaptorApp = ['rancher-webhooka', 'rancher-webhook'];
/**
* Helpers in order to
*/
class CspAdapterUtils {
static canPagination($store: VuexStore): boolean {
return $store.getters[`management/paginationEnabled`]({ id: CATALOG.APP, context: 'branding' });
}
static fetchCspAdaptorApp($store: VuexStore): Promise<any> {
// For the login page, the schemas won't be loaded - we don't need the apps in this case
if ($store.getters['management/canList'](CATALOG.APP)) {
if (CspAdapterUtils.canPagination($store)) {
// Restrict the amount of apps we need to fetch
return $store.dispatch('management/findPage', {
type: CATALOG.APP,
opt: { // Of type ActionFindPageArgs
pagination: new FilterArgs({
filters: PaginationParamFilter.createMultipleFields(CSP_ADAPTER_APPS.map(
(t) => new PaginationFilterField({
field: 'metadata.name',
value: t,
})
)),
})
}
});
}
return $store.dispatch('management/findAll', { type: CATALOG.APP });
}
return Promise.resolve([]);
}
static hasCspAdapter({ $store, apps }: { $store: VuexStore, apps: any[]}): Object {
// In theory this should contain the filtered apps when pagination is on, and all apps when off. Keep filtering though in both cases just in case
return apps?.find((a) => CSP_ADAPTER_APPS.includes(a.metadata?.name));
}
}
export default CspAdapterUtils;