Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

⚠️ Add "owner" and "contributors" columns to application import CSV format. #551

Merged
merged 2 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions api/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const (
RecordTypeDependency = "2"
)

const (
ExpectedFieldCount = 17
)

//
// Import Statuses
const (
Expand Down Expand Up @@ -270,8 +274,8 @@ func (h ImportHandler) UploadCSV(ctx *gin.Context) {
var imp model.Import
switch row[0] {
case RecordTypeApplication:
// Check row format - length, expecting 15 fields + tags
if len(row) < 15 {
// Check row format - length, expecting 17 fields + tags
if len(row) < ExpectedFieldCount {
h.Respond(ctx, http.StatusBadRequest, gin.H{"errorMessage": "Invalid Application Import CSV format."})
return
}
Expand Down Expand Up @@ -396,10 +400,12 @@ func (h ImportHandler) applicationFromRow(fileName string, row []string) (app mo
RepositoryURL: row[12],
RepositoryBranch: row[13],
RepositoryPath: row[14],
Owner: row[15],
Contributors: row[16],
}

// Tags
for i := 15; i < len(row); i++ {
for i := ExpectedFieldCount; i < len(row); i++ {
if i%2 == 0 {
tag := model.ImportTag{
Name: row[i],
Expand Down
84 changes: 84 additions & 0 deletions importer/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,54 @@ func (m *Manager) createApplication(imp *model.Import) (ok bool) {
}
}

if imp.Owner != "" {
name, email, parsed := parseStakeholder(imp.Owner)
if !parsed {
imp.ErrorMessage = fmt.Sprintf("Could not parse Owner '%s'.", imp.Owner)
return
}
owner, found := m.findStakeholder(email)
if !found {
if imp.ImportSummary.CreateEntities {
var err error
owner, err = m.createStakeholder(name, email)
if err != nil {
imp.ErrorMessage = fmt.Sprintf("Owner '%s' could not be created.", imp.Owner)
return
}
} else {
imp.ErrorMessage = fmt.Sprintf("Owner '%s' could not be found.", imp.Owner)
return
}
}
app.OwnerID = &owner.ID
}
if imp.Contributors != "" {
fields := strings.Split(imp.Contributors, ",")
for _, f := range fields {
name, email, parsed := parseStakeholder(f)
if !parsed {
imp.ErrorMessage = fmt.Sprintf("Could not parse Contributor '%s'.", f)
return
}
contributor, found := m.findStakeholder(email)
if !found {
if imp.ImportSummary.CreateEntities {
var err error
contributor, err = m.createStakeholder(name, email)
if err != nil {
imp.ErrorMessage = fmt.Sprintf("Contributor '%s' could not be created.", imp.Owner)
return
}
} else {
imp.ErrorMessage = fmt.Sprintf("Contributor '%s' could not be found.", imp.Owner)
return
}
}
app.Contributors = append(app.Contributors, contributor)
}
}

result := m.DB.Create(app)
if result.Error != nil {
imp.ErrorMessage = result.Error.Error()
Expand All @@ -295,6 +343,25 @@ func (m *Manager) createApplication(imp *model.Import) (ok bool) {
return
}

func (m *Manager) createStakeholder(name string, email string) (stakeholder model.Stakeholder, err error) {
stakeholder.Name = name
stakeholder.Email = email
err = m.DB.Create(&stakeholder).Error
if err != nil {
err = liberr.Wrap(err)
}
return
}

func (m *Manager) findStakeholder(email string) (stakeholder model.Stakeholder, found bool) {
result := m.DB.First(&stakeholder, "email = ?", email)
if result.Error != nil {
return
}
found = true
return
}

//
// normalizedName transforms given name to be comparable as same with similar names
// Example: normalizedName(" F oo-123 bar! ") returns "foo123bar!"
Expand All @@ -304,3 +371,20 @@ func normalizedName(name string) (normName string) {
normName = invalidSymbols.ReplaceAllString(normName, "")
return
}

//
// parseStakeholder attempts to parse a stakeholder's name and an email address
// out of a string like `John Smith <[email protected]>`. The pattern is very
// simple and treats anything before the first bracket as the name,
// and anything within the brackets as the email.
func parseStakeholder(s string) (name string, email string, parsed bool) {
pattern := regexp.MustCompile("(.+)\\s<(.+@.+)>")
matches := pattern.FindStringSubmatch(strings.TrimSpace(s))
if len(matches) != 3 {
return
}
parsed = true
name = matches[1]
email = strings.ToLower(matches[2])
return
}
2 changes: 2 additions & 0 deletions migration/v11/model/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ type Import struct {
RepositoryURL string
RepositoryBranch string
RepositoryPath string
Owner string
Contributors string
}

func (r *Import) AsMap() (m map[string]interface{}) {
Expand Down
Loading