diff --git a/assets/js/common/Table/CollapsibleTableRow.jsx b/assets/js/common/Table/CollapsibleTableRow.jsx index 2d995b180c..a86095facf 100644 --- a/assets/js/common/Table/CollapsibleTableRow.jsx +++ b/assets/js/common/Table/CollapsibleTableRow.jsx @@ -1,5 +1,6 @@ import React, { Fragment, useState } from 'react'; import classNames from 'classnames'; +import { EOS_KEYBOARD_ARROW_DOWN } from 'eos-icons-react'; function CollapsibleTableRow({ columns, @@ -11,30 +12,39 @@ function CollapsibleTableRow({ collapsedRowClassName, }) { const [rowExpanded, toggleRow] = useState(false); + const collapsibleRowSpan = collapsibleDetailRenderer ? colSpan + 1 : colSpan; return ( <> - { - if (collapsibleDetailRenderer) { - toggleRow(!rowExpanded); - } - }} - > + + {collapsibleDetailRenderer && ( + toggleRow(!rowExpanded)} + > + + + )} {renderCells(columns, item)} {collapsibleDetailRenderer && ( - {collapsibleDetailRenderer(item)} + + {collapsibleDetailRenderer(item)} + )} diff --git a/assets/js/common/Table/Table.jsx b/assets/js/common/Table/Table.jsx index b995ffd604..d13e9be089 100644 --- a/assets/js/common/Table/Table.jsx +++ b/assets/js/common/Table/Table.jsx @@ -78,6 +78,7 @@ function Table({ const { columns, collapsibleDetailRenderer = undefined, + headerClassName = '', rowClassName = '', collapsedRowClassName = '', pagination, @@ -197,6 +198,14 @@ function Table({ + {collapsibleDetailRenderer && ( + {renderedData.length === 0 ? ( ) : ( diff --git a/assets/js/common/Table/Table.stories.jsx b/assets/js/common/Table/Table.stories.jsx index fdce53d07c..ea90ee44e9 100644 --- a/assets/js/common/Table/Table.stories.jsx +++ b/assets/js/common/Table/Table.stories.jsx @@ -76,6 +76,15 @@ const filteredConfig = { ], }; +const collapsibleConfig = { + ...config, + collapsibleDetailRenderer: () => ( +
+

This is a collapsible row data

+
+ ), +}; + const data = [ { user: 'Tony Kekw', @@ -201,6 +210,10 @@ export function WithHeader(args) { ); } +export function WithCollapsibleRow(args) { + return
+ )} {columns.map( ({ title, @@ -214,6 +223,7 @@ function Table({ ? 'cursor-pointer hover:text-gray-700 ' : '' }px-5 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider bg-gray-100`, + headerClassName, columnClassName )} onClick={handleClick} @@ -231,7 +241,11 @@ function Table({
; +} + export function Empty() { return
; } diff --git a/assets/js/common/Table/Table.test.jsx b/assets/js/common/Table/Table.test.jsx index f3503c0f51..fa0e703f8c 100644 --- a/assets/js/common/Table/Table.test.jsx +++ b/assets/js/common/Table/Table.test.jsx @@ -344,4 +344,30 @@ describe('Table component', () => { }); }); }); + + describe('collapsible row', () => { + it('should display the collapsed row when chevron is clicked', async () => { + const data = tableDataFactory.buildList(1); + const content = 'This is a collapsible row data'; + + render( +

{content}

, + }} + data={data} + /> + ); + + const table = screen.getByRole('table'); + const collapsibleCell = table.querySelector( + 'tbody > tr:nth-child(1) > td:nth-child(1)' + ); + expect(screen.queryByText(content)).not.toBeVisible(); + + fireEvent.click(collapsibleCell); + expect(screen.queryByText(content)).toBeVisible(); + }); + }); }); diff --git a/assets/js/pages/DatabasesOverview/DatabasesOverview.test.jsx b/assets/js/pages/DatabasesOverview/DatabasesOverview.test.jsx index 54d11175c3..5cfed7fbb2 100644 --- a/assets/js/pages/DatabasesOverview/DatabasesOverview.test.jsx +++ b/assets/js/pages/DatabasesOverview/DatabasesOverview.test.jsx @@ -58,6 +58,11 @@ describe('DatabasesOverview component', () => { /> ); + const table = screen.getByRole('table'); + await user.click( + table.querySelector('tbody tr:nth-child(1) td:nth-child(1)') + ); + const cleanUpButton = screen.queryByRole('button', { name: 'Clean up' }); await user.click(cleanUpButton); expect( @@ -92,6 +97,11 @@ describe('DatabasesOverview component', () => { /> ); + const table = screen.getByRole('table'); + await user.click( + table.querySelector('tbody tr:nth-child(1) td:nth-child(1)') + ); + const cleanUpButton = screen.getByText('Clean up').closest('button'); expect(cleanUpButton).toBeDisabled(); diff --git a/assets/js/pages/ExecutionResults/ExecutionResults.jsx b/assets/js/pages/ExecutionResults/ExecutionResults.jsx index 2fed767833..f649f4e397 100644 --- a/assets/js/pages/ExecutionResults/ExecutionResults.jsx +++ b/assets/js/pages/ExecutionResults/ExecutionResults.jsx @@ -31,13 +31,14 @@ const defaultSavedFilters = []; const resultsTableConfig = { usePadding: false, + headerClassName: 'bg-gray-50 border-b h-auto', rowClassName: 'tn-check-result-row', columns: [ { title: 'Id', key: 'checkID', fontSize: 'text-base', - className: 'bg-gray-50 border-b w-1/6 h-auto', + className: 'w-1/6', render: (checkID, { customized, onClick }) => (
( {description} @@ -69,7 +69,7 @@ const resultsTableConfig = { title: 'Result', key: 'result', fontSize: 'text-base', - className: 'bg-gray-50 border-b w-1/6 h-auto', + className: 'w-1/6', render: (_, { result }) => , }, ], diff --git a/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.test.jsx b/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.test.jsx index 03f7a6934a..ff0589e8b5 100644 --- a/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.test.jsx +++ b/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.test.jsx @@ -75,28 +75,28 @@ describe('SapSystemsOverviews component', () => { const rows = screen.getByRole('table').querySelectorAll('tbody > tr'); const mainRow = rows[0]; - expect(mainRow.querySelector('td:nth-child(2)')).toHaveTextContent(sid); - expect(mainRow.querySelector('td:nth-child(2) > a')).toHaveAttribute( + expect(mainRow.querySelector('td:nth-child(3)')).toHaveTextContent(sid); + expect(mainRow.querySelector('td:nth-child(3) > a')).toHaveAttribute( 'href', `/sap_systems/${sapSystemID}` ); - expect(mainRow.querySelector('td:nth-child(3)')).toHaveTextContent( + expect(mainRow.querySelector('td:nth-child(4)')).toHaveTextContent( attachedRdbms ); - expect(mainRow.querySelector('td:nth-child(3) > a')).toHaveAttribute( + expect(mainRow.querySelector('td:nth-child(4) > a')).toHaveAttribute( 'href', `/databases/${databaseID}` ); - expect(mainRow.querySelector('td:nth-child(4)')).toHaveTextContent( + expect(mainRow.querySelector('td:nth-child(5)')).toHaveTextContent( tenant ); - expect(mainRow.querySelector('td:nth-child(5)')).toHaveTextContent( + expect(mainRow.querySelector('td:nth-child(6)')).toHaveTextContent( sapSystemType ); - expect(mainRow.querySelector('td:nth-child(6)')).toHaveTextContent( + expect(mainRow.querySelector('td:nth-child(7)')).toHaveTextContent( dbAddress ); - expect(mainRow.querySelector('td:nth-child(7)')).toHaveTextContent( + expect(mainRow.querySelector('td:nth-child(8)')).toHaveTextContent( 'ENSA1' ); }); @@ -137,7 +137,7 @@ describe('SapSystemsOverviews component', () => { expectedSapSystemTypes.forEach((expectedType, index) => { const rowIndex = index * 2; const sapSystemRow = rows[rowIndex]; - expect(sapSystemRow.querySelector('td:nth-child(5)')).toHaveTextContent( + expect(sapSystemRow.querySelector('td:nth-child(6)')).toHaveTextContent( expectedType ); }); @@ -175,7 +175,7 @@ describe('SapSystemsOverviews component', () => { /> ); const rows = screen.getByRole('table').querySelectorAll('tbody > tr'); - expect(rows[0].querySelector('td:nth-child(5)')).toHaveTextContent( + expect(rows[0].querySelector('td:nth-child(6)')).toHaveTextContent( expectedSapSystemTypes ); }); @@ -332,6 +332,11 @@ describe('SapSystemsOverviews component', () => { /> ); + const table = screen.getByRole('table'); + await user.click( + table.querySelector('tbody tr:nth-child(1) td:nth-child(1)') + ); + const cleanUpButton = screen.queryAllByRole('button', { name: 'Clean up', })[row]; @@ -372,6 +377,11 @@ describe('SapSystemsOverviews component', () => { /> ); + const table = screen.getByRole('table'); + await user.click( + table.querySelector('tbody tr:nth-child(1) td:nth-child(1)') + ); + const cleanUpButtons = screen.getAllByRole('button', { name: 'Clean up', }); diff --git a/test/e2e/cypress/e2e/databases_overview.cy.js b/test/e2e/cypress/e2e/databases_overview.cy.js index f12c44e4d4..ecd8734f2a 100644 --- a/test/e2e/cypress/e2e/databases_overview.cy.js +++ b/test/e2e/cypress/e2e/databases_overview.cy.js @@ -14,7 +14,7 @@ context('Databases Overview', () => { describe('Deregistration', () => { beforeEach(() => { databasesOverviewPage.refresh(); - databasesOverviewPage.clickHdqDatabaseRow(); + databasesOverviewPage.expandHdqDatabaseRow(); }); it(`should not display DB ${databasesOverviewPage.hdqDatabase.sid} after deregistering the primary instance`, () => { @@ -43,7 +43,7 @@ context('Databases Overview', () => { describe('Instance deregistration', () => { before(() => { - databasesOverviewPage.clickHddDatabaseRow(); + databasesOverviewPage.expandHddDatabaseRow(); }); beforeEach(() => { diff --git a/test/e2e/cypress/e2e/sap_systems_overview.cy.js b/test/e2e/cypress/e2e/sap_systems_overview.cy.js index 3074cfcdc9..d1c081839c 100644 --- a/test/e2e/cypress/e2e/sap_systems_overview.cy.js +++ b/test/e2e/cypress/e2e/sap_systems_overview.cy.js @@ -98,7 +98,7 @@ context('SAP Systems Overview', () => { beforeEach(() => { sapSystemsOverviewPage.revertMovedScenario(); sapSystemsOverviewPage.systemNwdIsVisible(); - sapSystemsOverviewPage.clickSystemToRemove(); + sapSystemsOverviewPage.expandSystemToRemove(); }); it('should move a clustered application instance', () => { @@ -153,7 +153,7 @@ context('SAP Systems Overview', () => { beforeEach(() => { sapSystemsOverviewPage.restoreNwdHost(); sapSystemsOverviewPage.sapSystemNwdIsDisplayed(); - sapSystemsOverviewPage.clickNwdSapSystem(); + sapSystemsOverviewPage.expandNwdSapSystem(); }); it('should mark an instance as absent and restore it as present on received respective discovery messages', () => { diff --git a/test/e2e/cypress/pageObject/databases_overview_po.js b/test/e2e/cypress/pageObject/databases_overview_po.js index 0b44782e32..1d631a3911 100644 --- a/test/e2e/cypress/pageObject/databases_overview_po.js +++ b/test/e2e/cypress/pageObject/databases_overview_po.js @@ -36,8 +36,10 @@ const hddDatabase = { // Selectors const hdqDatabaseCell = `tr:contains("${hdqDatabase.sid}")`; +const hdqDatabaseRowCollapsibleCell = `${hdqDatabaseCell} > td:eq(0)`; const hddDatabaseCell = `tr:contains("${hddDatabase.sid}")`; +const hddDatabaseRowCollapsibleCell = `${hddDatabaseCell} > td:eq(0)`; const databaseInstance1 = `a:contains("${hdqDatabase.instances[0].name}")`; const databaseInstance2 = `a:contains("${hdqDatabase.instances[1].name}")`; @@ -117,9 +119,11 @@ export const cleanUpButtonIsNotDisplayed = () => { // UI Interactions -export const clickHdqDatabaseRow = () => cy.get(hdqDatabaseCell).click(); +export const expandHdqDatabaseRow = () => + cy.get(hdqDatabaseRowCollapsibleCell).click(); -export const clickHddDatabaseRow = () => cy.get(hddDatabaseCell).click(); +export const expandHddDatabaseRow = () => + cy.get(hddDatabaseRowCollapsibleCell).click(); export const clickCleanUpButton = () => { const cleanUpButtonSelector = getCleanUpButtonByIdAndInstanceIndex( diff --git a/test/e2e/cypress/pageObject/sap_systems_overview_po.js b/test/e2e/cypress/pageObject/sap_systems_overview_po.js index 506b9920e0..0deb6321d6 100644 --- a/test/e2e/cypress/pageObject/sap_systems_overview_po.js +++ b/test/e2e/cypress/pageObject/sap_systems_overview_po.js @@ -54,20 +54,22 @@ const sapSystemNwq = { const instancesData = availableSAPSystems.flatMap((system) => system.instances); // Selectors -const sapSystemsTableRows = 'tbody tr[class*="pointer"]'; +const sapSystemsTableRows = 'tbody tr:nth-child(odd)'; const firstSystemApplicationLayerRows = - 'tbody tr[class*="cursor"]:eq(0) + tr td div[class*="row-group"]:eq(0) div[class*="row border"]'; + 'tbody tr:nth-child(odd):eq(0) + tr td div[class*="row-group"]:eq(0) div[class*="row border"]'; const cleanUpButton = 'button:contains("Clean up")'; -const nwdInstance01CleanUpButton = `tbody tr[class*="pointer"]:eq(0) + tr td div[class*="row-group"]:eq(0) div[class*="row border"]:eq(1) div[class*="cell"]:contains('Clean up')`; -const nwdInstance00CleanUpButton = `tbody tr[class*="pointer"]:eq(0) + tr td div[class*="row-group"]:eq(0) div[class*="row border"]:eq(0) div[class*="cell"]:contains('Clean up')`; +const nwdInstance01CleanUpButton = `tbody tr:eq(0) + tr td div[class*="row-group"]:eq(0) div[class*="row border"]:eq(1) div[class*="cell"]:contains('Clean up')`; +const nwdInstance00CleanUpButton = `tbody tr:eq(0) + tr td div[class*="row-group"]:eq(0) div[class*="row border"]:eq(0) div[class*="cell"]:contains('Clean up')`; const modalCleanUpConfirmationButton = 'div[id*="headlessui-dialog-panel"] button:contains("Clean up")'; const addTagButton = 'span:contains("Add Tag")'; const existentEnv3Tag = '[data-test-id="tag-env3"]'; const instancesRowsSelector = - 'tr[class*="cursor"] + tr div[class*="row-group"] div[class*="row border"]'; + 'tr:nth-child(odd) + tr div[class*="row-group"] div[class*="row border"]'; const hanaClusterLinks = 'div[class*="cell"] a span:contains("hana")'; const instanceHostLinks = 'div[class*="cell"] a[href*="/host"]'; +const systemToRemoveCollapsibleCell = `${sapSystemsTableRows}:eq(0) td:first-child`; +const nwdSystemRowCollapsibleCell = `tr:contains('${sapSystemNwd.sid}') > td:eq(0)`; // UI Interactions export const visit = () => { @@ -82,11 +84,11 @@ export const tagSapSystems = () => { }); }; -export const clickSystemToRemove = () => - cy.get(`${sapSystemsTableRows}:eq(0)`).click(); +export const expandSystemToRemove = () => + cy.get(systemToRemoveCollapsibleCell).click(); -export const clickNwdSapSystem = () => - cy.get(`tr:contains('${sapSystemNwd.sid}')`).click(); +export const expandNwdSapSystem = () => + cy.get(nwdSystemRowCollapsibleCell).click(); export const clickNwdInstance01CleanUpButton = () => cy.get(nwdInstance01CleanUpButton).click(); @@ -98,7 +100,9 @@ export const clickCleanUpModalConfirmationButton = () => cy.get(modalCleanUpConfirmationButton).click(); const clickAllRows = () => - cy.get(sapSystemsTableRows).each((row) => cy.wrap(row).click()); + cy + .get(`${sapSystemsTableRows} td:first-child`) + .each((cell) => cy.wrap(cell).click()); // UI Validations export const nwdInstance01CleanUpButtonIsVisible = () => @@ -132,24 +136,24 @@ export const eachSystemHasItsExpectedWorkingLink = () => { export const eachSystemHasExpectedHealth = () => { availableSAPSystems.forEach(({ health: health }, index) => { const healthClass = healthMap[health]; - const healthCellSelector = `tbody tr:eq(${index}) td:eq(0) svg`; + const healthCellSelector = `tbody tr:nth-child(odd):eq(${index}) td:eq(1) svg`; cy.get(healthCellSelector).should('have.class', healthClass); }); }; export const eachAttachedDatabaseDetailsAreTheExpected = () => { const tableCell = (rowIndex, columnIndex) => - `tbody tr[class*="cursor"]:eq(${rowIndex}) td:eq(${columnIndex})`; + `tbody tr:nth-child(odd):eq(${rowIndex}) td:eq(${columnIndex})`; availableSAPSystems.forEach( ({ attachedDatabase: attachedDatabase, type: type }, rowIndex) => { - cy.get(tableCell(rowIndex, 2)).should('have.text', attachedDatabase.sid); - cy.get(tableCell(rowIndex, 3)).should( + cy.get(tableCell(rowIndex, 3)).should('have.text', attachedDatabase.sid); + cy.get(tableCell(rowIndex, 4)).should( 'have.text', attachedDatabase.tenant ); - cy.get(tableCell(rowIndex, 4)).should('have.text', type); - cy.get(tableCell(rowIndex, 5)).should( + cy.get(tableCell(rowIndex, 5)).should('have.text', type); + cy.get(tableCell(rowIndex, 6)).should( 'have.text', attachedDatabase.dbAddress ); @@ -186,7 +190,7 @@ export const nwpSystemIsNotDisplayed = () => export const eachSystemHasItsDatabaseWorkingLink = () => { availableSAPSystems.forEach( ({ attachedDatabase: attachedDatabase }, index) => { - const databaseSidLink = `tr[class*="cursor"]:eq(${index}) td:contains("${attachedDatabase.sid}") a`; + const databaseSidLink = `tbody > tr:nth-child(odd):eq(${index}) td:contains("${attachedDatabase.sid}") a`; cy.get(databaseSidLink).click(); validateUrl(`/databases/${attachedDatabase.id}`); cy.go('back'); @@ -265,7 +269,7 @@ export const eachInstanceHasItsHostWorkingLinkg = () => { export const javaSystemIsDiscoveredCorrectly = () => { const javaSystemRowSelector = `tbody tr:contains('${availableJavaSystem.sid}')`; cy.get(javaSystemRowSelector).should('be.visible'); - const javaSystemTypeSelector = `${javaSystemRowSelector} td:eq(4)`; + const javaSystemTypeSelector = `${javaSystemRowSelector} td:eq(5)`; cy.get(javaSystemTypeSelector).should('have.text', availableJavaSystem.type); tableDisplaysExpectedAmountOfSystems(4); }; @@ -275,12 +279,13 @@ export const tableDisplaysExpectedAmountOfSystems = (systemsAmount) => export const eachInstanceHasItsHealthStatusCorrectlyUpdated = () => { const sapSystemsFirstRow = `${sapSystemsTableRows}:eq(0)`; - cy.get(sapSystemsFirstRow).click(); + const collapsibleCell = `${sapSystemsFirstRow} > td:eq(0)`; + cy.get(collapsibleCell).click(); Object.entries(healthMap).forEach(([state, health], index) => { basePage.loadScenario(`sap-systems-overview-${state}`); - const sapSystemInstanceHealthBadge = `${sapSystemsFirstRow} td:eq(0) svg`; + const sapSystemInstanceHealthBadge = `${sapSystemsFirstRow} td:eq(1) svg`; cy.get(sapSystemInstanceHealthBadge).should('have.class', health); const appLayerInstanceHealthBadge = `${sapSystemsFirstRow} + tr td div[class*="row border"]:eq(${ @@ -296,9 +301,10 @@ export const sapSystemHealthChangesToRedAsExpected = () => { const healthClass = healthMap['RED']; const sapSystemsFirstRow = `${sapSystemsTableRows}:eq(0)`; - cy.get(sapSystemsFirstRow).click(); + const collapsibleCell = `${sapSystemsFirstRow} > td:eq(0)`; + cy.get(collapsibleCell).click(); - const sapSystemInstanceHealthBadge = `${sapSystemsFirstRow} td:eq(0) svg`; + const sapSystemInstanceHealthBadge = `${sapSystemsFirstRow} td:eq(1) svg`; cy.get(sapSystemInstanceHealthBadge).should('have.class', healthClass); const appLayerInstanceHealthBadge =