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

Make component field configurable #150

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b129165
feat: fill components field with the first project component
lgecse Aug 1, 2023
2525200
feat: added configurable component #149
lgecse Aug 3, 2023
e1248c7
Using wrapped error instead of dynamic one.
lgecse Aug 3, 2023
1d15764
Solving rangeValCopy gocritic.
lgecse Aug 3, 2023
b13ccaa
Shortening line.
lgecse Aug 3, 2023
0042166
Added capability to set multiple components in config.
lgecse Aug 3, 2023
761b079
Added a better example to the readme.
lgecse Aug 3, 2023
e04f887
Refactored getComponents.
lgecse Aug 3, 2023
d24a9d0
Missed pluralization added in readme.
lgecse Aug 3, 2023
9529681
Updating component when it's not on the issue
lgecse Aug 7, 2023
619d942
Added omitempty to the new config variable.
lgecse Aug 7, 2023
f4b1d64
Missed pluralizations fixed.
lgecse Aug 7, 2023
967ed91
Formatting only.
lgecse Aug 7, 2023
c4926fa
Formatting only.
lgecse Aug 7, 2023
0d58e86
Changed c shorthand to C for components.
lgecse Aug 7, 2023
46ef7d1
Formatting only.
lgecse Aug 7, 2023
2c0f029
Applying suggested return part.
lgecse Aug 7, 2023
c68c439
Applying suggested config reading part.
lgecse Aug 7, 2023
8ec0626
Applying suggestions.
lgecse Aug 8, 2023
6138b9e
Applying suggestions.
lgecse Aug 9, 2023
76b802b
Merge branch 'main' into feature/add_component_when_creating_issue
justaugustus Aug 15, 2023
00ef6d0
Merge branch 'main' into feature/add_component_when_creating_issue
justaugustus Aug 15, 2023
3507d6a
Merge branch 'uwu-tools:main' into feature/add_component_when_creatin…
lgecse Sep 4, 2023
f0f5cce
config: Change jira-components type to string slice.
lgecse Sep 5, 2023
d60c7de
fix: linter errors.
lgecse Sep 5, 2023
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
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) {
lgecse marked this conversation as resolved.
Show resolved Hide resolved
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)
lgecse marked this conversation as resolved.
Show resolved Hide resolved
}
}

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