From 4467f38055eee775339ab5a5c6f7f7e3207ac5c7 Mon Sep 17 00:00:00 2001 From: Sam Lucidi Date: Thu, 27 Jun 2024 16:43:46 -0400 Subject: [PATCH] Use json.Serializer everywhere Signed-off-by: Sam Lucidi --- api/analysis.go | 81 +++++------- api/application.go | 28 ++-- api/archetype.go | 2 +- api/assessment.go | 44 ++++--- api/base.go | 6 - api/businessservice.go | 2 +- api/group.go | 2 +- api/identity.go | 2 +- api/jobfunction.go | 2 +- api/migrationwave.go | 2 +- api/proxy.go | 9 +- api/questionnaire.go | 44 +++---- api/review.go | 25 +--- api/ruleset.go | 18 +-- api/setting.go | 22 ++-- api/stakeholder.go | 2 +- api/tag.go | 2 +- api/tagcategory.go | 2 +- api/target.go | 33 ++--- api/ticket.go | 12 +- api/tracker.go | 2 +- assessment/assessment.go | 169 ++++++++++++------------ assessment/assessment_test.go | 198 +++++++++++++++-------------- assessment/membership.go | 4 +- assessment/pkg.go | 75 +---------- assessment/tag.go | 10 +- binding/application.go | 6 +- hack/cmd/addon/main.go | 20 +-- importer/manager.go | 6 +- migration/json/fields.go | 4 +- migration/migrate.go | 14 +- migration/migrate_test.go | 3 +- migration/v14/model/analysis.go | 77 ++++++----- migration/v14/model/application.go | 14 +- migration/v14/model/assessment.go | 68 +++++++++- migration/v14/model/core.go | 60 ++++----- model/pkg.go | 20 ++- seed/questionnaire.go | 11 +- seed/ruleset.go | 3 +- seed/seed.go | 24 ++-- seed/target.go | 8 +- test/api/application/facts_test.go | 2 +- test/api/assessment/samples.go | 18 +-- test/api/questionnaire/samples.go | 26 ++-- trigger/application.go | 2 +- 45 files changed, 576 insertions(+), 608 deletions(-) diff --git a/api/analysis.go b/api/analysis.go index eb115113e..4080157cc 100644 --- a/api/analysis.go +++ b/api/analysis.go @@ -1060,11 +1060,9 @@ func (h AnalysisHandler) RuleReports(ctx *gin.Context) { Name: m.Name, } resources = append(resources, r) - if m.Labels != nil { - _ = json.Unmarshal(m.Labels, &r.Labels) - } - if m.Links != nil { - _ = json.Unmarshal(m.Links, &r.Links) + r.Labels = m.Labels + for _, l := range m.Links { + r.Links = append(r.Links, Link(l)) } r.Effort += m.Effort } @@ -1197,11 +1195,9 @@ func (h AnalysisHandler) AppIssueReports(ctx *gin.Context) { ID: m.ID, } resources = append(resources, r) - if m.Labels != nil { - _ = json.Unmarshal(m.Labels, &r.Labels) - } - if m.Links != nil { - _ = json.Unmarshal(m.Links, &r.Links) + r.Labels = m.Labels + for _, l := range m.Links { + r.Links = append(r.Links, Link(l)) } r.Effort += m.Effort } @@ -1722,13 +1718,9 @@ func (h AnalysisHandler) DepReports(ctx *gin.Context) { Name: m.Name, Applications: m.Applications, } - if m.Labels != nil { - var aggregated []string - _ = json.Unmarshal(m.Labels, &aggregated) - for _, s := range aggregated { - if s != "" { - r.Labels = append(r.Labels, s) - } + for _, s := range m.Labels { + if s != "" { + r.Labels = append(r.Labels, s) } } resources = append(resources, r) @@ -2082,7 +2074,7 @@ func (h *AnalysisHandler) archive(ctx *gin.Context, q *gorm.DB) (err error) { db = db.Where("n.IssueID = i.ID") db = db.Where("i.AnalysisID", m.ID) db = db.Group("i.ID") - summary := []ArchivedIssue{} + summary := []model.ArchivedIssue{} err = db.Scan(&summary).Error if err != nil { return @@ -2091,8 +2083,8 @@ func (h *AnalysisHandler) archive(ctx *gin.Context, q *gorm.DB) (err error) { db = db.Model(m) db = db.Omit(clause.Associations) m.Archived = true - m.Summary, _ = json.Marshal(summary) - err = db.Updates(h.fields(&m)).Error + m.Summary = summary + err = db.Save(&m).Error if err != nil { return } @@ -2155,7 +2147,7 @@ type Issue struct { Effort int `json:"effort,omitempty" yaml:",omitempty"` Incidents []Incident `json:"incidents,omitempty" yaml:",omitempty"` Links []Link `json:"links,omitempty" yaml:",omitempty"` - Facts FactMap `json:"facts,omitempty" yaml:",omitempty"` + Facts Map `json:"facts,omitempty" yaml:",omitempty"` Labels []string `json:"labels"` } @@ -2176,15 +2168,11 @@ func (r *Issue) With(m *model.Issue) { r.Incidents, n) } - if m.Links != nil { - _ = json.Unmarshal(m.Links, &r.Links) - } - if m.Facts != nil { - _ = json.Unmarshal(m.Facts, &r.Facts) - } - if m.Labels != nil { - _ = json.Unmarshal(m.Labels, &r.Labels) + for _, l := range m.Links { + r.Links = append(r.Links, Link(l)) } + r.Facts = m.Facts + r.Labels = m.Labels r.Effort = m.Effort } @@ -2203,9 +2191,11 @@ func (r *Issue) Model() (m *model.Issue) { m.Incidents, *n) } - m.Links, _ = json.Marshal(r.Links) - m.Facts, _ = json.Marshal(r.Facts) - m.Labels, _ = json.Marshal(r.Labels) + for _, l := range r.Links { + m.Links = append(m.Links, model.Link(l)) + } + m.Facts = r.Facts + m.Labels = r.Labels m.Effort = r.Effort return } @@ -2231,9 +2221,7 @@ func (r *TechDependency) With(m *model.TechDependency) { r.Version = m.Version r.Indirect = m.Indirect r.SHA = m.SHA - if m.Labels != nil { - _ = json.Unmarshal(m.Labels, &r.Labels) - } + r.Labels = m.Labels } // Model builds a model. @@ -2244,7 +2232,7 @@ func (r *TechDependency) Model() (m *model.TechDependency) { m.Version = r.Version m.Provider = r.Provider m.Indirect = r.Indirect - m.Labels, _ = json.Marshal(r.Labels) + m.Labels = r.Labels m.SHA = r.SHA return } @@ -2252,12 +2240,12 @@ func (r *TechDependency) Model() (m *model.TechDependency) { // Incident REST resource. type Incident struct { Resource `yaml:",inline"` - Issue uint `json:"issue"` - File string `json:"file"` - Line int `json:"line"` - Message string `json:"message"` - CodeSnip string `json:"codeSnip" yaml:"codeSnip"` - Facts FactMap `json:"facts"` + Issue uint `json:"issue"` + File string `json:"file"` + Line int `json:"line"` + Message string `json:"message"` + CodeSnip string `json:"codeSnip" yaml:"codeSnip"` + Facts Map `json:"facts"` } // With updates the resource with the model. @@ -2268,9 +2256,7 @@ func (r *Incident) With(m *model.Incident) { r.Line = m.Line r.Message = m.Message r.CodeSnip = m.CodeSnip - if m.Facts != nil { - _ = json.Unmarshal(m.Facts, &r.Facts) - } + r.Facts = m.Facts } // Model builds a model. @@ -2280,7 +2266,7 @@ func (r *Incident) Model() (m *model.Incident) { m.Line = r.Line m.Message = r.Message m.CodeSnip = r.CodeSnip - m.Facts, _ = json.Marshal(r.Facts) + m.Facts = r.Facts return } @@ -2371,9 +2357,6 @@ type DepAppReport struct { } `json:"dependency"` } -// FactMap map. -type FactMap map[string]any - // IssueWriter used to create a file containing issues. type IssueWriter struct { encoder diff --git a/api/application.go b/api/application.go index 3d4940c89..6b61e3c6a 100644 --- a/api/application.go +++ b/api/application.go @@ -362,7 +362,7 @@ func (h ApplicationHandler) Update(ctx *gin.Context) { m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db = h.DB(ctx).Model(m) db = db.Omit(clause.Associations, "BucketID") - result = db.Updates(h.fields(m)) + result = db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return @@ -722,12 +722,10 @@ func (h ApplicationHandler) FactList(ctx *gin.Context, key FactKey) { return } - facts := FactMap{} + facts := Map{} for i := range list { fact := &list[i] - var v any - _ = json.Unmarshal(fact.Value, &v) - facts[fact.Key] = v + facts[fact.Key] = fact.Value } h.Respond(ctx, http.StatusOK, facts) } @@ -772,9 +770,7 @@ func (h ApplicationHandler) FactGet(ctx *gin.Context) { return } - var v any - _ = json.Unmarshal(list[0].Value, &v) - h.Respond(ctx, http.StatusOK, v) + h.Respond(ctx, http.StatusOK, list[0].Value) } // FactCreate godoc @@ -846,12 +842,11 @@ func (h ApplicationHandler) FactPut(ctx *gin.Context) { return } - value, _ := json.Marshal(f.Value) m := &model.Fact{ Key: key.Name(), Source: key.Source(), ApplicationID: id, - Value: value, + Value: f.Value, } db := h.DB(ctx) result = db.Save(m) @@ -906,7 +901,7 @@ func (h ApplicationHandler) FactDelete(ctx *gin.Context) { // @param factmap body api.FactMap true "Fact map" func (h ApplicationHandler) FactReplace(ctx *gin.Context, key FactKey) { id := h.pk(ctx) - facts := FactMap{} + facts := Map{} err := h.Bind(ctx, &facts) if err != nil { _ = ctx.Error(err) @@ -1145,7 +1140,10 @@ func (r *Application) With(m *model.Application, tags []model.ApplicationTag) { r.Bucket = r.refPtr(m.BucketID, m.Bucket) r.Comments = m.Comments r.Binary = m.Binary - _ = json.Unmarshal(m.Repository, &r.Repository) + if m.Repository != (model.Repository{}) { + repo := Repository(m.Repository) + r.Repository = &repo + } if m.Review != nil { ref := &Ref{} ref.With(m.Review.ID, "") @@ -1246,7 +1244,7 @@ func (r *Application) Model() (m *model.Application) { } m.ID = r.ID if r.Repository != nil { - m.Repository, _ = json.Marshal(r.Repository) + m.Repository = model.Repository(*r.Repository) } if r.BusinessService != nil { m.BusinessServiceID = &r.BusinessService.ID @@ -1307,14 +1305,14 @@ type Fact struct { func (r *Fact) With(m *model.Fact) { r.Key = m.Key r.Source = m.Source - _ = json.Unmarshal(m.Value, &r.Value) + r.Value = m.Value } func (r *Fact) Model() (m *model.Fact) { m = &model.Fact{} m.Key = r.Key m.Source = r.Source - m.Value, _ = json.Marshal(r.Value) + m.Value = r.Value return } diff --git a/api/archetype.go b/api/archetype.go index bcadf05cd..dafd141ef 100644 --- a/api/archetype.go +++ b/api/archetype.go @@ -232,7 +232,7 @@ func (h ArchetypeHandler) Update(ctx *gin.Context) { m.UpdateUser = h.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return diff --git a/api/assessment.go b/api/assessment.go index d024637eb..438c16eb8 100644 --- a/api/assessment.go +++ b/api/assessment.go @@ -1,7 +1,6 @@ package api import ( - "encoding/json" "net/http" "github.com/gin-gonic/gin" @@ -126,7 +125,7 @@ func (h AssessmentHandler) Update(ctx *gin.Context) { m.UpdateUser = h.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations, "Thresholds", "RiskMessages") - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return @@ -148,21 +147,25 @@ func (h AssessmentHandler) Update(ctx *gin.Context) { // Assessment REST resource. type Assessment struct { Resource `yaml:",inline"` - Application *Ref `json:"application,omitempty" yaml:",omitempty" binding:"excluded_with=Archetype"` - Archetype *Ref `json:"archetype,omitempty" yaml:",omitempty" binding:"excluded_with=Application"` - Questionnaire Ref `json:"questionnaire" binding:"required"` - Sections []assessment.Section `json:"sections" binding:"dive"` - Stakeholders []Ref `json:"stakeholders"` - StakeholderGroups []Ref `json:"stakeholderGroups" yaml:"stakeholderGroups"` + Application *Ref `json:"application,omitempty" yaml:",omitempty" binding:"excluded_with=Archetype"` + Archetype *Ref `json:"archetype,omitempty" yaml:",omitempty" binding:"excluded_with=Application"` + Questionnaire Ref `json:"questionnaire" binding:"required"` + Sections []Section `json:"sections" binding:"dive"` + Stakeholders []Ref `json:"stakeholders"` + StakeholderGroups []Ref `json:"stakeholderGroups" yaml:"stakeholderGroups"` // read only - Risk string `json:"risk"` - Confidence int `json:"confidence"` - Status string `json:"status"` - Thresholds assessment.Thresholds `json:"thresholds"` - RiskMessages assessment.RiskMessages `json:"riskMessages" yaml:"riskMessages"` - Required bool `json:"required"` + Risk string `json:"risk"` + Confidence int `json:"confidence"` + Status string `json:"status"` + Thresholds Thresholds `json:"thresholds"` + RiskMessages RiskMessages `json:"riskMessages" yaml:"riskMessages"` + Required bool `json:"required"` } +type Section model.Section +type Thresholds model.Thresholds +type RiskMessages model.RiskMessages + // With updates the resource with the model. func (r *Assessment) With(m *model.Assessment) { r.Resource.With(&m.Model) @@ -186,9 +189,12 @@ func (r *Assessment) With(m *model.Assessment) { r.Required = a.Questionnaire.Required r.Risk = a.Risk() r.Confidence = a.Confidence() - r.RiskMessages = a.RiskMessages - r.Thresholds = a.Thresholds - r.Sections = a.Sections + r.RiskMessages = RiskMessages(a.RiskMessages) + r.Thresholds = Thresholds(a.Thresholds) + r.Sections = []Section{} + for _, s := range a.Sections { + r.Sections = append(r.Sections, Section(s)) + } r.Status = a.Status() } @@ -196,8 +202,8 @@ func (r *Assessment) With(m *model.Assessment) { func (r *Assessment) Model() (m *model.Assessment) { m = &model.Assessment{} m.ID = r.ID - if r.Sections != nil { - m.Sections, _ = json.Marshal(r.Sections) + for _, s := range r.Sections { + m.Sections = append(m.Sections, model.Section(s)) } m.QuestionnaireID = r.Questionnaire.ID if r.Archetype != nil { diff --git a/api/base.go b/api/base.go index 602ac3be4..3e4e713a8 100644 --- a/api/base.go +++ b/api/base.go @@ -84,12 +84,6 @@ func (h *BaseHandler) preLoad(db *gorm.DB, fields ...string) (tx *gorm.DB) { return } -// fields builds a map of fields. -func (h *BaseHandler) fields(m any) (mp map[string]any) { - mp = reflect.Fields(m) - return -} - // pk returns the PK (ID) parameter. func (h *BaseHandler) pk(ctx *gin.Context) (id uint) { s := ctx.Param(ID) diff --git a/api/businessservice.go b/api/businessservice.go index 41caf95b5..dabc03c09 100644 --- a/api/businessservice.go +++ b/api/businessservice.go @@ -153,7 +153,7 @@ func (h BusinessServiceHandler) Update(ctx *gin.Context) { m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return diff --git a/api/group.go b/api/group.go index dee59ff97..daf876519 100644 --- a/api/group.go +++ b/api/group.go @@ -163,7 +163,7 @@ func (h StakeholderGroupHandler) Update(ctx *gin.Context) { m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return diff --git a/api/identity.go b/api/identity.go index 8c631c6f7..e01e89b08 100644 --- a/api/identity.go +++ b/api/identity.go @@ -202,7 +202,7 @@ func (h IdentityHandler) Update(ctx *gin.Context) { m.ID = id m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db := h.DB(ctx).Model(m) - err = db.Updates(h.fields(m)).Error + err = db.Save(m).Error if err != nil { _ = ctx.Error(err) return diff --git a/api/jobfunction.go b/api/jobfunction.go index 82e615956..135dd1cb2 100644 --- a/api/jobfunction.go +++ b/api/jobfunction.go @@ -153,7 +153,7 @@ func (h JobFunctionHandler) Update(ctx *gin.Context) { m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return diff --git a/api/migrationwave.go b/api/migrationwave.go index 128bb485f..b92b23d6b 100644 --- a/api/migrationwave.go +++ b/api/migrationwave.go @@ -145,7 +145,7 @@ func (h MigrationWaveHandler) Update(ctx *gin.Context) { m.UpdateUser = h.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return diff --git a/api/proxy.go b/api/proxy.go index 72f3dd67b..ccaf1551c 100644 --- a/api/proxy.go +++ b/api/proxy.go @@ -1,7 +1,6 @@ package api import ( - "encoding/json" "net/http" "github.com/gin-gonic/gin" @@ -161,7 +160,7 @@ func (h ProxyHandler) Update(ctx *gin.Context) { m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return @@ -189,7 +188,7 @@ func (r *Proxy) With(m *model.Proxy) { r.Host = m.Host r.Port = m.Port r.Identity = r.refPtr(m.IdentityID, m.Identity) - _ = json.Unmarshal(m.Excluded, &r.Excluded) + r.Excluded = m.Excluded if r.Excluded == nil { r.Excluded = []string{} } @@ -205,9 +204,7 @@ func (r *Proxy) Model() (m *model.Proxy) { } m.ID = r.ID m.IdentityID = r.idPtr(r.Identity) - if r.Excluded != nil { - m.Excluded, _ = json.Marshal(r.Excluded) - } + m.Excluded = r.Excluded return } diff --git a/api/questionnaire.go b/api/questionnaire.go index 1dcb8c50d..ba183afe7 100644 --- a/api/questionnaire.go +++ b/api/questionnaire.go @@ -1,11 +1,9 @@ package api import ( - "encoding/json" "net/http" "github.com/gin-gonic/gin" - "github.com/konveyor/tackle2-hub/assessment" "github.com/konveyor/tackle2-hub/model" "gorm.io/gorm/clause" ) @@ -167,19 +165,16 @@ func (h QuestionnaireHandler) Update(ctx *gin.Context) { updated := r.Model() updated.ID = id updated.UpdateUser = h.CurrentUser(ctx) - var fields map[string]any if m.Builtin() { - fields = map[string]any{ - "updateUser": updated.UpdateUser, - "required": updated.Required, - } + m.UpdateUser = updated.UpdateUser + m.Required = updated.Required } else { - fields = h.fields(updated) + m = updated } db = h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result = db.Updates(fields) + result = db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return @@ -190,13 +185,13 @@ func (h QuestionnaireHandler) Update(ctx *gin.Context) { type Questionnaire struct { Resource `yaml:",inline"` - Name string `json:"name" yaml:"name" binding:"required"` - Description string `json:"description" yaml:"description"` - Required bool `json:"required" yaml:"required"` - Sections []assessment.Section `json:"sections" yaml:"sections" binding:"required,min=1,dive"` - Thresholds assessment.Thresholds `json:"thresholds" yaml:"thresholds" binding:"required"` - RiskMessages assessment.RiskMessages `json:"riskMessages" yaml:"riskMessages" binding:"required"` - Builtin bool `json:"builtin,omitempty" yaml:"builtin,omitempty"` + Name string `json:"name" yaml:"name" binding:"required"` + Description string `json:"description" yaml:"description"` + Required bool `json:"required" yaml:"required"` + Sections []Section `json:"sections" yaml:"sections" binding:"required,min=1,dive"` + Thresholds Thresholds `json:"thresholds" yaml:"thresholds" binding:"required"` + RiskMessages RiskMessages `json:"riskMessages" yaml:"riskMessages" binding:"required"` + Builtin bool `json:"builtin,omitempty" yaml:"builtin,omitempty"` } // With updates the resource with the model. @@ -206,9 +201,12 @@ func (r *Questionnaire) With(m *model.Questionnaire) { r.Description = m.Description r.Required = m.Required r.Builtin = m.Builtin() - _ = json.Unmarshal(m.Sections, &r.Sections) - _ = json.Unmarshal(m.Thresholds, &r.Thresholds) - _ = json.Unmarshal(m.RiskMessages, &r.RiskMessages) + r.Sections = []Section{} + for _, s := range m.Sections { + r.Sections = append(r.Sections, Section(s)) + } + r.Thresholds = Thresholds(m.Thresholds) + r.RiskMessages = RiskMessages(m.RiskMessages) } // Model builds a model. @@ -219,9 +217,11 @@ func (r *Questionnaire) Model() (m *model.Questionnaire) { Required: r.Required, } m.ID = r.ID - m.Sections, _ = json.Marshal(r.Sections) - m.Thresholds, _ = json.Marshal(r.Thresholds) - m.RiskMessages, _ = json.Marshal(r.RiskMessages) + for _, s := range r.Sections { + m.Sections = append(m.Sections, model.Section(s)) + } + m.Thresholds = model.Thresholds(r.Thresholds) + m.RiskMessages = model.RiskMessages(r.RiskMessages) return } diff --git a/api/review.go b/api/review.go index 85fbbdcb8..db4c65222 100644 --- a/api/review.go +++ b/api/review.go @@ -30,7 +30,7 @@ func (h ReviewHandler) AddRoutes(e *gin.Engine) { routeGroup.GET(ReviewRoot, h.Get) routeGroup.PUT(ReviewRoot, h.Update) routeGroup.DELETE(ReviewRoot, h.Delete) - routeGroup.POST(CopyRoot, h.CopyReview) + routeGroup.POST(CopyRoot, h.CopyReview, Transaction) } // Get godoc @@ -155,7 +155,7 @@ func (h ReviewHandler) Update(ctx *gin.Context) { m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db := h.DB(ctx).Model(m) db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return @@ -194,26 +194,15 @@ func (h ReviewHandler) CopyReview(ctx *gin.Context) { Comments: m.Comments, ApplicationID: &id, } - existing := []model.Review{} - result = h.DB(ctx).Find(&existing, "applicationid = ?", id) + result = h.DB(ctx).Delete(&model.Review{}, "applicationid = ?", id) if result.Error != nil { _ = ctx.Error(result.Error) return } - // if the application doesn't already have a review, create one. - if len(existing) == 0 { - result = h.DB(ctx).Create(copied) - if result.Error != nil { - _ = ctx.Error(result.Error) - return - } - // if the application already has a review, replace it with the copied review. - } else { - result = h.DB(ctx).Model(&existing[0]).Updates(h.fields(copied)) - if result.Error != nil { - _ = ctx.Error(result.Error) - return - } + result = h.DB(ctx).Create(copied) + if result.Error != nil { + _ = ctx.Error(result.Error) + return } } h.Status(ctx, http.StatusNoContent) diff --git a/api/ruleset.go b/api/ruleset.go index 95bfabb58..43d8e4023 100644 --- a/api/ruleset.go +++ b/api/ruleset.go @@ -1,7 +1,6 @@ package api import ( - "encoding/json" "net/http" "github.com/gin-gonic/gin" @@ -249,7 +248,7 @@ func (h *RuleSetHandler) update(ctx *gin.Context, r *RuleSet) (err error) { m.UpdateUser = h.CurrentUser(ctx) db = h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - err = db.Updates(h.fields(m)).Error + err = db.Save(m).Error if err != nil { return } @@ -268,7 +267,7 @@ func (h *RuleSetHandler) update(ctx *gin.Context, r *RuleSet) (err error) { for i := range m.Rules { m := &m.Rules[i] db = h.DB(ctx).Model(m) - err = db.Updates(h.fields(m)).Error + err = db.Updates(m).Error if err != nil { return } @@ -313,7 +312,10 @@ func (r *RuleSet) With(m *model.RuleSet) { r.Name = m.Name r.Description = m.Description r.Identity = r.refPtr(m.IdentityID, m.Identity) - _ = json.Unmarshal(m.Repository, &r.Repository) + if m.Repository != (model.Repository{}) { + repo := Repository(m.Repository) + r.Repository = &repo + } r.Rules = []Rule{} for i := range m.Rules { rule := Rule{} @@ -344,7 +346,7 @@ func (r *RuleSet) Model() (m *model.RuleSet) { m.Rules = append(m.Rules, *rule.Model()) } if r.Repository != nil { - m.Repository, _ = json.Marshal(r.Repository) + m.Repository = model.Repository(*r.Repository) } for _, ref := range r.DependsOn { m.DependsOn = append( @@ -382,7 +384,7 @@ type Rule struct { func (r *Rule) With(m *model.Rule) { r.Resource.With(&m.Model) r.Name = m.Name - _ = json.Unmarshal(m.Labels, &r.Labels) + r.Labels = m.Labels r.File = r.refPtr(m.FileID, m.File) } @@ -391,9 +393,7 @@ func (r *Rule) Model() (m *model.Rule) { m = &model.Rule{} m.ID = r.ID m.Name = r.Name - if r.Labels != nil { - m.Labels, _ = json.Marshal(r.Labels) - } + m.Labels = r.Labels m.FileID = r.idPtr(r.File) return } diff --git a/api/setting.go b/api/setting.go index a758f4b81..ad5c6997e 100644 --- a/api/setting.go +++ b/api/setting.go @@ -1,7 +1,6 @@ package api import ( - "encoding/json" "fmt" "net/http" "strings" @@ -179,19 +178,19 @@ func (h SettingHandler) Update(ctx *gin.Context) { return } - updates := Setting{} - updates.Key = key - err := h.Bind(ctx, &updates.Value) + m := &model.Setting{} + result := h.DB(ctx).First(m, "key = ?", key) + if result.Error != nil { + _ = ctx.Error(result.Error) + return + } + err := h.Bind(ctx, &m.Value) if err != nil { _ = ctx.Error(err) return } - - m := updates.Model() m.UpdateUser = h.BaseHandler.CurrentUser(ctx) - db := h.DB(ctx).Model(m) - db = db.Where("key", key) - result := db.Updates(h.fields(m)) + result = h.DB(ctx).Save(m) if result.Error != nil { _ = ctx.Error(result.Error) } @@ -235,12 +234,11 @@ type Setting struct { func (r *Setting) With(m *model.Setting) { r.Key = m.Key - _ = json.Unmarshal(m.Value, &r.Value) - + r.Value = m.Value } func (r *Setting) Model() (m *model.Setting) { m = &model.Setting{Key: r.Key} - m.Value, _ = json.Marshal(r.Value) + m.Value = r.Value return } diff --git a/api/stakeholder.go b/api/stakeholder.go index 1a551cf4d..eead223b9 100644 --- a/api/stakeholder.go +++ b/api/stakeholder.go @@ -173,7 +173,7 @@ func (h StakeholderHandler) Update(ctx *gin.Context) { m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return diff --git a/api/tag.go b/api/tag.go index 17d472444..648456b5f 100644 --- a/api/tag.go +++ b/api/tag.go @@ -153,7 +153,7 @@ func (h TagHandler) Update(ctx *gin.Context) { m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return diff --git a/api/tagcategory.go b/api/tagcategory.go index 8ae7799b2..025065095 100644 --- a/api/tagcategory.go +++ b/api/tagcategory.go @@ -160,7 +160,7 @@ func (h TagCategoryHandler) Update(ctx *gin.Context) { m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return diff --git a/api/target.go b/api/target.go index efccc4d10..c27c8fe00 100644 --- a/api/target.go +++ b/api/target.go @@ -1,7 +1,6 @@ package api import ( - "encoding/json" "errors" "fmt" "net/http" @@ -225,7 +224,7 @@ func (h TargetHandler) Update(ctx *gin.Context) { } db = h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result = db.Updates(h.fields(m)) + result = db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return @@ -237,20 +236,17 @@ func (h TargetHandler) Update(ctx *gin.Context) { // Target REST resource. type Target struct { Resource `yaml:",inline"` - Name string `json:"name"` - Description string `json:"description"` - Provider string `json:"provider,omitempty" yaml:",omitempty"` - Choice bool `json:"choice,omitempty" yaml:",omitempty"` - Custom bool `json:"custom,omitempty" yaml:",omitempty"` - Labels []Label `json:"labels"` - Image Ref `json:"image"` - RuleSet *RuleSet `json:"ruleset,omitempty" yaml:"ruleset,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Provider string `json:"provider,omitempty" yaml:",omitempty"` + Choice bool `json:"choice,omitempty" yaml:",omitempty"` + Custom bool `json:"custom,omitempty" yaml:",omitempty"` + Labels []TargetLabel `json:"labels"` + Image Ref `json:"image"` + RuleSet *RuleSet `json:"ruleset,omitempty" yaml:"ruleset,omitempty"` } -type Label struct { - Name string `json:"name"` - Label string `json:"label"` -} +type TargetLabel model.TargetLabel // With updates the resource with the model. func (r *Target) With(m *model.Target) { @@ -269,7 +265,10 @@ func (r *Target) With(m *model.Target) { imgRef.Name = m.Image.Name } r.Image = imgRef - _ = json.Unmarshal(m.Labels, &r.Labels) + r.Labels = []TargetLabel{} + for _, l := range m.Labels { + r.Labels = append(r.Labels, TargetLabel(l)) + } } // Model builds a model. @@ -282,6 +281,8 @@ func (r *Target) Model() (m *model.Target) { } m.ID = r.ID m.ImageID = r.Image.ID - m.Labels, _ = json.Marshal(r.Labels) + for _, l := range r.Labels { + m.Labels = append(m.Labels, model.TargetLabel(l)) + } return } diff --git a/api/ticket.go b/api/ticket.go index e25af5611..e716d9161 100644 --- a/api/ticket.go +++ b/api/ticket.go @@ -1,7 +1,6 @@ package api import ( - "encoding/json" "net/http" "time" @@ -156,7 +155,7 @@ type Ticket struct { Message string `json:"message"` Status string `json:"status"` LastUpdated time.Time `json:"lastUpdated" yaml:"lastUpdated"` - Fields Fields `json:"fields"` + Fields Map `json:"fields"` Application Ref `json:"application" binding:"required"` Tracker Ref `json:"tracker" binding:"required"` } @@ -174,7 +173,7 @@ func (r *Ticket) With(m *model.Ticket) { r.LastUpdated = m.LastUpdated r.Application = r.ref(m.ApplicationID, m.Application) r.Tracker = r.ref(m.TrackerID, m.Tracker) - _ = json.Unmarshal(m.Fields, &r.Fields) + r.Fields = m.Fields } // Model builds a model. @@ -185,13 +184,8 @@ func (r *Ticket) Model() (m *model.Ticket) { ApplicationID: r.Application.ID, TrackerID: r.Tracker.ID, } - if r.Fields == nil { - r.Fields = Fields{} - } - m.Fields, _ = json.Marshal(r.Fields) + m.Fields = r.Fields m.ID = r.ID return } - -type Fields map[string]any diff --git a/api/tracker.go b/api/tracker.go index 650607bac..96434ed12 100644 --- a/api/tracker.go +++ b/api/tracker.go @@ -180,7 +180,7 @@ func (h TrackerHandler) Update(ctx *gin.Context) { m.UpdateUser = h.BaseHandler.CurrentUser(ctx) db := h.DB(ctx).Model(m) db = db.Omit(clause.Associations) - result := db.Updates(h.fields(m)) + result := db.Save(m) if result.Error != nil { _ = ctx.Error(result.Error) return diff --git a/assessment/assessment.go b/assessment/assessment.go index 6a874a519..aa9b56cb4 100644 --- a/assessment/assessment.go +++ b/assessment/assessment.go @@ -1,7 +1,6 @@ package assessment import ( - "encoding/json" "math" "github.com/konveyor/tackle2-hub/model" @@ -10,17 +9,11 @@ import ( // Assessment represents a deserialized Assessment. type Assessment struct { *model.Assessment - Sections []Section `json:"sections"` - Thresholds Thresholds `json:"thresholds"` - RiskMessages RiskMessages `json:"riskMessages"` } // With updates the Assessment with the db model and deserializes its fields. func (r *Assessment) With(m *model.Assessment) { r.Assessment = m - _ = json.Unmarshal(m.Sections, &r.Sections) - _ = json.Unmarshal(m.Thresholds, &r.Thresholds) - _ = json.Unmarshal(m.RiskMessages, &r.RiskMessages) } // Status returns the started status of the assessment. @@ -37,7 +30,7 @@ func (r *Assessment) Status() string { // Complete returns whether all sections have been completed. func (r *Assessment) Complete() bool { for _, s := range r.Sections { - if !s.Complete() { + if !r.sectionComplete(&s) { return false } } @@ -47,7 +40,7 @@ func (r *Assessment) Complete() bool { // Started returns whether any sections have been started. func (r *Assessment) Started() bool { for _, s := range r.Sections { - if s.Started() { + if r.sectionStarted(&s) { return true } } @@ -59,7 +52,7 @@ func (r *Assessment) Risk() string { var total uint colors := make(map[string]uint) for _, s := range r.Sections { - for _, risk := range s.Risks() { + for _, risk := range r.sectionRisks(&s) { colors[risk]++ total++ } @@ -85,8 +78,8 @@ func (r *Assessment) Confidence() (score int) { totalQuestions := 0 riskCounts := make(map[string]int) for _, s := range r.Sections { - for _, r := range s.Risks() { - riskCounts[r]++ + for _, risk := range r.sectionRisks(&s) { + riskCounts[risk]++ totalQuestions++ } } @@ -118,18 +111,73 @@ func (r *Assessment) Confidence() (score int) { return } -// Section represents a group of questions in a questionnaire. -type Section struct { - Order *uint `json:"order" yaml:"order" binding:"required"` - Name string `json:"name" yaml:"name"` - Questions []Question `json:"questions" yaml:"questions" binding:"min=1,dive"` - Comment string `json:"comment,omitempty" yaml:"comment,omitempty"` +func (r *Assessment) Prepare(tagResolver *TagResolver, tags Set) { + for i := range r.Sections { + s := &r.Sections[i] + includedQuestions := []model.Question{} + for _, q := range s.Questions { + for j := range q.Answers { + a := &q.Answers[j] + autoAnswerTags := NewSet() + for _, t := range a.AutoAnswerFor { + tag, found := tagResolver.Resolve(t.Category, t.Tag) + if found { + autoAnswerTags.Add(tag.ID) + } + } + if tags.Intersects(autoAnswerTags) { + a.AutoAnswered = true + a.Selected = true + break + } + } + + if len(q.IncludeFor) > 0 { + includeForTags := NewSet() + for _, t := range q.IncludeFor { + tag, found := tagResolver.Resolve(t.Category, t.Tag) + if found { + includeForTags.Add(tag.ID) + } + } + if tags.Intersects(includeForTags) { + includedQuestions = append(includedQuestions, q) + } + continue + } + + if len(q.ExcludeFor) > 0 { + excludeForTags := NewSet() + for _, t := range q.ExcludeFor { + tag, found := tagResolver.Resolve(t.Category, t.Tag) + if found { + excludeForTags.Add(tag.ID) + } + } + if tags.Intersects(excludeForTags) { + continue + } + } + includedQuestions = append(includedQuestions, q) + } + s.Questions = includedQuestions + } + return +} + +func (r *Assessment) Tags() (tags []model.CategorizedTag) { + for _, s := range r.Sections { + for _, t := range r.sectionTags(&s) { + tags = append(tags, t) + } + } + return } // Complete returns whether all questions in the section have been answered. -func (r *Section) Complete() bool { - for _, q := range r.Questions { - if !q.Answered() { +func (r *Assessment) sectionComplete(s *model.Section) bool { + for _, q := range s.Questions { + if !r.questionAnswered(&q) { return false } } @@ -137,9 +185,10 @@ func (r *Section) Complete() bool { } // Started returns whether any questions in the section have been answered. -func (r *Section) Started() bool { - for _, q := range r.Questions { - if q.Answered() && !q.AutoAnswered() { + +func (r *Assessment) sectionStarted(s *model.Section) bool { + for _, q := range s.Questions { + if r.questionAnswered(&q) && !r.questionAutoAnswered(&q) { return true } } @@ -147,36 +196,26 @@ func (r *Section) Started() bool { } // Risks returns a slice of the risks of each of its questions. -func (r *Section) Risks() []string { +func (r *Assessment) sectionRisks(s *model.Section) []string { risks := []string{} - for _, q := range r.Questions { - risks = append(risks, q.Risk()) + for _, q := range s.Questions { + risks = append(risks, r.questionRisk(&q)) } return risks } // Tags returns all the tags that should be applied based on how // the questions in the section have been answered. -func (r *Section) Tags() (tags []CategorizedTag) { - for _, q := range r.Questions { - tags = append(tags, q.Tags()...) +func (r *Assessment) sectionTags(s *model.Section) (tags []model.CategorizedTag) { + for _, q := range s.Questions { + tags = append(tags, r.questionTags(&q)...) } return } -// Question represents a question in a questionnaire. -type Question struct { - Order *uint `json:"order" yaml:"order" binding:"required"` - Text string `json:"text" yaml:"text"` - Explanation string `json:"explanation" yaml:"explanation"` - IncludeFor []CategorizedTag `json:"includeFor,omitempty" yaml:"includeFor,omitempty"` - ExcludeFor []CategorizedTag `json:"excludeFor,omitempty" yaml:"excludeFor,omitempty"` - Answers []Answer `json:"answers" yaml:"answers" binding:"min=1,dive"` -} - // Risk returns the risk level for the question based on how it has been answered. -func (r *Question) Risk() string { - for _, a := range r.Answers { +func (r *Assessment) questionRisk(q *model.Question) string { + for _, a := range q.Answers { if a.Selected { return a.Risk } @@ -185,8 +224,8 @@ func (r *Question) Risk() string { } // Answered returns whether the question has had an answer selected. -func (r *Question) Answered() bool { - for _, a := range r.Answers { +func (r *Assessment) questionAnswered(q *model.Question) bool { + for _, a := range q.Answers { if a.Selected { return true } @@ -196,8 +235,8 @@ func (r *Question) Answered() bool { // AutoAnswered returns whether the question has had an // answer pre-selected by the system. -func (r *Question) AutoAnswered() bool { - for _, a := range r.Answers { +func (r *Assessment) questionAutoAnswered(q *model.Question) bool { + for _, a := range q.Answers { if a.AutoAnswered { return true } @@ -206,8 +245,8 @@ func (r *Question) AutoAnswered() bool { } // Tags returns any tags to be applied based on how the question is answered. -func (r *Question) Tags() (tags []CategorizedTag) { - for _, answer := range r.Answers { +func (r *Assessment) questionTags(q *model.Question) (tags []model.CategorizedTag) { + for _, answer := range q.Answers { if answer.Selected { tags = answer.ApplyTags return @@ -215,37 +254,3 @@ func (r *Question) Tags() (tags []CategorizedTag) { } return } - -// Answer represents an answer to a question in a questionnaire. -type Answer struct { - Order *uint `json:"order" yaml:"order" binding:"required"` - Text string `json:"text" yaml:"text"` - Risk string `json:"risk" yaml:"risk" binding:"oneof=red yellow green unknown"` - Rationale string `json:"rationale" yaml:"rationale"` - Mitigation string `json:"mitigation" yaml:"mitigation"` - ApplyTags []CategorizedTag `json:"applyTags,omitempty" yaml:"applyTags,omitempty"` - AutoAnswerFor []CategorizedTag `json:"autoAnswerFor,omitempty" yaml:"autoAnswerFor,omitempty"` - Selected bool `json:"selected,omitempty" yaml:"selected,omitempty"` - AutoAnswered bool `json:"autoAnswered,omitempty" yaml:"autoAnswered,omitempty"` -} - -// CategorizedTag represents a human-readable pair of category and tag. -type CategorizedTag struct { - Category string `json:"category" yaml:"category"` - Tag string `json:"tag" yaml:"tag"` -} - -// RiskMessages contains messages to display for each risk level. -type RiskMessages struct { - Red string `json:"red" yaml:"red"` - Yellow string `json:"yellow" yaml:"yellow"` - Green string `json:"green" yaml:"green"` - Unknown string `json:"unknown" yaml:"unknown"` -} - -// Thresholds contains the threshold values for determining risk for the questionnaire. -type Thresholds struct { - Red uint `json:"red" yaml:"red"` - Yellow uint `json:"yellow" yaml:"yellow"` - Unknown uint `json:"unknown" yaml:"unknown"` -} diff --git a/assessment/assessment_test.go b/assessment/assessment_test.go index 28efe1154..69df6702b 100644 --- a/assessment/assessment_test.go +++ b/assessment/assessment_test.go @@ -7,15 +7,16 @@ import ( "github.com/onsi/gomega" ) -func TestPrepareSections(t *testing.T) { +func TestPrepare(t *testing.T) { g := gomega.NewGomegaWithT(t) - sections := []Section{ + assessment := model.Assessment{} + assessment.Sections = []model.Section{ { - Questions: []Question{ + Questions: []model.Question{ { Text: "Default", - Answers: []Answer{ + Answers: []model.Answer{ { Text: "Answer1", }, @@ -26,10 +27,10 @@ func TestPrepareSections(t *testing.T) { }, { Text: "Should Include", - IncludeFor: []CategorizedTag{ + IncludeFor: []model.CategorizedTag{ {Category: "Category", Tag: "Tag"}, }, - Answers: []Answer{ + Answers: []model.Answer{ { Text: "Answer1", }, @@ -40,10 +41,10 @@ func TestPrepareSections(t *testing.T) { }, { Text: "Should Exclude", - ExcludeFor: []CategorizedTag{ + ExcludeFor: []model.CategorizedTag{ {Category: "Category", Tag: "Tag"}, }, - Answers: []Answer{ + Answers: []model.Answer{ { Text: "Answer1", }, @@ -54,10 +55,10 @@ func TestPrepareSections(t *testing.T) { }, { Text: "AutoAnswer", - Answers: []Answer{ + Answers: []model.Answer{ { Text: "Answer1", - AutoAnswerFor: []CategorizedTag{ + AutoAnswerFor: []model.CategorizedTag{ {Category: "Category", Tag: "Tag"}, }, }, @@ -69,6 +70,9 @@ func TestPrepareSections(t *testing.T) { }, }, } + a := Assessment{} + a.With(&assessment) + tagResolver := TagResolver{ cache: map[string]map[string]*model.Tag{ "Category": {"Tag": {Model: model.Model{ID: 1}}}, @@ -77,16 +81,16 @@ func TestPrepareSections(t *testing.T) { tags := NewSet() tags.Add(1) - preparedSections := prepareSections(&tagResolver, tags, sections) - questions := preparedSections[0].Questions + a.Prepare(&tagResolver, tags) + questions := a.Sections[0].Questions g.Expect(len(questions)).To(gomega.Equal(3)) g.Expect(questions[0].Text).To(gomega.Equal("Default")) - g.Expect(questions[0].Answered()).To(gomega.BeFalse()) + g.Expect(a.questionAnswered(&questions[0])).To(gomega.BeFalse()) g.Expect(questions[1].Text).To(gomega.Equal("Should Include")) - g.Expect(questions[1].Answered()).To(gomega.BeFalse()) + g.Expect(a.questionAnswered(&questions[1])).To(gomega.BeFalse()) g.Expect(questions[2].Text).To(gomega.Equal("AutoAnswer")) - g.Expect(questions[2].Answered()).To(gomega.BeTrue()) + g.Expect(a.questionAnswered(&questions[2])).To(gomega.BeTrue()) g.Expect(questions[2].Answers[0].Text).To(gomega.Equal("Answer1")) g.Expect(questions[2].Answers[0].AutoAnswered).To(gomega.BeTrue()) g.Expect(questions[2].Answers[0].Selected).To(gomega.BeTrue()) @@ -95,113 +99,117 @@ func TestPrepareSections(t *testing.T) { func TestAssessmentStarted(t *testing.T) { g := gomega.NewGomegaWithT(t) - assessment := Assessment{ - Sections: []Section{ - { - Questions: []Question{ - { - Text: "S1Q1", - Answers: []Answer{ - { - Text: "A1", - Selected: true, - }, - { - Text: "A2", - }, + assessment := model.Assessment{} + assessment.Sections = []model.Section{ + { + Questions: []model.Question{ + { + Text: "S1Q1", + Answers: []model.Answer{ + { + Text: "A1", + Selected: true, + }, + { + Text: "A2", }, }, - { - Text: "S1Q2", - Answers: []Answer{ - { - Text: "A1", - }, - { - Text: "A2", - }, + }, + { + Text: "S1Q2", + Answers: []model.Answer{ + { + Text: "A1", + }, + { + Text: "A2", }, }, }, }, - { - Questions: []Question{ - { - Text: "S2Q1", - Answers: []Answer{ - { - Text: "A1", - }, - { - Text: "A2", - }, + }, + { + Questions: []model.Question{ + { + Text: "S2Q1", + Answers: []model.Answer{ + { + Text: "A1", + }, + { + Text: "A2", }, }, }, }, }, } - g.Expect(assessment.Started()).To(gomega.BeTrue()) - g.Expect(assessment.Status()).To(gomega.Equal(StatusStarted)) - assessment.Sections[0].Questions[0].Answers[0].AutoAnswered = true - g.Expect(assessment.Started()).To(gomega.BeFalse()) - g.Expect(assessment.Status()).To(gomega.Equal(StatusEmpty)) + + a := Assessment{} + a.With(&assessment) + g.Expect(a.Started()).To(gomega.BeTrue()) + g.Expect(a.Status()).To(gomega.Equal(StatusStarted)) + a.Sections[0].Questions[0].Answers[0].AutoAnswered = true + g.Expect(a.Started()).To(gomega.BeFalse()) + g.Expect(a.Status()).To(gomega.Equal(StatusEmpty)) } func TestAssessmentComplete(t *testing.T) { g := gomega.NewGomegaWithT(t) - assessment := Assessment{ - Sections: []Section{ - { - Questions: []Question{ - { - Text: "S1Q1", - Answers: []Answer{ - { - Text: "A1", - }, - { - Text: "A2", - }, + assessment := model.Assessment{} + assessment.Sections = []model.Section{ + { + Questions: []model.Question{ + { + Text: "S1Q1", + Answers: []model.Answer{ + { + Text: "A1", + }, + { + Text: "A2", }, }, - { - Text: "S1Q2", - Answers: []Answer{ - { - Text: "A1", - Selected: true, - }, - { - Text: "A2", - }, + }, + { + Text: "S1Q2", + Answers: []model.Answer{ + { + Text: "A1", + Selected: true, + }, + { + Text: "A2", }, }, }, }, - { - Questions: []Question{ - { - Text: "S2Q1", - Answers: []Answer{ - { - Text: "A1", - }, - { - Text: "A2", - Selected: true, - AutoAnswered: true, - }, + }, + { + Questions: []model.Question{ + { + Text: "S2Q1", + Answers: []model.Answer{ + { + Text: "A1", + }, + { + Text: "A2", + Selected: true, + AutoAnswered: true, }, }, }, }, }, } - g.Expect(assessment.Complete()).To(gomega.BeFalse()) - g.Expect(assessment.Status()).To(gomega.Equal(StatusStarted)) - assessment.Sections[0].Questions[0].Answers[0].Selected = true - g.Expect(assessment.Complete()).To(gomega.BeTrue()) - g.Expect(assessment.Status()).To(gomega.Equal(StatusComplete)) + + a := Assessment{} + a.With(&assessment) + g.Expect(a.Complete()).To(gomega.BeFalse()) + g.Expect(a.Status()).To(gomega.Equal(StatusStarted)) + a.Sections[0].Questions[0].Answers[0].Selected = true + g.Expect(a.Complete()).To(gomega.BeTrue()) + g.Expect(a.Status()).To(gomega.Equal(StatusComplete)) } diff --git a/assessment/membership.go b/assessment/membership.go index b6180c5f3..71c74fad7 100644 --- a/assessment/membership.go +++ b/assessment/membership.go @@ -22,6 +22,7 @@ type MembershipResolver struct { tagSets map[uint]Set archetypeMembers map[uint][]Application membersCached bool + archetypesCached bool } // Applications returns the list of applications that are members of the given archetype. @@ -77,7 +78,7 @@ loop: } func (r *MembershipResolver) cacheArchetypes() (err error) { - if len(r.archetypes) > 0 { + if r.archetypesCached { return } @@ -101,6 +102,7 @@ func (r *MembershipResolver) cacheArchetypes() (err error) { } r.tagSets[a.ID] = set } + r.archetypesCached = true return } diff --git a/assessment/pkg.go b/assessment/pkg.go index 263eca7a0..c0268833e 100644 --- a/assessment/pkg.go +++ b/assessment/pkg.go @@ -1,8 +1,6 @@ package assessment import ( - "encoding/json" - "github.com/konveyor/tackle2-hub/model" ) @@ -93,25 +91,19 @@ func Confidence(assessments []Assessment) (confidence int) { // PrepareForApplication prepares the sections of an assessment by including, excluding, // or auto-answering questions based on a set of tags. func PrepareForApplication(tagResolver *TagResolver, application *model.Application, assessment *model.Assessment) { - sections := []Section{} - _ = json.Unmarshal(assessment.Sections, §ions) - tagSet := NewSet() for _, t := range application.Tags { tagSet.Add(t.ID) } - - assessment.Sections, _ = json.Marshal(prepareSections(tagResolver, tagSet, sections)) - + a := Assessment{} + a.With(assessment) + a.Prepare(tagResolver, tagSet) return } // PrepareForArchetype prepares the sections of an assessment by including, excluding, // or auto-answering questions based on a set of tags. func PrepareForArchetype(tagResolver *TagResolver, archetype *model.Archetype, assessment *model.Assessment) { - sections := []Section{} - _ = json.Unmarshal(assessment.Sections, §ions) - tagSet := NewSet() for _, t := range archetype.CriteriaTags { tagSet.Add(t.ID) @@ -119,63 +111,8 @@ func PrepareForArchetype(tagResolver *TagResolver, archetype *model.Archetype, a for _, t := range archetype.Tags { tagSet.Add(t.ID) } - - assessment.Sections, _ = json.Marshal(prepareSections(tagResolver, tagSet, sections)) - - return -} - -func prepareSections(tagResolver *TagResolver, tags Set, sections []Section) (preparedSections []Section) { - for i := range sections { - s := §ions[i] - includedQuestions := []Question{} - for _, q := range s.Questions { - for j := range q.Answers { - a := &q.Answers[j] - autoAnswerTags := NewSet() - for _, t := range a.AutoAnswerFor { - tag, found := tagResolver.Resolve(t.Category, t.Tag) - if found { - autoAnswerTags.Add(tag.ID) - } - } - if tags.Intersects(autoAnswerTags) { - a.AutoAnswered = true - a.Selected = true - break - } - } - - if len(q.IncludeFor) > 0 { - includeForTags := NewSet() - for _, t := range q.IncludeFor { - tag, found := tagResolver.Resolve(t.Category, t.Tag) - if found { - includeForTags.Add(tag.ID) - } - } - if tags.Intersects(includeForTags) { - includedQuestions = append(includedQuestions, q) - } - continue - } - - if len(q.ExcludeFor) > 0 { - excludeForTags := NewSet() - for _, t := range q.ExcludeFor { - tag, found := tagResolver.Resolve(t.Category, t.Tag) - if found { - excludeForTags.Add(tag.ID) - } - } - if tags.Intersects(excludeForTags) { - continue - } - } - includedQuestions = append(includedQuestions, q) - } - s.Questions = includedQuestions - } - preparedSections = sections + a := Assessment{} + a.With(assessment) + a.Prepare(tagResolver, tagSet) return } diff --git a/assessment/tag.go b/assessment/tag.go index 930ed9b31..39eb44658 100644 --- a/assessment/tag.go +++ b/assessment/tag.go @@ -30,12 +30,10 @@ func (r *TagResolver) Resolve(category string, tag string) (t *model.Tag, found // Assessment returns all the Tag models that should be applied from the assessment. func (r *TagResolver) Assessment(assessment Assessment) (tags []model.Tag) { - for _, s := range assessment.Sections { - for _, t := range s.Tags() { - tag, found := r.Resolve(t.Category, t.Tag) - if found { - tags = append(tags, *tag) - } + for _, t := range assessment.Tags() { + tag, found := r.Resolve(t.Category, t.Tag) + if found { + tags = append(tags, *tag) } } return diff --git a/binding/application.go b/binding/application.go index b8b02fd62..ec7406f43 100644 --- a/binding/application.go +++ b/binding/application.go @@ -229,8 +229,8 @@ func (h *AppFacts) Source(source string) { } // List facts. -func (h *AppFacts) List() (facts api.FactMap, err error) { - facts = api.FactMap{} +func (h *AppFacts) List() (facts api.Map, err error) { + facts = api.Map{} key := api.FactKey("") key.Qualify(h.source) path := Path(api.ApplicationFactsRoot).Inject(Params{api.ID: h.appId, api.Key: key}) @@ -278,7 +278,7 @@ func (h *AppFacts) Delete(name string) (err error) { } // Replace facts. -func (h *AppFacts) Replace(facts api.FactMap) (err error) { +func (h *AppFacts) Replace(facts api.Map) (err error) { key := api.FactKey("") key.Qualify(h.source) path := Path(api.ApplicationFactsRoot).Inject(Params{api.ID: h.appId, api.Key: key}) diff --git a/hack/cmd/addon/main.go b/hack/cmd/addon/main.go index 3b5ddb5d3..88cb13333 100644 --- a/hack/cmd/addon/main.go +++ b/hack/cmd/addon/main.go @@ -9,16 +9,17 @@ package main import ( "bytes" "errors" - hub "github.com/konveyor/tackle2-hub/addon" - "github.com/konveyor/tackle2-hub/api" - "github.com/konveyor/tackle2-hub/nas" - "k8s.io/apimachinery/pkg/util/rand" "os" "os/exec" pathlib "path" "strconv" "strings" "time" + + hub "github.com/konveyor/tackle2-hub/addon" + "github.com/konveyor/tackle2-hub/api" + "github.com/konveyor/tackle2-hub/nas" + "k8s.io/apimachinery/pkg/util/rand" ) var ( @@ -32,7 +33,6 @@ const ( TmpDir = "/tmp/list" ) -// // main func main() { addon.Run(func() (err error) { @@ -76,7 +76,7 @@ func main() { // // Replace facts. err = facts.Replace( - api.FactMap{ + api.Map{ "Listed": true, "Color": "blue", "Length": 100, @@ -100,7 +100,6 @@ func main() { }) } -// // listDir builds and populates the bucket. func listDir(d *Data, application *api.Application, paths []string) (err error) { // @@ -178,7 +177,6 @@ func listDir(d *Data, application *api.Application, paths []string) (err error) return } -// // playWithBucket func playWithBucket(bucket *hub.BucketContent) (err error) { tmpDir := tmpDir() @@ -215,7 +213,6 @@ func playWithBucket(bucket *hub.BucketContent) (err error) { return } -// // Build index.html func buildIndex(output string) (err error) { addon.Activity("Building index.") @@ -247,7 +244,6 @@ func buildIndex(output string) (err error) { return } -// // find files. func find(path string, max int) (paths []string, err error) { Log.Info("Listing.", "path", path) @@ -279,7 +275,6 @@ func find(path string, max int) (paths []string, err error) { return } -// // Play with files. func playWithFiles() (err error) { f, err := addon.File.Put("/etc/hosts") @@ -301,7 +296,6 @@ func playWithFiles() (err error) { return } -// // addTags ensure tags created and associated with application. // Ensure tag exists and associated with the application. func addTags(application *api.Application, source string, names ...string) (err error) { @@ -337,7 +331,6 @@ func addTags(application *api.Application, source string, names ...string) (err return } -// // replaceTags replaces current set of tags for the source with a new set. // Ensures desired tags exist before replacing. func replaceTags(application *api.Application, source string, names ...string) (err error) { @@ -395,7 +388,6 @@ func tmpDir() (p string) { return } -// // Data Addon input. type Data struct { // Path to be listed. diff --git a/importer/manager.go b/importer/manager.go index b11f45e4b..540214e2c 100644 --- a/importer/manager.go +++ b/importer/manager.go @@ -2,7 +2,6 @@ package importer import ( "context" - "encoding/json" "fmt" "regexp" @@ -136,7 +135,7 @@ func (m *Manager) createApplication(imp *model.Import) (ok bool) { return } - repository := api.Repository{ + repository := model.Repository{ Kind: imp.RepositoryKind, URL: imp.RepositoryURL, Branch: imp.RepositoryBranch, @@ -148,8 +147,7 @@ func (m *Manager) createApplication(imp *model.Import) (ok bool) { repository.Kind = "git" } - app.Repository, _ = json.Marshal(repository) - + app.Repository = repository // Validate Binary-related fields (allow all 3 empty or present) if imp.BinaryGroup != "" || imp.BinaryArtifact != "" || imp.BinaryVersion != "" { if imp.BinaryGroup == "" || imp.BinaryArtifact == "" || imp.BinaryVersion == "" { diff --git a/migration/json/fields.go b/migration/json/fields.go index 926aa40b4..b79cd6529 100644 --- a/migration/json/fields.go +++ b/migration/json/fields.go @@ -1,6 +1,8 @@ package json -import "gopkg.in/yaml.v2" +import ( + "gopkg.in/yaml.v2" +) // Ref represents a FK. type Ref struct { diff --git a/migration/migrate.go b/migration/migrate.go index 4c8ec8b71..43f582a79 100644 --- a/migration/migrate.go +++ b/migration/migrate.go @@ -1,7 +1,6 @@ package migration import ( - "encoding/json" "errors" "os" "path" @@ -44,12 +43,9 @@ func Migrate(migrations []Migration) (err error) { } var v Version - if setting.Value != nil { - err = json.Unmarshal(setting.Value, &v) - if err != nil { - err = liberr.Wrap(err) - return - } + err = setting.As(&v) + if err != nil { + return } var start = v.Version if start != 0 && start < MinimumVersion { @@ -112,9 +108,7 @@ func Migrate(migrations []Migration) (err error) { // Set the version record. func setVersion(db *gorm.DB, version int) (err error) { setting := &model.Setting{Key: VersionKey} - v := Version{Version: version} - value, _ := json.Marshal(v) - setting.Value = value + setting.Value = Version{Version: version} result := db.Where("key", VersionKey).Updates(setting) if result.Error != nil { err = liberr.Wrap(result.Error) diff --git a/migration/migrate_test.go b/migration/migrate_test.go index 943a85a1a..a82e59fa3 100644 --- a/migration/migrate_test.go +++ b/migration/migrate_test.go @@ -1,7 +1,6 @@ package migration import ( - "encoding/json" "os" "testing" @@ -132,7 +131,7 @@ func expectVersion(g *gomega.GomegaWithT, version int) { result := db.Find(setting, "key", VersionKey) g.Expect(result.Error).To(gomega.BeNil()) var v Version - _ = json.Unmarshal(setting.Value, &v) + _ = setting.As(&v) g.Expect(v.Version).To(gomega.Equal(version)) _ = database.Close(db) } diff --git a/migration/v14/model/analysis.go b/migration/v14/model/analysis.go index 4b61e1f40..bc33da893 100644 --- a/migration/v14/model/analysis.go +++ b/migration/v14/model/analysis.go @@ -1,6 +1,9 @@ package model -import "gorm.io/gorm" +import ( + "github.com/konveyor/tackle2-hub/migration/json" + "gorm.io/gorm" +) // Analysis report. type Analysis struct { @@ -8,7 +11,7 @@ type Analysis struct { Effort int Commit string Archived bool - Summary JSON `gorm:"type:json"` + Summary []ArchivedIssue `gorm:"type:json;serializer:json"` Issues []Issue `gorm:"constraint:OnDelete:CASCADE"` Dependencies []TechDependency `gorm:"constraint:OnDelete:CASCADE"` ApplicationID uint `gorm:"index;not null"` @@ -23,8 +26,8 @@ type TechDependency struct { Version string `gorm:"uniqueIndex:depA"` SHA string `gorm:"uniqueIndex:depA"` Indirect bool - Labels JSON `gorm:"type:json"` - AnalysisID uint `gorm:"index;uniqueIndex:depA;not null"` + Labels []string `gorm:"type:json;serializer:json"` + AnalysisID uint `gorm:"index;uniqueIndex:depA;not null"` Analysis *Analysis } @@ -37,9 +40,9 @@ type Issue struct { Description string Category string `gorm:"index;not null"` Incidents []Incident `gorm:"foreignKey:IssueID;constraint:OnDelete:CASCADE"` - Links JSON `gorm:"type:json"` - Facts JSON `gorm:"type:json"` - Labels JSON `gorm:"type:json"` + Links []Link `gorm:"type:json;serializer:json"` + Facts json.Map `gorm:"type:json;serializer:json"` + Labels []string `gorm:"type:json;serializer:json"` Effort int `gorm:"index;not null"` AnalysisID uint `gorm:"index;uniqueIndex:issueA;not null"` Analysis *Analysis @@ -52,28 +55,11 @@ type Incident struct { Line int Message string CodeSnip string - Facts JSON `gorm:"type:json"` - IssueID uint `gorm:"index;not null"` + Facts json.Map `gorm:"type:json;serializer:json"` + IssueID uint `gorm:"index;not null"` Issue *Issue } -// Link URL link. -type Link struct { - URL string `json:"url"` - Title string `json:"title,omitempty"` -} - -// ArchivedIssue resource created when issues are archived. -type ArchivedIssue struct { - RuleSet string `json:"ruleSet"` - Rule string `json:"rule"` - Name string `json:"name,omitempty" yaml:",omitempty"` - Description string `json:"description,omitempty" yaml:",omitempty"` - Category string `json:"category"` - Effort int `json:"effort"` - Incidents int `json:"incidents"` -} - // RuleSet - Analysis ruleset. type RuleSet struct { Model @@ -81,8 +67,8 @@ type RuleSet struct { Kind string Name string `gorm:"uniqueIndex;not null"` Description string - Repository JSON `gorm:"type:json"` - IdentityID *uint `gorm:"index"` + Repository Repository `gorm:"type:json;serializer:json"` + IdentityID *uint `gorm:"index"` Identity *Identity Rules []Rule `gorm:"constraint:OnDelete:CASCADE"` DependsOn []RuleSet `gorm:"many2many:RuleSetDependencies;constraint:OnDelete:CASCADE"` @@ -130,8 +116,8 @@ type Rule struct { Model Name string Description string - Labels JSON `gorm:"type:json"` - RuleSetID uint `gorm:"uniqueIndex:RuleA;not null"` + Labels []string `gorm:"type:json;serializer:json"` + RuleSetID uint `gorm:"uniqueIndex:RuleA;not null"` RuleSet *RuleSet FileID *uint `gorm:"uniqueIndex:RuleA" ref:"file"` File *File @@ -145,8 +131,8 @@ type Target struct { Description string Provider string Choice bool - Labels JSON `gorm:"type:json"` - ImageID uint `gorm:"index" ref:"file"` + Labels []TargetLabel `gorm:"type:json;serializer:json"` + ImageID uint `gorm:"index" ref:"file"` Image *File RuleSetID *uint `gorm:"index"` RuleSet *RuleSet @@ -155,3 +141,30 @@ type Target struct { func (r *Target) Builtin() bool { return r.UUID != nil } + +// +// JSON Fields. +// + +// ArchivedIssue resource created when issues are archived. +type ArchivedIssue struct { + RuleSet string `json:"ruleSet"` + Rule string `json:"rule"` + Name string `json:"name,omitempty" yaml:",omitempty"` + Description string `json:"description,omitempty" yaml:",omitempty"` + Category string `json:"category"` + Effort int `json:"effort"` + Incidents int `json:"incidents"` +} + +// Link URL link. +type Link struct { + URL string `json:"url"` + Title string `json:"title,omitempty"` +} + +// TargetLabel - label format specific to Targets +type TargetLabel struct { + Name string `json:"name"` + Label string `json:"label"` +} diff --git a/migration/v14/model/application.go b/migration/v14/model/application.go index 49a9a959a..cb363b78d 100644 --- a/migration/v14/model/application.go +++ b/migration/v14/model/application.go @@ -5,6 +5,7 @@ import ( "sync" "time" + "github.com/konveyor/tackle2-hub/migration/json" "gorm.io/gorm" ) @@ -13,8 +14,8 @@ type Application struct { BucketOwner Name string `gorm:"index;unique;not null"` Description string - Review *Review `gorm:"constraint:OnDelete:CASCADE"` - Repository JSON `gorm:"type:json"` + Review *Review `gorm:"constraint:OnDelete:CASCADE"` + Repository Repository `gorm:"type:json;serializer:json"` Binary string Facts []Fact `gorm:"constraint:OnDelete:CASCADE"` Comments string @@ -37,7 +38,7 @@ type Fact struct { ApplicationID uint `gorm:"<-:create;primaryKey"` Key string `gorm:"<-:create;primaryKey"` Source string `gorm:"<-:create;primaryKey;not null"` - Value JSON `gorm:"type:json;not null"` + Value any `gorm:"type:json;not null;serializer:json"` Application *Application } @@ -195,7 +196,7 @@ type Ticket struct { // Parent resource that this ticket should belong to in the tracker. (e.g. Jira project) Parent string `gorm:"not null"` // Custom fields to send to the tracker when creating the ticket - Fields JSON `gorm:"type:json"` + Fields json.Map `gorm:"type:json;serializer:json"` // Whether the last attempt to do something with the ticket reported an error Error bool // Error message, if any @@ -297,6 +298,11 @@ type ImportTag struct { Import *Import } +// +// JSON Fields. +// + +// Repository represents an SCM repository. type Repository struct { Kind string `json:"kind"` URL string `json:"url"` diff --git a/migration/v14/model/assessment.go b/migration/v14/model/assessment.go index 3a734e86e..0b51e714d 100644 --- a/migration/v14/model/assessment.go +++ b/migration/v14/model/assessment.go @@ -6,9 +6,9 @@ type Questionnaire struct { Name string `gorm:"unique"` Description string Required bool - Sections JSON `gorm:"type:json"` - Thresholds JSON `gorm:"type:json"` - RiskMessages JSON `gorm:"type:json"` + Sections []Section `gorm:"type:json;serializer:json"` + Thresholds Thresholds `gorm:"type:json;serializer:json"` + RiskMessages RiskMessages `gorm:"type:json;serializer:json"` Assessments []Assessment `gorm:"constraint:OnDelete:CASCADE"` } @@ -25,9 +25,9 @@ type Assessment struct { Archetype *Archetype QuestionnaireID uint `gorm:"uniqueIndex:AssessmentA;uniqueIndex:AssessmentB"` Questionnaire Questionnaire - Sections JSON `gorm:"type:json"` - Thresholds JSON `gorm:"type:json"` - RiskMessages JSON `gorm:"type:json"` + Sections []Section `gorm:"type:json;serializer:json"` + Thresholds Thresholds `gorm:"type:json;serializer:json"` + RiskMessages RiskMessages `gorm:"type:json;serializer:json"` Stakeholders []Stakeholder `gorm:"many2many:AssessmentStakeholders;constraint:OnDelete:CASCADE"` StakeholderGroups []StakeholderGroup `gorm:"many2many:AssessmentStakeholderGroups;constraint:OnDelete:CASCADE"` } @@ -44,3 +44,59 @@ type Review struct { ArchetypeID *uint `gorm:"uniqueIndex"` Archetype *Archetype } + +// +// JSON Fields. +// + +// Section represents a group of questions in a questionnaire. +type Section struct { + Order uint `json:"order" yaml:"order"` + Name string `json:"name" yaml:"name"` + Questions []Question `json:"questions" yaml:"questions" binding:"min=1,dive"` + Comment string `json:"comment,omitempty" yaml:"comment,omitempty"` +} + +// Question represents a question in a questionnaire. +type Question struct { + Order uint `json:"order" yaml:"order"` + Text string `json:"text" yaml:"text"` + Explanation string `json:"explanation" yaml:"explanation"` + IncludeFor []CategorizedTag `json:"includeFor,omitempty" yaml:"includeFor,omitempty"` + ExcludeFor []CategorizedTag `json:"excludeFor,omitempty" yaml:"excludeFor,omitempty"` + Answers []Answer `json:"answers" yaml:"answers" binding:"min=1,dive"` +} + +// Answer represents an answer to a question in a questionnaire. +type Answer struct { + Order uint `json:"order" yaml:"order"` + Text string `json:"text" yaml:"text"` + Risk string `json:"risk" yaml:"risk" binding:"oneof=red yellow green unknown"` + Rationale string `json:"rationale" yaml:"rationale"` + Mitigation string `json:"mitigation" yaml:"mitigation"` + ApplyTags []CategorizedTag `json:"applyTags,omitempty" yaml:"applyTags,omitempty"` + AutoAnswerFor []CategorizedTag `json:"autoAnswerFor,omitempty" yaml:"autoAnswerFor,omitempty"` + Selected bool `json:"selected,omitempty" yaml:"selected,omitempty"` + AutoAnswered bool `json:"autoAnswered,omitempty" yaml:"autoAnswered,omitempty"` +} + +// CategorizedTag represents a human-readable pair of category and tag. +type CategorizedTag struct { + Category string `json:"category" yaml:"category"` + Tag string `json:"tag" yaml:"tag"` +} + +// RiskMessages contains messages to display for each risk level. +type RiskMessages struct { + Red string `json:"red" yaml:"red"` + Yellow string `json:"yellow" yaml:"yellow"` + Green string `json:"green" yaml:"green"` + Unknown string `json:"unknown" yaml:"unknown"` +} + +// Thresholds contains the threshold values for determining risk for the questionnaire. +type Thresholds struct { + Red uint `json:"red" yaml:"red"` + Yellow uint `json:"yellow" yaml:"yellow"` + Unknown uint `json:"unknown" yaml:"unknown"` +} diff --git a/migration/v14/model/core.go b/migration/v14/model/core.go index 06c02bd6d..a5d6a5ab1 100644 --- a/migration/v14/model/core.go +++ b/migration/v14/model/core.go @@ -23,22 +23,16 @@ type Model struct { type Setting struct { Model Key string `gorm:"<-:create;uniqueIndex"` - Value JSON `gorm:"type:json"` + Value any `gorm:"type:json;serializer:json"` } -// With updates the value of the Setting with the json representation -// of the `value` parameter. -func (r *Setting) With(value any) (err error) { - r.Value, err = json.Marshal(value) +// As unmarshalls the value of the Setting into the `ptr` parameter. +func (r *Setting) As(ptr any) (err error) { + bytes, err := json.Marshal(r.Value) if err != nil { err = liberr.Wrap(err) } - return -} - -// As unmarshalls the value of the Setting into the `ptr` parameter. -func (r *Setting) As(ptr any) (err error) { - err = json.Unmarshal(r.Value, ptr) + err = json.Unmarshal(bytes, ptr) if err != nil { err = liberr.Wrap(err) } @@ -146,26 +140,6 @@ func (m *Task) BeforeCreate(db *gorm.DB) (err error) { return } -// TaskError used in Task.Errors. -type TaskError struct { - Severity string `json:"severity"` - Description string `json:"description"` -} - -// TaskPolicy scheduling policy. -type TaskPolicy struct { - Isolated bool `json:"isolated,omitempty" yaml:",omitempty"` - PreemptEnabled bool `json:"preemptEnabled,omitempty" yaml:"preemptEnabled,omitempty"` - PreemptExempt bool `json:"preemptExempt,omitempty" yaml:"preemptExempt,omitempty"` -} - -// Attachment file attachment. -type Attachment struct { - ID uint `json:"id" binding:"required"` - Name string `json:"name,omitempty" yaml:",omitempty"` - Activity int `json:"activity,omitempty" yaml:",omitempty"` -} - type TaskReport struct { Model Status string @@ -202,8 +176,8 @@ type Proxy struct { Kind string `gorm:"uniqueIndex"` Host string `gorm:"not null"` Port int - Excluded JSON `gorm:"type:json"` - IdentityID *uint `gorm:"index"` + Excluded []string `gorm:"type:json;serializer:json"` + IdentityID *uint `gorm:"index"` Identity *Identity } @@ -289,6 +263,19 @@ func (r *Identity) Decrypt() (err error) { // JSON Fields. // +// Attachment file attachment. +type Attachment struct { + ID uint `json:"id" binding:"required"` + Name string `json:"name,omitempty" yaml:",omitempty"` + Activity int `json:"activity,omitempty" yaml:",omitempty"` +} + +// TaskError used in Task.Errors. +type TaskError struct { + Severity string `json:"severity"` + Description string `json:"description"` +} + // TaskEvent task event. type TaskEvent struct { Kind string `json:"kind"` @@ -297,6 +284,13 @@ type TaskEvent struct { Last time.Time `json:"last"` } +// TaskPolicy scheduling policy. +type TaskPolicy struct { + Isolated bool `json:"isolated,omitempty" yaml:",omitempty"` + PreemptEnabled bool `json:"preemptEnabled,omitempty" yaml:"preemptEnabled,omitempty"` + PreemptExempt bool `json:"preemptExempt,omitempty" yaml:"preemptExempt,omitempty"` +} + // TTL time-to-live. type TTL struct { Created int `json:"created,omitempty" yaml:",omitempty"` diff --git a/model/pkg.go b/model/pkg.go index 2cb906e60..5cf00cd5a 100644 --- a/model/pkg.go +++ b/model/pkg.go @@ -16,7 +16,6 @@ type Assessment = model.Assessment type TechDependency = model.TechDependency type Incident = model.Incident type Analysis = model.Analysis -type ArchivedIssue = model.ArchivedIssue type Issue = model.Issue type Bucket = model.Bucket type BucketOwner = model.BucketOwner @@ -47,16 +46,27 @@ type TaskReport = model.TaskReport type Ticket = model.Ticket type Tracker = model.Tracker -type TTL = model.TTL +// JSON fields type Ref = json.Ref type Map = json.Map type Data = json.Data - +type ArchivedIssue = model.ArchivedIssue +type Attachment = model.Attachment +type Link = model.Link +type Repository = model.Repository +type TargetLabel = model.TargetLabel type TaskError = model.TaskError type TaskEvent = model.TaskEvent type TaskPolicy = model.TaskPolicy -type Attachment = model.Attachment -type Repository = model.Repository +type TTL = model.TTL + +// Assessment JSON fields +type Section = model.Section +type Question = model.Question +type Answer = model.Answer +type Thresholds = model.Thresholds +type RiskMessages = model.RiskMessages +type CategorizedTag = model.CategorizedTag // Join tables type ApplicationTag = model.ApplicationTag diff --git a/seed/questionnaire.go b/seed/questionnaire.go index 87e419817..134e63c7f 100644 --- a/seed/questionnaire.go +++ b/seed/questionnaire.go @@ -77,9 +77,14 @@ func (r *Questionnaire) Apply(db *gorm.DB) (err error) { questionnaire.Name = q.Name questionnaire.UUID = &q.UUID questionnaire.Description = q.Description - questionnaire.Sections, _ = json.Marshal(q.Sections) - questionnaire.RiskMessages, _ = json.Marshal(q.RiskMessages) - questionnaire.Thresholds, _ = json.Marshal(q.Thresholds) + questionnaire.RiskMessages = model.RiskMessages(q.RiskMessages) + questionnaire.Thresholds = model.Thresholds(q.Thresholds) + bytes, jErr := json.Marshal(q.Sections) + if jErr != nil { + err = liberr.Wrap(jErr) + return + } + err = json.Unmarshal(bytes, &questionnaire.Sections) result := db.Save(&questionnaire) if result.Error != nil { err = liberr.Wrap(result.Error) diff --git a/seed/ruleset.go b/seed/ruleset.go index 3354b6aea..f746d1f08 100644 --- a/seed/ruleset.go +++ b/seed/ruleset.go @@ -128,14 +128,13 @@ func (r *RuleSet) applyRules(db *gorm.DB, ruleSet *model.RuleSet, rs libseed.Rul return } for _, rl := range rs.Rules { - labels, _ := json.Marshal(rl.Labels()) f, fErr := file(db, rl.Path) if fErr != nil { err = liberr.Wrap(fErr) return } rule := model.Rule{ - Labels: labels, + Labels: rl.Labels(), RuleSetID: ruleSet.ID, FileID: &f.ID, } diff --git a/seed/seed.go b/seed/seed.go index 1ae605a9d..a674e1c73 100644 --- a/seed/seed.go +++ b/seed/seed.go @@ -1,7 +1,6 @@ package seed import ( - "encoding/json" "fmt" "strings" @@ -75,12 +74,10 @@ func compareChecksum(db *gorm.DB, checksum []byte) (match bool, err error) { return } var seededChecksum string - if setting.Value != nil { - err = json.Unmarshal(setting.Value, &seededChecksum) - if err != nil { - err = liberr.Wrap(err) - return - } + err = setting.As(&seededChecksum) + if err != nil { + err = liberr.Wrap(err) + return } match = seededChecksum == fmt.Sprintf("%x", checksum) @@ -90,8 +87,7 @@ func compareChecksum(db *gorm.DB, checksum []byte) (match bool, err error) { // saveChecksum saves the seed checksum to the setting specified by SeedKey. func saveChecksum(db *gorm.DB, checksum []byte) (err error) { setting := &model.Setting{Key: SeedKey} - value, _ := json.Marshal(fmt.Sprintf("%x", checksum)) - setting.Value = value + setting.Value = fmt.Sprintf("%x", checksum) result := db.Where("key", SeedKey).Updates(setting) if result.Error != nil { err = liberr.Wrap(result.Error) @@ -110,12 +106,10 @@ func migrationVersion(db *gorm.DB) (version uint, err error) { } var v migration.Version - if setting.Value != nil { - err = json.Unmarshal(setting.Value, &v) - if err != nil { - err = liberr.Wrap(err) - return - } + err = setting.As(&v) + if err != nil { + err = liberr.Wrap(err) + return } version = uint(v.Version) diff --git a/seed/target.go b/seed/target.go index 6e2888091..9f665ee8d 100644 --- a/seed/target.go +++ b/seed/target.go @@ -2,7 +2,6 @@ package seed import ( "container/list" - "encoding/json" "errors" "fmt" @@ -80,7 +79,6 @@ func (r *Target) Apply(db *gorm.DB) (err error) { err = liberr.Wrap(fErr) return } - labels, _ := json.Marshal(t.Labels) target.UUID = &t.UUID target.Name = t.Name @@ -88,7 +86,9 @@ func (r *Target) Apply(db *gorm.DB) (err error) { target.Provider = t.Provider target.Choice = t.Choice target.ImageID = f.ID - target.Labels = labels + for _, l := range t.Labels { + target.Labels = append(target.Labels, model.TargetLabel(l)) + } result := db.Save(&target) if result.Error != nil { err = liberr.Wrap(result.Error) @@ -126,7 +126,7 @@ func (r *Target) reorder(db *gorm.DB, seedIds []uint) (err error) { } userOrder := []uint{} _ = s.As(&userOrder) - _ = s.With(merge(userOrder, seedIds, targetIds)) + s.Value = merge(userOrder, seedIds, targetIds) result = db.Where("key", UITargetOrder).Updates(s) if result.Error != nil { diff --git a/test/api/application/facts_test.go b/test/api/application/facts_test.go index d6d487bf1..f294e9304 100644 --- a/test/api/application/facts_test.go +++ b/test/api/application/facts_test.go @@ -108,7 +108,7 @@ func TestApplicationFactsList(t *testing.T) { factsPathSuffix := []string{"facts/test:", "facts/test:/"} for _, pathSuffix := range factsPathSuffix { t.Run(fmt.Sprintf("Fact list application %s with %s", application.Name, pathSuffix), func(t *testing.T) { - got := api.FactMap{} + got := api.Map{} err := Client.Get(fmt.Sprintf("%s/%s", binding.Path(api.ApplicationRoot).Inject(binding.Params{api.ID: application.ID}), pathSuffix), &got) if err != nil { t.Errorf("Get list error: %v", err.Error()) diff --git a/test/api/assessment/samples.go b/test/api/assessment/samples.go index 1a1ec5493..ccfb779e1 100644 --- a/test/api/assessment/samples.go +++ b/test/api/assessment/samples.go @@ -2,7 +2,7 @@ package assessment import ( "github.com/konveyor/tackle2-hub/api" - "github.com/konveyor/tackle2-hub/assessment" + "github.com/konveyor/tackle2-hub/model" "github.com/konveyor/tackle2-hub/test/api/application" "github.com/konveyor/tackle2-hub/test/api/questionnaire" ) @@ -17,28 +17,28 @@ var ( Questionnaire: api.Ref{ Name: questionnaire.Questionnaire1.Name, }, - Sections: []assessment.Section{ + Sections: []api.Section{ { - Order: uint2ptr(1), + Order: 1, Name: "Section 1", - Questions: []assessment.Question{ + Questions: []model.Question{ { - Order: uint2ptr(1), + Order: 1, Text: "What is your favorite color?", Explanation: "Please tell us your favorite color.", - Answers: []assessment.Answer{ + Answers: []model.Answer{ { - Order: uint2ptr(1), + Order: 1, Text: "Red", Risk: "red", }, { - Order: uint2ptr(2), + Order: 2, Text: "Green", Risk: "green", }, { - Order: uint2ptr(3), + Order: 3, Text: "Blue", Risk: "yellow", Selected: true, diff --git a/test/api/questionnaire/samples.go b/test/api/questionnaire/samples.go index 988dbf0c9..b1792f187 100644 --- a/test/api/questionnaire/samples.go +++ b/test/api/questionnaire/samples.go @@ -2,7 +2,7 @@ package questionnaire import ( "github.com/konveyor/tackle2-hub/api" - "github.com/konveyor/tackle2-hub/assessment" + "github.com/konveyor/tackle2-hub/model" ) // Set of valid resources for tests and reuse. @@ -11,30 +11,30 @@ var ( Name: "Questionnaire1", Description: "Questionnaire minimal sample 1", Required: true, - Thresholds: assessment.Thresholds{}, - RiskMessages: assessment.RiskMessages{}, - Sections: []assessment.Section{ + Thresholds: api.Thresholds{}, + RiskMessages: api.RiskMessages{}, + Sections: []api.Section{ { - Order: uint2ptr(1), + Order: 1, Name: "Section 1", - Questions: []assessment.Question{ + Questions: []model.Question{ { - Order: uint2ptr(1), + Order: 1, Text: "What is your favorite color?", Explanation: "Please tell us your favorite color.", - Answers: []assessment.Answer{ + Answers: []model.Answer{ { - Order: uint2ptr(1), + Order: 1, Text: "Red", Risk: "red", }, { - Order: uint2ptr(2), + Order: 2, Text: "Green", Risk: "green", }, { - Order: uint2ptr(3), + Order: 3, Text: "Blue", Risk: "yellow", Selected: true, @@ -47,7 +47,3 @@ var ( } Samples = []api.Questionnaire{Questionnaire1} ) - -func uint2ptr(u uint) *uint { - return &u -} diff --git a/trigger/application.go b/trigger/application.go index f40c8d297..607f368bf 100644 --- a/trigger/application.go +++ b/trigger/application.go @@ -23,7 +23,7 @@ func (r *Application) Updated(m *model.Application) (err error) { if !Settings.Discovery.Enabled { return } - if len(m.Repository) == 0 || string(m.Repository) == "null" { + if m.Repository == (model.Repository{}) { return } kinds, err := r.FindTasks(Settings.Discovery.Label)