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

Feature/157 #158

Merged
merged 2 commits into from
Oct 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
80 changes: 80 additions & 0 deletions jira/internal/jql_impl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package internal

import (
"context"
"fmt"
model "github.com/ctreminiom/go-atlassian/pkg/infra/models"
"github.com/ctreminiom/go-atlassian/service"
"github.com/ctreminiom/go-atlassian/service/jira"
"net/http"
"net/url"
"strings"
)

func NewJQLService(client service.Client, version string) (*JQLService, error) {

if version == "" {
return nil, model.ErrNoVersionProvided
}

return &JQLService{
internalClient: &internalJQLServiceImpl{c: client, version: version},
}, nil
}

type JQLService struct {
internalClient jira.JQLConnector
}

// Parse parses and validates JQL queries.
//
// Validation is performed in context of the current user.
//
// POST /rest/api/{2-3}/jql/parse
//
// https://docs.go-atlassian.io/jira-software-cloud/jql#parse-jql-query
func (j *JQLService) Parse(ctx context.Context, validationType string, JqlQueries []string) (*model.ParsedQueryPageScheme, *model.ResponseScheme, error) {
return j.internalClient.Parse(ctx, validationType, JqlQueries)
}

type internalJQLServiceImpl struct {
c service.Client
version string
}

func (i *internalJQLServiceImpl) Parse(ctx context.Context, validationType string, JqlQueries []string) (*model.ParsedQueryPageScheme, *model.ResponseScheme, error) {

var endpoint strings.Builder
endpoint.WriteString(fmt.Sprintf("/rest/api/%v/jql/parse", i.version))

if validationType != "" {
params := url.Values{}
params.Add("validation", validationType)

endpoint.WriteString(fmt.Sprintf("?%v", params.Encode()))
}

payload := struct {
Queries []string `json:"queries,omitempty"`
}{
Queries: JqlQueries,
}

reader, err := i.c.TransformStructToReader(&payload)
if err != nil {
return nil, nil, err
}

request, err := i.c.NewRequest(ctx, http.MethodPost, endpoint.String(), reader)
if err != nil {
return nil, nil, err
}

page := new(model.ParsedQueryPageScheme)
response, err := i.c.Call(request, page)
if err != nil {
return nil, response, err
}

return page, response, nil
}
189 changes: 189 additions & 0 deletions jira/internal/jql_impl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package internal

import (
"bytes"
"context"
"errors"
model "github.com/ctreminiom/go-atlassian/pkg/infra/models"
"github.com/ctreminiom/go-atlassian/service"
"github.com/ctreminiom/go-atlassian/service/mocks"
"github.com/stretchr/testify/assert"
"net/http"
"testing"
)

func Test_internalJQLServiceImpl_Parse(t *testing.T) {

payloadMocked := &struct {
Queries []string "json:\"queries,omitempty\""
}{Queries: []string{"summary ~ test AND (labels in (urgent, blocker) OR lastCommentedBy = currentUser()) AND status CHANGED AFTER startOfMonth(-1M) ORDER BY updated DESC", "invalid query", "summary = test", "summary in test", "project = INVALID", "universe = 42"}}

type fields struct {
c service.Client
version string
}

type args struct {
ctx context.Context
validationType string
JqlQueries []string
}

testCases := []struct {
name string
fields fields
args args
on func(*fields)
wantErr bool
Err error
}{
{
name: "when the api version is v3",
fields: fields{version: "3"},
args: args{
ctx: context.TODO(),
validationType: "strict",
JqlQueries: []string{
"summary ~ test AND (labels in (urgent, blocker) OR lastCommentedBy = currentUser()) AND status CHANGED AFTER startOfMonth(-1M) ORDER BY updated DESC",
"invalid query",
"summary = test",
"summary in test",
"project = INVALID",
"universe = 42"},
},
on: func(fields *fields) {

client := mocks.NewClient(t)

client.On("TransformStructToReader",
payloadMocked).
Return(bytes.NewReader([]byte{}), nil)

client.On("NewRequest",
context.Background(),
http.MethodPost,
"/rest/api/3/jql/parse?validation=strict",
bytes.NewReader([]byte{})).
Return(&http.Request{}, nil)

client.On("Call",
&http.Request{},
&model.ParsedQueryPageScheme{}).
Return(&model.ResponseScheme{}, nil)

fields.c = client

},
wantErr: false,
Err: nil,
},

{
name: "when the api version is v2",
fields: fields{version: "2"},
args: args{
ctx: context.TODO(),
validationType: "strict",
JqlQueries: []string{
"summary ~ test AND (labels in (urgent, blocker) OR lastCommentedBy = currentUser()) AND status CHANGED AFTER startOfMonth(-1M) ORDER BY updated DESC",
"invalid query",
"summary = test",
"summary in test",
"project = INVALID",
"universe = 42"},
},
on: func(fields *fields) {

client := mocks.NewClient(t)

client.On("TransformStructToReader",
payloadMocked).
Return(bytes.NewReader([]byte{}), nil)

client.On("NewRequest",
context.Background(),
http.MethodPost,
"/rest/api/2/jql/parse?validation=strict",
bytes.NewReader([]byte{})).
Return(&http.Request{}, nil)

client.On("Call",
&http.Request{},
&model.ParsedQueryPageScheme{}).
Return(&model.ResponseScheme{}, nil)

fields.c = client

},
wantErr: false,
Err: nil,
},

{
name: "when the http request cannot be created",
fields: fields{version: "3"},
args: args{
ctx: context.TODO(),
validationType: "strict",
JqlQueries: []string{
"summary ~ test AND (labels in (urgent, blocker) OR lastCommentedBy = currentUser()) AND status CHANGED AFTER startOfMonth(-1M) ORDER BY updated DESC",
"invalid query",
"summary = test",
"summary in test",
"project = INVALID",
"universe = 42"},
},
on: func(fields *fields) {

client := mocks.NewClient(t)

client.On("TransformStructToReader",
payloadMocked).
Return(bytes.NewReader([]byte{}), nil)

client.On("NewRequest",
context.Background(),
http.MethodPost,
"/rest/api/3/jql/parse?validation=strict",
bytes.NewReader([]byte{})).
Return(&http.Request{}, errors.New("error, unable to create the http request"))

fields.c = client

},
wantErr: true,
Err: errors.New("error, unable to create the http request"),
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {

if testCase.on != nil {
testCase.on(&testCase.fields)
}

fieldService, err := NewJQLService(testCase.fields.c, testCase.fields.version)
assert.NoError(t, err)

gotResult, gotResponse, err := fieldService.Parse(testCase.args.ctx, testCase.args.validationType,
testCase.args.JqlQueries)

if testCase.wantErr {

if err != nil {
t.Logf("error returned: %v", err.Error())
}

assert.EqualError(t, err, testCase.Err.Error())

} else {

assert.NoError(t, err)
assert.NotEqual(t, gotResponse, nil)
assert.NotEqual(t, gotResult, nil)
}

})
}
}
7 changes: 7 additions & 0 deletions jira/v2/api_client_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,11 @@ func New(httpClient common.HttpClient, site string) (*Client, error) {
return nil, err
}

