Skip to content

Commit

Permalink
Add application parsing and validation, test issues
Browse files Browse the repository at this point in the history
  • Loading branch information
jodyheavener committed Mar 20, 2024
1 parent 4b37c28 commit e2a4404
Show file tree
Hide file tree
Showing 11 changed files with 702 additions and 8 deletions.
198 changes: 198 additions & 0 deletions script/application.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package main

import (
"encoding/json"
"fmt"
"log"
"strings"
"time"

"github.com/google/go-github/v60/github"
)

type Project struct {
Name string `json:"name"`
Description string `json:"description"`
Contributors int `json:"contributors"`
HomeUrl string `json:"home_url"`
RepoUrl string `json:"repo_url,omitempty"`
LicenseType string `json:"license_type,omitempty"`
LicenseUrl string `json:"license_url,omitempty"`
IsEvent bool `json:"is_event"`
IsTeam bool `json:"is_team"`
}

type Applicant struct {
Name string `json:"name"`
Email string `json:"email"`
Role string `json:"role"`
Id int64 `json:"id"`
}

type Application struct {
validator Validator `json:"-"`
sections map[string]string `json:"-"`
Problems []error `json:"-"`

Account string `json:"account"`
Project Project `json:"project"`
Applicant Applicant `json:"applicant"`
CanContact bool `json:"can_contact"`
ApproverId int `json:"approver_id,omitempty"`
IssueNumber int `json:"issue_number"`
CreatedAt time.Time `json:"created_at"`
}

func (a *Application) Parse(issue github.Issue) {
a.validator = Validator{}

if strings.Contains(*issue.Title, "[project name]") {
a.validator.AddError("Application title", *issue.Title, "is missing project name")
}

a.sections = a.extractSections(*issue.Body)

if isTestingIssue() {
data, err := json.MarshalIndent(a.sections, "", "\t")
if err != nil {
log.Fatalf("Could not marshal Sections input data: %s", err.Error())
}

debugMessage("Parsed input data:", string(data))
}

a.CreatedAt = issue.CreatedAt.Time
a.IssueNumber = *issue.Number
a.Account = a.stringSection("Account URL", IsPresent, ParseAccountUrl)
a.boolSection("Non-commercial confirmation", IsPresent, ParseCheckbox, IsChecked)

a.Project.IsTeam = a.boolSection("Team application", ParseCheckbox)
a.Project.IsEvent = a.boolSection("Event application", ParseCheckbox)

isProject := !a.Project.IsTeam && !a.Project.IsEvent

a.Project.Name = a.stringSection("Project name", IsPresent, IsRegularString)
a.Project.Description = a.stringSection("Short description", IsPresent, IsRegularString)
a.Project.Contributors = a.intSection("Number of team members/core contributors", IsPresent, IsRegularString)
a.Project.HomeUrl = a.stringSection("Homepage URL", IsPresent, IsUrl)
a.Project.RepoUrl = a.stringSection("Repository URL", IsUrl)
a.Project.LicenseType = a.stringSection("License type", When(isProject, IsPresent), IsRegularString)
a.Project.LicenseUrl = a.stringSection("License URL", When(isProject, IsPresent), IsUrl)
a.boolSection("Age confirmation", When(isProject, IsPresent), ParseCheckbox, When(isProject, IsChecked))

a.Applicant.Name = a.stringSection("Name", IsPresent, IsRegularString)
a.Applicant.Email = a.stringSection("Email", IsPresent, IsEmail)
a.Applicant.Role = a.stringSection("Project role", IsPresent, IsProjectRole)
a.Applicant.Id = *issue.User.ID

a.stringSection("Profile or website", IsUrl)
a.stringSection("Additional comments")

a.CanContact = a.boolSection("Can we contact you?", ParseCheckbox)

if isTestingIssue() {
debugMessage("Application data:", a.GetData())
}

for _, err := range a.validator.Errors {
a.Problems = append(a.Problems, fmt.Errorf(err.Error()))
}
}

func (a *Application) IsValid() bool {
return len(a.Problems) == 0
}

