From 983a70c8d238b481061f6d7b286beb8aa2fdc638 Mon Sep 17 00:00:00 2001 From: Jordon Leach <40806497+jordojordo@users.noreply.github.com> Date: Thu, 29 Feb 2024 09:49:40 -0500 Subject: [PATCH] Restructure longhorn UI proxy link to contain dynamic namespace (#10263) * Restructure longhorn UI proxy link to contain dynamic namespace * Update fetch to use findMatching --- shell/assets/translations/en-us.yaml | 1 + .../longhorn/__tests__/longhorn.index.test.ts | 89 +++++++++++++++++++ shell/pages/c/_cluster/longhorn/index.vue | 69 ++++++++++---- 3 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 shell/pages/c/_cluster/longhorn/__tests__/longhorn.index.test.ts diff --git a/shell/assets/translations/en-us.yaml b/shell/assets/translations/en-us.yaml index bc2f590d3df..e4de89d3cf9 100644 --- a/shell/assets/translations/en-us.yaml +++ b/shell/assets/translations/en-us.yaml @@ -3072,6 +3072,7 @@ longhorn: label: 'Longhorn' description: 'Manage storage system via UI' na: Resource Unavailable + uiServiceUnavailable: The Longhorn UI service is not available or you do not have permission to access it. neuvector: overview: diff --git a/shell/pages/c/_cluster/longhorn/__tests__/longhorn.index.test.ts b/shell/pages/c/_cluster/longhorn/__tests__/longhorn.index.test.ts new file mode 100644 index 00000000000..307d7342831 --- /dev/null +++ b/shell/pages/c/_cluster/longhorn/__tests__/longhorn.index.test.ts @@ -0,0 +1,89 @@ +import { shallowMount } from '@vue/test-utils'; + +import IconMessage from '@shell/components/IconMessage'; +import LonghornOverview from '@shell/pages/c/_cluster/longhorn/index.vue'; + +const longhornFrontend = { + id: 'default/longhorn-frontend', + type: 'service', + apiVersion: 'v1', + kind: 'Service', + metadata: { + labels: { app: 'longhorn-ui' }, + name: 'longhorn-frontend', + namespace: 'default', + } +}; + +describe('page: LonghornOverview', () => { + const commonMocks = { + $fetchState: { pending: false }, + $store: { + dispatch: () => jest.fn, + getters: { + currentProduct: () => 'cluster', + 'cluster/findAll': () => jest.fn(), + 'cluster/schemaFor': () => jest.fn(), + 'cluster/matching': () => jest.fn(), + 'i18n/t': () => jest.fn(), + }, + }, + }; + + const createWrapper = (overrides?: any) => { + return shallowMount(LonghornOverview, { + mocks: commonMocks, + ...overrides, + }); + }; + + it('initializes externalLinks as an empty array', () => { + const wrapper = createWrapper({ + stubs: { + IconMessage: { template: '' }, + LazyImage: { template: '' }, + } + }); + + expect(wrapper.vm.externalLinks).toStrictEqual([]); + }); + + it('populates externalLinks proxy link correctly when uiServices contain service', async() => { + const proxyUrl = `/k8s/clusters/_/api/v1/namespaces/${ longhornFrontend.metadata.namespace }/services/http:longhorn-frontend:80/proxy/`; + + interface LinkConfig { + enabled: boolean; + iconSrc: string; + label: string; + description: string; + link: string; + } + + const wrapper = createWrapper({ + computed: { currentCluster: () => ({ id: '_' }) }, + stubs: { + Banner: { template: '' }, + LazyImage: { template: '' }, + } + }); + + wrapper.setData({ uiServices: [longhornFrontend] }); + + await wrapper.vm.$nextTick(); + + const containsProxyUrl = wrapper.vm.externalLinks.find((link: LinkConfig) => link.link); + + expect(containsProxyUrl).toBeTruthy(); + expect(containsProxyUrl.link).toStrictEqual(proxyUrl); + }); + + it('displays IconMessage when externalLinks array is empty', () => { + const wrapper = createWrapper({ stubs: { LazyImage: { template: '' } } }); + + expect(wrapper.vm.externalLinks).toStrictEqual([]); + + const iconMessage = wrapper.findComponent(IconMessage); + + expect(iconMessage.exists()).toBe(true); + }); +}); diff --git a/shell/pages/c/_cluster/longhorn/index.vue b/shell/pages/c/_cluster/longhorn/index.vue index 3cb62ac7fa7..cd421e971c7 100644 --- a/shell/pages/c/_cluster/longhorn/index.vue +++ b/shell/pages/c/_cluster/longhorn/index.vue @@ -3,42 +3,61 @@ import { mapGetters } from 'vuex'; import InstallRedirect from '@shell/utils/install-redirect'; +import { SERVICE } from '@shell/config/types'; import { NAME, CHART_NAME } from '@shell/config/product/longhorn'; +import IconMessage from '@shell/components/IconMessage'; import LazyImage from '@shell/components/LazyImage'; +import Loading from '@shell/components/Loading'; export default { - components: { LazyImage }, + components: { + IconMessage, LazyImage, Loading + }, middleware: InstallRedirect(NAME, CHART_NAME), + async fetch() { + if ( this.$store.getters['cluster/schemaFor'](SERVICE) ) { + this.uiServices = await this.$store.dispatch('cluster/findMatching', { + type: SERVICE, + selector: 'app=longhorn-ui' + }); + } + }, + data() { return { - externalLinks: [], longhornImgSrc: require('~shell/assets/images/vendor/longhorn.svg'), + uiServices: null }; }, - computed: { ...mapGetters(['currentCluster']) }, + computed: { + ...mapGetters(['currentCluster']), - mounted() { - this.externalLinks = [ - { - enabled: true, - iconSrc: this.longhornImgSrc, - label: 'longhorn.overview.linkedList.longhorn.label', - description: 'longhorn.overview.linkedList.longhorn.description', - link: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/longhorn-system/services/http:longhorn-frontend:80/proxy/` - }, - ]; - }, + externalLinks() { + if ( this.uiServices && this.uiServices.length === 1 && this.uiServices[0].metadata?.namespace ) { + return [ + { + enabled: true, + iconSrc: this.longhornImgSrc, + label: 'longhorn.overview.linkedList.longhorn.label', + description: 'longhorn.overview.linkedList.longhorn.description', + link: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/${ this.uiServices[0].metadata.namespace }/services/http:longhorn-frontend:80/proxy/` + }, + ]; + } - methods: {} + return []; + } + } }; - + + @@ -52,7 +71,10 @@ export default { - + + + + + + {{ t('longhorn.overview.linkedList.longhorn.uiServiceUnavailable') }} + + +
+ {{ t('longhorn.overview.linkedList.longhorn.uiServiceUnavailable') }} +