diff --git a/api/archetype.go b/api/archetype.go index 5218ebc6e..2131b92b8 100644 --- a/api/archetype.go +++ b/api/archetype.go @@ -64,16 +64,24 @@ func (h ArchetypeHandler) Get(ctx *gin.Context) { return } - resolver, err := assessment.NewQuestionnaireResolver(h.DB(ctx)) + questionnaires, err := assessment.NewQuestionnaireResolver(h.DB(ctx)) if err != nil { _ = ctx.Error(err) return } + tags, err := assessment.NewTagResolver(h.DB(ctx)) + if err != nil { + _ = ctx.Error(err) + } + + resolver := assessment.NewArchetypeResolver(m, tags) + r := Archetype{} r.With(m) r.WithApplications(applications) - r.Assessed = resolver.Assessed(m.Assessments) + r.WithAssessmentTags(resolver.AssessmentTags()) + r.Assessed = questionnaires.Assessed(m.Assessments) h.Respond(ctx, http.StatusOK, r) } @@ -93,11 +101,15 @@ func (h ArchetypeHandler) List(ctx *gin.Context) { return } - resolver, err := assessment.NewQuestionnaireResolver(h.DB(ctx)) + questionnaires, err := assessment.NewQuestionnaireResolver(h.DB(ctx)) if err != nil { _ = ctx.Error(err) return } + tags, err := assessment.NewTagResolver(h.DB(ctx)) + if err != nil { + _ = ctx.Error(err) + } membership := assessment.NewMembershipResolver(h.DB(ctx)) resources := []Archetype{} @@ -109,9 +121,11 @@ func (h ArchetypeHandler) List(ctx *gin.Context) { _ = ctx.Error(err) return } + resolver := assessment.NewArchetypeResolver(m, tags) r.With(m) r.WithApplications(applications) - r.Assessed = resolver.Assessed(m.Assessments) + r.WithAssessmentTags(resolver.AssessmentTags()) + r.Assessed = questionnaires.Assessed(m.Assessments) resources = append(resources, r) } @@ -331,6 +345,7 @@ type Archetype struct { Comments string `json:"comments" yaml:"comments"` Tags []Ref `json:"tags" yaml:"tags"` CriteriaTags []Ref `json:"criteriaTags" yaml:"criteriaTags"` + AssessmentTags []Ref `json:"assessmentTags" yaml:"assessmentTags"` Stakeholders []Ref `json:"stakeholders" yaml:"stakeholders"` StakeholderGroups []Ref `json:"stakeholderGroups" yaml:"stakeholderGroups"` Applications []Ref `json:"applications" yaml:"applications"` @@ -346,6 +361,7 @@ func (r *Archetype) With(m *model.Archetype) { r.Name = m.Name r.Description = m.Description r.Comments = m.Comments + r.AssessmentTags = []Ref{} r.Tags = []Ref{} for _, t := range m.Tags { r.Tags = append(r.Tags, r.ref(t.ID, &t)) @@ -383,6 +399,16 @@ func (r *Archetype) WithApplications(apps []model.Application) { } } +// +// WithAssessmentTags updates the Archetype resource with tags inherited from assessments. +func (r *Archetype) WithAssessmentTags(tags []model.Tag) { + for _, t := range tags { + ref := Ref{} + ref.With(t.ID, t.Name) + r.AssessmentTags = append(r.AssessmentTags, ref) + } +} + // // Model builds a model from the resource. func (r *Archetype) Model() (m *model.Archetype) { diff --git a/assessment/archetype.go b/assessment/archetype.go new file mode 100644 index 000000000..550330d25 --- /dev/null +++ b/assessment/archetype.go @@ -0,0 +1,38 @@ +package assessment + +import "github.com/konveyor/tackle2-hub/model" + +// +// NewArchetypeResolver creates a new ArchetypeResolver. +func NewArchetypeResolver(archetype *model.Archetype, tags *TagResolver) (a *ArchetypeResolver) { + a = &ArchetypeResolver{ + archetype: archetype, + tagResolver: tags, + } + return +} + +// +// ArchetypeResolver wraps an Archetype model +// with assessment-related functionality. +type ArchetypeResolver struct { + archetype *model.Archetype + tagResolver *TagResolver +} + +// +// AssessmentTags returns the list of tags that the archetype should +// inherit from the answers given to its assessments. +func (r *ArchetypeResolver) AssessmentTags() (tags []model.Tag) { + seenTags := make(map[uint]bool) + for _, assessment := range r.archetype.Assessments { + aTags := r.tagResolver.Assessment(&assessment) + for _, t := range aTags { + if _, found := seenTags[t.ID]; !found { + seenTags[t.ID] = true + tags = append(tags, t) + } + } + } + return +} diff --git a/assessment/membership.go b/assessment/membership.go index e6eeee83a..2119e0011 100644 --- a/assessment/membership.go +++ b/assessment/membership.go @@ -54,7 +54,7 @@ func (r *MembershipResolver) Archetypes(m *model.Application) (archetypes []mode matches := []model.Archetype{} for _, a := range r.archetypes { - if appTags.Superset(r.tagSets[a.ID]) { + if appTags.Superset(r.tagSets[a.ID], false) { matches = append(matches, a) } } @@ -69,7 +69,7 @@ loop: } a1tags := r.tagSets[a1.ID] a2tags := r.tagSets[a2.ID] - if a1tags.Subset(a2tags) { + if a1tags.Subset(a2tags, true) { continue loop } } diff --git a/assessment/questionnaire.go b/assessment/questionnaire.go index d1fa86d01..c1e1a3a49 100644 --- a/assessment/questionnaire.go +++ b/assessment/questionnaire.go @@ -60,7 +60,7 @@ loop: answered.Add(a.QuestionnaireID) } } - assessed = answered.Superset(r.requiredQuestionnaires) + assessed = answered.Superset(r.requiredQuestionnaires, false) return } diff --git a/assessment/set.go b/assessment/set.go index c71006248..5a49ee930 100644 --- a/assessment/set.go +++ b/assessment/set.go @@ -23,8 +23,10 @@ func (r Set) Size() int { // // Add a member to the set. -func (r Set) Add(member uint) { - r.members[member] = true +func (r Set) Add(members ...uint) { + for _, member := range members { + r.members[member] = true + } } // @@ -35,7 +37,10 @@ func (r Set) Contains(element uint) bool { // // Superset tests whether every element of other is in the set. -func (r Set) Superset(other Set) bool { +func (r Set) Superset(other Set, strict bool) bool { + if strict && r.Size() <= other.Size() { + return false + } for m := range other.members { if !r.Contains(m) { return false @@ -46,8 +51,8 @@ func (r Set) Superset(other Set) bool { // // Subset tests whether every element of this set is in the other. -func (r Set) Subset(other Set) bool { - return other.Superset(r) +func (r Set) Subset(other Set, strict bool) bool { + return other.Superset(r, strict) } // diff --git a/assessment/set_test.go b/assessment/set_test.go new file mode 100644 index 000000000..b87df2ac2 --- /dev/null +++ b/assessment/set_test.go @@ -0,0 +1,46 @@ +package assessment + +import ( + "github.com/onsi/gomega" + "testing" +) + +func TestSet_Superset(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + a := NewSet() + b := NewSet() + a.Add(1, 2, 3, 4) + b.Add(1, 2, 3, 4) + + g.Expect(a.Superset(b, false)).To(gomega.BeTrue()) + g.Expect(b.Superset(a, false)).To(gomega.BeTrue()) + g.Expect(a.Superset(b, true)).To(gomega.BeFalse()) + g.Expect(b.Superset(a, true)).To(gomega.BeFalse()) + + a.Add(5) + g.Expect(a.Superset(b, false)).To(gomega.BeTrue()) + g.Expect(a.Superset(b, true)).To(gomega.BeTrue()) + g.Expect(b.Superset(a, false)).To(gomega.BeFalse()) + g.Expect(b.Superset(a, true)).To(gomega.BeFalse()) +} + +func TestSet_Subset(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + a := NewSet() + b := NewSet() + a.Add(1, 2, 3, 4) + b.Add(1, 2, 3, 4) + + g.Expect(a.Subset(b, false)).To(gomega.BeTrue()) + g.Expect(b.Subset(a, false)).To(gomega.BeTrue()) + g.Expect(a.Subset(b, true)).To(gomega.BeFalse()) + g.Expect(b.Subset(a, true)).To(gomega.BeFalse()) + + b.Add(5) + g.Expect(a.Subset(b, false)).To(gomega.BeTrue()) + g.Expect(a.Subset(b, true)).To(gomega.BeTrue()) + g.Expect(b.Subset(a, false)).To(gomega.BeFalse()) + g.Expect(b.Subset(a, true)).To(gomega.BeFalse()) +}