func (a *Application) GetData() string {
data, err := json.MarshalIndent(a, "", "\t")
if err != nil {
log.Fatalf("Could not marshal Application data: %s", err.Error())
}

return string(data)
}

func (a *Application) extractSections(body string) map[string]string {
sections := make(map[string]string)

lines := strings.Split(body, "\n")
var currentHeader string
contentBuilder := strings.Builder{}

for _, line := range lines {
trimmedLine := strings.TrimSpace(line)
if strings.HasPrefix(trimmedLine, "### ") {
if currentHeader != "" {
sections[currentHeader] = strings.TrimSpace(contentBuilder.String())
contentBuilder.Reset()
}
currentHeader = strings.TrimSpace(trimmedLine[4:])
} else if currentHeader != "" {
contentBuilder.WriteString(line + "\n")
}
}

if currentHeader != "" {
sections[currentHeader] = strings.TrimSpace(contentBuilder.String())
}

return sections
}

func (a *Application) stringSection(sectionName string, callbacks ...ValidatorCallback) string {
value, exists := a.sections[sectionName]

if !exists {
a.validator.AddError(sectionName, value, "was not completed for application")
return value
}

// everything gets passed through ParseInput first
callbacks = append([]ValidatorCallback{ParseInput}, callbacks...)

for _, callback := range callbacks {
pass, newValue, message := callback(value)
value = newValue

if !pass {
a.validator.AddError(sectionName, value, message)
break
}
}

return value
}

func (a *Application) intSection(sectionName string, callbacks ...ValidatorCallback) int {
value := a.stringSection(sectionName, callbacks...)

// don't bother proceeding if there's already an error parsing the string
if a.validator.HasError(sectionName) {
return 0
}

pass, number, message := ParseNumber(value)
if !pass {
a.validator.AddError(sectionName, fmt.Sprintf("%d", number), message)
return 0
}

return number
}

func (a *Application) boolSection(sectionName string, callbacks ...ValidatorCallback) bool {
value := a.stringSection(sectionName, callbacks...)

// don't bother proceeding if there's already an error parsing the string
if a.validator.HasError(sectionName) {
return false
}

pass, boolean, message := ParseBool(value)
if !pass {
a.validator.AddError(sectionName, fmt.Sprintf("%t", boolean), message)
return false
}

return boolean
}
3 changes: 3 additions & 0 deletions script/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ module 1password-open-source-processor
go 1.19

require (
github.com/PuerkitoBio/goquery v1.9.1
github.com/google/go-github/v60 v60.0.0
github.com/russross/blackfriday/v2 v2.1.0
golang.org/x/oauth2 v0.18.0
)

