Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Add the productionOnly query parameter to search API #383

Merged
merged 5 commits into from
Apr 10, 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
9 changes: 5 additions & 4 deletions internal/interactor/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ type (
SearchDeploymentsOfUserOptions struct {
ListOptions

Statuses []deployment.Status
Owned bool
From time.Time
To time.Time
Statuses []deployment.Status
Owned bool
ProductionOnly bool
From time.Time
To time.Time
}

// ListInactiveDeploymentsLessThanTimeOptions specifies the optional parameters that
Expand Down
65 changes: 26 additions & 39 deletions internal/pkg/store/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,56 +35,43 @@ func (s *Store) SearchDeploymentsOfUser(ctx context.Context, u *ent.User, opt *i
return deployment.StatusIn(ss...)
}

if opt.Owned {
return s.c.Deployment.
Query().
Where(
deployment.And(
deployment.UserIDEQ(u.ID),
statusIn(opt.Statuses),
deployment.CreatedAtGTE(opt.From),
deployment.CreatedAtLT(opt.To),
),
).
WithRepo().
WithUser().
Order(ent.Desc(deployment.FieldCreatedAt)).
Offset(offset(opt.Page, opt.PerPage)).
Limit(opt.PerPage).
All(ctx)
}

return s.c.Deployment.
// Build the query searching all deployments under the accessible repositories.
qry := s.c.Deployment.
Query().
Where(func(s *sql.Selector) {
t := sql.Table(perm.Table)

// Join with Perm for Repo.ID
s.Join(t).
On(
s.C(deployment.FieldRepoID),
s.C(perm.FieldRepoID),
).
Where(
sql.EQ(
t.C(perm.FieldUserID),
u.ID,
),
)
p := sql.Table(perm.Table)

s.Join(p).OnP(
sql.And(
sql.ColumnsEQ(p.C(perm.FieldRepoID), s.C(deployment.FieldRepoID)),
sql.EQ(p.C(perm.FieldUserID), u.ID),
),
)
Comment on lines +44 to +49
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bugfix:

-- Returns deployments under the accessible repositories
JOIN 
  ON `perms`.`repo_id` = `deployments`.`repo_id`
  AND `perms`.`user_id` = ?

}).
Where(
deployment.And(
statusIn(opt.Statuses),
deployment.CreatedAtGTE(opt.From),
deployment.CreatedAtLT(opt.To),
deployment.CreatedAtGTE(opt.From.UTC()),
deployment.CreatedAtLT(opt.To.UTC()),
),
).
WithRepo().
WithUser().
Order(ent.Desc(deployment.FieldCreatedAt)).
Offset(offset(opt.Page, opt.PerPage)).
Limit(opt.PerPage).
All(ctx)
WithRepo().
WithUser()

// Search only deployments that were triggered by the user.
if opt.Owned {
qry.Where(deployment.UserIDEQ(u.ID))
}

// Search only deployments for production environment.
if opt.ProductionOnly {
qry.Where(deployment.ProductionEnvironment(true))
}

return qry.All(ctx)
}

