diff --git a/Makefile b/Makefile index 4045d18a..db4a2d62 100644 --- a/Makefile +++ b/Makefile @@ -97,5 +97,5 @@ prove: test-password-store OSHT_VERBOSE=1 prove -v _t/*.t generate: - cd schemas && ./fetch-schemas.py + cd schemas && go run ./fetch-schemas.go grep -h slipscheme jiradata/*.go | grep json | sort | uniq | awk -F\/\/ '{print $$2}' | while read cmd; do $$cmd; done diff --git a/jiracli/templates.go b/jiracli/templates.go index 39ae6cb5..3c1c6046 100644 --- a/jiracli/templates.go +++ b/jiracli/templates.go @@ -20,8 +20,8 @@ import ( "github.com/coryb/figtree" shellquote "github.com/kballard/go-shellquote" "github.com/mgutz/ansi" - "github.com/olekukonko/tablewriter" wordwrap "github.com/mitchellh/go-wordwrap" + "github.com/olekukonko/tablewriter" "golang.org/x/crypto/ssh/terminal" ) @@ -312,6 +312,7 @@ var AllTemplates = map[string]string{ "issuetypes": defaultIssuetypesTemplate, "json": defaultDebugTemplate, "list": defaultListTemplate, + "releases": defaultReleasesTemplate, "request": defaultDebugTemplate, "subtask": defaultSubtaskTemplate, "table": defaultTableTemplate, @@ -459,6 +460,9 @@ const defaultTransitionsTemplate = `{{ range .transitions }}{{.id }}: {{.name}} const defaultComponentsTemplate = `{{ range . }}{{.id }}: {{.name}} {{end}}` +const defaultReleasesTemplate = `{{ range . }}{{.id }}: {{.name}} +{{end}}` + const defaultComponentAddTemplate = `{{/* compoinent add template */ -}} project: {{or .project ""}} name: {{or .name ""}} diff --git a/jiracmd/registry.go b/jiracmd/registry.go index 0dad82c6..a01190ab 100644 --- a/jiracmd/registry.go +++ b/jiracmd/registry.go @@ -39,6 +39,7 @@ func RegisterAllCommands() { jiracli.RegisterCommand(jiracli.CommandRegistry{Command: "login", Entry: CmdLoginRegistry()}) jiracli.RegisterCommand(jiracli.CommandRegistry{Command: "logout", Entry: CmdLogoutRegistry()}) jiracli.RegisterCommand(jiracli.CommandRegistry{Command: "rank", Entry: CmdRankRegistry()}) + jiracli.RegisterCommand(jiracli.CommandRegistry{Command: "releases", Entry: CmdReleasesRegistry()}) jiracli.RegisterCommand(jiracli.CommandRegistry{Command: "reopen", Entry: CmdTransitionRegistry("reopen")}) jiracli.RegisterCommand(jiracli.CommandRegistry{Command: "request", Entry: CmdRequestRegistry(), Aliases: []string{"req"}}) jiracli.RegisterCommand(jiracli.CommandRegistry{Command: "resolve", Entry: CmdTransitionRegistry("resolve")}) diff --git a/jiracmd/releases.go b/jiracmd/releases.go new file mode 100644 index 00000000..4a54228d --- /dev/null +++ b/jiracmd/releases.go @@ -0,0 +1,57 @@ +package jiracmd + +import ( + "github.com/coryb/figtree" + "github.com/coryb/oreo" + "github.com/go-jira/jira" + "github.com/go-jira/jira/jiracli" + kingpin "gopkg.in/alecthomas/kingpin.v2" +) + +type ReleasesOptions struct { + jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` + Project string `yaml:"project,omitempty" json:"project,omitempty"` + Status []string + Query string + OrderBy string +} + +func CmdReleasesRegistry() *jiracli.CommandRegistryEntry { + opts := ReleasesOptions{ + CommonOptions: jiracli.CommonOptions{ + Template: figtree.NewStringOption("releases"), + }, + } + + return &jiracli.CommandRegistryEntry{ + Help: "List project releases", + UsageFunc: func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { + jiracli.LoadConfigs(cmd, fig, &opts) + return CmdReleasesUsage(cmd, &opts) + }, + ExecuteFunc: func(o *oreo.Client, globals *jiracli.GlobalOptions) error { + return CmdReleases(o, globals, &opts) + }, + } +} + +func CmdReleasesUsage(cmd *kingpin.CmdClause, opts *ReleasesOptions) error { + jiracli.TemplateUsage(cmd, &opts.CommonOptions) + jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) + cmd.Flag("query", "filter the results using a literal string").Short('q').StringVar(&opts.Query) + cmd.Flag("status", "list of status values used to filter the results by version status").Short('s').StringsVar(&opts.Status) + cmd.Flag("order", "order the results by a field: description, name, releaseDate, sequence, startDate").StringVar(&opts.OrderBy) + cmd.Arg("PROJECT", "project id or key").Required().StringVar(&opts.Project) + return nil +} + +func CmdReleases(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ReleasesOptions) error { + data, err := jira.GetProjectVersionsPaginated(o, globals.Endpoint.Value, opts.Project, opts.Status, opts.Query, opts.OrderBy) + if err != nil { + return err + } + if err := opts.PrintTemplate(data); err != nil { + return err + } + return nil +} diff --git a/jiradata/Operations.go b/jiradata/Operations.go index efd11ef9..18ceb3c7 100644 --- a/jiradata/Operations.go +++ b/jiradata/Operations.go @@ -5,7 +5,7 @@ package jiradata // https://github.com/coryb/slipscheme // // Generated with command: -// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json +// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/PageofVersion.json ///////////////////////////////////////////////////////////////////////// // DO NOT EDIT // ///////////////////////////////////////////////////////////////////////// @@ -15,7 +15,38 @@ package jiradata // "title": "operations", // "type": "array", // "items": { -// "type": "string" +// "title": "Simple Link", +// "type": "object", +// "properties": { +// "href": { +// "title": "href", +// "type": "string" +// }, +// "iconClass": { +// "title": "iconClass", +// "type": "string" +// }, +// "id": { +// "title": "id", +// "type": "string" +// }, +// "label": { +// "title": "label", +// "type": "string" +// }, +// "styleClass": { +// "title": "styleClass", +// "type": "string" +// }, +// "title": { +// "title": "title", +// "type": "string" +// }, +// "weight": { +// "title": "weight", +// "type": "integer" +// } +// } // } // } -type Operations []string +type Operations []*SimpleLink diff --git a/jiradata/PageOfVersion.go b/jiradata/PageOfVersion.go new file mode 100644 index 00000000..93c1c33e --- /dev/null +++ b/jiradata/PageOfVersion.go @@ -0,0 +1,174 @@ +package jiradata + +///////////////////////////////////////////////////////////////////////// +// This Code is Generated by SlipScheme Project: +// https://github.com/coryb/slipscheme +// +// Generated with command: +// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/PageofVersion.json +///////////////////////////////////////////////////////////////////////// +// DO NOT EDIT // +///////////////////////////////////////////////////////////////////////// + +// PageOfVersion defined from schema: +// { +// "title": "Page of Version", +// "id": "https://docs.atlassian.com/jira/REST/schema/page-of-version#", +// "type": "object", +// "properties": { +// "isLast": { +// "title": "isLast", +// "type": "boolean" +// }, +// "maxResults": { +// "title": "maxResults", +// "type": "integer" +// }, +// "nextPage": { +// "title": "nextPage", +// "type": "string" +// }, +// "self": { +// "title": "self", +// "type": "string" +// }, +// "startAt": { +// "title": "startAt", +// "type": "integer" +// }, +// "total": { +// "title": "total", +// "type": "integer" +// }, +// "values": { +// "title": "values", +// "type": "array", +// "items": { +// "title": "Version", +// "type": "object", +// "properties": { +// "archived": { +// "title": "archived", +// "type": "boolean" +// }, +// "description": { +// "title": "description", +// "type": "string" +// }, +// "expand": { +// "title": "expand", +// "type": "string" +// }, +// "id": { +// "title": "id", +// "type": "string" +// }, +// "moveUnfixedIssuesTo": { +// "title": "moveUnfixedIssuesTo", +// "type": "string" +// }, +// "name": { +// "title": "name", +// "type": "string" +// }, +// "operations": { +// "title": "operations", +// "type": "array", +// "items": { +// "title": "Simple Link", +// "type": "object", +// "properties": { +// "href": { +// "title": "href", +// "type": "string" +// }, +// "iconClass": { +// "title": "iconClass", +// "type": "string" +// }, +// "id": { +// "title": "id", +// "type": "string" +// }, +// "label": { +// "title": "label", +// "type": "string" +// }, +// "styleClass": { +// "title": "styleClass", +// "type": "string" +// }, +// "title": { +// "title": "title", +// "type": "string" +// }, +// "weight": { +// "title": "weight", +// "type": "integer" +// } +// } +// } +// }, +// "overdue": { +// "title": "overdue", +// "type": "boolean" +// }, +// "project": { +// "title": "project", +// "type": "string" +// }, +// "projectId": { +// "title": "projectId", +// "type": "integer" +// }, +// "released": { +// "title": "released", +// "type": "boolean" +// }, +// "remotelinks": { +// "title": "remotelinks", +// "type": "array", +// "items": { +// "title": "Remote Entity Link", +// "type": "object", +// "properties": { +// "link": { +// "title": "link" +// }, +// "name": { +// "title": "name", +// "type": "string" +// }, +// "self": { +// "title": "self", +// "type": "string" +// } +// } +// } +// }, +// "self": { +// "title": "self", +// "type": "string" +// }, +// "userReleaseDate": { +// "title": "userReleaseDate", +// "type": "string" +// }, +// "userStartDate": { +// "title": "userStartDate", +// "type": "string" +// } +// } +// } +// } +// } +// } +type PageOfVersion struct { + IsLast bool `json:"isLast,omitempty" yaml:"isLast,omitempty"` + MaxResults int `json:"maxResults,omitempty" yaml:"maxResults,omitempty"` + NextPage string `json:"nextPage,omitempty" yaml:"nextPage,omitempty"` + Self string `json:"self,omitempty" yaml:"self,omitempty"` + StartAt int `json:"startAt,omitempty" yaml:"startAt,omitempty"` + Total int `json:"total,omitempty" yaml:"total,omitempty"` + Values Values `json:"values,omitempty" yaml:"values,omitempty"` +} diff --git a/jiradata/RemoteEntityLink.go b/jiradata/RemoteEntityLink.go index 1dc7478d..66afdadc 100644 --- a/jiradata/RemoteEntityLink.go +++ b/jiradata/RemoteEntityLink.go @@ -5,7 +5,7 @@ package jiradata // https://github.com/coryb/slipscheme // // Generated with command: -// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/Project.json +// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/PageofVersion.json ///////////////////////////////////////////////////////////////////////// // DO NOT EDIT // ///////////////////////////////////////////////////////////////////////// diff --git a/jiradata/Remotelinks.go b/jiradata/Remotelinks.go index 89004d4d..24300c08 100644 --- a/jiradata/Remotelinks.go +++ b/jiradata/Remotelinks.go @@ -5,7 +5,7 @@ package jiradata // https://github.com/coryb/slipscheme // // Generated with command: -// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/Project.json +// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/PageofVersion.json ///////////////////////////////////////////////////////////////////////// // DO NOT EDIT // ///////////////////////////////////////////////////////////////////////// diff --git a/jiradata/SimpleLink.go b/jiradata/SimpleLink.go index 669125b2..52fe4aef 100644 --- a/jiradata/SimpleLink.go +++ b/jiradata/SimpleLink.go @@ -5,7 +5,7 @@ package jiradata // https://github.com/coryb/slipscheme // // Generated with command: -// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/Project.json +// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/PageofVersion.json ///////////////////////////////////////////////////////////////////////// // DO NOT EDIT // ///////////////////////////////////////////////////////////////////////// diff --git a/jiradata/Values.go b/jiradata/Values.go new file mode 100644 index 00000000..b087cb52 --- /dev/null +++ b/jiradata/Values.go @@ -0,0 +1,135 @@ +package jiradata + +///////////////////////////////////////////////////////////////////////// +// This Code is Generated by SlipScheme Project: +// https://github.com/coryb/slipscheme +// +// Generated with command: +// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/PageofVersion.json +///////////////////////////////////////////////////////////////////////// +// DO NOT EDIT // +///////////////////////////////////////////////////////////////////////// + +// Values defined from schema: +// { +// "title": "values", +// "type": "array", +// "items": { +// "title": "Version", +// "type": "object", +// "properties": { +// "archived": { +// "title": "archived", +// "type": "boolean" +// }, +// "description": { +// "title": "description", +// "type": "string" +// }, +// "expand": { +// "title": "expand", +// "type": "string" +// }, +// "id": { +// "title": "id", +// "type": "string" +// }, +// "moveUnfixedIssuesTo": { +// "title": "moveUnfixedIssuesTo", +// "type": "string" +// }, +// "name": { +// "title": "name", +// "type": "string" +// }, +// "operations": { +// "title": "operations", +// "type": "array", +// "items": { +// "title": "Simple Link", +// "type": "object", +// "properties": { +// "href": { +// "title": "href", +// "type": "string" +// }, +// "iconClass": { +// "title": "iconClass", +// "type": "string" +// }, +// "id": { +// "title": "id", +// "type": "string" +// }, +// "label": { +// "title": "label", +// "type": "string" +// }, +// "styleClass": { +// "title": "styleClass", +// "type": "string" +// }, +// "title": { +// "title": "title", +// "type": "string" +// }, +// "weight": { +// "title": "weight", +// "type": "integer" +// } +// } +// } +// }, +// "overdue": { +// "title": "overdue", +// "type": "boolean" +// }, +// "project": { +// "title": "project", +// "type": "string" +// }, +// "projectId": { +// "title": "projectId", +// "type": "integer" +// }, +// "released": { +// "title": "released", +// "type": "boolean" +// }, +// "remotelinks": { +// "title": "remotelinks", +// "type": "array", +// "items": { +// "title": "Remote Entity Link", +// "type": "object", +// "properties": { +// "link": { +// "title": "link" +// }, +// "name": { +// "title": "name", +// "type": "string" +// }, +// "self": { +// "title": "self", +// "type": "string" +// } +// } +// } +// }, +// "self": { +// "title": "self", +// "type": "string" +// }, +// "userReleaseDate": { +// "title": "userReleaseDate", +// "type": "string" +// }, +// "userStartDate": { +// "title": "userStartDate", +// "type": "string" +// } +// } +// } +// } +type Values []*Version diff --git a/jiradata/Version.go b/jiradata/Version.go index 56889a07..0ff9ed08 100644 --- a/jiradata/Version.go +++ b/jiradata/Version.go @@ -5,7 +5,7 @@ package jiradata // https://github.com/coryb/slipscheme // // Generated with command: -// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/Project.json +// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/PageofVersion.json ///////////////////////////////////////////////////////////////////////// // DO NOT EDIT // ///////////////////////////////////////////////////////////////////////// diff --git a/project.go b/project.go index 9139b20b..d7505d12 100644 --- a/project.go +++ b/project.go @@ -2,7 +2,11 @@ package jira import ( "encoding/json" + "fmt" + "net/url" + "strings" + "github.com/coryb/oreo" "github.com/go-jira/jira/jiradata" ) @@ -45,3 +49,51 @@ func GetProjectVersions(ua HttpClient, endpoint string, project string) (*jirada } return nil, responseError(resp) } + +func GetProjectVersionsPaginated(ua HttpClient, endpoint string, project string, status []string, query string, order string) (*jiradata.Versions, error) { + startAt := 0 + total := 1 + maxResults := 100 + releases := jiradata.Versions{} + for startAt < total { + uri, err := url.Parse(URLJoin(endpoint, "rest/api/2/project", project, "version")) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(status) > 0 { + params.Add("status", strings.Join(status, ",")) + } + if len(query) > 0 { + params.Add("query", query) + } + if len(order) > 0 { + params.Add("orderBy", order) + } + params.Add("maxResults", fmt.Sprintf("%d", maxResults)) + params.Add("startAt", fmt.Sprintf("%d", startAt)) + + uri.RawQuery = params.Encode() + + resp, err := ua.Do(oreo.RequestBuilder(uri).WithHeader("Accept", "application/json").Build()) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode == 200 { + results := &jiradata.PageOfVersion{} + err := json.NewDecoder(resp.Body).Decode(&results) + if err != nil { + return nil, err + } + startAt = startAt + maxResults + total = results.Total + releases = append(releases, results.Values...) + } else { + return nil, responseError(resp) + } + } + return &releases, nil +}