Skip to content

Commit

Permalink
Merge pull request #57 from clamp-orchestrator/show_workflow
Browse files Browse the repository at this point in the history
Fetch workflows (GET api)
  • Loading branch information
priyaaank authored Oct 16, 2020
2 parents 555c447 + 3cb9ac3 commit 7ffda0f
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 22 deletions.
8 changes: 3 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ jobs:
environment:
TEST_RESULTS: /tmp/test-results

steps:
steps:

- checkout

- run: mkdir -p $TEST_RESULTS
Expand All @@ -59,7 +59,7 @@ jobs:
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
chmod +x ./cc-test-reporter
- run:
- run:
name: run unit tests
environment:
CLAMP_DB_CONNECTION_STR: "host=localhost:5432 user=clamptest dbname=clamptest"
Expand All @@ -75,7 +75,6 @@ jobs:
if [ $CIRCLE_BRANCH == 'master' ]; then
./cc-test-reporter after-build --coverage-input-type gocov -p clamp-core --exit-code $?
fi
- store_test_results:
path: /tmp/test-results

Expand All @@ -86,7 +85,6 @@ jobs:
steps:
- checkout
- setup_remote_docker

- run:
name: build and publish docker img
command: |
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
Expand Down
11 changes: 10 additions & 1 deletion handlers/workflow_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var (
})
)

//CustomError is a customised error format for our library
type CustomError struct {
StatusCode int
Err error
Expand All @@ -43,6 +44,7 @@ func (r *CustomError) Error() string {
return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err)
}

