diff --git a/app/api/controller/gitspace/list_all.go b/app/api/controller/gitspace/list_all.go index 14b102e89..63bcd5902 100644 --- a/app/api/controller/gitspace/list_all.go +++ b/app/api/controller/gitspace/list_all.go @@ -32,9 +32,13 @@ func (c *Controller) ListAllGitspaces( // nolint:gocognit ctx context.Context, session *auth.Session, ) ([]*types.GitspaceConfig, error) { + filter := &types.GitspaceFilter{ + GitspaceInstanceFilter: types.GitspaceInstanceFilter{UserIdentifier: session.Principal.UID}, + } + var result []*types.GitspaceConfig err := c.tx.WithTx(ctx, func(ctx context.Context) (err error) { - allGitspaceConfigs, err := c.gitspaceConfigStore.ListAll(ctx, session.Principal.UID) + allGitspaceConfigs, err := c.gitspaceConfigStore.ListWithLatestInstance(ctx, filter) if err != nil { return fmt.Errorf("failed to list gitspace configs: %w", err) } @@ -61,10 +65,7 @@ func (c *Controller) ListAllGitspaces( // nolint:gocognit return err } - finalGitspaceConfigs, err := c.filterAndPopulateInstanceDetails(ctx, allGitspaceConfigs, authorizedSpaceIDs) - if err != nil { - return err - } + finalGitspaceConfigs := c.filter(allGitspaceConfigs, authorizedSpaceIDs) result = finalGitspaceConfigs @@ -75,48 +76,14 @@ func (c *Controller) ListAllGitspaces( // nolint:gocognit return nil, err } - for _, gitspaceConfig := range result { - gitspaceConfig.BranchURL = c.gitspaceSvc.GetBranchURL(ctx, gitspaceConfig) - } - return result, nil } -func (c *Controller) filterAndPopulateInstanceDetails( - ctx context.Context, +func (c *Controller) filter( allGitspaceConfigs []*types.GitspaceConfig, authorizedSpaceIDs map[int64]bool, -) ([]*types.GitspaceConfig, error) { - authorizedGitspaceConfigs := c.getAuthorizedGitspaceConfigs(allGitspaceConfigs, authorizedSpaceIDs) - - gitspaceInstancesMap, err := c.getLatestInstanceMap(ctx, authorizedGitspaceConfigs) - if err != nil { - return nil, err - } - - var result []*types.GitspaceConfig - - for _, gitspaceConfig := range authorizedGitspaceConfigs { - instance := gitspaceInstancesMap[gitspaceConfig.ID] - - gitspaceConfig.GitspaceInstance = instance - - if instance != nil { - gitspaceStateType, stateErr := enum.GetGitspaceStateFromInstance(instance.State, instance.Updated) - if stateErr != nil { - return nil, stateErr - } - - gitspaceConfig.State = gitspaceStateType - - instance.SpacePath = gitspaceConfig.SpacePath - } else { - gitspaceConfig.State = enum.GitspaceStateUninitialized - } - - result = append(result, gitspaceConfig) - } - return result, nil +) []*types.GitspaceConfig { + return c.getAuthorizedGitspaceConfigs(allGitspaceConfigs, authorizedSpaceIDs) } func (c *Controller) getAuthorizedGitspaceConfigs( @@ -150,26 +117,3 @@ func (c *Controller) getAuthorizedSpaces( return authorizedSpaceIDs, nil } - -func (c *Controller) getLatestInstanceMap( - ctx context.Context, - authorizedGitspaceConfigs []*types.GitspaceConfig, -) (map[int64]*types.GitspaceInstance, error) { - var authorizedConfigIDs = make([]int64, 0) - for _, config := range authorizedGitspaceConfigs { - authorizedConfigIDs = append(authorizedConfigIDs, config.ID) - } - - var gitspaceInstances, err = c.gitspaceInstanceStore.FindAllLatestByGitspaceConfigID(ctx, authorizedConfigIDs) - if err != nil { - return nil, err - } - - var gitspaceInstancesMap = make(map[int64]*types.GitspaceInstance) - - for _, gitspaceEntry := range gitspaceInstances { - gitspaceInstancesMap[gitspaceEntry.GitSpaceConfigID] = gitspaceEntry - } - - return gitspaceInstancesMap, nil -} diff --git a/app/api/controller/space/list_gitspaces.go b/app/api/controller/space/list_gitspaces.go index 35aadfa20..8bc8405df 100644 --- a/app/api/controller/space/list_gitspaces.go +++ b/app/api/controller/space/list_gitspaces.go @@ -28,7 +28,7 @@ func (c *Controller) ListGitspaces( ctx context.Context, session *auth.Session, spaceRef string, - filter types.ListQueryFilter, + filter types.GitspaceFilter, ) ([]*types.GitspaceConfig, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { @@ -38,5 +38,10 @@ func (c *Controller) ListGitspaces( if err != nil { return nil, 0, fmt.Errorf("failed to authorize gitspace: %w", err) } - return c.gitspaceSvc.ListGitspacesForSpace(ctx, space, session.Principal.UID, filter) + + filter.UserIdentifier = session.Principal.UID + filter.SpaceIDs = []int64{space.ID} + filter.IncludeMarkedForDeletion = false + + return c.gitspaceSvc.ListGitspacesForSpace(ctx, space, filter) } diff --git a/app/api/handler/space/list_gitspaces.go b/app/api/handler/space/list_gitspaces.go index 8ef506e2b..5d1376eff 100644 --- a/app/api/handler/space/list_gitspaces.go +++ b/app/api/handler/space/list_gitspaces.go @@ -31,13 +31,13 @@ func HandleListGitspaces(spacesCtrl *space.Controller) http.HandlerFunc { render.TranslatedUserError(ctx, w, err) return } - filter := request.ParseListQueryFilterFromRequest(r) + filter := request.ParseGitspaceFilter(r) repos, totalCount, err := spacesCtrl.ListGitspaces(ctx, session, spaceRef, filter) if err != nil { render.TranslatedUserError(ctx, w, err) return } - render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) + render.Pagination(r, w, filter.QueryFilter.Page, filter.QueryFilter.Size, int(totalCount)) render.JSON(w, http.StatusOK, repos) } } diff --git a/app/api/request/gitspace.go b/app/api/request/gitspace.go index b4f024dcd..d43bf4861 100644 --- a/app/api/request/gitspace.go +++ b/app/api/request/gitspace.go @@ -16,6 +16,9 @@ package request import ( "net/http" + + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" ) const ( @@ -25,3 +28,19 @@ const ( func GetGitspaceRefFromPath(r *http.Request) (string, error) { return PathParamOrError(r, PathParamGitspaceIdentifier) } + +// ParseGitspaceSort extracts the gitspace sort parameter from the url. +func ParseGitspaceSort(r *http.Request) enum.GitspaceSort { + return enum.ParseGitspaceSort( + r.URL.Query().Get(QueryParamSort), + ) +} + +// ParseGitspaceFilter extracts the gitspace filter from the url. +func ParseGitspaceFilter(r *http.Request) types.GitspaceFilter { + return types.GitspaceFilter{ + QueryFilter: ParseListQueryFilterFromRequest(r), + Sort: ParseGitspaceSort(r), + Order: ParseOrder(r), + } +} diff --git a/app/services/gitspace/gitspace.go b/app/services/gitspace/gitspace.go index a576cd6a0..53890192a 100644 --- a/app/services/gitspace/gitspace.go +++ b/app/services/gitspace/gitspace.go @@ -25,7 +25,6 @@ import ( "github.com/harness/gitness/app/store" "github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/types" - "github.com/harness/gitness/types/enum" "github.com/rs/zerolog/log" ) @@ -69,70 +68,37 @@ type Service struct { func (c *Service) ListGitspacesForSpace( ctx context.Context, space *types.Space, - userIdentifier string, - filter types.ListQueryFilter, + filter types.GitspaceFilter, ) ([]*types.GitspaceConfig, int64, error) { - gitspaceFilter := &types.GitspaceFilter{ - QueryFilter: filter, - UserID: userIdentifier, - SpaceIDs: []int64{space.ID}, - } var gitspaceConfigs []*types.GitspaceConfig var count int64 err := c.tx.WithTx(ctx, func(ctx context.Context) (err error) { - gitspaceConfigs, err = c.gitspaceConfigStore.List(ctx, gitspaceFilter) + gitspaceConfigs, err = c.gitspaceConfigStore.ListWithLatestInstance(ctx, &filter) if err != nil { return fmt.Errorf("failed to list gitspace configs: %w", err) } - count, err = c.gitspaceConfigStore.Count(ctx, gitspaceFilter) + + count, err = c.gitspaceConfigStore.Count(ctx, &filter) if err != nil { return fmt.Errorf("failed to count gitspaces in space: %w", err) } - gitspaceInstancesMap, err := c.getLatestInstanceMap(ctx, gitspaceConfigs) - if err != nil { - return err - } - for _, gitspaceConfig := range gitspaceConfigs { - instance := gitspaceInstancesMap[gitspaceConfig.ID] - gitspaceConfig.GitspaceInstance = instance - gitspaceConfig.SpacePath = space.Path - if instance != nil { - gitspaceStateType, err := enum.GetGitspaceStateFromInstance(instance.State, instance.Updated) - if err != nil { - return err - } - gitspaceConfig.State = gitspaceStateType - instance.SpacePath = gitspaceConfig.SpacePath - } else { - gitspaceConfig.State = enum.GitspaceStateUninitialized - } - gitspaceConfig.BranchURL = c.GetBranchURL(ctx, gitspaceConfig) - } + return nil }, dbtx.TxDefaultReadOnly) if err != nil { return nil, 0, err } - return gitspaceConfigs, count, nil -} -func (c *Service) getLatestInstanceMap( - ctx context.Context, - gitspaceConfigs []*types.GitspaceConfig, -) (map[int64]*types.GitspaceInstance, error) { - var gitspaceConfigIDs = make([]int64, 0) - for idx := 0; idx < len(gitspaceConfigs); idx++ { - gitspaceConfigIDs = append(gitspaceConfigIDs, gitspaceConfigs[idx].ID) + for _, gitspaceConfig := range gitspaceConfigs { + gitspaceConfig.SpacePath = space.Path + if gitspaceConfig.GitspaceInstance != nil { + gitspaceConfig.GitspaceInstance.SpacePath = space.Path + } + + gitspaceConfig.BranchURL = c.GetBranchURL(ctx, gitspaceConfig) } - var gitspaceInstances, err = c.gitspaceInstanceStore.FindAllLatestByGitspaceConfigID(ctx, gitspaceConfigIDs) - if err != nil { - return nil, err - } - var gitspaceInstancesMap = make(map[int64]*types.GitspaceInstance) - for _, gitspaceEntry := range gitspaceInstances { - gitspaceInstancesMap[gitspaceEntry.GitSpaceConfigID] = gitspaceEntry - } - return gitspaceInstancesMap, nil + + return gitspaceConfigs, count, nil } func (c *Service) GetBranchURL(ctx context.Context, config *types.GitspaceConfig) string { diff --git a/app/store/database.go b/app/store/database.go index 3ca224552..e2729f04b 100644 --- a/app/store/database.go +++ b/app/store/database.go @@ -662,14 +662,12 @@ type ( // Update tries to update a gitspace config in the datastore with optimistic locking. Update(ctx context.Context, gitspaceConfig *types.GitspaceConfig) error - // List lists the gitspace configs present in a parent space ID in the datastore. - List(ctx context.Context, filter *types.GitspaceFilter) ([]*types.GitspaceConfig, error) + // ListWithLatestInstance returns gitspace configs for the given filter with the latest gitspace instance + // information. + ListWithLatestInstance(ctx context.Context, filter *types.GitspaceFilter) ([]*types.GitspaceConfig, error) // Count the number of gitspace configs in a space matching the given filter. Count(ctx context.Context, filter *types.GitspaceFilter) (int64, error) - - // ListAll lists all the gitspace configs present for a user in the given spaces in the datastore. - ListAll(ctx context.Context, userUID string) ([]*types.GitspaceConfig, error) } GitspaceInstanceStore interface { @@ -692,13 +690,10 @@ type ( Update(ctx context.Context, gitspaceInstance *types.GitspaceInstance) error // List lists the gitspace instance present in a parent space ID in the datastore. - List(ctx context.Context, filter *types.GitspaceFilter) ([]*types.GitspaceInstance, error) + List(ctx context.Context, filter *types.GitspaceInstanceFilter) ([]*types.GitspaceInstance, error) - // ListDead lists dead gitspace instances whose heartbeat stopped coming after the given time. - ListDead(ctx context.Context, filter *types.GitspaceFilter) ([]*types.GitspaceInstance, error) - - // FetchInactiveGitspaceConfigs lists the inactive gitspace instance present in the datastore - FetchInactiveGitspaceConfigs(ctx context.Context, filter *types.GitspaceFilter) ([]int64, error) + // Count the number of gitspace instances in a space matching the given filter. + Count(ctx context.Context, filter *types.GitspaceInstanceFilter) (int64, error) // List lists the latest gitspace instance present for the gitspace configs in the datastore. FindAllLatestByGitspaceConfigID( diff --git a/app/store/database/gitspace_config.go b/app/store/database/gitspace_config.go index 23231c3f6..4583ccc81 100644 --- a/app/store/database/gitspace_config.go +++ b/app/store/database/gitspace_config.go @@ -29,6 +29,7 @@ import ( "github.com/guregu/null" "github.com/jmoiron/sqlx" "github.com/pkg/errors" + "github.com/rs/zerolog/log" ) const ( @@ -84,6 +85,12 @@ type gitspaceConfig struct { IsMarkedForDeletion bool `db:"gconf_is_marked_for_deletion"` } +type gitspaceConfigWithLatestInstance struct { + gitspaceConfig + // gitspace instance information + gitspaceInstance +} + var _ store.GitspaceConfigStore = (*gitspaceConfigStore)(nil) // NewGitspaceConfigStore returns a new GitspaceConfigStore. @@ -109,20 +116,10 @@ func (s gitspaceConfigStore) Count(ctx context.Context, filter *types.GitspaceFi db := dbtx.GetAccessor(ctx, s.db) countStmt := database.Builder. Select("COUNT(*)"). - From(gitspaceConfigsTable) + From(gitspaceConfigsTable). + PlaceholderFormat(squirrel.Dollar) - if !filter.IncludeDeleted { - countStmt = countStmt.Where(squirrel.Eq{"gconf_is_deleted": false}) - } - if filter.UserID != "" { - countStmt = countStmt.Where(squirrel.Eq{"gconf_user_uid": filter.UserID}) - } - if len(filter.SpaceIDs) > 0 { - countStmt = countStmt.Where(squirrel.Eq{"gconf_space_id": filter.SpaceIDs}) - } - if filter.IncludeMarkedForDeletion { - countStmt = countStmt.Where(squirrel.Eq{"gconf_is_marked_for_deletion": true}) - } + countStmt = addGitspaceFilter(countStmt, filter) sql, args, err := countStmt.ToSql() if err != nil { @@ -155,7 +152,7 @@ func (s gitspaceConfigStore) Find(ctx context.Context, id int64, includeDeleted if err := db.GetContext(ctx, dst, sql, args...); err != nil { return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find gitspace config for %d", id) } - return s.mapToGitspaceConfig(ctx, dst) + return s.mapDBToGitspaceConfig(ctx, dst) } func (s gitspaceConfigStore) FindByIdentifier( @@ -179,7 +176,7 @@ func (s gitspaceConfigStore) FindByIdentifier( if err := db.GetContext(ctx, dst, sql, args...); err != nil { return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find gitspace config for %s", identifier) } - return s.mapToGitspaceConfig(ctx, dst) + return s.mapDBToGitspaceConfig(ctx, dst) } func (s gitspaceConfigStore) Create(ctx context.Context, gitspaceConfig *types.GitspaceConfig) error { @@ -271,63 +268,75 @@ func mapToInternalGitspaceConfig(config *types.GitspaceConfig) *gitspaceConfig { } } -func (s gitspaceConfigStore) List(ctx context.Context, filter *types.GitspaceFilter) ([]*types.GitspaceConfig, error) { - stmt := database.Builder. - Select(gitspaceConfigSelectColumns). - From(gitspaceConfigsTable) +// ListWithLatestInstance returns gitspace configs for the given filter with the latest gitspace instance information. +func (s gitspaceConfigStore) ListWithLatestInstance( + ctx context.Context, + filter *types.GitspaceFilter, +) ([]*types.GitspaceConfig, error) { + gitsSelectStr := getLatestInstanceQuery() + stmt := squirrel.Select( + gitspaceConfigSelectColumns, + gitspaceInstanceSelectColumns). + From(gitspaceConfigsTable). + LeftJoin("(" + gitsSelectStr + + ") AS gits ON gitspace_configs.gconf_id = gits.gits_gitspace_config_id AND gits.rn = 1"). + PlaceholderFormat(squirrel.Dollar) + stmt = addGitspaceFilter(stmt, filter) + stmt = addOrderBy(stmt, filter) + stmt = stmt.Limit(database.Limit(filter.QueryFilter.Size)) + stmt = stmt.Offset(database.Offset(filter.QueryFilter.Page, filter.QueryFilter.Size)) + + sql, args, err := stmt.ToSql() + + if err != nil { + return nil, errors.Wrap(err, "Failed to convert squirrel builder to sql") + } + db := dbtx.GetAccessor(ctx, s.db) + var dst []*gitspaceConfigWithLatestInstance + if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { + return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing list gitspace config query") + } + return s.ToGitspaceConfigs(ctx, dst) +} + +func getLatestInstanceQuery() string { + return fmt.Sprintf("SELECT %s, %s FROM %s", + gitspaceInstanceSelectColumns, + "ROW_NUMBER() OVER (PARTITION BY gits_gitspace_config_id ORDER BY gits_created DESC) AS rn", + gitspaceInstanceTable, + ) +} + +func addGitspaceFilter(stmt squirrel.SelectBuilder, filter *types.GitspaceFilter) squirrel.SelectBuilder { if !filter.IncludeDeleted { stmt = stmt.Where(squirrel.Eq{"gconf_is_deleted": false}) } - if filter.UserID != "" { - stmt = stmt.Where(squirrel.Eq{"gconf_user_uid": filter.UserID}) + if filter.UserIdentifier != "" { + stmt = stmt.Where(squirrel.Eq{"gconf_user_uid": filter.UserIdentifier}) + } + + if !filter.IncludeMarkedForDeletion { + stmt = stmt.Where(squirrel.Eq{"gconf_is_marked_for_deletion": false}) } if len(filter.SpaceIDs) > 0 { stmt = stmt.Where(squirrel.Eq{"gconf_space_id": filter.SpaceIDs}) } - if filter.IncludeMarkedForDeletion { - stmt = stmt.Where(squirrel.Eq{"gconf_is_marked_for_deletion": true}) - } - - queryFilter := filter.QueryFilter - stmt = stmt.Limit(database.Limit(queryFilter.Size)) - stmt = stmt.Offset(database.Offset(queryFilter.Page, queryFilter.Size)) - - sql, args, err := stmt.ToSql() - if err != nil { - return nil, errors.Wrap(err, "Failed to convert squirrel builder to sql") - } - db := dbtx.GetAccessor(ctx, s.db) - var dst []*gitspaceConfig - if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { - return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing gitspace config list space query") - } - return s.mapToGitspaceConfigs(ctx, dst) + return stmt } -func (s gitspaceConfigStore) ListAll( - ctx context.Context, - userUID string, -) ([]*types.GitspaceConfig, error) { - stmt := database.Builder. - Select(gitspaceConfigSelectColumns). - From(gitspaceConfigsTable). - Where(squirrel.Eq{"gconf_is_deleted": false}). - Where(squirrel.Eq{"gconf_user_uid": userUID}) - - sql, args, err := stmt.ToSql() - if err != nil { - return nil, errors.Wrap(err, "Failed to convert squirrel builder to sql") +func addOrderBy(stmt squirrel.SelectBuilder, filter *types.GitspaceFilter) squirrel.SelectBuilder { + switch filter.Sort { + case enum.GitspaceSortLastUsed: + return stmt.OrderBy("gits_last_used " + filter.Order.String()) + case enum.GitspaceSortCreated: + return stmt.OrderBy("gconf_created " + filter.Order.String()) + default: + return stmt.OrderBy("gits_last_used " + filter.Order.String()) } - db := dbtx.GetAccessor(ctx, s.db) - var dst []*gitspaceConfig - if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { - return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing gitspace config list all query") - } - return s.mapToGitspaceConfigs(ctx, dst) } func (s gitspaceConfigStore) FindAll(ctx context.Context, ids []int64) ([]*types.GitspaceConfig, error) { @@ -348,7 +357,7 @@ func (s gitspaceConfigStore) FindAll(ctx context.Context, ids []int64) ([]*types return s.mapToGitspaceConfigs(ctx, dst) } -func (s *gitspaceConfigStore) mapToGitspaceConfig( +func (s gitspaceConfigStore) mapDBToGitspaceConfig( ctx context.Context, in *gitspaceConfig, ) (*types.GitspaceConfig, error) { @@ -362,7 +371,7 @@ func (s *gitspaceConfigStore) mapToGitspaceConfig( AuthType: in.CodeAuthType, AuthID: in.CodeAuthID, } - var res = &types.GitspaceConfig{ + var result = &types.GitspaceConfig{ ID: in.ID, Identifier: in.Identifier, Name: in.Name, @@ -378,30 +387,78 @@ func (s *gitspaceConfigStore) mapToGitspaceConfig( ID: in.CreatedBy.Ptr(), Identifier: in.UserUID}, } - if res.GitspaceUser.ID != nil { - author, _ := s.pCache.Get(ctx, *res.GitspaceUser.ID) + if result.GitspaceUser.ID != nil { + author, _ := s.pCache.Get(ctx, *result.GitspaceUser.ID) if author != nil { - res.GitspaceUser.DisplayName = author.DisplayName - res.GitspaceUser.Email = author.Email + result.GitspaceUser.DisplayName = author.DisplayName + result.GitspaceUser.Email = author.Email } } if resource, err := s.rCache.Get(ctx, in.InfraProviderResourceID); err == nil { - res.InfraProviderResource = *resource + result.InfraProviderResource = *resource } else { return nil, fmt.Errorf("couldn't set resource to the config in DB: %s", in.Identifier) } - return res, nil + return result, nil } -func (s *gitspaceConfigStore) mapToGitspaceConfigs( +func (s gitspaceConfigStore) ToGitspaceConfig( + ctx context.Context, + in *gitspaceConfigWithLatestInstance, +) (*types.GitspaceConfig, error) { + var result, err = s.mapDBToGitspaceConfig(ctx, &in.gitspaceConfig) + if err != nil { + return nil, err + } + instance, err := mapDBToGitspaceInstance(ctx, &in.gitspaceInstance) + if err != nil { + log.Ctx(ctx).Error().Err(err).Msgf("Failed to convert to gitspace instance, gitspace configID: %d", + in.gitspaceInstance.ID, + ) + instance = nil + } + + if instance != nil { + gitspaceStateType, err2 := enum.GetGitspaceStateFromInstance( + instance.State, + instance.Updated, + ) + if err2 != nil { + return nil, err2 + } + result.State = gitspaceStateType + } else { + result.State = enum.GitspaceStateUninitialized + } + result.GitspaceInstance = instance + + return result, nil +} + +func (s gitspaceConfigStore) mapToGitspaceConfigs( ctx context.Context, configs []*gitspaceConfig, ) ([]*types.GitspaceConfig, error) { var err error res := make([]*types.GitspaceConfig, len(configs)) for i := range configs { - res[i], err = s.mapToGitspaceConfig(ctx, configs[i]) + res[i], err = s.mapDBToGitspaceConfig(ctx, configs[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (s gitspaceConfigStore) ToGitspaceConfigs( + ctx context.Context, + configs []*gitspaceConfigWithLatestInstance, +) ([]*types.GitspaceConfig, error) { + var err error + res := make([]*types.GitspaceConfig, len(configs)) + for i := range configs { + res[i], err = s.ToGitspaceConfig(ctx, configs[i]) if err != nil { return nil, err } diff --git a/app/store/database/gitspace_instance.go b/app/store/database/gitspace_instance.go index a3d63b9d9..42214c84e 100644 --- a/app/store/database/gitspace_instance.go +++ b/app/store/database/gitspace_instance.go @@ -166,7 +166,7 @@ func (g gitspaceInstanceStore) Find(ctx context.Context, id int64) (*types.Gitsp if err := db.GetContext(ctx, gitspace, sql, args...); err != nil { return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find gitspace %d", id) } - return g.mapToGitspaceInstance(ctx, gitspace) + return mapDBToGitspaceInstance(ctx, gitspace) } func (g gitspaceInstanceStore) FindByIdentifier( @@ -187,7 +187,7 @@ func (g gitspaceInstanceStore) FindByIdentifier( if err := db.GetContext(ctx, gitspace, sql, args...); err != nil { return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find gitspace %s", identifier) } - return g.mapToGitspaceInstance(ctx, gitspace) + return mapDBToGitspaceInstance(ctx, gitspace) } func (g gitspaceInstanceStore) Create(ctx context.Context, gitspaceInstance *types.GitspaceInstance) error { @@ -276,34 +276,18 @@ func (g gitspaceInstanceStore) FindLatestByGitspaceConfigID( return nil, database.ProcessSQLErrorf( ctx, err, "Failed to find latest gitspace instance for %d", gitspaceConfigID) } - return g.mapToGitspaceInstance(ctx, gitspace) + return mapDBToGitspaceInstance(ctx, gitspace) } func (g gitspaceInstanceStore) List( ctx context.Context, - filter *types.GitspaceFilter, + filter *types.GitspaceInstanceFilter, ) ([]*types.GitspaceInstance, error) { stmt := database.Builder. Select(gitspaceInstanceSelectColumns). From(gitspaceInstanceTable). OrderBy("gits_created ASC") - - if len(filter.SpaceIDs) > 0 { - stmt = stmt.Where(squirrel.Eq{"gits_space_id": filter.SpaceIDs}) - } - - if filter.UserID != "" { - stmt = stmt.Where(squirrel.Eq{"gits_user_id": filter.UserID}) - } - - if len(filter.State) > 0 { - stmt = stmt.Where(squirrel.Eq{"gits_state": filter.State}) - } - - if filter.Limit > 0 { - stmt = stmt.Limit(database.Limit(filter.Limit)) - } - + stmt = addGitspaceInstanceFilter(stmt, filter) sql, args, err := stmt.ToSql() if err != nil { return nil, errors.Wrap(err, "Failed to convert squirrel builder to sql") @@ -316,53 +300,24 @@ func (g gitspaceInstanceStore) List( return g.mapToGitspaceInstances(ctx, dst) } -func (g gitspaceInstanceStore) ListDead( - ctx context.Context, - filter *types.GitspaceFilter, -) ([]*types.GitspaceInstance, error) { - stmt := database.Builder. - Select(gitspaceInstanceSelectColumns). - From(gitspaceInstanceTable). - Where(squirrel.Lt{"gits_last_heartbeat": filter.LastHeartBeatBefore}). - Where(squirrel.Eq{"gits_state": filter.State}). - OrderBy("gits_created ASC") - - sqlStr, args, err := stmt.ToSql() - if err != nil { - return nil, errors.Wrap(err, "Failed to convert squirrel builder to sql") - } - - var dst []*gitspaceInstance +func (g gitspaceInstanceStore) Count(ctx context.Context, filter *types.GitspaceInstanceFilter) (int64, error) { db := dbtx.GetAccessor(ctx, g.db) - if err = db.SelectContext(ctx, &dst, sqlStr, args...); err != nil { - return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing gitspace instance list query") - } - return g.mapToGitspaceInstances(ctx, dst) -} + countStmt := database.Builder. + Select("COUNT(*)"). + From(gitspaceInstanceTable) -func (g gitspaceInstanceStore) FetchInactiveGitspaceConfigs( - ctx context.Context, - filter *types.GitspaceFilter, -) ([]int64, error) { - stmt := database.Builder. - Select("gits_gitspace_config_id"). - From(gitspaceInstanceTable). - Where(squirrel.Lt{"gits_last_used": filter.LastUsedBefore}). - Where(squirrel.Eq{"gits_state": filter.State}). - OrderBy("gits_created ASC") - if filter.Limit > 0 { - stmt = stmt.Limit(database.Limit(filter.Limit)) - } - sql, args, err := stmt.ToSql() + countStmt = addGitspaceInstanceFilter(countStmt, filter) + + sql, args, err := countStmt.ToSql() if err != nil { - return nil, errors.Wrap(err, "Failed to convert squirrel builder to sql") + return 0, errors.Wrap(err, "Failed to convert squirrel builder to sql") } - db := dbtx.GetAccessor(ctx, g.db) - var dst []int64 - if err := db.SelectContext(ctx, &dst, sql, args...); err != nil { - return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing gitspace instance list query") + var count int64 + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, database.ProcessSQLErrorf(ctx, err, "Failed executing custom count query") } - return dst, nil + return count, nil } func (g gitspaceInstanceStore) FindAllLatestByGitspaceConfigID( @@ -399,7 +354,33 @@ func (g gitspaceInstanceStore) FindAllLatestByGitspaceConfigID( return g.mapToGitspaceInstances(ctx, dst) } -func (g gitspaceInstanceStore) mapToGitspaceInstance( +func addGitspaceInstanceFilter( + stmt squirrel.SelectBuilder, + filter *types.GitspaceInstanceFilter, +) squirrel.SelectBuilder { + if len(filter.SpaceIDs) > 0 { + stmt = stmt.Where(squirrel.Eq{"gits_space_id": filter.SpaceIDs}) + } + + if filter.UserIdentifier != "" { + stmt = stmt.Where(squirrel.Eq{"gits_user_id": filter.UserIdentifier}) + } + + if filter.LastHeartBeatBefore > 0 { + stmt = stmt.Where(squirrel.Lt{"gits_last_heartbeat": filter.LastHeartBeatBefore}) + } + + if len(filter.States) > 0 { + stmt = stmt.Where(squirrel.Eq{"gits_state": filter.States}) + } + + if filter.Limit > 0 { + stmt = stmt.Limit(database.Limit(filter.Limit)) + } + return stmt +} + +func mapDBToGitspaceInstance( _ context.Context, in *gitspaceInstance, ) (*types.GitspaceInstance, error) { @@ -434,7 +415,7 @@ func (g gitspaceInstanceStore) mapToGitspaceInstances( var err error res := make([]*types.GitspaceInstance, len(instances)) for i := range instances { - res[i], err = g.mapToGitspaceInstance(ctx, instances[i]) + res[i], err = mapDBToGitspaceInstance(ctx, instances[i]) if err != nil { return nil, err } diff --git a/types/enum/common.go b/types/enum/common.go index fc5129a80..58ebb1264 100644 --- a/types/enum/common.go +++ b/types/enum/common.go @@ -62,6 +62,7 @@ const ( desc = "desc" descending = "descending" value = "value" + lastUsed = "last_used" ) func toInterfaceSlice[T interface{}](vals []T) []interface{} { diff --git a/types/enum/gitspace.go b/types/enum/gitspace.go new file mode 100644 index 000000000..11e17124c --- /dev/null +++ b/types/enum/gitspace.go @@ -0,0 +1,48 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enum + +import ( + "strings" +) + +// GitspaceSort represents gitspace sort order. +type GitspaceSort string + +// GitspaceSort enumeration. +const ( + GitspaceSortLastUsed GitspaceSort = lastUsed + GitspaceSortCreated GitspaceSort = created +) + +var GitspaceSorts = sortEnum([]GitspaceSort{ + GitspaceSortLastUsed, + GitspaceSortCreated, +}) + +func (GitspaceSort) Enum() []interface{} { return toInterfaceSlice(GitspaceSorts) } + +// ParseGitspaceSort parses the gitspace sort attribute string +// and returns the equivalent enumeration. +func ParseGitspaceSort(s string) GitspaceSort { + switch strings.ToLower(s) { + case lastUsed: + return GitspaceSortLastUsed + case created, createdAt: + return GitspaceSortCreated + default: + return GitspaceSortLastUsed + } +} diff --git a/types/gitspace.go b/types/gitspace.go index b35f8fc26..e1d088dec 100644 --- a/types/gitspace.go +++ b/types/gitspace.go @@ -82,12 +82,18 @@ type GitspaceInstance struct { type GitspaceFilter struct { QueryFilter ListQueryFilter - UserID string - LastUsedBefore int64 - LastHeartBeatBefore int64 - State []enum.GitspaceInstanceStateType - SpaceIDs []int64 + Sort enum.GitspaceSort `json:"sort"` + Order enum.Order `json:"order"` IncludeDeleted bool IncludeMarkedForDeletion bool - Limit int + GitspaceInstanceFilter +} + +type GitspaceInstanceFilter struct { + UserIdentifier string + LastUsedBefore int64 + LastHeartBeatBefore int64 + States []enum.GitspaceInstanceStateType + SpaceIDs []int64 + Limit int }