Skip to content

Commit

Permalink
Installations count endpoint (#292)
Browse files Browse the repository at this point in the history
* Installations count endpoint

A new endpoint to get the number of installations either all or only
the active ones
  • Loading branch information
ethervoid authored Aug 26, 2020
1 parent 6c80d05 commit 3dbef0d
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 0 deletions.
1 change: 1 addition & 0 deletions internal/api/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Store interface {
CreateInstallation(installation *model.Installation) error
GetInstallation(installationID string, includeGroupConfig, includeGroupConfigOverrides bool) (*model.Installation, error)
GetInstallations(filter *model.InstallationFilter, includeGroupConfig, includeGroupConfigOverrides bool) ([]*model.Installation, error)
GetInstallationsCount(includeDeleted bool) (int, error)
UpdateInstallation(installation *model.Installation) error
LockInstallation(installationID, lockerID string) (bool, error)
UnlockInstallation(installationID, lockerID string, force bool) (bool, error)
Expand Down
20 changes: 20 additions & 0 deletions internal/api/installation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func initInstallation(apiRouter *mux.Router, context *Context) {

installationsRouter := apiRouter.PathPrefix("/installations").Subrouter()
installationsRouter.Handle("", addContext(handleGetInstallations)).Methods("GET")
installationsRouter.Handle("/count", addContext(handleGetNumberOfInstallations)).Methods("GET")
installationsRouter.Handle("", addContext(handleCreateInstallation)).Methods("POST")

installationRouter := apiRouter.PathPrefix("/installation/{installation:[A-Za-z0-9]{26}}").Subrouter()
Expand Down Expand Up @@ -109,6 +110,25 @@ func handleGetInstallations(c *Context, w http.ResponseWriter, r *http.Request)
outputJSON(c, w, installations)
}

// handlerGetNumberOfInstallations responds to GET /api/installations/count, returning the
// number of non-deleted installations
func handleGetNumberOfInstallations(c *Context, w http.ResponseWriter, r *http.Request) {
includeDeleted, err := parseBool(r.URL, "include_deleted", false)
if err != nil {
includeDeleted = false
}
installationsCount, err := c.Store.GetInstallationsCount(includeDeleted)
if err != nil {
c.Logger.WithError(err).Error("failed to query the number of installations")
w.WriteHeader(http.StatusInternalServerError)
return
}
result := model.InstallationsCount{Count: installationsCount}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
outputJSON(c, w, result)
}

// handleCreateInstallation responds to POST /api/installations, beginning the process of creating
// a new installation.
func handleCreateInstallation(c *Context, w http.ResponseWriter, r *http.Request) {
Expand Down
26 changes: 26 additions & 0 deletions internal/api/installation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,32 @@ func TestGetInstallations(t *testing.T) {
})
}
})

t.Run("get installations count", func(t *testing.T) {
testCases := []struct {
Description string
IncludeDeleted bool
Expected int
}{
{
"count without deleted",
false,
3,
},
{
"count with deleted",
true,
4,
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
installations, err := client.GetInstallationsCount(testCase.IncludeDeleted)
require.NoError(t, err)
require.Equal(t, testCase.Expected, installations)
})
}
})
})
}

Expand Down
19 changes: 19 additions & 0 deletions internal/store/installation.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,25 @@ func (sqlStore *SQLStore) GetInstallations(filter *model.InstallationFilter, inc
return installations, nil
}

// GetInstallationsCount returns the number of installations filtered by the deletedat
// field
func (sqlStore *SQLStore) GetInstallationsCount(includeDeleted bool) (int, error) {
builder := sq.Select("COUNT(*) as InstallationsCount").From("Installation")
if !includeDeleted {
builder = builder.Where("DeleteAt = 0")
}
var numberOfInstallations int
query, _, err := builder.ToSql()
if err != nil {
return 0, errors.Wrap(err, "failed to parse query for installations count")
}
err = sqlStore.get(sqlStore.db, &numberOfInstallations, query)
if err != nil {
return 0, errors.Wrap(err, "failed to query for installations count")
}
return numberOfInstallations, nil
}

// GetUnlockedInstallationsPendingWork returns an unlocked installation in a pending state.
func (sqlStore *SQLStore) GetUnlockedInstallationsPendingWork() ([]*model.Installation, error) {
builder := installationSelect.
Expand Down
24 changes: 24 additions & 0 deletions model/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,30 @@ func (c *Client) GetInstallations(request *GetInstallationsRequest) ([]*Installa
}
}

// GetInstallationsCount returns then number of installations filtered by deleted field
func (c *Client) GetInstallationsCount(includeDeleted bool) (int, error) {
u, err := url.Parse(c.buildURL("/api/installations/count"))
if err != nil {
return 0, err
}
if includeDeleted {
q := u.Query()
q.Add("include_deleted", "true")
u.RawQuery = q.Encode()
}
resp, err := c.doGet(u.String())
if err != nil {
return 0, errors.Wrap(err, "problem getting installations count")
}
defer closeBody(resp)
switch resp.StatusCode {
case http.StatusOK:
return InstallationsCountFromReader(resp.Body)
default:
return 0, errors.Errorf("failed with status code %d", resp.StatusCode)
}
}

// UpdateInstallation updates an installation.
func (c *Client) UpdateInstallation(installationID string, request *PatchInstallationRequest) (*Installation, error) {
resp, err := c.doPut(c.buildURL("/api/installation/%s/mattermost", installationID), request)
Expand Down
18 changes: 18 additions & 0 deletions model/installation.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ type Installation struct {
configMergeGroupSequence int64
}

// InstallationsCount represents the number of installations
type InstallationsCount struct {
Count int
}

// InstallationFilter describes the parameters used to constrain a set of installations.
type InstallationFilter struct {
OwnerID string
Expand Down Expand Up @@ -149,3 +154,16 @@ func InstallationsFromReader(reader io.Reader) ([]*Installation, error) {

return installations, nil
}

// InstallationsCountFromReader decodes a json-encoded installations count data from the
// given io.Reader
func InstallationsCountFromReader(reader io.Reader) (int, error) {
installationsCount := InstallationsCount{}
decoder := json.NewDecoder(reader)
err := decoder.Decode(&installationsCount)
if err != nil && err != io.EOF {
return 0, err
}

return installationsCount.Count, nil
}

0 comments on commit 3dbef0d

Please sign in to comment.