//ErrorRequest sends base 503 error
func ErrorRequest() error {
return &CustomError{
StatusCode: 503,
Expand Down Expand Up @@ -123,6 +125,7 @@ func getWorkflows() gin.HandlerFunc {
return func(c *gin.Context) {
pageSizeStr := c.Query("pageSize")
pageNumberStr := c.Query("pageNumber")
sortByQuery := c.Query("sortBy")
if pageSizeStr == "" || pageNumberStr == "" {
err := errors.New("page number or page size is not been defined")
prepareErrorResponse(err, c)
Expand All @@ -135,7 +138,13 @@ func getWorkflows() gin.HandlerFunc {
prepareErrorResponse(err, c)
return
}
workflows, err := services.GetWorkflows(pageNumber, pageSize)

sortByFields, err := models.ParseFromQuery(sortByQuery)
if err != nil {
prepareErrorResponse(err, c)
return
}
workflows, err := services.GetWorkflows(pageNumber, pageSize, sortByFields)
if err != nil {
prepareErrorResponse(err, c)
return
Expand Down
18 changes: 18 additions & 0 deletions handlers/workflow_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"math/rand"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -250,3 +251,20 @@ func TestShouldThrowErrorIfQueryParamsAreNotValidValuesInGetAllWorkflows(t *test
assert.NotNil(t, jsonResp)
assert.Equal(t, "page number or page size is not in proper format", jsonResp.Message)
}

func TestShouldThrowErrorIfSortByStringIsNotInTheRightFormat(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
sortByString := `id,`
urlEncodedSortValue := url.QueryEscape(sortByString)
req, _ := http.NewRequest("GET", "/workflows?pageNumber=0&pageSize=1&sortBy="+urlEncodedSortValue, nil)
router.ServeHTTP(w, req)

bodyStr := w.Body.String()
var jsonResp models.ClampErrorResponse
json.Unmarshal([]byte(bodyStr), &jsonResp)

assert.Equal(t, 400, w.Code)
assert.NotNil(t, jsonResp)
assert.Equal(t, "Unsupported value provided for sortBy query", jsonResp.Message)
}
66 changes: 66 additions & 0 deletions models/sort_fields.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package models

import (
"errors"
"fmt"
"strings"
)

//SortByFields contains an array of []sortBy type
//Used to store all sortBy key-value pairs
type SortByFields []sortBy

//SortBy contains a key and an order to be used to sort a DB query by
//Order supported is asc/desc
type sortBy struct {
Key string
Order string
}

//ParseFromQuery is used to parse sortBy query string to a custom SortByFields type
//It returns an ordered sortBy struct containing KEY VALUE pair
//If an unknown key is used, an error is raised
//Fields are seperated using a comma and key values are sepearted using a colon
func ParseFromQuery(sortByString string) (SortByFields, error) {
var sortArr SortByFields = []sortBy{}
if len(sortByString) == 0 {
return sortArr, nil
}
sortByString = cleanUpQuery(sortByString)
sortByArgs := strings.Split(sortByString, ",")
for _, value := range sortByArgs {
sortPair := strings.Split(value, ":")

if len(sortPair) != 2 || !verifySortValues(sortPair[0], sortPair[1]) {
return SortByFields{}, errors.New("Unsupported value provided for sortBy query")
}
key := sortPair[0]
value := sortPair[1]
sort := sortBy{Key: key, Order: value}
sortArr = append(sortArr, sort)
fmt.Println(sortArr)
}
return sortArr, nil
}

func cleanUpQuery(sortByQuery string) string {
length := len(sortByQuery)
sortByQuery = strings.ToLower(sortByQuery)
if sortByQuery[length-1] == ',' {
sortByQuery = sortByQuery[0 : length-1]
}
return sortByQuery
}

func verifySortValues(key string, value string) bool {
if value != "desc" && value != "asc" {
return false
}
supportedKeys := []string{"id", "name", "createdate"}
for _, keyVal := range supportedKeys {
if key == keyVal {
return true
}
}
return false
}
104 changes: 104 additions & 0 deletions models/sort_fields_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package models

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestSortByParser(t *testing.T) {
sortByQuery := `id:asc,createdate:desc,name:desc`
sortUsing, err := ParseFromQuery(sortByQuery)

assert.Nil(t, err)
assert.Equal(t, 3, len(sortUsing))
assert.Equal(t, "id", sortUsing[0].Key)
assert.Equal(t, "asc", sortUsing[0].Order)
assert.Equal(t, "createdate", sortUsing[1].Key)
assert.Equal(t, "desc", sortUsing[1].Order)
assert.Equal(t, "name", sortUsing[2].Key)
assert.Equal(t, "desc", sortUsing[2].Order)
}

func TestSortByParserFailsOnUnknownFieldName(t *testing.T) {
sortByQuery := "id:asc,createdate:desc,name:desc,invalid:desc"
_, err := ParseFromQuery(sortByQuery)

assert.NotNil(t, err)
assert.Equal(t, "Unsupported value provided for sortBy query", err.Error())
}

func TestSortByParserThrowErrorOnIllegalValue(t *testing.T) {
sortByQuery := `"id":"randomValue","createddate": "desc","name": "desc"`
_, err := ParseFromQuery(sortByQuery)

assert.NotNil(t, err)
assert.Equal(t, "Unsupported value provided for sortBy query", err.Error())
}

func TestSortByParserAllowEmptyString(t *testing.T) {
sortByQuery := ""
sortUsing, err := ParseFromQuery(sortByQuery)

assert.Nil(t, err)
assert.Equal(t, 0, len(sortUsing))
}

func TestSortByParserAllowAnyCaseString(t *testing.T) {
sortByQuery := `id:aSc,CReatedaTe:DeSc,NAME:desc`
sortUsing, err := ParseFromQuery(sortByQuery)

assert.Nil(t, err)
assert.Equal(t, 3, len(sortUsing))
assert.Equal(t, "id", sortUsing[0].Key)
assert.Equal(t, "asc", sortUsing[0].Order)
assert.Equal(t, "createdate", sortUsing[1].Key)
assert.Equal(t, "desc", sortUsing[1].Order)
assert.Equal(t, "name", sortUsing[2].Key)
assert.Equal(t, "desc", sortUsing[2].Order)
}

func TestSortByParserNotAllowEmptyValueForSoryByString(t *testing.T) {
sortByQuery := "id:,creaTeDate:dEsc,naMe:desc"
_, err := ParseFromQuery(sortByQuery)

assert.NotNil(t, err)
assert.Equal(t, "Unsupported value provided for sortBy query", err.Error())
}

func TestSortByParserAllowCommaAtTheEnd(t *testing.T) {
sortByQuery := `id:asc,createdate:desc,name:desc,`
sortUsing, err := ParseFromQuery(sortByQuery)

assert.Nil(t, err)
assert.Equal(t, 3, len(sortUsing))
assert.Equal(t, "id", sortUsing[0].Key)
assert.Equal(t, "asc", sortUsing[0].Order)
assert.Equal(t, "createdate", sortUsing[1].Key)
assert.Equal(t, "desc", sortUsing[1].Order)
assert.Equal(t, "name", sortUsing[2].Key)
assert.Equal(t, "desc", sortUsing[2].Order)
}

func TestPreserveOrderOfSortKeys(t *testing.T) {
sortByQuery := `createdate:desc,id:asc,name:desc,`

sortUsing, err := ParseFromQuery(sortByQuery)

assert.Nil(t, err)
assert.Equal(t, 3, len(sortUsing))
assert.Equal(t, "createdate", sortUsing[0].Key)
assert.Equal(t, "desc", sortUsing[0].Order)
assert.Equal(t, "id", sortUsing[1].Key)
assert.Equal(t, "asc", sortUsing[1].Order)
assert.Equal(t, "name", sortUsing[2].Key)
assert.Equal(t, "desc", sortUsing[2].Order)
}

func TestSortByParserAllowEmptyKeyValue(t *testing.T) {
sortByQuery := "id:desc,creaTeDate:dEsc,naMe:desc,,"
_, err := ParseFromQuery(sortByQuery)

assert.NotNil(t, err)
assert.Equal(t, "Unsupported value provided for sortBy query", err.Error())
}
2 changes: 1 addition & 1 deletion repository/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type dbInterface interface {
FindStepStatusByServiceRequestIDAndStatus(serviceRequestID uuid.UUID, status models.Status) ([]models.StepsStatus, error)
FindStepStatusByServiceRequestIDAndStepIDAndStatus(serviceRequestID uuid.UUID, stepID int, status models.Status) (models.StepsStatus, error)
FindAllStepStatusByServiceRequestIDAndStepID(serviceRequestID uuid.UUID, stepID int) ([]models.StepsStatus, error)
GetWorkflows(pageNumber int, pageSize int) ([]models.Workflow, error)
GetWorkflows(pageNumber int, pageSize int, sortBy models.SortByFields) ([]models.Workflow, error)
FindServiceRequestsByWorkflowName(workflowName string, pageNumber int, pageSize int) ([]models.ServiceRequest, error)
}

Expand Down
28 changes: 23 additions & 5 deletions repository/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"clamp-core/config"
"clamp-core/models"
"context"
"errors"
"log"
"strings"
"sync"
Expand All @@ -12,6 +13,10 @@ import (
"github.com/google/uuid"
)

//reference human readable keys to DB key values
var keyReferences = map[string]string{"id": "id", "createdate": "created_at", "name": "name"}

//LogSQLQueries used to decide logging
const LogSQLQueries bool = true

var singletonOnce sync.Once
Expand All @@ -36,6 +41,7 @@ func connectDB() (db *pg.DB) {
return db
}

//GetPostgresOptions returns connection details for postgres DB
func GetPostgresOptions() *pg.Options {
connStr := config.ENV.DBConnectionStr
connArr := strings.Split(connStr, " ")
Expand Down Expand Up @@ -157,12 +163,24 @@ func (p *postgres) SaveServiceRequest(serviceReq models.ServiceRequest) (models.
return pgServReq.ToServiceRequest(), err
}

func (p *postgres) GetWorkflows(pageNumber int, pageSize int) ([]models.Workflow, error) {
func (p *postgres) GetWorkflows(pageNumber int, pageSize int, sortFields models.SortByFields) ([]models.Workflow, error) {
var pgWorkflows []models.PGWorkflow
err := p.getDb().Model(&pgWorkflows).
Limit(pageSize).
Offset(pageNumber).
Select()
query := p.getDb().Model(&pgWorkflows)
for _, sortField := range sortFields {
reference, found := keyReferences[sortField.Key]
if !found {
return []models.Workflow{}, errors.New("Undefined key reference used")
}
order := sortField.Order
if found {
query = query.Order(reference + " " + order)
}
}
err := query.Offset(pageSize * pageNumber).
Limit(pageSize).Select()
if err != nil {
return []models.Workflow{}, err
}
var workflows []models.Workflow
for _, pgWorkflow := range pgWorkflows {
workflows = append(workflows, pgWorkflow.ToWorkflow())
Expand Down
6 changes: 3 additions & 3 deletions services/abstract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ func (m mockDB) FindServiceRequestsByWorkflowName(workflowName string, pageNumbe
return findServiceRequestsByWorkflowName(workflowName, pageNumber, pageSize)
}

func (m mockDB) GetWorkflows(pageNumber int, pageSize int) ([]models.Workflow, error) {
return getWorkflowsMock(pageNumber, pageSize)
func (m mockDB) GetWorkflows(pageNumber int, pageSize int, sortBy models.SortByFields) ([]models.Workflow, error) {
return getWorkflowsMock(pageNumber, pageSize, sortBy)
}

func (m mockDB) FindStepStatusByServiceRequestIDAndStepIDAndStatus(serviceRequestID uuid.UUID, stepID int, status models.Status) (models.StepsStatus, error) {
Expand All @@ -36,7 +36,7 @@ var findStepStatusByServiceRequestIDAndStatusMock func(serviceRequestID uuid.UUI
var findAllStepStatusByServiceRequestIDAndStepIDMock func(serviceRequestID uuid.UUID, stepID int) ([]models.StepsStatus, error)
var findStepStatusByServiceRequestIDAndStepNameAndStatusMock func(serviceRequestID uuid.UUID, stepName string, status models.Status) (models.StepsStatus, error)
var findStepStatusByServiceRequestIDAndStepIDAndStatusMock func(serviceRequestID uuid.UUID, stepID int, status models.Status) (models.StepsStatus, error)
var getWorkflowsMock func(pageNumber int, pageSize int) ([]models.Workflow, error)
var getWorkflowsMock func(pageNumber int, pageSize int, sortBy models.SortByFields) ([]models.Workflow, error)

func (m mockDB) SaveServiceRequest(serReq models.ServiceRequest) (models.ServiceRequest, error) {
return saveServiceRequestMock(serReq)
Expand Down
4 changes: 3 additions & 1 deletion services/service_request_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/google/uuid"
)

//FindServiceRequestByID is
//FindServiceRequestByID is used to fetch service requests by their ID values
func FindServiceRequestByID(serviceRequestID uuid.UUID) (models.ServiceRequest, error) {
log.Printf("Find service Request request by id: %s", serviceRequestID)
serviceRequest, err := repository.GetDB().FindServiceRequestByID(serviceRequestID)
Expand All @@ -18,6 +18,7 @@ func FindServiceRequestByID(serviceRequestID uuid.UUID) (models.ServiceRequest,
return serviceRequest, err
}

//SaveServiceRequest is used to save the created service requests to DB
func SaveServiceRequest(serviceReq models.ServiceRequest) (models.ServiceRequest, error) {
log.Printf("Saving service request: %v", serviceReq)
serviceRequest, err := repository.GetDB().SaveServiceRequest(serviceReq)
Expand All @@ -27,6 +28,7 @@ func SaveServiceRequest(serviceReq models.ServiceRequest) (models.ServiceRequest
return serviceRequest, err
}

//FindServiceRequestByWorkflowName fetches all ServiceRequests that are associated to a workflow type
func FindServiceRequestByWorkflowName(workflowName string, pageNumber int, pageSize int) ([]models.ServiceRequest, error) {
log.Printf("Getting service request by workflow name: %s", workflowName)
serviceRequests, err := repository.GetDB().FindServiceRequestsByWorkflowName(workflowName, pageNumber, pageSize)
Expand Down
Loading

0 comments on commit 7ffda0f

Please sign in to comment.