Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): service tags #522

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"tabWidth": 2,
"useTabs": true,
"singleQuote": true,
"trailingComma": "all",
"semi": true
"tabWidth": 2,
"useTabs": true,
"singleQuote": true,
"trailingComma": "all",
"semi": true
}
2 changes: 1 addition & 1 deletion config/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func testServiceURL(id string) *service.Service {
LatestVersion: lv,
DeployedVersionLookup: dv,
Dashboard: *service.NewDashboardOptions(
test.BoolPtr(false), "test", "https://release-argus.io", "https://release-argus.io/docs",
test.BoolPtr(false), "test", "https://release-argus.io", "https://release-argus.io/docs", nil,
&service.DashboardOptionsDefaults{}, &service.DashboardOptionsDefaults{}),
Options: *options,
Status: *status.New(
Expand Down
92 changes: 89 additions & 3 deletions service/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
package service

import (
"encoding/json"
"errors"
"fmt"

"github.com/release-argus/Argus/util"
"gopkg.in/yaml.v3"
)

// DashboardOptionsBase are the base options for the Dashboard.
Expand All @@ -44,9 +47,10 @@ func NewDashboardOptionsDefaults(
type DashboardOptions struct {
DashboardOptionsBase `yaml:",inline" json:",inline"`

Icon string `yaml:"icon,omitempty" json:"icon,omitempty"` // Icon URL to use for messages/Web UI.
IconLinkTo string `yaml:"icon_link_to,omitempty" json:"icon_link_to,omitempty"` // URL to redirect Icon clicks to.
WebURL string `yaml:"web_url,omitempty" json:"web_url,omitempty"` // URL to provide on the Web UI.
Icon string `yaml:"icon,omitempty" json:"icon,omitempty"` // Icon URL to use for messages/Web UI.
IconLinkTo string `yaml:"icon_link_to,omitempty" json:"icon_link_to,omitempty"` // URL to redirect Icon clicks to.
WebURL string `yaml:"web_url,omitempty" json:"web_url,omitempty"` // URL to provide on the Web UI.
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"` // Tags for the Service.

Defaults *DashboardOptionsDefaults `yaml:"-" json:"-"` // Defaults.
HardDefaults *DashboardOptionsDefaults `yaml:"-" json:"-"` // Hard defaults.
Expand All @@ -58,6 +62,7 @@ func NewDashboardOptions(
icon string,
iconLinkTo string,
webURL string,
tags []string,
defaults, hardDefaults *DashboardOptionsDefaults,
) *DashboardOptions {
return &DashboardOptions{
Expand All @@ -66,10 +71,91 @@ func NewDashboardOptions(
Icon: icon,
IconLinkTo: iconLinkTo,
WebURL: webURL,
Tags: tags,
Defaults: defaults,
HardDefaults: hardDefaults}
}

// UnmarshalJSON handles the unmarshalling of a DashboardOptions.
func (d *DashboardOptions) UnmarshalJSON(data []byte) error {
aux := &struct {
*DashboardOptionsBase `json:",inline"`

Icon *string `json:"icon,omitempty"` // Icon URL to use for messages/Web UI.
IconLinkTo *string `json:"icon_link_to,omitempty"` // URL to redirect Icon clicks to.
WebURL *string `json:"web_url,omitempty"` // URL to provide on the Web UI.
Tags json.RawMessage `json:"tags,omitempty"` // Tags for the Service.
}{
DashboardOptionsBase: &d.DashboardOptionsBase,
Icon: &d.Icon,
IconLinkTo: &d.IconLinkTo,
WebURL: &d.WebURL,
}

// Unmarshal into aux.
if err := json.Unmarshal(data, &aux); err != nil {
return fmt.Errorf("failed to unmarshal DashboardOptions:\n%w", err)
}

// Tags
if len(aux.Tags) > 0 {
var tagsAsString string
var tagsAsArray []string

// Try to unmarshal as a list of strings
if err := json.Unmarshal(aux.Tags, &tagsAsArray); err == nil {
d.Tags = tagsAsArray
// Try to unmarshal as a single string
} else if err := json.Unmarshal(aux.Tags, &tagsAsString); err == nil {
d.Tags = []string{tagsAsString}
} else {
return errors.New("error in tags field:\ntype: <invalid> (expected string or list of strings)")
}
}

return nil
}

// UnmarshalYAML handles the unmarshalling of a DashboardOptions.
func (d *DashboardOptions) UnmarshalYAML(value *yaml.Node) error {
aux := &struct {
*DashboardOptionsBase `yaml:",inline"`

Icon *string `yaml:"icon,omitempty"` // Icon URL to use for messages/Web UI.
IconLinkTo *string `yaml:"icon_link_to,omitempty"` // URL to redirect Icon clicks to.
WebURL *string `yaml:"web_url,omitempty"` // URL to provide on the Web UI.
Tags util.RawNode `yaml:"tags,omitempty"` // Tags for the Service.
}{
DashboardOptionsBase: &d.DashboardOptionsBase,
Icon: &d.Icon,
IconLinkTo: &d.IconLinkTo,
WebURL: &d.WebURL,
}

// Unmarshal into aux.
if err := value.Decode(&aux); err != nil {
return fmt.Errorf("failed to unmarshal DashboardOptions:\n%w", err)
}

// Tags
if aux.Tags.Node != nil {
var tagsAsString string
var tagsAsArray []string

// Try to unmarshal as a list of strings
if err := aux.Tags.Decode(&tagsAsArray); err == nil {
d.Tags = tagsAsArray
// Try to unmarshal as a single string
} else if err := aux.Tags.Decode(&tagsAsString); err == nil {
d.Tags = []string{tagsAsString}
} else {
return errors.New("error in tags field:\ntype: <invalid> (expected string or list of strings)")
}
}

return nil
}

// GetAutoApprove returns whether new releases are auto-approved.
func (d *DashboardOptions) GetAutoApprove() bool {
return *util.FirstNonDefault(
Expand Down
215 changes: 215 additions & 0 deletions service/dashboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,223 @@ import (

"github.com/release-argus/Argus/test"
"github.com/release-argus/Argus/util"
"gopkg.in/yaml.v3"
)

func TestNewDashboardOptions(t *testing.T) {
// GIVEN a set of input values
tests := map[string]struct {
autoApprove *bool
icon string
iconLinkTo string
webURL string
tags []string
defaults *DashboardOptionsDefaults
hardDefaults *DashboardOptionsDefaults
want *DashboardOptions
}{
"all fields set": {
autoApprove: test.BoolPtr(true),
icon: "icon-url",
iconLinkTo: "icon-link",
webURL: "web-url",
tags: []string{"tag1", "tag2"},
defaults: &DashboardOptionsDefaults{DashboardOptionsBase: DashboardOptionsBase{AutoApprove: test.BoolPtr(false)}},
hardDefaults: &DashboardOptionsDefaults{DashboardOptionsBase: DashboardOptionsBase{AutoApprove: test.BoolPtr(false)}},
want: &DashboardOptions{
DashboardOptionsBase: DashboardOptionsBase{AutoApprove: test.BoolPtr(true)},
Icon: "icon-url",
IconLinkTo: "icon-link",
WebURL: "web-url",
Tags: []string{"tag1", "tag2"},
Defaults: &DashboardOptionsDefaults{DashboardOptionsBase: DashboardOptionsBase{AutoApprove: test.BoolPtr(false)}},
HardDefaults: &DashboardOptionsDefaults{DashboardOptionsBase: DashboardOptionsBase{AutoApprove: test.BoolPtr(false)}},
},
},
"defaults": {
autoApprove: nil,
icon: "",
iconLinkTo: "",
webURL: "",
tags: nil,
defaults: nil,
hardDefaults: nil,
want: &DashboardOptions{
DashboardOptionsBase: DashboardOptionsBase{AutoApprove: nil},
Icon: "",
IconLinkTo: "",
WebURL: "",
Tags: nil,
Defaults: nil,
HardDefaults: nil,
},
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

// WHEN NewDashboardOptions is called with them.
got := NewDashboardOptions(tc.autoApprove, tc.icon, tc.iconLinkTo, tc.webURL, tc.tags, tc.defaults, tc.hardDefaults)

// THEN the result is as expected.
gotStr := util.ToJSONString(got)
wantStr := util.ToJSONString(tc.want)
if gotStr != wantStr {
t.Errorf("NewDashboardOptions() result mismatch\n%q\ngot:\n%v",
wantStr, gotStr)
}
})
}
}

func TestDashboardOptions_UnmarshalJSON(t *testing.T) {
// GIVEN a JSON string that represents a DashboardOptions.
tests := map[string]struct {
jsonData string
errRegex string
want *DashboardOptions
}{
"invalid json": {
jsonData: `{invalid: json}`,
errRegex: test.TrimYAML(`
failed to unmarshal DashboardOptions:
invalid character.*$`),
want: &DashboardOptions{},
},
"tags - []string": {
jsonData: `{
"tags": [
"foo",
"bar"
]
}`,
errRegex: `^$`,
want: &DashboardOptions{
Tags: []string{"foo", "bar"},
},
},
"tags - string": {
jsonData: `{
"tags": "foo"
}`,
errRegex: `^$`,
want: &DashboardOptions{
Tags: []string{"foo"},
},
},
"tags - invalid": {
jsonData: `{
"tags": {
"foo": "bar"
}
}`,
errRegex: test.TrimYAML(`
^error in tags field:
type: <invalid>.*$`),
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

// Default to an empty DashboardOptions.
dashboardOptions := &DashboardOptions{}

// WHEN the JSON is unmarshalled into a DashboardOptions.
err := dashboardOptions.UnmarshalJSON([]byte(test.TrimJSON(tc.jsonData)))

// THEN the error is as expected.
e := util.ErrorToString(err)
if !util.RegexCheck(tc.errRegex, e) {
t.Errorf("DashboardOptions.UnmarshalJSON() error mismatch\nwant: %q\ngot: %q",
tc.errRegex, e)
}
// AND the result is as expected.
gotString := util.ToJSONString(dashboardOptions)
wantString := util.ToJSONString(tc.want)
if tc.want != nil && gotString != wantString {
t.Errorf("DashboardOptions.UnmarshalJSON() result mismatch\n%q\ngot:\n%q",
wantString, gotString)
}
})
}
}

func TestDashboardOptions_UnmarshalYAML(t *testing.T) {
tests := map[string]struct {
yamlData string
errRegex string
want *DashboardOptions
}{
"invalid yaml": {
yamlData: `invalid yaml`,
errRegex: test.TrimYAML(`
failed to unmarshal DashboardOptions:
yaml: unmarshal errors:
.*cannot unmarshal.*$`),
want: &DashboardOptions{},
},
"tags - []string": {
yamlData: `
tags:
- foo
- bar
`,
errRegex: `^$`,
want: &DashboardOptions{
Tags: []string{"foo", "bar"},
},
},
"tags - string": {
yamlData: `
tags: foo
`,
errRegex: `^$`,
want: &DashboardOptions{
Tags: []string{"foo"},
},
},
"tags - invalid": {
yamlData: `
tags:
foo: bar
`,
errRegex: test.TrimYAML(`
^error in tags field:
type: <invalid>.*$`),
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

// Default to an empty DashboardOptions.
dashboardOptions := &DashboardOptions{}

// WHEN the YAML is unmarshalled into a DashboardOptions
err := yaml.Unmarshal([]byte(test.TrimYAML(tc.yamlData)), &dashboardOptions)

// THEN the error is as expected
e := util.ErrorToString(err)
if !util.RegexCheck(tc.errRegex, e) {
t.Errorf("DashboardOptions.UnmarshalYAML() error mismatch\nwant: %q\ngot: %q",
tc.errRegex, e)
}
// AND the result is as expected
gotStr := util.ToJSONString(dashboardOptions)
wantStr := util.ToJSONString(tc.want)
if tc.want != nil && gotStr != wantStr {
t.Errorf("DashboardOptions.UnmarshalYAML() result mismatch\nwant: %s\ngot: %s",
wantStr, gotStr)
}
})
}
}

func TestDashboardOptions_GetAutoApprove(t *testing.T) {
// GIVEN a DashboardOptions
tests := map[string]struct {
Expand Down
2 changes: 1 addition & 1 deletion service/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func testService(t *testing.T, id string, sType string) *Service {
LatestVersion: testLatestVersion(t, sType, false),
DeployedVersionLookup: testDeployedVersionLookup(t, false),
Dashboard: *NewDashboardOptions(
test.BoolPtr(false), "test", "https://release-argus.io", "https://release-argus.io",
test.BoolPtr(false), "test", "https://release-argus.io", "https://release-argus.io", nil,
&DashboardOptionsDefaults{}, &DashboardOptionsDefaults{}),
Status: *testStatus(),
Options: *testOptions(),
Expand Down
2 changes: 1 addition & 1 deletion service/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (s *Service) Init(

// Notify/
// use defaults?
if s.Notify == nil && len(defaults.Notify) != 0 {
if len(s.Notify) == 0 && len(defaults.Notify) != 0 {
s.Notify = make(shoutrrr.Slice, len(defaults.Notify))
for key := range defaults.Notify {
s.Notify[key] = &shoutrrr.Shoutrrr{}
Expand Down
Loading
Loading