From 433456986dc7a0e738e33c2790126a3df2716e58 Mon Sep 17 00:00:00 2001 From: James Read Date: Sun, 22 Jun 2025 22:09:03 +0100 Subject: [PATCH] fix: #603 - Dashboards that have fieldsets without entities are hidden (#609) --- .../dashboardsWithBasicFieldsets/config.yaml | 26 +++++++++ integration-tests/lib/elements.js | 58 ++++++++++++++++++- .../test/dashboardsWithBasicFieldsets.js | 50 ++++++++++++++++ .../test/emptyDashboardsAreHidden.js | 12 ++-- integration-tests/test/general.mjs | 2 +- integration-tests/test/multipleDropdowns.js | 2 +- integration-tests/test/onlyDashboards.mjs | 51 ++++++++++++++++ service/internal/grpcapi/grpcApiDashboard.go | 6 +- 8 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 integration-tests/configs/dashboardsWithBasicFieldsets/config.yaml create mode 100644 integration-tests/test/dashboardsWithBasicFieldsets.js create mode 100644 integration-tests/test/onlyDashboards.mjs diff --git a/integration-tests/configs/dashboardsWithBasicFieldsets/config.yaml b/integration-tests/configs/dashboardsWithBasicFieldsets/config.yaml new file mode 100644 index 0000000..79304cc --- /dev/null +++ b/integration-tests/configs/dashboardsWithBasicFieldsets/config.yaml @@ -0,0 +1,26 @@ +logLevel: debug + +actions: + - title: Ping + + - title: Action 1 + - title: Action 2 + + - title: Action 3 + - title: Action 4 + +dashboards: + - title: Test + contents: + # Uncomment to see the dashboard with the "Ping" action only + - title: Ping + - title: Fieldset 1 + type: fieldset + contents: + - title: Action 1 + - title: Action 2 + - title: Fieldset 2 + type: fieldset + contents: + - title: Action 3 + - title: Action 4 diff --git a/integration-tests/lib/elements.js b/integration-tests/lib/elements.js index 65addc7..705cbcc 100644 --- a/integration-tests/lib/elements.js +++ b/integration-tests/lib/elements.js @@ -3,8 +3,12 @@ import fs from 'fs' import { expect } from 'chai' import { Condition } from 'selenium-webdriver' -export async function getActionButtons (webdriver) { - return await webdriver.findElement(By.id('contentActions')).findElements(By.tagName('button')) +export async function getActionButtons (dashboardTitle = null) { + if (dashboardTitle == null) { + return await webdriver.findElement(By.id('contentActions')).findElements(By.tagName('button')) + } else { + return await webdriver.findElements(By.css('section[title="' + dashboardTitle + '"] button')) + } } export function takeScreenshotOnFailure (test, webdriver) { @@ -41,6 +45,56 @@ export async function getRootAndWait() { })) } +export async function closeSidebar() { + await webdriver.findElement(By.id('sidebar-toggler-button')).click() + + const sidebar = await webdriver.findElement(By.id('mainnav')) + + const neededLeft = '-250px' // Assuming sidebar is closed at this position + + let lastLeft = '' + + await webdriver.wait(new Condition('wait for sidebar to close', async function() { + const left = await sidebar.getCssValue('left') + + if (left !== lastLeft) { + lastLeft = left + console.log('Sidebar left changed to: ', left) + return false + } else { + console.log('Sidebar closed, left is: *' + left, left === neededLeft ? ' (as expected)' : '') + return left === neededLeft + } + }), 10000); // Wait up to 10 seconds for the sidebar to close +} + +export async function openSidebar() { + await webdriver.findElement(By.id('sidebar-toggler-button')).click() + + const sidebar = await webdriver.findElement(By.id('mainnav')) + + let lastLeft = 0 + + await webdriver.wait(new Condition('wait for sidebar to open', async function() { + const left = await sidebar.getCssValue('left') + + if (left !== lastLeft) { + lastLeft = left + console.log('Sidebar left changed to: ', left) + return false + } else { + console.log('Sidebar opened, left is: ', left) + return true + } + })); +} + +export async function getNavigationLinks() { + const navigationLinks = await webdriver.findElements(By.css('#navigation-links a')) + + return navigationLinks +} + export async function requireExecutionDialogStatus (webdriver, expected) { // It seems that webdriver will not give us text if domStatus is hidden (which it will be until complete) await webdriver.executeScript('window.executionDialog.domExecutionDetails.hidden = false') diff --git a/integration-tests/test/dashboardsWithBasicFieldsets.js b/integration-tests/test/dashboardsWithBasicFieldsets.js new file mode 100644 index 0000000..73e9aaa --- /dev/null +++ b/integration-tests/test/dashboardsWithBasicFieldsets.js @@ -0,0 +1,50 @@ +import { describe, it, before, after } from 'mocha' +import { expect } from 'chai' +import { By, until, Condition } from 'selenium-webdriver' +//import * as waitOn from 'wait-on' +import { + getRootAndWait, + getActionButtons, + openSidebar, + getNavigationLinks, + takeScreenshotOnFailure, +} from '../lib/elements.js' + +describe('config: dashboards with basic fieldsets', function () { + before(async function () { + await runner.start('dashboardsWithBasicFieldsets') + }) + + after(async () => { + await runner.stop() + }) + + afterEach(function () { + takeScreenshotOnFailure(this.currentTest, webdriver); + }); + + it('Dashboards with basic fieldsets', async function () { + await getRootAndWait() + + const title = await webdriver.getTitle() + expect(title).to.be.equal("OliveTin ยป Test") + + const navigationLinks = await getNavigationLinks() + expect(navigationLinks.length).to.be.equal(2, 'Expected the nav to only have 2 links') + + const firstLink = await navigationLinks[0] + + expect(await firstLink.getAttribute('id')).to.be.equal('showActions', 'Expected the first link to be the actions link') + expect(await firstLink.isDisplayed()).to.be.false + + const secondLink = await navigationLinks[1] + expect(await secondLink.getAttribute('href')).to.be.equal('http://localhost:1337/Test', 'Expected the second link to be the test dashboard with basic fieldsets link') + + const actionButtons = await getActionButtons('Test') + expect(actionButtons).to.have.length(5, 'Expected 5 action buttons') + + const fieldsets = await webdriver.findElements(By.css('section[title="Test"] fieldset')) + expect(fieldsets).to.have.length(3, 'Expected 3 fieldsets in the Test dashboard') + + }) +}) diff --git a/integration-tests/test/emptyDashboardsAreHidden.js b/integration-tests/test/emptyDashboardsAreHidden.js index fa6f01d..a56935d 100644 --- a/integration-tests/test/emptyDashboardsAreHidden.js +++ b/integration-tests/test/emptyDashboardsAreHidden.js @@ -4,6 +4,8 @@ import { By, until, Condition } from 'selenium-webdriver' //import * as waitOn from 'wait-on' import { getRootAndWait, + openSidebar, + getNavigationLinks, takeScreenshotOnFailure, } from '../lib/elements.js' @@ -26,17 +28,15 @@ describe('config: empty dashboards are hidden', function () { const title = await webdriver.getTitle() expect(title).to.be.equal("OliveTin") - await webdriver.findElement(By.id('sidebar-toggler-button')).click() - - const navigationLinks = await webdriver.findElements(By.css('#navigation-links a')) - - console.log('navigationLinks', navigationLinks) + await openSidebar() + const navigationLinks = await getNavigationLinks() expect(navigationLinks).to.not.be.empty expect(navigationLinks.length).to.be.equal(1, 'Expected the nav to only have 1 link') - + const firstLinkId = await navigationLinks[0].getAttribute('id') expect(firstLinkId).to.be.equal('showActions', 'Expected the first link to be the actions link') + }) }) diff --git a/integration-tests/test/general.mjs b/integration-tests/test/general.mjs index 36cb37b..f776757 100644 --- a/integration-tests/test/general.mjs +++ b/integration-tests/test/general.mjs @@ -56,7 +56,7 @@ describe('config: general', function () { it('Default buttons are rendered', async function() { await getRootAndWait() - const buttons = await getActionButtons(webdriver) + const buttons = await getActionButtons() expect(buttons).to.have.length(8) }) diff --git a/integration-tests/test/multipleDropdowns.js b/integration-tests/test/multipleDropdowns.js index 7dba75d..21a4ef6 100644 --- a/integration-tests/test/multipleDropdowns.js +++ b/integration-tests/test/multipleDropdowns.js @@ -24,7 +24,7 @@ describe('config: multipleDropdowns', function () { it('Multiple dropdowns are possible', async function() { await getRootAndWait() - const buttons = await getActionButtons(webdriver) + const buttons = await getActionButtons() let button = null for (const b of buttons) { diff --git a/integration-tests/test/onlyDashboards.mjs b/integration-tests/test/onlyDashboards.mjs new file mode 100644 index 0000000..3e31dd6 --- /dev/null +++ b/integration-tests/test/onlyDashboards.mjs @@ -0,0 +1,51 @@ +import { describe, it, before, after } from 'mocha' +import { assert } from 'chai' +import { By } from 'selenium-webdriver' +import { + getRootAndWait, + getActionButtons, + getNavigationLinks, + openSidebar, + closeSidebar, + takeScreenshotOnFailure, +} from '../lib/elements.js' + +describe('config: onlyDashboards', function () { + before(async function () { + await runner.start('onlyDashboards') + }) + + after(async () => { + await runner.stop() + }) + + afterEach(function () { + takeScreenshotOnFailure(this.currentTest, webdriver); + }); + + it('When there are only dashboards, actions are hidden', async function () { + await getRootAndWait() + + await openSidebar() + + const navLinks = await getNavigationLinks() + + const actionsLink = navLinks[0]; + assert.isNotNull(actionsLink, 'Actions link should not be null') + assert.equal(await actionsLink.getAttribute('title'), 'Actions', 'Actions link should have the title "Actions"') + assert.isFalse(await actionsLink.isDisplayed(), 'Actions link should not be displayed when there are only dashboards') + + const firstDashboardLink = await webdriver.findElement(By.css('li[title="My Dashboard"]'), 'The first dashboard link should be present') + assert.isNotNull(firstDashboardLink, 'First dashboard link should not be null') + assert.isTrue(await firstDashboardLink.isDisplayed(), 'First dashboard link should be displayed') + + const actionButtons = await getActionButtons() + + assert.isArray(actionButtons, 'Action buttons should be an array') + assert.lengthOf(actionButtons, 0, 'Action buttons should be empty when everything is added to the dashboard') + + const actionButtonsOnDashboard = await getActionButtons('MyDashboard') + assert.isArray(actionButtonsOnDashboard, 'Action buttons on dashboard should be an array') + assert.lengthOf(actionButtonsOnDashboard, 3, 'Action buttons on dashboard should have 3 buttons') + }) +}) diff --git a/service/internal/grpcapi/grpcApiDashboard.go b/service/internal/grpcapi/grpcApiDashboard.go index 009ff2a..b40c0ab 100644 --- a/service/internal/grpcapi/grpcApiDashboard.go +++ b/service/internal/grpcapi/grpcApiDashboard.go @@ -60,8 +60,10 @@ func getDashboardComponentContents(dashboard *config.DashboardComponent, rr *Das } func buildDashboardComponentSimple(subitem *config.DashboardComponent, rr *DashboardRenderRequest) *apiv1.DashboardComponent { - if !slices.Contains(rr.AllowedActionTitles, subitem.Title) { - return nil + if subitem.Type == "" || subitem.Type == "link" { + if !slices.Contains(rr.AllowedActionTitles, subitem.Title) { + return nil + } } newitem := &apiv1.DashboardComponent{