Skip to content

Commit

Permalink
refactor: move generate workflow to FSM
Browse files Browse the repository at this point in the history
  • Loading branch information
HandOfGod94 committed Sep 9, 2023
1 parent b30c543 commit 6a48c41
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 91 deletions.
2 changes: 1 addition & 1 deletion cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var (
fromRef string
toRef string
writeTo string
DefaultTimeout = 5 * time.Second
DefaultTimeout = 60 * time.Second
requiredFlags = []string{"base_url", "email_id", "api_token", "repo_url"}
)

Expand Down
8 changes: 8 additions & 0 deletions pkg/jira_changelog/fsm_util/fsm_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package fsm_util

type State = string
type Event = string

func Before(event Event) string {
return "before_" + event
}
105 changes: 76 additions & 29 deletions pkg/jira_changelog/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"context"
"fmt"

. "github.com/handofgod94/gh-jira-changelog/pkg/jira_changelog/fsm_util"
"github.com/handofgod94/gh-jira-changelog/pkg/jira_changelog/git"
"github.com/handofgod94/gh-jira-changelog/pkg/jira_changelog/jira"
"github.com/looplab/fsm"
"github.com/samber/lo"
"golang.org/x/exp/slog"
)
Expand All @@ -16,6 +18,73 @@ type Generator struct {
toRef string
repoURL string
client jira.Client

commits []git.Commit
jiraIssues []jira.Issue
changes Changes
FSM *fsm.FSM
}

const (
Initial = State("initial")
CommitsFetched = State("commits_fetched")
JiraIssuesFetched = State("jira_issues_fetched")
JiraIssueGrouped = State("jira_issues_grouped")
ChangelogRecored = State("changelog_recorded")

FetchCommits = Event("fetch_commits")
FetchJiraIssues = Event("fetch_jira_issues")
GroupJiraIssues = Event("group_jira_issues")
RecordChangelog = Event("record_changelog")
)

func NewGenerator(jiraConfig jira.Config, fromRef, toRef, repoURL string) *Generator {
client := jira.NewClient(jiraConfig)
generator := &Generator{
JiraConfig: jiraConfig,
fromRef: fromRef,
toRef: toRef,
repoURL: repoURL,
client: client,
}

generator.FSM = fsm.NewFSM(
Initial,
fsm.Events{
{Name: FetchCommits, Src: []string{Initial}, Dst: CommitsFetched},
{Name: FetchJiraIssues, Src: []string{CommitsFetched}, Dst: JiraIssuesFetched},
{Name: GroupJiraIssues, Src: []string{JiraIssuesFetched}, Dst: JiraIssueGrouped},
{Name: RecordChangelog, Src: []string{JiraIssueGrouped}, Dst: ChangelogRecored},
},
fsm.Callbacks{
Before(FetchCommits): func(ctx context.Context, e *fsm.Event) {
gcw := git.NewCommitParseWorkflow(fromRef, toRef)
commits, err := gcw.Commits(ctx)
if err != nil {
e.Cancel(err)
return
}
generator.commits = commits
},
Before(FetchJiraIssues): func(ctx context.Context, e *fsm.Event) {
issues, err := generator.fetchJiraIssues(generator.commits)
if err != nil {
e.Cancel(err)
return
}
generator.jiraIssues = issues
},
Before(GroupJiraIssues): func(ctx context.Context, e *fsm.Event) {
jiraIssues := lo.Uniq(generator.jiraIssues)
slog.Debug("Total jira issues ids", "count", len(jiraIssues))

issuesByEpic := lo.GroupBy(jiraIssues, func(issue jira.Issue) string { return issue.Epic() })
generator.changes = issuesByEpic
},
},
)

return generator
}