func (s *Store) ListInactiveDeploymentsLessThanTime(ctx context.Context, opt *i.ListInactiveDeploymentsLessThanTimeOptions) ([]*ent.Deployment, error) {
Expand Down
114 changes: 66 additions & 48 deletions internal/pkg/store/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,62 +23,70 @@ func TestStore_SearchDeployments(t *testing.T) {

ctx := context.Background()

const (
u1 = 1
u2 = 2
r1 = 1
r2 = 2
)
t.Log("User_1 has the permissions for repo_1 and repo_2.")
client.Perm.
Create().
SetUserID(1).
SetRepoID(1).
SaveX(ctx)

client.Perm.
Create().
SetUserID(u1).
SetRepoID(r1).
SetUserID(1).
SetRepoID(2).
SaveX(ctx)

t.Log("Create the deployments under the repo_1.")
client.Deployment.Create().
SetType(deployment.TypeBranch).
SetNumber(1).
SetRef("main").
SetEnv("local").
SetUserID(u1).
SetRepoID(r1).
SetStatus(deployment.StatusCreated).
SetProductionEnvironment(true).
SetUserID(1).
SetRepoID(1).
SaveX(ctx)

t.Log("Create the deployments under the repo_2.")
client.Deployment.Create().
SetType(deployment.TypeBranch).
SetNumber(1).
SetRef("main").
SetEnv("dev").
SetUserID(1).
SetRepoID(2).
SaveX(ctx)

client.Deployment.Create().
SetType(deployment.TypeBranch).
SetNumber(2).
SetRef("main").
SetEnv("dev").
SetUserID(u2).
SetRepoID(r1).
SetUserID(2).
SetRepoID(2).
SaveX(ctx)

t.Log("Create the deployments under the repo_3.")
client.Deployment.Create().
SetType(deployment.TypeBranch).
SetNumber(3).
SetNumber(1).
SetRef("main").
SetEnv("dev").
SetUserID(u2).
SetRepoID(r1).
SetUserID(2).
SetRepoID(3).
SetStatus(deployment.StatusCreated).
SaveX(ctx)

t.Run("u1 searchs all deployments of r1.", func(t *testing.T) {
const (
owned = false
page = 1
perPage = 30
)

t.Run("Returns all deployments under the accessible repositories.", func(t *testing.T) {
store := NewStore(client)

res, err := store.SearchDeploymentsOfUser(ctx,
&ent.User{ID: u1},
&ent.User{ID: 1},
&i.SearchDeploymentsOfUserOptions{
ListOptions: i.ListOptions{Page: page, PerPage: perPage},
ListOptions: i.ListOptions{Page: 1, PerPage: 30},
Statuses: []deployment.Status{},
Owned: owned,
Owned: false,
From: time.Now().UTC().Add(-time.Minute),
To: time.Now().UTC(),
})
Expand All @@ -92,21 +100,15 @@ func TestStore_SearchDeployments(t *testing.T) {
}
})

t.Run("u1 searchs waiting deployments of r1.", func(t *testing.T) {
const (
owned = false
page = 1
perPage = 30
)

t.Run("Returns all deployment triggered by myself.", func(t *testing.T) {
store := NewStore(client)

res, err := store.SearchDeploymentsOfUser(ctx,
&ent.User{ID: u1},
&ent.User{ID: 1},
&i.SearchDeploymentsOfUserOptions{
ListOptions: i.ListOptions{Page: page, PerPage: perPage},
Statuses: []deployment.Status{deployment.StatusWaiting},
Owned: owned,
ListOptions: i.ListOptions{Page: 1, PerPage: 30},
Statuses: []deployment.Status{},
Owned: true,
From: time.Now().UTC().Add(-time.Minute),
To: time.Now().UTC(),
})
Expand All @@ -116,25 +118,41 @@ func TestStore_SearchDeployments(t *testing.T) {

expected := 2
if len(res) != expected {
t.Fatalf("SearchDeployments = %v, wanted %v", len(res), expected)
t.Fatalf("SearchDeployments = %v, wanted %v", res, expected)
}
})

t.Run("u1 searchs owned deployments of r1.", func(t *testing.T) {
const (
owned = true
page = 1
perPage = 30
)
t.Run("Returns deployments which are 'created'.", func(t *testing.T) {
store := NewStore(client)

res, err := store.SearchDeploymentsOfUser(ctx,
&ent.User{ID: 1},
&i.SearchDeploymentsOfUserOptions{
ListOptions: i.ListOptions{Page: 1, PerPage: 30},
Statuses: []deployment.Status{deployment.StatusCreated},
Owned: false,
From: time.Now().UTC().Add(-time.Minute),
To: time.Now().UTC(),
})
if err != nil {
t.Fatalf("SearchDeployments return an error: %s", err)
}

expected := 1
if len(res) != expected {
t.Fatalf("SearchDeployments = %v, wanted %v", len(res), expected)
}
})

t.Run("Returns deployments for production environment.", func(t *testing.T) {
store := NewStore(client)

res, err := store.SearchDeploymentsOfUser(ctx,
&ent.User{ID: u1},
&ent.User{ID: 1},
&i.SearchDeploymentsOfUserOptions{
ListOptions: i.ListOptions{Page: page, PerPage: perPage},
Statuses: []deployment.Status{},
Owned: owned,
ListOptions: i.ListOptions{Page: 1, PerPage: 30},
Statuses: []deployment.Status{deployment.StatusCreated},
Owned: false,
From: time.Now().UTC().Add(-time.Minute),
To: time.Now().UTC(),
})
Expand All @@ -144,7 +162,7 @@ func TestStore_SearchDeployments(t *testing.T) {

expected := 1
if len(res) != expected {
t.Fatalf("SearchDeployments = %v, wanted %v", res, expected)
t.Fatalf("SearchDeployments = %v, wanted %v", len(res), expected)
}
})
}
Expand Down
69 changes: 28 additions & 41 deletions internal/server/api/v1/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,68 +38,54 @@ func (s *Search) SearchDeployments(c *gin.Context) {
ctx := c.Request.Context()

var (
statuses = c.DefaultQuery("statuses", "")
owned = c.DefaultQuery("owned", "true")
from = c.DefaultQuery("from", time.Now().Add(-activeDuration).Format(time.RFC3339))
to = c.DefaultQuery("to", time.Now().Format(time.RFC3339))
page = c.DefaultQuery("page", "1")
perPage = c.DefaultQuery("per_page", "30")
)

var (
ss = make([]deployment.Status, 0)
o bool
f time.Time
t time.Time
p int
pp int
err error
statuses = []deployment.Status{}
owned bool
productionOnly bool
from, to time.Time
page, perPage int
err error
)

// Validate query parameters.
for _, st := range strings.Split(statuses, ",") {
if st != "" {
ss = append(ss, deployment.Status(st))
for _, s := range strings.Split(c.DefaultQuery("statuses", ""), ",") {
if s != "" {
statuses = append(statuses, deployment.Status(s))
}
}

if o, err = strconv.ParseBool(owned); err != nil {
gb.ResponseWithError(
c,
e.NewErrorWithMessage(e.ErrorCodeParameterInvalid, "The owned must be boolean.", err),
)
if owned, err = strconv.ParseBool(c.DefaultQuery("owned", "true")); err != nil {
gb.ResponseWithError(c, e.NewErrorWithMessage(e.ErrorCodeParameterInvalid, "The owned must be boolean.", err))
return
}

if productionOnly, err = strconv.ParseBool(c.DefaultQuery("production_only", "false")); err != nil {
gb.ResponseWithError(c, e.NewErrorWithMessage(e.ErrorCodeParameterInvalid, "The production must be boolean.", err))
return
}

if f, err = time.Parse(time.RFC3339, from); err != nil {
if from, err = time.Parse(time.RFC3339, c.DefaultQuery("from", time.Now().Add(-activeDuration).Format(time.RFC3339))); err != nil {
gb.ResponseWithError(
c,
e.NewErrorWithMessage(e.ErrorCodeParameterInvalid, "Invalid format of \"from\" parameter, RFC3339 format only.", err),
)
return
}

if t, err = time.Parse(time.RFC3339, to); err != nil {
if to, err = time.Parse(time.RFC3339, c.DefaultQuery("to", time.Now().Format(time.RFC3339))); err != nil {
gb.ResponseWithError(
c,
e.NewErrorWithMessage(e.ErrorCodeParameterInvalid, "Invalid format of \"to\" parameter, RFC3339 format only.", err),
)
return
}

if p, err = strconv.Atoi(page); err != nil {
gb.ResponseWithError(
c,
e.NewErrorWithMessage(e.ErrorCodeParameterInvalid, "Invalid format of \"page\" parameter.", err),
)
if page, err = strconv.Atoi(c.DefaultQuery("page", "1")); err != nil {
gb.ResponseWithError(c, e.NewErrorWithMessage(e.ErrorCodeParameterInvalid, "The page must be number.", err))
return
}

if pp, err = strconv.Atoi(perPage); err != nil {
gb.ResponseWithError(
c,
e.NewErrorWithMessage(e.ErrorCodeParameterInvalid, "Invalid format of \"per_page\" parameter.", err),
)
if perPage, err = strconv.Atoi(c.DefaultQuery("per_page", "1")); err != nil {
gb.ResponseWithError(c, e.NewErrorWithMessage(e.ErrorCodeParameterInvalid, "The per_page must be number.", err))
return
}

Expand All @@ -112,11 +98,12 @@ func (s *Search) SearchDeployments(c *gin.Context) {
u := v.(*ent.User)

if ds, err = s.i.SearchDeploymentsOfUser(ctx, u, &i.SearchDeploymentsOfUserOptions{
ListOptions: i.ListOptions{Page: p, PerPage: pp},
Statuses: ss,
Owned: o,
From: f.UTC(),
To: t.UTC(),
ListOptions: i.ListOptions{Page: page, PerPage: perPage},
Statuses: statuses,
Owned: owned,
ProductionOnly: productionOnly,
From: from,
To: to,
Comment on lines +105 to +106
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the way converting the format into the store package.

}); err != nil {
s.log.Check(gb.GetZapLogLevel(err), "Failed to search deployments.").Write(zap.Error(err))
gb.ResponseWithError(c, err)
Expand Down
Loading