From f82aaf2fdd856158fdb407bd45e8bc7054c1598e Mon Sep 17 00:00:00 2001 From: Ugo Palatucci Date: Thu, 21 Mar 2024 14:39:57 +0100 Subject: [PATCH] CNV-39761: filter lldp enabled or disabled --- cypress/e2e/StatusList.spec.cy.ts | 40 +++++++++++++++++++ cypress/support/selectors.ts | 4 ++ src/utils/helpers.ts | 3 ++ src/views/states/list/components/utils.ts | 15 +++++-- src/views/states/list/constants.ts | 4 ++ .../states/list/hooks/useStateFilters.ts | 39 +++++++++++++++--- 6 files changed, 95 insertions(+), 10 deletions(-) diff --git a/cypress/e2e/StatusList.spec.cy.ts b/cypress/e2e/StatusList.spec.cy.ts index b349e83e..516ef14d 100644 --- a/cypress/e2e/StatusList.spec.cy.ts +++ b/cypress/e2e/StatusList.spec.cy.ts @@ -3,6 +3,8 @@ import { EXPAND_INTERFACES_LIST_TEST_ID, INTERFACE_DRAWER_TEST_ID, LLDP_DRAWER_DETAILS_SECTION_TEST_ID, + LLDP_ENABLED_FILTER, + ROW_FILTERS_BUTTON, } from '../support/selectors'; describe('NodeNetworkState list', () => { @@ -83,4 +85,42 @@ describe('NodeNetworkState list', () => { .should('contain', 'VLANS'); }); }); + + it('filter by lldp', () => { + cy.intercept('GET', '/api/kubernetes/apis/nmstate.io/v1beta1/nodenetworkstates*', { + fixture: 'NodeNetworkStatusWithLLDP.json', + }).as('getStatuses'); + + cy.fixture('NodeNetworkStatusWithLLDP.json').then((nnsResponse) => { + const nns = nnsResponse.items[0]; + const ifaceWithLLDP = nns.status.currentState.interfaces.find((iface) => iface.lldp?.enabled); + const ifaceWithoutLLDP = nns.status.currentState.interfaces.find( + (iface) => !iface.lldp?.enabled, + ); + + cy.visit('/k8s/cluster/nmstate.io~v1beta1~NodeNetworkState'); + + cy.wait(['@getStatuses'], { timeout: 40000 }); + + cy.get('table').should('contain', nns.metadata.name); + + // open filter toolbar + cy.get(ROW_FILTERS_BUTTON).click(); + + cy.get(LLDP_ENABLED_FILTER).check(); + + // close filter toolbar + cy.get(ROW_FILTERS_BUTTON).click(); + + cy.get('table').should('contain', nns.metadata.name); + cy.get(EXPAND_INTERFACES_LIST_TEST_ID).click(); + cy.byTestID(EXPAND_INTERFACE_INFO).find('button').click(); + + cy.byTestID(`${ifaceWithLLDP.type}-${ifaceWithLLDP.name}-open-drawer`).contains( + ifaceWithLLDP.name, + ); + + cy.byTestID(`${ifaceWithoutLLDP.type}-expandable-section-toggle`).should('not.exist'); + }); + }); }); diff --git a/cypress/support/selectors.ts b/cypress/support/selectors.ts index 6143a991..ed912935 100644 --- a/cypress/support/selectors.ts +++ b/cypress/support/selectors.ts @@ -2,3 +2,7 @@ export const EXPAND_INTERFACES_LIST_TEST_ID = '#expand-interfaces-list0'; export const EXPAND_INTERFACE_INFO = 'ethernet-expandable-section-toggle'; export const INTERFACE_DRAWER_TEST_ID = 'interface-drawer'; export const LLDP_DRAWER_DETAILS_SECTION_TEST_ID = 'lldp-section'; + +export const ROW_FILTERS_BUTTON = 'button.pf-v5-c-select__toggle'; + +export const LLDP_ENABLED_FILTER = '#enabled[type="checkbox"]'; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index e4c43a11..6d539b16 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -7,6 +7,9 @@ import { import { ALL_NAMESPACES_SESSION_KEY } from './constants'; +export const isEmpty = (obj) => + [Array, Object].includes((obj || {}).constructor) && !Object.entries(obj || {}).length; + export const ensurePath = (data: T, paths: string | string[]) => { let current = data; diff --git a/src/views/states/list/components/utils.ts b/src/views/states/list/components/utils.ts index b9fadae1..7ee22681 100644 --- a/src/views/states/list/components/utils.ts +++ b/src/views/states/list/components/utils.ts @@ -1,6 +1,7 @@ import { InterfaceType, NodeNetworkConfigurationInterface } from '@types'; +import { isEmpty } from '@utils/helpers'; -import { FILTER_TYPES } from '../constants'; +import { FILTER_TYPES, LLDP_ENABLED } from '../constants'; import { searchInterfaceByIP } from '../utilts'; export const interfaceFilters: Record< @@ -8,15 +9,15 @@ export const interfaceFilters: Record< (selectedInput: string[], obj: NodeNetworkConfigurationInterface) => boolean > = { [FILTER_TYPES.INTERFACE_STATE]: (selectedInput, obj) => { - if (!selectedInput.length) return true; + if (isEmpty(selectedInput)) return true; return selectedInput.some((status) => obj.state.toLowerCase() === status); }, [FILTER_TYPES.INTERFACE_TYPE]: (selectedInput, obj) => { - if (!selectedInput.length) return true; + if (isEmpty(selectedInput)) return true; return selectedInput.some((interfaceType) => obj.type === interfaceType); }, [FILTER_TYPES.IP_FILTER]: (selectedInput, obj) => { - if (!selectedInput.length) return true; + if (isEmpty(selectedInput)) return true; return selectedInput.some((ipType) => !!obj[ipType]); }, [FILTER_TYPES.IP_ADDRESS]: (selectedInput, obj) => { @@ -24,6 +25,12 @@ export const interfaceFilters: Record< return searchInterfaceByIP(searchIPAddress, obj); }, + [FILTER_TYPES.LLDP]: (selectedInput, obj) => { + if (isEmpty(selectedInput)) return true; + return selectedInput.some((status) => + status === LLDP_ENABLED ? Boolean(obj?.lldp?.enabled) : !obj?.lldp?.enabled, + ); + }, } as const; export const filterInterfaces = ( diff --git a/src/views/states/list/constants.ts b/src/views/states/list/constants.ts index f73b9d2f..299ec834 100644 --- a/src/views/states/list/constants.ts +++ b/src/views/states/list/constants.ts @@ -9,4 +9,8 @@ export const FILTER_TYPES = { INTERFACE_TYPE: 'interface-type', IP_FILTER: 'ip-filter', IP_ADDRESS: 'ip-address', + LLDP: 'lldp', } as const; + +export const LLDP_ENABLED = 'enabled'; +export const LLDP_DISABLED = 'disabled'; diff --git a/src/views/states/list/hooks/useStateFilters.ts b/src/views/states/list/hooks/useStateFilters.ts index 8bd085bc..844828fc 100644 --- a/src/views/states/list/hooks/useStateFilters.ts +++ b/src/views/states/list/hooks/useStateFilters.ts @@ -2,8 +2,9 @@ import { useNMStateTranslation } from 'src/utils/hooks/useNMStateTranslation'; import { RowFilter } from '@openshift-console/dynamic-plugin-sdk'; import { InterfaceType, NodeNetworkConfigurationInterface, V1beta1NodeNetworkState } from '@types'; +import { isEmpty } from '@utils/helpers'; -import { FILTER_TYPES } from '../constants'; +import { FILTER_TYPES, LLDP_DISABLED, LLDP_ENABLED } from '../constants'; import { searchInterfaceByIP } from '../utilts'; const useStateFilters = (): RowFilter[] => { @@ -25,12 +26,38 @@ const useStateFilters = (): RowFilter[] => { filterGroupName: t('Search IP address'), items: [], }, + { + filterGroupName: t('LLDP'), + type: FILTER_TYPES.LLDP, + filter: (selectedLLDPStatus, obj) => { + if (isEmpty(selectedLLDPStatus.selected)) return true; + return selectedLLDPStatus.selected.some((status) => + obj?.status?.currentState?.interfaces.some((iface) => + status === LLDP_ENABLED ? Boolean(iface?.lldp?.enabled) : !iface?.lldp?.enabled, + ), + ); + }, + isMatch: (obj, status) => + obj?.status?.currentState?.interfaces.some((iface) => + status === LLDP_ENABLED ? Boolean(iface?.lldp?.enabled) : !iface?.lldp?.enabled, + ), + items: [ + { + id: LLDP_ENABLED, + title: t('Enabled'), + }, + { + id: LLDP_DISABLED, + title: t('Disabled'), + }, + ], + }, { filterGroupName: t('Interface state'), type: FILTER_TYPES.INTERFACE_STATE, - filter: (selectedIpTypes, obj) => { - if (!selectedIpTypes.selected.length) return true; - return selectedIpTypes.selected.some((status) => + filter: (selectedInfaceState, obj) => { + if (isEmpty(selectedInfaceState.selected)) return true; + return selectedInfaceState.selected.some((status) => obj?.status?.currentState?.interfaces.some( (iface) => iface.state.toLowerCase() === status, ), @@ -53,7 +80,7 @@ const useStateFilters = (): RowFilter[] => { filterGroupName: t('Interface type'), type: FILTER_TYPES.INTERFACE_TYPE, filter: (selectedInterfaceTypes, obj) => { - if (!selectedInterfaceTypes.selected.length) return true; + if (isEmpty(selectedInterfaceTypes.selected)) return true; return selectedInterfaceTypes.selected.some((interfaceType) => obj?.status?.currentState?.interfaces.some( (iface: NodeNetworkConfigurationInterface) => iface.type === interfaceType, @@ -79,7 +106,7 @@ const useStateFilters = (): RowFilter[] => { filterGroupName: t('IP'), type: FILTER_TYPES.IP_FILTER, filter: (selectedIpTypes, obj) => { - if (!selectedIpTypes.selected.length) return true; + if (isEmpty(selectedIpTypes.selected)) return true; return selectedIpTypes.selected.some((ipType) => obj?.status?.currentState?.interfaces.some((i) => !!i[ipType]), );