jql, err := internal.NewJQLService(client, "2")
if err != nil {
return nil, err
}

client.Permission = permission
client.MySelf = mySelf
client.Auth = internal.NewAuthenticationService(client)
Expand All @@ -347,6 +352,7 @@ func New(httpClient common.HttpClient, site string) (*Client, error) {
client.Task = task
client.User = user
client.Workflow = workflow
client.JQL = jql

return client, nil
}
Expand All @@ -368,6 +374,7 @@ type Client struct {
Server *internal.ServerService
User *internal.UserService
Workflow *internal.WorkflowService
JQL *internal.JQLService
}

func (c *Client) NewFormRequest(ctx context.Context, method, apiEndpoint, contentType string, payload io.Reader) (*http.Request, error) {
Expand Down
7 changes: 7 additions & 0 deletions jira/v3/api_client_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,11 @@ func New(httpClient common.HttpClient, site string) (*Client, error) {
return nil, err
}

jql, err := internal.NewJQLService(client, "3")
if err != nil {
return nil, err
}

client.Permission = permission
client.MySelf = mySelf
client.Auth = internal.NewAuthenticationService(client)
Expand All @@ -347,6 +352,7 @@ func New(httpClient common.HttpClient, site string) (*Client, error) {
client.Server = server
client.User = user
client.Workflow = workflow
client.JQL = jql

return client, nil
}
Expand All @@ -368,6 +374,7 @@ type Client struct {
Server *internal.ServerService
User *internal.UserService
Workflow *internal.WorkflowService
JQL *internal.JQLService
}

func (c *Client) NewFormRequest(ctx context.Context, method, apiEndpoint, contentType string, payload io.Reader) (*http.Request, error) {
Expand Down
40 changes: 40 additions & 0 deletions pkg/infra/models/jira_jql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package models

type ParsedQueryPageScheme struct {
Queries []*ParseQueryScheme `json:"queries"`
}

type ParseQueryScheme struct {
Query string `json:"query"`
Structure struct {
Where struct {
} `json:"where"`
OrderBy *QueryStructureOrderScheme `json:"orderBy"`
} `json:"structure"`
Errors []string `json:"errors"`
}

type QueryStructureScheme struct {
OrderBy *QueryStructureOrderScheme `json:"orderBy"`
}

type QueryStructureOrderScheme struct {
Fields []*QueryStructureOrderFieldScheme `json:"fields"`
}

type QueryStructureOrderFieldScheme struct {
Field *QueryStructureOrderFieldNodeScheme `json:"field"`
Direction string `json:"direction"`
}

type QueryStructureOrderFieldNodeScheme struct {
Name string `json:"name"`
Property []*QueryPropertyScheme `json:"property"`
}

type QueryPropertyScheme struct {
Entity string `json:"entity"`
Key string `json:"key"`
Path string `json:"path"`
Type string `json:"type"`
}
18 changes: 18 additions & 0 deletions service/jira/jql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package jira

import (
"context"
"github.com/ctreminiom/go-atlassian/pkg/infra/models"
)

type JQLConnector interface {

// Parse parses and validates JQL queries.
//
// Validation is performed in context of the current user.
//
// POST /rest/api/{2-3}/jql/parse
//
// https://docs.go-atlassian.io/jira-software-cloud/jql#parse-jql-query
Parse(ctx context.Context, validationType string, JqlQueries []string) (*models.ParsedQueryPageScheme, *models.ResponseScheme, error)
}