require (
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-querystring v1.1.0 // indirect
golang.org/x/net v0.22.0 // indirect
Expand Down
17 changes: 17 additions & 0 deletions script/go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI=
github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
Expand All @@ -11,35 +15,48 @@ github.com/google/go-github/v60 v60.0.0 h1:oLG98PsLauFvvu4D/YPxq374jhSxFYdzQGNCy
github.com/google/go-github/v60 v60.0.0/go.mod h1:ByhX2dP9XT9o/ll2yXAu2VD8l5eNVg8hD4Cr0S/LmQk=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
Expand Down
34 changes: 27 additions & 7 deletions script/reviewer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,43 @@ package main
import (
"fmt"
"log"
"strings"
)

type Reviewer struct {
GitHub GitHub
gitHub GitHub
application Application
}

func (r *Reviewer) Review() {
r.GitHub = GitHub{}
r.gitHub = GitHub{}
r.application = Application{}

if err := r.GitHub.Init(); err != nil {
r.PrintErrorAndExit(err)
if err := r.gitHub.Init(); err != nil {
r.printErrorAndExit(err)
}

// TODO: parse and validate the issue's body contents
fmt.Println(r.GitHub.Issue)
r.application.Parse(*r.gitHub.Issue)

if isTestingIssue() {
if r.application.IsValid() {
debugMessage("Application has no problems")
} else {
debugMessage("Application problems:", r.renderProblems())
}
}
}

func (r *Reviewer) PrintErrorAndExit(err error) {
func (r *Reviewer) printErrorAndExit(err error) {
log.Fatalf("Error reviewing issue: %s\n", err.Error())
}

func (r *Reviewer) renderProblems() string {
var problemStrings []string

for _, err := range r.application.Problems {
problemStrings = append(problemStrings, fmt.Sprintf("- %s", err.Error()))
}

return strings.Join(problemStrings, "\n")
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"number": 6,
"state": "open",
"locked": false,
"title": "Application for Some Project",
"title": "Application for Foo",
"body": "",
"user": {
"login": "wendyappleseed",
Expand Down
49 changes: 49 additions & 0 deletions script/test-issues/invalid-examples-1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"id": 1801650328,
"number": 6,
"state": "open",
"locked": false,
"title": "Application for [project name]",
"body": "### Account URL\n\nfoo\n\n### Non-commercial confirmation\n\n- [ ] No, this account won't be used for commercial activity\n\n### Team application\n\n- [ ] Yes, this application is for a team\n\n### Event application\n\n- [ ] Yes, this application is for an event\n\n### Project name\n\nTestDB 🎁\n\n### Short description\n\n[TestDB](https://testdb.com) is a free and open source, community-based forum software project.\n\n### Number of team members/core contributors\n\nfoo\n\n### Homepage URL\n\n@wendyappleed\n\n### Repository URL\n\nhttps://github.com/wendyappleed/test-db\n\n### License type\n\nMIT\n\n### License URL\n\nhttps://github.com/wendyappleed/test-db/blob/main/LICENSE.md\n\n### Age confirmation\n\n- [X] Yes, this project is at least 30 days old\n\n### Name\n\nWendy Appleseed\n\n### Email\n\[email protected]\n\n### Project role\n\nLead Dev\n\n### Profile or website\n\nhttps://github.com/wendyappleseed/\n\n### Can we contact you?\n\n- [X] Yes, you may contact me\n\n### Additional comments\n\nThank you! ✨",
"user": {
"login": "wendyappleseed",
"id": 38230737,
"node_id": "MDQ6VXNlcjYzOTIwNDk=",
"avatar_url": "https://avatars.githubusercontent.com/u/38230737?v=4",
"html_url": "https://github.com/wendyappleseed",
"gravatar_id": "",
"type": "User",
"site_admin": false,
"url": "https://api.github.com/users/wendyappleseed",
"events_url": "https://api.github.com/users/wendyappleseed/events{/privacy}",
"following_url": "https://api.github.com/users/wendyappleseed/following{/other_user}",
"followers_url": "https://api.github.com/users/wendyappleseed/followers",
"gists_url": "https://api.github.com/users/wendyappleseed/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/wendyappleseed/orgs",
"received_events_url": "https://api.github.com/users/wendyappleseed/received_events",
"repos_url": "https://api.github.com/users/wendyappleseed/repos",
"starred_url": "https://api.github.com/users/wendyappleseed/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/wendyappleseed/subscriptions"
},
"comments": 11,
"closed_at": "2023-07-13T05:03:51Z",
"created_at": "2023-07-12T19:49:35Z",
"updated_at": "2023-07-13T05:03:51Z",
"url": "https://api.github.com/repos/1Password/1password-teams-open-source/issues/6",
"html_url": "https://github.com/wendyappleseed/1password-teams-open-source/issues/6",
"comments_url": "https://api.github.com/repos/1Password/1password-teams-open-source/issues/6/comments",
"events_url": "https://api.github.com/repos/1Password/1password-teams-open-source/issues/6/events",
"labels_url": "https://api.github.com/repos/1Password/1password-teams-open-source/issues/6/labels{/name}",
"repository_url": "https://api.github.com/repos/1Password/1password-teams-open-source",
"reactions": {
"total_count": 0,
"+1": 0,
"-1": 0,
"laugh": 0,
"confused": 0,
"heart": 0,
"hooray": 0,
"url": "https://api.github.com/repos/1Password/1password-teams-open-source/issues/6/reactions"
},
"node_id": "I_kwDOJ6JE6M5rYwCY"
}
Loading

0 comments on commit e2a4404

Please sign in to comment.