Skip to content

Commit

Permalink
Make component field configurable (#150)
Browse files Browse the repository at this point in the history
Signed-off-by: Laszlo Gecse <[email protected]>
Co-authored-by: Stephen Augustus <[email protected]>
  • Loading branch information
lgecse and justaugustus authored Sep 5, 2023
1 parent 0af3507 commit fcec535
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 18 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Configuration arguments are as follows:
| repo-name | string | "uwu-tools/gh-jira-issue-sync" | true | null |
| jira-uri | string | "https://jira.example.com" | true | null |
| jira-project | string | "SYNC" | true | null |
| jira-components | []string | ["Core","Payment"] | false | null |
| since | string | "2017-07-01T13:45:00-0800" | false | "1970-01-01T00:00:00+0000" |
| timeout | duration | 500ms | false | 1m |

Expand Down Expand Up @@ -102,6 +103,11 @@ lives at a non-root URL, the path must be included. For example,
`jira-project` is the key (not the name) of the project in Jira to
which the issues will be synchronized.

`jira-components` is the names of the components in Jira
that will be added to the issues when synchronized. If a component
not found on the project or the set value is otherwise invalid,
an error will return. (optional)

`since` is the cutoff date issue-sync will use when searching for issues
to synchronize. If an issue was last updated before this time, it will
not be synchronized. Usually this is the last run of the tool. It is in
Expand Down
8 changes: 8 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ func init() {
"set the key of the Jira project",
)

RootCmd.PersistentFlags().StringSliceVarP(
&opts.JiraComponents,
options.ConfigKeyJiraComponents,
"C",
nil,
"set the Jira components to be used",
)

RootCmd.PersistentFlags().StringVarP(
&opts.Since,
options.ConfigKeySince,
Expand Down
83 changes: 69 additions & 14 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ type Config struct {
// project represents the Jira project the user has requested.
project *jira.Project

// components represents the Jira components the user would like use for the sync.
// Comes from the value of the `jira-components` configuration parameter.
// Items in Jira will have the components field set to these values.
components []*jira.Component

// since is the parsed value of the `since` configuration parameter, which is the earliest that
// a GitHub issue can have been updated to be retrieved.
since time.Time
Expand Down Expand Up @@ -167,6 +172,11 @@ func (c *Config) LoadJiraConfig(client *jira.Client) error {
}
c.project = proj

c.components, err = c.getComponents(proj)
if err != nil {
return err
}

c.fieldIDs, err = c.getFieldIDs(client)
if err != nil {
return err
Expand Down Expand Up @@ -263,6 +273,11 @@ func (c *Config) GetRepo() (string, string) {
return github.GetRepo(repoPath)
}

// GetJiraComponents returns the Jira component the user has configured.
func (c *Config) GetJiraComponents() []*jira.Component {
return c.components
}

// SetJiraToken adds the Jira OAuth tokens in the Viper configuration, ensuring that they
// are saved for future runs.
func (c *Config) SetJiraToken(token *oauth1.Token) {
Expand All @@ -272,20 +287,21 @@ func (c *Config) SetJiraToken(token *oauth1.Token) {

// configFile is a serializable representation of the current Viper configuration.
type configFile struct {
LogLevel string `json:"log-level,omitempty" mapstructure:"log-level"`
GithubToken string `json:"github-token,omitempty" mapstructure:"github-token"`
JiraUser string `json:"jira-user,omitempty" mapstructure:"jira-user"`
JiraPass string `json:"jira-pass,omitempty" mapstructure:"jira-pass"`
JiraToken string `json:"jira-token,omitempty" mapstructure:"jira-token"`
JiraSecret string `json:"jira-secret,omitempty" mapstructure:"jira-secret"`
JiraKey string `json:"jira-private-key-path,omitempty" mapstructure:"jira-private-key-path"`
JiraCKey string `json:"jira-consumer-key,omitempty" mapstructure:"jira-consumer-key"`
RepoName string `json:"repo-name,omitempty" mapstructure:"repo-name"`
JiraURI string `json:"jira-uri,omitempty" mapstructure:"jira-uri"`
JiraProject string `json:"jira-project,omitempty" mapstructure:"jira-project"`
Since string `json:"since,omitempty" mapstructure:"since"`
Confirm bool `json:"confirm,omitempty" mapstructure:"confirm"`
Timeout time.Duration `json:"timeout,omitempty" mapstructure:"timeout"`
LogLevel string `json:"log-level,omitempty" mapstructure:"log-level"`
GithubToken string `json:"github-token,omitempty" mapstructure:"github-token"`
JiraUser string `json:"jira-user,omitempty" mapstructure:"jira-user"`
JiraPass string `json:"jira-pass,omitempty" mapstructure:"jira-pass"`
JiraToken string `json:"jira-token,omitempty" mapstructure:"jira-token"`
JiraSecret string `json:"jira-secret,omitempty" mapstructure:"jira-secret"`
JiraKey string `json:"jira-private-key-path,omitempty" mapstructure:"jira-private-key-path"`
JiraCKey string `json:"jira-consumer-key,omitempty" mapstructure:"jira-consumer-key"`
RepoName string `json:"repo-name,omitempty" mapstructure:"repo-name"`
JiraURI string `json:"jira-uri,omitempty" mapstructure:"jira-uri"`
JiraProject string `json:"jira-project,omitempty" mapstructure:"jira-project"`
Since string `json:"since,omitempty" mapstructure:"since"`
JiraComponents []string `json:"jira-components,omitempty" mapstructure:"jira-components"`
Confirm bool `json:"confirm,omitempty" mapstructure:"confirm"`
Timeout time.Duration `json:"timeout,omitempty" mapstructure:"timeout"`
}

// SaveConfig updates the `since` parameter to now, then saves the configuration file.
Expand Down Expand Up @@ -517,6 +533,39 @@ func (c *Config) getFieldIDs(client *jira.Client) (*fields, error) {
return &fieldIDs, nil
}

// getComponents resolves every component set in config against
// Jira project, and returns with these components used by issue-sync.
func (c *Config) getComponents(proj *jira.Project) ([]*jira.Component, error) {
var returnComponents []*jira.Component

components := c.cmdConfig.GetStringSlice(options.ConfigKeyJiraComponents)

for _, configComponent := range components {
found := false

for j := range proj.Components {
projComponent := &proj.Components[j]

if projComponent.Name == configComponent {
found = true
foundComponent := jira.Component{
Name: projComponent.Name,
ID: projComponent.ID,
}

returnComponents = append(returnComponents, &foundComponent)
}
}

if !found {
log.Errorf("The Jira project does not have such component defined: %s", configComponent)
return nil, ReadingJiraComponentError(configComponent)
}
}

return returnComponents, nil
}

// Errors

var (
Expand All @@ -539,3 +588,9 @@ var (
func errCustomFieldIDNotFound(field string) error {
return fmt.Errorf("could not find ID custom field '%s'; check that it is named correctly", field) //nolint:goerr113
}

type ReadingJiraComponentError string

func (r ReadingJiraComponentError) Error() string {
return fmt.Sprintf("could not find Jira component: %s; check that it is named correctly", string(r))
}
32 changes: 32 additions & 0 deletions internal/jira/issue/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ func DidIssueChange(cfg *config.Config, ghIssue *gogh.Issue, jIssue *gojira.Issu
anyDifferent = true
}

if GetMissingComponents(cfg, jIssue) != nil {
anyDifferent = true
}

if len(ghIssue.Labels) > 0 { //nolint:nestif // TODO(lint)
ghLabels := githubLabelsToStrSlice(ghIssue.Labels)

Expand Down Expand Up @@ -211,6 +215,9 @@ func UpdateIssue(
ID: jIssue.ID,
}

missingComponents := GetMissingComponents(cfg, jIssue)
issue.Fields.Components = append(issue.Fields.Components, missingComponents...)

_, err := jClient.UpdateIssue(issue)
if err != nil {
return fmt.Errorf("updating Jira issue: %w", err)
Expand All @@ -233,6 +240,30 @@ func UpdateIssue(
return nil
}

// GetMissingComponents compares configurated components with the Jira issue
// components. Returns the components that are missing from the Jira issue.
func GetMissingComponents(cfg *config.Config, jIssue *gojira.Issue) []*gojira.Component {
var returnComponents []*gojira.Component

components := cfg.GetJiraComponents()
for _, configComponent := range components {
found := false

for _, issueComponent := range jIssue.Fields.Components {
if issueComponent.Name == configComponent.Name {
found = true
break
}
}

if !found {
returnComponents = append(returnComponents, configComponent)
}
}

return returnComponents
}

// CreateIssue generates a Jira issue from the various fields on the given GitHub issue, then
// sends it to the Jira API.
func CreateIssue(cfg *config.Config, issue *gogh.Issue, ghClient github.Client, jClient jira.Client) error {
Expand All @@ -258,6 +289,7 @@ func CreateIssue(cfg *config.Config, issue *gogh.Issue, ghClient github.Client,
Summary: issue.GetTitle(),
Description: issue.GetBody(),
Unknowns: unknowns,
Components: cfg.GetJiraComponents(),
}

jIssue := &gojira.Issue{
Expand Down
10 changes: 6 additions & 4 deletions internal/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ type Options struct {
JiraURI string
JiraProject string
// TODO(options): Should this be a time type?
Since string
Confirm bool
Timeout time.Duration
Period time.Duration
Since string
JiraComponents []string
Confirm bool
Timeout time.Duration
Period time.Duration
}

const (
Expand Down Expand Up @@ -65,6 +66,7 @@ const (
ConfigKeyJiraSecret = "jira-secret"
ConfigKeyJiraConsumerKey = "jira-consumer-key"
ConfigKeyJiraPrivateKeyPath = "jira-private-key-path"
ConfigKeyJiraComponents = "jira-components"

// Default values
//
Expand Down

0 comments on commit fcec535

Please sign in to comment.