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

Commit

Permalink
Convert the Survey Responses store into a basestore (#17459)
Browse files Browse the repository at this point in the history
  • Loading branch information
asdine authored Jan 20, 2021
1 parent 4e01662 commit 2a9c509
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 17 deletions.
2 changes: 1 addition & 1 deletion internal/db/stores.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var (
UserPublicRepos = &UserPublicRepoStore{}
EventLogs = &EventLogStore{}

SurveyResponses = &surveyResponses{}
SurveyResponses = &SurveyResponseStore{}

ExternalAccounts = &userExternalAccounts{}

Expand Down
81 changes: 65 additions & 16 deletions internal/db/survey_responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"context"
"database/sql"
"math"
"sync"
"time"

"github.com/keegancsmith/sqlf"

"github.com/sourcegraph/sourcegraph/internal/db/basestore"
"github.com/sourcegraph/sourcegraph/internal/db/dbconn"
"github.com/sourcegraph/sourcegraph/internal/db/dbutil"
"github.com/sourcegraph/sourcegraph/internal/types"
)

Expand All @@ -17,19 +20,57 @@ type SurveyResponseListOptions struct {
*LimitOffset
}

type surveyResponses struct{}
type SurveyResponseStore struct {
*basestore.Store

once sync.Once
}

// NewSurveyResponseStoreWithDB instantiates and returns a new SurveyResponseStore with prepared statements.
func NewSurveyResponseStoreWithDB(db dbutil.DB) *SurveyResponseStore {
return &SurveyResponseStore{Store: basestore.NewWithDB(db, sql.TxOptions{})}
}

// NewSurveyResponseStoreWithDB instantiates and returns a new SurveyResponseStore using the other store handle.
func NewSurveyResponseStoreWith(other basestore.ShareableStore) *SurveyResponseStore {
return &SurveyResponseStore{Store: basestore.NewWithHandle(other.Handle())}
}

func (s *SurveyResponseStore) With(other basestore.ShareableStore) *SurveyResponseStore {
return &SurveyResponseStore{Store: s.Store.With(other)}
}

func (s *SurveyResponseStore) Transact(ctx context.Context) (*SurveyResponseStore, error) {
txBase, err := s.Store.Transact(ctx)
return &SurveyResponseStore{Store: txBase}, err
}

// ensureStore instantiates a basestore.Store if necessary, using the dbconn.Global handle.
// This function ensures access to dbconn happens after the rest of the code or tests have
// initialized it.
func (s *SurveyResponseStore) ensureStore() {
s.once.Do(func() {
if s.Store == nil {
s.Store = basestore.NewWithDB(dbconn.Global, sql.TxOptions{})
}
})
}

// Create creates a survey response.
func (s *surveyResponses) Create(ctx context.Context, userID *int32, email *string, score int, reason *string, better *string) (id int64, err error) {
err = dbconn.Global.QueryRowContext(ctx,
func (s *SurveyResponseStore) Create(ctx context.Context, userID *int32, email *string, score int, reason *string, better *string) (id int64, err error) {
s.ensureStore()

err = s.Handle().DB().QueryRowContext(ctx,
"INSERT INTO survey_responses(user_id, email, score, reason, better) VALUES($1, $2, $3, $4, $5) RETURNING id",
userID, email, score, reason, better,
).Scan(&id)
return id, err
}

func (*surveyResponses) getBySQL(ctx context.Context, query string, args ...interface{}) ([]*types.SurveyResponse, error) {
rows, err := dbconn.Global.QueryContext(ctx, "SELECT id, user_id, email, score, reason, better, created_at FROM survey_responses "+query, args...)
func (s *SurveyResponseStore) getBySQL(ctx context.Context, query string, args ...interface{}) ([]*types.SurveyResponse, error) {
s.ensureStore()

rows, err := s.Handle().DB().QueryContext(ctx, "SELECT id, user_id, email, score, reason, better, created_at FROM survey_responses "+query, args...)
if err != nil {
return nil, err
}
Expand All @@ -50,36 +91,42 @@ func (*surveyResponses) getBySQL(ctx context.Context, query string, args ...inte
}

// GetAll gets all survey responses.
func (s *surveyResponses) GetAll(ctx context.Context) ([]*types.SurveyResponse, error) {
func (s *SurveyResponseStore) GetAll(ctx context.Context) ([]*types.SurveyResponse, error) {
return s.getBySQL(ctx, "ORDER BY created_at DESC")
}

// GetByUserID gets all survey responses by a given user.
func (s *surveyResponses) GetByUserID(ctx context.Context, userID int32) ([]*types.SurveyResponse, error) {
func (s *SurveyResponseStore) GetByUserID(ctx context.Context, userID int32) ([]*types.SurveyResponse, error) {
return s.getBySQL(ctx, "WHERE user_id=$1 ORDER BY created_at DESC", userID)
}

// Count returns the count of all survey responses.
func (s *surveyResponses) Count(ctx context.Context) (int, error) {
func (s *SurveyResponseStore) Count(ctx context.Context) (int, error) {
s.ensureStore()

q := sqlf.Sprintf("SELECT COUNT(*) FROM survey_responses")

var count int
err := dbconn.Global.QueryRowContext(ctx, q.Query(sqlf.PostgresBindVar), q.Args()...).Scan(&count)
err := s.QueryRow(ctx, q).Scan(&count)
return count, err
}

// Last30DaysAverageScore returns the average score for all surveys submitted in the last 30 days.
func (s *surveyResponses) Last30DaysAverageScore(ctx context.Context) (float64, error) {
func (s *SurveyResponseStore) Last30DaysAverageScore(ctx context.Context) (float64, error) {
s.ensureStore()

q := sqlf.Sprintf("SELECT AVG(score) FROM survey_responses WHERE created_at>%s", thirtyDaysAgo())

var avg sql.NullFloat64
err := dbconn.Global.QueryRowContext(ctx, q.Query(sqlf.PostgresBindVar), q.Args()...).Scan(&avg)
err := s.QueryRow(ctx, q).Scan(&avg)
return avg.Float64, err
}

// Last30DaysNPS returns the net promoter score for all surveys submitted in the last 30 days.
// This is calculated as 100*((% of responses that are >= 9) - (% of responses that are <= 6))
func (s *surveyResponses) Last30DaysNetPromoterScore(ctx context.Context) (int, error) {
func (s *SurveyResponseStore) Last30DaysNetPromoterScore(ctx context.Context) (int, error) {
s.ensureStore()

since := thirtyDaysAgo()
promotersQ := sqlf.Sprintf("SELECT COUNT(*) FROM survey_responses WHERE created_at>%s AND score>8", since)
detractorsQ := sqlf.Sprintf("SELECT COUNT(*) FROM survey_responses WHERE created_at>%s AND score<7", since)
Expand All @@ -91,23 +138,25 @@ func (s *surveyResponses) Last30DaysNetPromoterScore(ctx context.Context) (int,
}

var promoters, detractors int
err = dbconn.Global.QueryRowContext(ctx, promotersQ.Query(sqlf.PostgresBindVar), promotersQ.Args()...).Scan(&promoters)
err = s.QueryRow(ctx, promotersQ).Scan(&promoters)
if err != nil {
return 0, err
}
err = dbconn.Global.QueryRowContext(ctx, detractorsQ.Query(sqlf.PostgresBindVar), detractorsQ.Args()...).Scan(&detractors)
err = s.QueryRow(ctx, detractorsQ).Scan(&detractors)
promoterPercent := math.Round(float64(promoters) / float64(count) * 100.0)
detractorPercent := math.Round(float64(detractors) / float64(count) * 100.0)

return int(promoterPercent - detractorPercent), err
}

// Last30Count returns the count of surveys submitted in the last 30 days.
func (s *surveyResponses) Last30DaysCount(ctx context.Context) (int, error) {
func (s *SurveyResponseStore) Last30DaysCount(ctx context.Context) (int, error) {
s.ensureStore()

q := sqlf.Sprintf("SELECT COUNT(*) FROM survey_responses WHERE created_at>%s", thirtyDaysAgo())

var count int
err := dbconn.Global.QueryRowContext(ctx, q.Query(sqlf.PostgresBindVar), q.Args()...).Scan(&count)
err := s.QueryRow(ctx, q).Scan(&count)
return count, err
}

Expand Down

0 comments on commit 2a9c509

Please sign in to comment.