feat: inline actions on dashboards
This commit is contained in:
parent
2e82a3d1ed
commit
e58677e12c
|
|
@ -0,0 +1,21 @@
|
||||||
|
#
|
||||||
|
# Integration Test Config: inline dashboard actions
|
||||||
|
#
|
||||||
|
|
||||||
|
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||||
|
|
||||||
|
logLevel: "DEBUG"
|
||||||
|
checkForUpdates: false
|
||||||
|
|
||||||
|
# No top-level actions – actions will be defined inline on dashboard components.
|
||||||
|
actions: []
|
||||||
|
|
||||||
|
dashboards:
|
||||||
|
- title: Inline Dashboard
|
||||||
|
contents:
|
||||||
|
- title: Inline Dashboard Action
|
||||||
|
inlineAction:
|
||||||
|
shell: date
|
||||||
|
icon: clock
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { describe, it, before, after } from 'mocha'
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import {
|
||||||
|
getRootAndWait,
|
||||||
|
getActionButtons,
|
||||||
|
takeScreenshotOnFailure,
|
||||||
|
} from '../../lib/elements.js'
|
||||||
|
|
||||||
|
describe('config: inlineActions', function () {
|
||||||
|
before(async function () {
|
||||||
|
await runner.start('inlineActions')
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await runner.stop()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
takeScreenshotOnFailure(this.currentTest, webdriver);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Inline dashboard actions are rendered as clickable buttons', async function () {
|
||||||
|
await getRootAndWait()
|
||||||
|
|
||||||
|
const buttons = await getActionButtons()
|
||||||
|
assert.isArray(buttons, 'Action buttons should be an array')
|
||||||
|
assert.isAtLeast(buttons.length, 1, 'There should be at least one action button')
|
||||||
|
|
||||||
|
const texts = await Promise.all(buttons.map(b => b.getText()))
|
||||||
|
const combinedText = texts.join(' ')
|
||||||
|
|
||||||
|
assert.include(
|
||||||
|
combinedText,
|
||||||
|
'Inline Dashboard Action',
|
||||||
|
'Inline dashboard action should be rendered as a button'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -207,12 +207,13 @@ type LogDebugOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type DashboardComponent struct {
|
type DashboardComponent struct {
|
||||||
Title string `koanf:"title"`
|
Title string `koanf:"title"`
|
||||||
Type string `koanf:"type"`
|
Type string `koanf:"type"`
|
||||||
Entity string `koanf:"entity"`
|
Entity string `koanf:"entity"`
|
||||||
Icon string `koanf:"icon"`
|
Icon string `koanf:"icon"`
|
||||||
CssClass string `koanf:"cssClass"`
|
CssClass string `koanf:"cssClass"`
|
||||||
Contents []*DashboardComponent `koanf:"contents"`
|
InlineAction *Action `koanf:"inlineAction"`
|
||||||
|
Contents []*DashboardComponent `koanf:"contents"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,82 @@ func (cfg *Config) Sanitize() {
|
||||||
for idx := range cfg.Actions {
|
for idx := range cfg.Actions {
|
||||||
cfg.Actions[idx].sanitize(cfg)
|
cfg.Actions[idx].sanitize(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg.sanitizeDashboardsForInlineActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) sanitizeDashboardsForInlineActions() {
|
||||||
|
for _, dashboard := range cfg.Dashboards {
|
||||||
|
cfg.sanitizeDashboardComponentForInlineActions(dashboard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (cfg *Config) sanitizeDashboardComponentForInlineActions(component *DashboardComponent) {
|
||||||
|
if component == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.sanitizeInlineAction(component)
|
||||||
|
cfg.sanitizeChildDashboardComponents(component)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) sanitizeInlineAction(component *DashboardComponent) {
|
||||||
|
if component.InlineAction == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if component.InlineAction.Title == "" {
|
||||||
|
component.InlineAction.Title = component.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
component.InlineAction.sanitize(cfg)
|
||||||
|
|
||||||
|
if cfg.inlineActionExists(component.InlineAction) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Actions = append(cfg.Actions, component.InlineAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) inlineActionExists(action *Action) bool {
|
||||||
|
if cfg.inlineActionPointerExists(action) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.inlineActionIDExists(action) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) inlineActionPointerExists(action *Action) bool {
|
||||||
|
for _, existingAction := range cfg.Actions {
|
||||||
|
if existingAction == action {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) inlineActionIDExists(action *Action) bool {
|
||||||
|
if action.ID == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, existingAction := range cfg.Actions {
|
||||||
|
if existingAction.ID == action.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) sanitizeChildDashboardComponents(component *DashboardComponent) {
|
||||||
|
for _, child := range component.Contents {
|
||||||
|
cfg.sanitizeDashboardComponentForInlineActions(child)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) sanitizeLogLevel() {
|
func (cfg *Config) sanitizeLogLevel() {
|
||||||
|
|
|
||||||
|
|
@ -36,3 +36,39 @@ func TestSanitizeConfig(t *testing.T) {
|
||||||
assert.Equal(t, "Carrots", a2.Arguments[0].Title, "Arg title is set to name")
|
assert.Equal(t, "Carrots", a2.Arguments[0].Title, "Arg title is set to name")
|
||||||
assert.Equal(t, "Waffle", a2.Arguments[0].Choices[0].Title, "Choice title is set to name")
|
assert.Equal(t, "Waffle", a2.Arguments[0].Choices[0].Title, "Choice title is set to name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSanitizeConfigInlineDashboardActions(t *testing.T) {
|
||||||
|
c := DefaultConfig()
|
||||||
|
|
||||||
|
inline := &Action{
|
||||||
|
Shell: "date",
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardActionTitle := "Inline Dashboard Action"
|
||||||
|
|
||||||
|
c.Dashboards = []*DashboardComponent{
|
||||||
|
{
|
||||||
|
Title: "My Dashboard",
|
||||||
|
Contents: []*DashboardComponent{
|
||||||
|
{
|
||||||
|
Title: dashboardActionTitle,
|
||||||
|
InlineAction: inline,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Sanitize()
|
||||||
|
|
||||||
|
// Inline action should have been appended to the global Actions slice.
|
||||||
|
assert.GreaterOrEqual(t, len(c.Actions), 1, "At least one action should exist after sanitization")
|
||||||
|
|
||||||
|
// It should be discoverable by the dashboard component title when no explicit title was set.
|
||||||
|
found := c.findAction(dashboardActionTitle)
|
||||||
|
if assert.NotNil(t, found, "Inline dashboard action should be discoverable by title") {
|
||||||
|
assert.Equal(t, dashboardActionTitle, found.Title, "Inline action title should default from dashboard component title")
|
||||||
|
assert.Equal(t, 3, found.Timeout, "Inline action should have default timeout applied")
|
||||||
|
assert.NotEmpty(t, found.Icon, "Inline action should have default icon applied")
|
||||||
|
assert.NotEmpty(t, found.ID, "Inline action should have a generated ID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue