diff --git a/script/application_test.go b/script/application_test.go index 37191223..34e942f4 100644 --- a/script/application_test.go +++ b/script/application_test.go @@ -7,35 +7,35 @@ import ( ) func errNoProjectName(sectionTitle string) error { - return fmt.Errorf("%s: is missing project name", sectionTitle) + return fmt.Errorf("**%s** is missing project name", sectionTitle) } func errIncomplete(sectionTitle string) error { - return fmt.Errorf("%s: was not completed for application", sectionTitle) + return fmt.Errorf("**%s** was not completed for application", sectionTitle) } func errEmpty(sectionTitle string) error { - return fmt.Errorf("%s: is empty", sectionTitle) + return fmt.Errorf("**%s** is empty", sectionTitle) } func errMustBeChecked(sectionTitle string) error { - return fmt.Errorf("%s: must be checked", sectionTitle) + return fmt.Errorf("**%s** must be checked", sectionTitle) } func errInvalidAccountURL(sectionTitle string) error { - return fmt.Errorf("%s: is invalid 1Password account URL", sectionTitle) + return fmt.Errorf("**%s** is invalid 1Password account URL", sectionTitle) } func errContainsEmoji(sectionTitle string) error { - return fmt.Errorf("%s: cannot contain emoji characters", sectionTitle) + return fmt.Errorf("**%s** cannot contain emoji characters", sectionTitle) } func errParsingNumber(sectionTitle string) error { - return fmt.Errorf("%s: could not be parsed into a number", sectionTitle) + return fmt.Errorf("**%s** could not be parsed into a number", sectionTitle) } func errInvalidURL(sectionTitle string) error { - return fmt.Errorf("%s: is an invalid URL", sectionTitle) + return fmt.Errorf("**%s** is an invalid URL", sectionTitle) } func TestApplication(t *testing.T) { @@ -71,6 +71,10 @@ func TestApplication(t *testing.T) { name: "event", expectedValid: true, }, + { + name: "project-character-test", + expectedValid: true, + }, { name: "empty-body", expectedValid: false, diff --git a/script/reviewer.go b/script/reviewer.go index 04c1371f..a47a1ebc 100644 --- a/script/reviewer.go +++ b/script/reviewer.go @@ -1,9 +1,19 @@ package main import ( + "fmt" "log" ) +type Status int + +const ( + Approved Status = iota + Reviewing + Invalid + New +) + type Reviewer struct { gitHub GitHub application Application @@ -19,11 +29,99 @@ func (r *Reviewer) Review() { r.application.Parse(r.gitHub.Issue) - if isTestingIssue() { - if r.application.IsValid() { - debugMessage("Application has no problems") + status := r.getStatus() + isClosed := *r.gitHub.Issue.State == "closed" + + r.updateLabels(status, isClosed) + r.createComment(status, isClosed) +} + +func (r *Reviewer) getStatus() Status { + if r.gitHub.IssueHasLabel(LabelStatusApproved) { + return Approved + } else if r.gitHub.IssueHasLabel(LabelStatusReviewing) { + return Reviewing + } else if r.gitHub.IssueHasLabel(LabelStatusInvalid) { + return Invalid + } else { + return New + } +} + +func (r *Reviewer) createComment(status Status, isClosed bool) { + title := "" + body := "" + + applicationData := fmt.Sprintf("
\nApplication data...\n\n```json\n%s\n```\n
", r.application.GetData()) + applicationFilePath := fmt.Sprintf("https://github.com/1Password/1password-teams-open-source/blob/main/data/%s", r.application.FileName()) + approvedBody := fmt.Sprintf("This application has already been approved and changes will not be reviewed. If you would like to modify the details of your application, submit a pull request against the stored [application data](%s). If you have any questions, contact us at [opensource@1password.com](mailto:opensource@1password.com).", applicationFilePath) + closedBody := "This application is closed and changes will not be reviewed. If you have any questions, contact us at [opensource@1password.com](mailto:opensource@1password.com)." + + // If the issue is closed, let the user know that they can't make changes. + // If the issue was closed because it got approved, let them know how they can + // modify their application details after the fact. + if isClosed { + if status == Approved { + body = approvedBody + } else { + body = closedBody + } + // This scanerio should never occur, as an approved issue should + // immediately be closed, but let's cover all bases. + } else if status == Approved { + body = approvedBody + } else if r.application.IsValid() { + if status == Reviewing { + title = "### πŸ‘ Application still valid" + body = fmt.Sprintf("\n\n%s\n\nWe’ve run our automated pre-checks and your updated application is still valid.", applicationData) } else { - debugMessage("Application problems:", r.application.RenderProblems()) + title = "### βœ… Your application is valid" + body = fmt.Sprintf("\n\n%s\n\nThanks for applying! Our automated pre-checks have determined your application is valid. Next step: our team will review your application and may have follow-up questions. You can still make changes to your application and it’ll be re-evaluated.", applicationData) + } + } else { + title = "### ❌ Your application is invalid" + body = fmt.Sprintf("\n\n%s\n\nOur automated pre-checks have detected the following problems:\n\n%s\n\nUpdate this issue to correct these problems and we’ll automatically re-evaluate your application.", applicationData, r.application.RenderProblems()) + } + + r.gitHub.CreateIssueComment(fmt.Sprintf("%s%s", title, body)) +} + +func (r *Reviewer) updateLabels(status Status, isClosed bool) { + if status == Approved || isClosed { + return + } + + if r.application.IsValid() { + if status == Invalid { + if err := r.gitHub.RemoveIssueLabel(LabelStatusInvalid); err != nil { + r.printErrorAndExit( + fmt.Errorf("could not remove issue label '%s': %s", LabelStatusInvalid, err.Error()), + ) + } + } + + if status != Reviewing { + if err := r.gitHub.AddIssueLabel(LabelStatusReviewing); err != nil { + r.printErrorAndExit( + fmt.Errorf("could not add issue label '%s': %s", LabelStatusReviewing, err.Error()), + ) + } + } + } else { + if status != Invalid { + if err := r.gitHub.AddIssueLabel(LabelStatusInvalid); err != nil { + r.printErrorAndExit( + fmt.Errorf("could not add issue label '%s': %s", LabelStatusInvalid, err.Error()), + ) + } + } + + if status == Reviewing { + if err := r.gitHub.RemoveIssueLabel(LabelStatusReviewing); err != nil { + r.printErrorAndExit( + fmt.Errorf("could not remove issue label '%s': %s", LabelStatusReviewing, err.Error()), + ) + } } } } diff --git a/script/test-issues/valid-project-approved-closed.json b/script/test-issues/valid-project-approved-closed.json new file mode 100644 index 00000000..e8af7923 --- /dev/null +++ b/script/test-issues/valid-project-approved-closed.json @@ -0,0 +1,60 @@ +{ + "id": 1801650328, + "number": 6, + "state": "closed", + "locked": false, + "title": "Application for TestDB", + "body": "### Account URL\n\ntestdb.1password.com\n\n### Non-commercial confirmation\n\n- [X] 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\nTestDB is a free and open source, community-based forum software project.\n\n### Number of team members/core contributors\n\n1\n\n### Homepage URL\n\nhttps://github.com/wendyappleed/test-db\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\nwendyappleseed@example.com\n\n### Project role\n\nCore Maintainer\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" + }, + "labels": [ + { + "id": 5728067083, + "url": "https://api.github.com/repos/1Password/1password-teams-open-source/labels/status:%20approved", + "name": "status: approved", + "color": "0052CC", + "description": "The application has been approved", + "default": false, + "node_id": "LA_kwDOJ6JE6M8AAAABVWteCw" + } + ], + "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" +} diff --git a/script/test-issues/valid-project-character-test.json b/script/test-issues/valid-project-character-test.json new file mode 100644 index 00000000..59363858 --- /dev/null +++ b/script/test-issues/valid-project-character-test.json @@ -0,0 +1,49 @@ +{ + "id": 1801650328, + "number": 6, + "state": "open", + "locked": false, + "title": "Application for TestDB", + "body": "### Account URL\n\n`testdb.1password.com`\n\n### Non-commercial confirmation\n\n- [X] No, this account won't be used for commercial activity\n\n### πŸ‘¨β€πŸ’» Team application\n\n- [X] 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\n`_TestDB_\n\n- test``\n\n### Short description\n\nTestDB is a free and open source, community-based forum software project.```\n\n## **This is a test comment** \n\n### Number of team members/core contributors\n\n1\n\n### Homepage URL\n\nhttps://github.com/wendyappleed/test-db\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\nwendyappleseed@example.com\n\n### Project role\n\nCore Maintainer\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\n Thank 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" +} diff --git a/script/test-issues/valid-project-reviewing.json b/script/test-issues/valid-project-reviewing.json new file mode 100644 index 00000000..80042b67 --- /dev/null +++ b/script/test-issues/valid-project-reviewing.json @@ -0,0 +1,60 @@ +{ + "id": 1801650328, + "number": 6, + "state": "open", + "locked": false, + "title": "Application for TestDB", + "body": "### Account URL\n\ntestdb.1password.com\n\n### Non-commercial confirmation\n\n- [X] 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\nTestDB is a free and open source, community-based forum software project.\n\n### Number of team members/core contributors\n\n1\n\n### Homepage URL\n\nhttps://github.com/wendyappleed/test-db\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\nwendyappleseed@example.com\n\n### Project role\n\nCore Maintainer\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" + }, + "labels": [ + { + "id": 5728067083, + "url": "https://api.github.com/repos/1Password/1password-teams-open-source/labels/status:%20reviewing", + "name": "status: reviewing", + "color": "0052CC", + "description": "The application is being reviewed", + "default": false, + "node_id": "LA_kwDOJ6JE6M8AAAABVWteCw" + } + ], + "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" +} diff --git a/script/validator.go b/script/validator.go index 9dcd247b..b783a858 100644 --- a/script/validator.go +++ b/script/validator.go @@ -30,7 +30,7 @@ type ValidationError struct { type ValidatorCallback func(string) (bool, string, string) func (e *ValidationError) Error() string { - return fmt.Sprintf("%s: %s", e.Section, e.Message) + return fmt.Sprintf("**%s** %s", e.Section, e.Message) } type Validator struct { @@ -69,11 +69,7 @@ func ParseInput(value string) (bool, string, string) { return true, "", "" } - return true, value, "" -} - -func ParsePlainString(value string) (bool, string, string) { - // strip all formattig, except for newlines + // strip all formatting, except for newlines html := blackfriday.Run([]byte(value)) doc, err := goquery.NewDocumentFromReader(bytes.NewReader(html)) if err != nil { @@ -81,6 +77,10 @@ func ParsePlainString(value string) (bool, string, string) { } value = strings.TrimSpace(doc.Text()) + return true, value, "" +} + +func ParsePlainString(value string) (bool, string, string) { if urlRegex.MatchString(value) { return false, value, "cannot contain URLs" } diff --git a/script/validator_test.go b/script/validator_test.go index 3a458240..cbeb9a65 100644 --- a/script/validator_test.go +++ b/script/validator_test.go @@ -30,6 +30,7 @@ func TestParseInput(t *testing.T) { {"_No response_", true, "", ""}, {"None", true, "", ""}, {"hello", true, "hello", ""}, + {"Testing formatting and link stripping", true, "Testing formatting and link stripping", ""}, } runValidationTests(t, testCases, ParseInput, "ParseInput") } @@ -39,7 +40,6 @@ func TestParsePlainString(t *testing.T) { {"", true, "", ""}, {"Hello world", true, "Hello world", ""}, {"πŸ‘‹ howdy", false, "πŸ‘‹ howdy", "cannot contain emoji characters"}, - {"Testing formatting and link stripping", true, "Testing formatting and link stripping", ""}, } runValidationTests(t, testCases, ParsePlainString, "ParsePlainString") }