From 5764669fdd57635e6b5ba25aea37beed256c4284 Mon Sep 17 00:00:00 2001 From: Davide Bizzarri Date: Fri, 14 Jan 2022 12:20:37 +0100 Subject: [PATCH 1/3] Add kind Dashboard to yaml defintion --- pkg/rpdac/dashboard.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pkg/rpdac/dashboard.go b/pkg/rpdac/dashboard.go index 5ea03c8..6f712b8 100644 --- a/pkg/rpdac/dashboard.go +++ b/pkg/rpdac/dashboard.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "log" "strings" "github.com/b1zzu/reportportal-dashboards-as-code/pkg/reportportal" @@ -14,6 +15,7 @@ import ( type Dashboard struct { Name string `json:"name"` + Kind string `json:"kind"` Widgets []*Widget `json:"widgets"` } @@ -44,7 +46,7 @@ type WidgetContentParameters struct { } func ToDashboard(d *reportportal.Dashboard, widgets []*Widget) *Dashboard { - return &Dashboard{Name: d.Name, Widgets: widgets} + return &Dashboard{Name: d.Name, Kind: "Dashboard", Widgets: widgets} } // convert 'statistics$defects$system_issue$xx_xxxxxxxxxxx' fields to 'statistics$defects$system_issue$shortname` @@ -150,6 +152,15 @@ func LoadDashboardFromFile(file string) (*Dashboard, error) { return nil, err } + if d.Kind != "" && d.Kind != "Dashboard" { + return nil, fmt.Errorf("error invalid kind '%s' in file '%s'", d.Kind, file) + } + + if d.Kind == "" { + log.Printf("warning: assuming kind 'Dashboard' for file '%s'", file) + d.Kind = "Dashboard" + } + return d, nil } From f0024f4dc157ee8d89f8cdd2aafd5ed1a64e2c89 Mon Sep 17 00:00:00 2001 From: Davide Bizzarri Date: Fri, 14 Jan 2022 13:20:47 +0100 Subject: [PATCH 2/3] feat: implemented export cmd for filters --- .gitignore | 6 +-- cmd/export.go | 80 +++++++++++++++++++++++++++++++++----- examples/dashone.yaml | 1 + examples/filterone.yaml | 13 +++++++ pkg/reportportal/filter.go | 40 +++++++++++++++++-- pkg/rpdac/dashboard.go | 22 ++--------- pkg/rpdac/filter.go | 46 ++++++++++++++++++++++ pkg/rpdac/object.go | 35 +++++++++++++++++ pkg/rpdac/reportportal.go | 11 ++++++ 9 files changed, 220 insertions(+), 34 deletions(-) create mode 100644 examples/filterone.yaml create mode 100644 pkg/rpdac/filter.go create mode 100644 pkg/rpdac/object.go diff --git a/.gitignore b/.gitignore index b6692ac..d40cee6 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ # Dependency directories (remove the comment below to include it) # vendor/ -.rpdac.toml -rpdac +/.rpdac.toml +/rpdac -/dist +/dist/ diff --git a/cmd/export.go b/cmd/export.go index ae2c173..2baa680 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -14,12 +14,22 @@ type Test struct { } var ( - exportFile string - exportProject string - exportDashboard int + exportFile string + exportProject string + exportDashboardID int + exportFilterID int exportCmd = &cobra.Command{ - Use: "export", + Use: "export", + RunE: func(cmd *cobra.Command, args []string) error { + + log.Printf("warning: 'rpdac export' is deprecated, please use 'rpdac export dashboard' instead") + return exportDashboardCmd.RunE(cmd, args) + }, + } + + exportDashboardCmd = &cobra.Command{ + Use: "dashboard", Short: "Exprt a ReportPortal dashboard to YAML", RunE: func(cmd *cobra.Command, args []string) error { @@ -31,13 +41,13 @@ var ( r := rpdac.NewReportPortal(c) // retrieve the Dashboard and Widgets in a single reusable object - d, err := r.GetDashboard(exportProject, exportDashboard) + d, err := r.GetDashboard(exportProject, exportDashboardID) if err != nil { return err } // write the Dashboard object to file in YAML - err = d.WriteToFile(exportFile) + err = rpdac.WriteToFile(d, exportFile) if err != nil { return err } @@ -46,16 +56,68 @@ var ( return nil }, } + + exportFilterCmd = &cobra.Command{ + Use: "filter", + Short: "Export a ReportPortal filter to YAML", + RunE: func(cmd *cobra.Command, args []string) error { + + c, err := requireReportPortalClient() + if err != nil { + return err + } + + r := rpdac.NewReportPortal(c) + + // retrieve the Filter object + f, err := r.GetFilter(exportProject, exportFilterID) + if err != nil { + return err + } + + // write the Filter object to file in YAML + err = rpdac.WriteToFile(f, exportFile) + if err != nil { + return err + } + + log.Printf("Filter \"%s\" exported to \"%s\"", f.Name, exportFile) + return nil + }, + } ) +func decorateCommonOptions(cmd *cobra.Command) { + cmd.Flags().StringVarP(&exportFile, "file", "f", "", "YAML File") + cmd.Flags().StringVarP(&exportProject, "project", "p", "", "ReportPortal Project") + + cmd.MarkFlagRequired("file") + cmd.MarkFlagRequired("project") +} + func init() { - exportCmd.Flags().StringVarP(&exportFile, "file", "f", "", "YAML File") - exportCmd.Flags().StringVarP(&exportProject, "project", "p", "", "ReportPortal Project") - exportCmd.Flags().IntVarP(&exportDashboard, "dashboard", "d", -1, "ReportPortal Dashboard ID") + // Export CMD + exportCmd.Flags().StringVarP(&exportFile, "file", "f", "", "(Deprecated) YAML File") + exportCmd.Flags().StringVarP(&exportProject, "project", "p", "", "(Deprecated) ReportPortal Project") + exportCmd.Flags().IntVarP(&exportDashboardID, "dashboard", "d", -1, "(Deprecated) ReportPortal Dashboard ID") exportCmd.MarkFlagRequired("file") exportCmd.MarkFlagRequired("project") exportCmd.MarkFlagRequired("dashboard") rootCmd.AddCommand(exportCmd) + + // Export Dashboard CMD + exportDashboardCmd.Flags().IntVar(&exportDashboardID, "id", -1, "ReportPortal Dashboard ID") + exportDashboardCmd.MarkFlagRequired("id") + decorateCommonOptions(exportDashboardCmd) + + exportCmd.AddCommand(exportDashboardCmd) + + // Export Filter CMD + exportFilterCmd.Flags().IntVar(&exportFilterID, "id", -1, "ReportPortal Filter ID") + exportFilterCmd.MarkFlagRequired("id") + decorateCommonOptions(exportFilterCmd) + + exportCmd.AddCommand(exportFilterCmd) } diff --git a/examples/dashone.yaml b/examples/dashone.yaml index 355d605..e23bc80 100644 --- a/examples/dashone.yaml +++ b/examples/dashone.yaml @@ -1,4 +1,5 @@ name: MK E2E Tests Overview +kind: Dashboard widgets: - name: Failed/Skipped/Passed [Last 7 days] description: "" diff --git a/examples/filterone.yaml b/examples/filterone.yaml new file mode 100644 index 0000000..d7f5d5c --- /dev/null +++ b/examples/filterone.yaml @@ -0,0 +1,13 @@ +name: mk-e2e-test-suite-sandbox +kind: Filter +type: Launch +description: "" +conditions: +- filteringfield: name + condition: eq + value: mk-e2e-test-suite-sandbox +orders: +- sortingcolumn: startTime + isasc: false +- sortingcolumn: number + isasc: false diff --git a/pkg/reportportal/filter.go b/pkg/reportportal/filter.go index c93582a..af571aa 100644 --- a/pkg/reportportal/filter.go +++ b/pkg/reportportal/filter.go @@ -12,10 +12,25 @@ type FilterList struct { } type Filter struct { - Share bool `json:"share"` - ID int `json:"id"` - Name string `json:"name"` - // incomplete + Share bool `json:"share"` + ID int `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Description string `json:"description"` + Owner string `json:"onwer"` + Conditions []*FilterCondition `json:"conditions"` + Orders []*FilterOrder `json:"orders"` +} + +type FilterCondition struct { + Condition string `json:"condition"` + FilteringField string `json:"filteringField"` + Value string `json:"value"` +} + +type FilterOrder struct { + IsAsc bool `json:"isAsc"` + SortingColumn string `json:"sortingColumn"` } type FilterNotFoundError struct { @@ -30,6 +45,23 @@ func (e *FilterNotFoundError) Error() string { return e.Message } +func (s *FilterService) GetByID(projectName string, id int) (*Filter, *Response, error) { + u := fmt.Sprintf("v1/%s/filter/%d", projectName, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + f := new(Filter) + resp, err := s.client.Do(req, f) + if err != nil { + return nil, resp, err + } + + return f, resp, nil +} + func (s *FilterService) GetByName(projectName, name string) (*Filter, *Response, error) { u := fmt.Sprintf("v1/%s/filter?%s", projectName, url.Values{"filter.eq.name": []string{name}}.Encode()) diff --git a/pkg/rpdac/dashboard.go b/pkg/rpdac/dashboard.go index 6f712b8..feae223 100644 --- a/pkg/rpdac/dashboard.go +++ b/pkg/rpdac/dashboard.go @@ -164,26 +164,12 @@ func LoadDashboardFromFile(file string) (*Dashboard, error) { return d, nil } -func (d *Dashboard) ToYaml() ([]byte, error) { - b, err := yaml.Marshal(d) - if err != nil { - return []byte{}, fmt.Errorf("error marshal dashboard %s: %w", d.Name, err) - } - return b, nil +func (d *Dashboard) GetName() string { + return d.Name } -func (d *Dashboard) WriteToFile(file string) error { - - y, err := d.ToYaml() - if err != nil { - return err - } - - err = ioutil.WriteFile(file, y, 0660) - if err != nil { - return fmt.Errorf("error writing yaml dashboard %s to file %s: %w", d.Name, file, err) - } - return nil +func (d *Dashboard) GetKind() string { + return d.Kind } func (d *Dashboard) HashName() string { diff --git a/pkg/rpdac/filter.go b/pkg/rpdac/filter.go new file mode 100644 index 0000000..e7498e6 --- /dev/null +++ b/pkg/rpdac/filter.go @@ -0,0 +1,46 @@ +package rpdac + +import "github.com/b1zzu/reportportal-dashboards-as-code/pkg/reportportal" + +type Filter struct { + Name string `json:"name"` + Kind string `json:"kind"` + Type string `json:"type"` + Description string `json:"description"` + Conditions []*FilterCondition `json:"conditions"` + Orders []*FilterOrder `json:"orders"` +} + +type FilterCondition struct { + FilteringField string `json:"filteringField"` + Condition string `json:"condition"` + Value string `json:"value"` +} + +type FilterOrder struct { + SortingColumn string `json:"sortingColumn"` + IsAsc bool `json:"isAsc"` +} + +func ToFilter(f *reportportal.Filter) *Filter { + + conditions := make([]*FilterCondition, len(f.Conditions)) + for i, c := range f.Conditions { + conditions[i] = &FilterCondition{Condition: c.Condition, FilteringField: c.FilteringField, Value: c.Value} + } + + orders := make([]*FilterOrder, len(f.Orders)) + for i, o := range f.Orders { + orders[i] = &FilterOrder{IsAsc: o.IsAsc, SortingColumn: o.SortingColumn} + } + + return &Filter{Name: f.Name, Kind: "Filter", Type: f.Type, Description: f.Description, Conditions: conditions, Orders: orders} +} + +func (f *Filter) GetName() string { + return f.Name +} + +func (f *Filter) GetKind() string { + return f.Kind +} diff --git a/pkg/rpdac/object.go b/pkg/rpdac/object.go new file mode 100644 index 0000000..7f71ab9 --- /dev/null +++ b/pkg/rpdac/object.go @@ -0,0 +1,35 @@ +package rpdac + +import ( + "fmt" + "io/ioutil" + + "gopkg.in/yaml.v2" +) + +type Object interface { + GetName() string + GetKind() string +} + +func ToYaml(o Object) ([]byte, error) { + b, err := yaml.Marshal(o) + if err != nil { + return []byte{}, fmt.Errorf("error marshal %s %s: %w", o.GetKind(), o.GetName(), err) + } + return b, nil +} + +func WriteToFile(o Object, file string) error { + + y, err := ToYaml(o) + if err != nil { + return err + } + + err = ioutil.WriteFile(file, y, 0660) + if err != nil { + return fmt.Errorf("error writing yaml %s %s to file %s: %w", o.GetKind(), o.GetName(), file, err) + } + return nil +} diff --git a/pkg/rpdac/reportportal.go b/pkg/rpdac/reportportal.go index cffdc50..2f06d34 100644 --- a/pkg/rpdac/reportportal.go +++ b/pkg/rpdac/reportportal.go @@ -46,6 +46,17 @@ func (r *ReportPortal) GetDashboard(project string, dashboardID int) (*Dashboard return ToDashboard(d, widgets), nil } +func (r *ReportPortal) GetFilter(project string, filterID int) (*Filter, error) { + + // retireve the filter defintion + f, _, err := r.client.Filter.GetByID(project, filterID) + if err != nil { + return nil, fmt.Errorf("error retrieving filter %d from project %s: %w", filterID, project, err) + } + + return ToFilter(f), nil +} + func (r *ReportPortal) CreateDashboard(project string, d *Dashboard) error { dashboardHash := d.HashName() From c7eabc4ce644c7043149c206f8214f8eb1b2eefc Mon Sep 17 00:00:00 2001 From: Davide Bizzarri Date: Wed, 19 Jan 2022 13:20:04 +0100 Subject: [PATCH 3/3] feat: implemented create cmd for filters --- cmd/create.go | 42 +++++++++++++++++++++++++++++++++----- pkg/reportportal/filter.go | 26 +++++++++++++++++++++++ pkg/rpdac/dashboard.go | 25 +++++------------------ pkg/rpdac/filter.go | 34 ++++++++++++++++++++++++++++-- pkg/rpdac/object.go | 31 ++++++++++++++++++++++++++++ pkg/rpdac/reportportal.go | 11 ++++++++++ 6 files changed, 142 insertions(+), 27 deletions(-) diff --git a/cmd/create.go b/cmd/create.go index 18daa83..0d55e9c 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/b1zzu/reportportal-dashboards-as-code/pkg/rpdac" "github.com/spf13/cobra" ) @@ -11,14 +13,19 @@ var ( createCmd = &cobra.Command{ Use: "create", - Short: "create ReportPortal dashboard from a YAML definition", + Short: "create ReportPortal object from a YAML definition", RunE: func(cmd *cobra.Command, args []string) error { - d, err := rpdac.LoadDashboardFromFile(createFile) + file, err := rpdac.LoadFile(createFile) if err != nil { return err } + object, err := rpdac.LoadObjectFromFile(file) + if err != nil { + return fmt.Errorf("error loading '%s': %w", createFile, err) + } + c, err := requireReportPortalClient() if err != nil { return err @@ -26,9 +33,34 @@ var ( r := rpdac.NewReportPortal(c) - err = r.CreateDashboard(createProject, d) - if err != nil { - return err + switch object.Kind { + case rpdac.DashboardKind: + + d, err := rpdac.LoadDashboardFromFile(file) + if err != nil { + return err + } + + err = r.CreateDashboard(createProject, d) + if err != nil { + return err + } + + case rpdac.FilterKind: + + f, err := rpdac.LoadFilterFromFile(file) + if err != nil { + return err + } + + err = r.CreateFilter(createProject, f) + if err != nil { + return err + } + + default: + return fmt.Errorf("unknown Kind '%s' in file '%s'", object.Kind, createFile) + } return nil diff --git a/pkg/reportportal/filter.go b/pkg/reportportal/filter.go index af571aa..6dc1995 100644 --- a/pkg/reportportal/filter.go +++ b/pkg/reportportal/filter.go @@ -37,6 +37,15 @@ type FilterNotFoundError struct { Message string } +type NewFilter struct { + Name string `json:"name"` + Type string `json:"type"` + Description string `json:"description"` + Share bool `json:"share"` + Conditions []*FilterCondition `json:"conditions"` + Orders []*FilterOrder `json:"orders"` +} + func NewFilterNotFoundError(projectName, filterName string) *DashboardNotFoundError { return &DashboardNotFoundError{Message: fmt.Sprintf("error filter with name \"%s\" in project \"%s\" not found", filterName, projectName)} } @@ -82,3 +91,20 @@ func (s *FilterService) GetByName(projectName, name string) (*Filter, *Response, return fl.Content[0], resp, nil } + +func (s *FilterService) Create(projectName string, f *NewFilter) (int, *Response, error) { + u := fmt.Sprintf("v1/%v/filter", projectName) + + req, err := s.client.NewRequest("POST", u, f) + if err != nil { + return 0, nil, err + } + + e := new(EntryCreated) + resp, err := s.client.Do(req, e) + if err != nil { + return 0, resp, err + } + + return e.ID, resp, nil +} diff --git a/pkg/rpdac/dashboard.go b/pkg/rpdac/dashboard.go index feae223..8119707 100644 --- a/pkg/rpdac/dashboard.go +++ b/pkg/rpdac/dashboard.go @@ -5,14 +5,14 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" - "log" "strings" "github.com/b1zzu/reportportal-dashboards-as-code/pkg/reportportal" "gopkg.in/yaml.v2" ) +const DashboardKind = "Dashboard" + type Dashboard struct { Name string `json:"name"` Kind string `json:"kind"` @@ -46,7 +46,7 @@ type WidgetContentParameters struct { } func ToDashboard(d *reportportal.Dashboard, widgets []*Widget) *Dashboard { - return &Dashboard{Name: d.Name, Kind: "Dashboard", Widgets: widgets} + return &Dashboard{Name: d.Name, Kind: DashboardKind, Widgets: widgets} } // convert 'statistics$defects$system_issue$xx_xxxxxxxxxxx' fields to 'statistics$defects$system_issue$shortname` @@ -139,28 +139,13 @@ func FromWidget(dashboardHash string, w *Widget, filtersMap map[string]int, enco return nw, dw, nil } -func LoadDashboardFromFile(file string) (*Dashboard, error) { - - b, err := ioutil.ReadFile(file) - if err != nil { - return nil, err - } +func LoadDashboardFromFile(file []byte) (*Dashboard, error) { d := new(Dashboard) - err = yaml.Unmarshal(b, d) + err := yaml.Unmarshal(file, d) if err != nil { return nil, err } - - if d.Kind != "" && d.Kind != "Dashboard" { - return nil, fmt.Errorf("error invalid kind '%s' in file '%s'", d.Kind, file) - } - - if d.Kind == "" { - log.Printf("warning: assuming kind 'Dashboard' for file '%s'", file) - d.Kind = "Dashboard" - } - return d, nil } diff --git a/pkg/rpdac/filter.go b/pkg/rpdac/filter.go index e7498e6..1fa74f9 100644 --- a/pkg/rpdac/filter.go +++ b/pkg/rpdac/filter.go @@ -1,6 +1,11 @@ package rpdac -import "github.com/b1zzu/reportportal-dashboards-as-code/pkg/reportportal" +import ( + "github.com/b1zzu/reportportal-dashboards-as-code/pkg/reportportal" + "gopkg.in/yaml.v2" +) + +const FilterKind = "Filter" type Filter struct { Name string `json:"name"` @@ -34,7 +39,32 @@ func ToFilter(f *reportportal.Filter) *Filter { orders[i] = &FilterOrder{IsAsc: o.IsAsc, SortingColumn: o.SortingColumn} } - return &Filter{Name: f.Name, Kind: "Filter", Type: f.Type, Description: f.Description, Conditions: conditions, Orders: orders} + return &Filter{Name: f.Name, Kind: FilterKind, Type: f.Type, Description: f.Description, Conditions: conditions, Orders: orders} +} + +func FromFilter(f *Filter) *reportportal.NewFilter { + + conditions := make([]*reportportal.FilterCondition, len(f.Conditions)) + for i, c := range f.Conditions { + conditions[i] = &reportportal.FilterCondition{Condition: c.Condition, FilteringField: c.FilteringField, Value: c.Value} + } + + orders := make([]*reportportal.FilterOrder, len(f.Orders)) + for i, o := range f.Orders { + orders[i] = &reportportal.FilterOrder{IsAsc: o.IsAsc, SortingColumn: o.SortingColumn} + } + + return &reportportal.NewFilter{Name: f.Name, Type: f.Type, Description: f.Description, Share: true, Conditions: conditions, Orders: orders} +} + +func LoadFilterFromFile(file []byte) (*Filter, error) { + + f := new(Filter) + err := yaml.Unmarshal(file, f) + if err != nil { + return nil, err + } + return f, nil } func (f *Filter) GetName() string { diff --git a/pkg/rpdac/object.go b/pkg/rpdac/object.go index 7f71ab9..4d3419c 100644 --- a/pkg/rpdac/object.go +++ b/pkg/rpdac/object.go @@ -3,6 +3,7 @@ package rpdac import ( "fmt" "io/ioutil" + "log" "gopkg.in/yaml.v2" ) @@ -12,6 +13,11 @@ type Object interface { GetKind() string } +type GenericObject struct { + Name string `json:"name"` + Kind string `json:"string"` +} + func ToYaml(o Object) ([]byte, error) { b, err := yaml.Marshal(o) if err != nil { @@ -33,3 +39,28 @@ func WriteToFile(o Object, file string) error { } return nil } + +func LoadFile(file string) ([]byte, error) { + + b, err := ioutil.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("error loading '%s': %w", file, err) + } + return b, nil +} + +func LoadObjectFromFile(file []byte) (*GenericObject, error) { + + o := new(GenericObject) + err := yaml.Unmarshal(file, o) + if err != nil { + return nil, err + } + + if o.Kind == "" { + log.Printf("warning: assuming kind '%s'", DashboardKind) + o.Kind = DashboardKind + } + + return o, nil +} diff --git a/pkg/rpdac/reportportal.go b/pkg/rpdac/reportportal.go index 2f06d34..2301d0e 100644 --- a/pkg/rpdac/reportportal.go +++ b/pkg/rpdac/reportportal.go @@ -115,6 +115,17 @@ func (r *ReportPortal) CreateDashboard(project string, d *Dashboard) error { return nil } +func (r *ReportPortal) CreateFilter(project string, f *Filter) error { + + filterID, _, err := r.client.Filter.Create(project, FromFilter(f)) + if err != nil { + return fmt.Errorf("error creating filter %s: %w", f.Name, err) + } + + log.Printf("filter %s created with id: %d", f.Name, filterID) + return nil +} + // Delete the Dashboard with the given name and Widgets created for it func (r *ReportPortal) DeleteDashboard(project, dashboard string) error {