func panicIfErr(err error) {
Expand All @@ -25,21 +94,15 @@ func panicIfErr(err error) {
}

func (c Generator) Generate(ctx context.Context) *Changelog {
gcw := git.NewCommitParseWorkflow(c.fromRef, c.toRef)
commits, err := gcw.Commits(ctx)
if err != nil {
panic(fmt.Errorf("failed at \"%s\" state. %w. State: %+v", gcw.FSM.Current(), err, gcw))
}

changes, err := c.changesFromCommits(commits)
panicIfErr(err)

slog.Debug("changes fetched", "changes", changes)
panicIfErr(c.FSM.Event(ctx, FetchCommits))
panicIfErr(c.FSM.Event(ctx, FetchJiraIssues))
panicIfErr(c.FSM.Event(ctx, GroupJiraIssues))
panicIfErr(c.FSM.Event(ctx, RecordChangelog))

return NewChangelog(c.fromRef, c.toRef, c.repoURL, changes)
return NewChangelog(c.fromRef, c.toRef, c.repoURL, c.changes)
}

func (c Generator) changesFromCommits(commits []git.Commit) (Changes, error) {
func (c Generator) fetchJiraIssues(commits []git.Commit) ([]jira.Issue, error) {
slog.Debug("Total commit messages", "count", len(commits))

jiraIssues := make([]jira.Issue, 0)
Expand All @@ -53,12 +116,7 @@ func (c Generator) changesFromCommits(commits []git.Commit) (Changes, error) {
slog.Debug("fetched issue", "issue", issue)
jiraIssues = append(jiraIssues, issue)
}

jiraIssues = lo.Uniq(jiraIssues)
slog.Debug("Total jira issues ids", "count", len(jiraIssues))

issuesByEpic := lo.GroupBy(jiraIssues, func(issue jira.Issue) string { return issue.Epic() })
return issuesByEpic, nil
return jiraIssues, nil
}

func (c Generator) fetchJiraIssue(commit git.Commit) (jira.Issue, error) {
Expand All @@ -75,14 +133,3 @@ func (c Generator) fetchJiraIssue(commit git.Commit) (jira.Issue, error) {
}
return issue, nil
}

func NewGenerator(jiraConfig jira.Config, fromRef, toRef, repoURL string) *Generator {
client := jira.NewClient(jiraConfig)
return &Generator{
jiraConfig,
fromRef,
toRef,
repoURL,
client,
}
}
2 changes: 1 addition & 1 deletion pkg/jira_changelog/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestChangelogFromCommits(t *testing.T) {
generator := Generator{}
generator.client = mockedClient

result, err := generator.changesFromCommits(commits)
result, err := generator.fetchJiraIssues(commits)

assert.NoError(t, err)
assert.Equal(t, expected, result)
Expand Down
23 changes: 13 additions & 10 deletions pkg/jira_changelog/git/commits.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"regexp"
"time"

. "github.com/handofgod94/gh-jira-changelog/pkg/jira_changelog/fsm_util"
"github.com/looplab/fsm"
)

Expand All @@ -19,15 +20,17 @@ type Commit struct {
var gitoutputPattern = regexp.MustCompile(`^\((\d+)\)\s+\{(\w+)\}\s*(.*)`)

const (
InitialState = "initial"
CommandExecuted = "command_executed"
OutuptParsed = "output_parsed"
InitialState = State("initial_state")
CommandExecuted = State("command_executed")
OutuptParsed = State("output_parsed")
ExecuteGitLog = Event("execute_git_log")
ParseOutput = Event("parse_output")
)

type commitParseWorkflow struct {
fromRef string
toRef string
gitOutput GitOutput
GitOutput GitOutput
commits []Commit
FSM *fsm.FSM
}
Expand All @@ -41,20 +44,20 @@ func NewCommitParseWorkflow(fromRef, toRef string) *commitParseWorkflow {
cpw.FSM = fsm.NewFSM(
InitialState,
fsm.Events{
{Name: "execute_git_log", Src: []string{InitialState}, Dst: CommandExecuted},
{Name: "parse_output", Src: []string{CommandExecuted}, Dst: OutuptParsed},
{Name: ExecuteGitLog, Src: []string{InitialState}, Dst: CommandExecuted},
{Name: ParseOutput, Src: []string{CommandExecuted}, Dst: OutuptParsed},
},
fsm.Callbacks{
"before_execute_git_log": func(ctx context.Context, e *fsm.Event) {
Before(ExecuteGitLog): func(ctx context.Context, e *fsm.Event) {
ouptut, err := execGitLog(ctx, fromRef, toRef)
if err != nil {
e.Cancel(err)
return
}
cpw.gitOutput = ouptut
cpw.GitOutput = ouptut
},
"before_parse_output": func(ctx context.Context, e *fsm.Event) {
commits, err := cpw.gitOutput.Commits()
Before(ParseOutput): func(ctx context.Context, e *fsm.Event) {
commits, err := cpw.GitOutput.Commits()
if err != nil {
e.Cancel(err)
return
Expand Down
65 changes: 15 additions & 50 deletions pkg/jira_changelog/git/commits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,35 @@ package git_test

import (
"testing"
"time"

"github.com/handofgod94/gh-jira-changelog/pkg/jira_changelog/fsm_util"
"github.com/handofgod94/gh-jira-changelog/pkg/jira_changelog/git"
"github.com/stretchr/testify/assert"
)

func TestCommits(t *testing.T) {
func TestCommitParseWorkflow_Events(t *testing.T) {
testCases := []struct {
desc string
gitOutput git.GitOutput
want []git.Commit
wantErr bool
desc string
currentState string
allowedEvents []string
}{
{
desc: "returns commits when gitoutput is valid",
gitOutput: git.GitOutput(`
(1687839814) {3cefgdr} use extra space while generating template
(1688059937) {4567uge} [JIRA-123] refactor: extract out structs from jira/types
(1687799347) {3456cdw} add warning emoji for changelog lineitem
`),
want: []git.Commit{
{
Message: "use extra space while generating template",
Time: time.Unix(1687839814, 0),
Sha: "3cefgdr",
},
{
Message: "[JIRA-123] refactor: extract out structs from jira/types",
Time: time.Unix(1688059937, 0),
Sha: "4567uge",
},
{
Message: "add warning emoji for changelog lineitem",
Time: time.Unix(1687799347, 0),
Sha: "3456cdw",
},
},
desc: "stateTransition: InitialState -> CommandExecuted",
currentState: git.InitialState,
allowedEvents: []fsm_util.Event{git.ExecuteGitLog},
},
{
desc: "returns single commit if gitoutput has single line",
gitOutput: git.GitOutput(`
(1688059937) {3456cdw} refactor: extract out structs from jira/types
`),
want: []git.Commit{{Message: "refactor: extract out structs from jira/types", Time: time.Unix(1688059937, 0), Sha: "3456cdw"}},
},
{
desc: "returns error when output is not in correct format",
gitOutput: git.GitOutput(`foobar`),
wantErr: true,
},
{
desc: "returns error when output is empty",
gitOutput: git.GitOutput(""),
wantErr: true,
desc: "stateTransition: CommandExecuted -> OutuptParsed",
currentState: git.CommandExecuted,
allowedEvents: []fsm_util.Event{git.ParseOutput},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
got, err := tc.gitOutput.Commits()
if tc.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tc.want, got)
cpw := git.NewCommitParseWorkflow("fromRef", "toRef")
cpw.FSM.SetState(tc.currentState)
for _, transition := range tc.allowedEvents {
assert.True(t, cpw.FSM.Can(transition))
}
})
}
Expand Down
72 changes: 72 additions & 0 deletions pkg/jira_changelog/git/git_output_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package git_test

import (
"testing"
"time"

"github.com/handofgod94/gh-jira-changelog/pkg/jira_changelog/git"
"github.com/stretchr/testify/assert"
)

func TestCommits(t *testing.T) {
testCases := []struct {
desc string
gitOutput git.GitOutput
want []git.Commit
wantErr bool
}{
{
desc: "returns commits when gitoutput is valid",
gitOutput: git.GitOutput(`
(1687839814) {3cefgdr} use extra space while generating template
(1688059937) {4567uge} [JIRA-123] refactor: extract out structs from jira/types
(1687799347) {3456cdw} add warning emoji for changelog lineitem
`),
want: []git.Commit{
{
Message: "use extra space while generating template",
Time: time.Unix(1687839814, 0),
Sha: "3cefgdr",
},
{
Message: "[JIRA-123] refactor: extract out structs from jira/types",
Time: time.Unix(1688059937, 0),
Sha: "4567uge",
},
{
Message: "add warning emoji for changelog lineitem",
Time: time.Unix(1687799347, 0),
Sha: "3456cdw",
},
},
},
{
desc: "returns single commit if gitoutput has single line",
gitOutput: git.GitOutput(`
(1688059937) {3456cdw} refactor: extract out structs from jira/types
`),
want: []git.Commit{{Message: "refactor: extract out structs from jira/types", Time: time.Unix(1688059937, 0), Sha: "3456cdw"}},
},
{
desc: "returns error when output is not in correct format",
gitOutput: git.GitOutput(`foobar`),
wantErr: true,
},
{
desc: "returns error when output is empty",
gitOutput: git.GitOutput(""),
wantErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
got, err := tc.gitOutput.Commits()
if tc.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tc.want, got)
}
})
}
}
4 changes: 4 additions & 0 deletions staticcheck.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
checks = ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1023", "-ST1001"]
initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
dot_import_whitelist = ["github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg"]
http_status_code_whitelist = ["200", "400", "404", "500"]

0 comments on commit 6a48c41

Please sign in to comment.