Skip to content

Commit 04067da

Browse files
authored
Feature/add filters and groups in prospects list api (#893)
* feat: add filter and group in list prospects * feat: add filter and group in list prospects * feat: add group and pagination support in list prospects * feat: add group and pagination support in list prospects * refactor: remove filter file and filterGetValue method
1 parent 1e8edf9 commit 04067da

File tree

11 files changed

+611
-130
lines changed

11 files changed

+611
-130
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ TAG := $(shell git rev-list --tags --max-count=1)
44
VERSION := $(shell git describe --tags ${TAG})
55
.PHONY: build check fmt lint test test-race vet test-cover-html help install proto ui compose-up-dev
66
.DEFAULT_GOAL := build
7-
PROTON_COMMIT := "4cbb0dbb7f414c03b0113e86923158e27c7a60cd"
7+
PROTON_COMMIT := "00bbe6326ed0356efb71d0e66bebff2f8d49403a"
88

99
ui:
1010
@echo " > generating ui build"

core/prospect/filter.go

Lines changed: 0 additions & 4 deletions
This file was deleted.

core/prospect/prospect.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"time"
66

77
"github.com/raystack/frontier/pkg/metadata"
8+
"github.com/raystack/frontier/pkg/utils"
9+
"github.com/raystack/salt/rql"
810
)
911

1012
type Status string
@@ -34,24 +36,30 @@ func (s Status) ToDB() Status {
3436
}
3537

3638
type Prospect struct {
37-
ID string
38-
Name string
39-
Email string
40-
Phone string
41-
Activity string
42-
Status Status // subscription status
43-
ChangedAt time.Time
44-
Source string
45-
Verified bool
46-
CreatedAt time.Time
47-
UpdatedAt time.Time
39+
ID string `rql:"name=id,type=string"`
40+
Name string `rql:"name=name,type=string"`
41+
Email string `rql:"name=email,type=string"`
42+
Phone string `rql:"name=phone,type=string"`
43+
Activity string `rql:"name=activity,type=string"`
44+
Status Status `rql:"name=status,type=string"` // subscription status
45+
ChangedAt time.Time `rql:"name=changed_at,type=datetime"`
46+
Source string `rql:"name=source,type=string"`
47+
Verified bool `rql:"name=verified,type=bool"`
48+
CreatedAt time.Time `rql:"name=created_at,type=datetime"`
49+
UpdatedAt time.Time `rql:"name=updated_at,type=datetime"`
4850
Metadata metadata.Metadata
4951
}
5052

53+
type ListProspects struct {
54+
Prospects []Prospect `json:"prospects"`
55+
Group *utils.Group `json:"group,omitempty"`
56+
Page utils.Page `json:"pagination"`
57+
}
58+
5159
type Repository interface {
5260
Create(ctx context.Context, prospect Prospect) (Prospect, error)
5361
Get(ctx context.Context, id string) (Prospect, error)
54-
List(ctx context.Context, filter Filter) ([]Prospect, error)
62+
List(ctx context.Context, query *rql.Query) (ListProspects, error)
5563
Update(ctx context.Context, prospect Prospect) (Prospect, error)
5664
Delete(ctx context.Context, id string) error
5765
}

core/prospect/service.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package prospect
33
import (
44
"context"
55
"strings"
6+
7+
"github.com/raystack/salt/rql"
68
)
79

810
type Service struct {
@@ -34,8 +36,8 @@ func (s *Service) Create(ctx context.Context, prospect Prospect) (Prospect, erro
3436
})
3537
}
3638

37-
func (s *Service) List(ctx context.Context, filters Filter) ([]Prospect, error) {
38-
return s.repository.List(ctx, filters)
39+
func (s *Service) List(ctx context.Context, query *rql.Query) (ListProspects, error) {
40+
return s.repository.List(ctx, query)
3941
}
4042

4143
func (s *Service) Update(ctx context.Context, prospect Prospect) (Prospect, error) {

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2021,8 +2021,6 @@ github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT
20212021
github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI=
20222022
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
20232023
github.com/rakyll/embedmd v0.0.0-20171029212350-c8060a0752a2/go.mod h1:7jOTMgqac46PZcF54q6l2hkLEG8op93fZu61KmxWDV4=
2024-
github.com/raystack/salt v0.3.7 h1:cC3RRwQrhlyAit+AO7O5NhI79tE9GsQNXZrXU8iR0qc=
2025-
github.com/raystack/salt v0.3.7/go.mod h1:Gm0ocYhbSKsPitohigP8b65KfMU2iKPrkB09X4I4T68=
20262024
github.com/raystack/salt v0.3.8 h1:eESWm9vuELGRMSMAPC20Sk9l6uulXZCxN8FwkX00SDI=
20272025
github.com/raystack/salt v0.3.8/go.mod h1:Gm0ocYhbSKsPitohigP8b65KfMU2iKPrkB09X4I4T68=
20282026
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=

internal/api/v1beta1/prospect.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77

88
"github.com/raystack/frontier/core/prospect"
99
"github.com/raystack/frontier/pkg/metadata"
10+
"github.com/raystack/frontier/pkg/utils"
1011
frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1"
12+
"github.com/raystack/salt/rql"
1113
"google.golang.org/grpc/codes"
1214
"google.golang.org/grpc/status"
1315
"google.golang.org/protobuf/types/known/timestamppb"
@@ -20,11 +22,13 @@ var (
2022
grpcStatusRequiredError = status.Errorf(codes.InvalidArgument, "status is required")
2123
grpcProspectIdRequiredError = status.Errorf(codes.InvalidArgument, "prospect ID is required")
2224
grpcProspectNotFoundError = status.Errorf(codes.NotFound, "record not found for the given input")
25+
grpcRQLParseError = status.Errorf(codes.NotFound, "error parsing RQL query")
26+
grpcRQLInvalidError = status.Errorf(codes.NotFound, "datatype or operator not supported in RQL query")
2327
)
2428

2529
type ProspectService interface {
2630
Create(ctx context.Context, prospect prospect.Prospect) (prospect.Prospect, error)
27-
List(ctx context.Context, filter prospect.Filter) ([]prospect.Prospect, error)
31+
List(ctx context.Context, query *rql.Query) (prospect.ListProspects, error)
2832
Get(ctx context.Context, prospectId string) (prospect.Prospect, error)
2933
Update(ctx context.Context, prospect prospect.Prospect) (prospect.Prospect, error)
3034
Delete(ctx context.Context, prospectId string) error
@@ -111,20 +115,55 @@ func (h Handler) CreateProspect(ctx context.Context, request *frontierv1beta1.Cr
111115
}
112116

113117
func (h Handler) ListProspects(ctx context.Context, request *frontierv1beta1.ListProspectsRequest) (*frontierv1beta1.ListProspectsResponse, error) {
114-
prospects, err := h.prospectService.List(ctx, prospect.Filter{})
118+
requestQuery, err := utils.TransformProtoToRQL(request.GetQuery(), prospect.Prospect{})
119+
if err != nil {
120+
return nil, grpcRQLParseError
121+
}
122+
123+
err = rql.ValidateQuery(requestQuery, prospect.Prospect{})
124+
if err != nil {
125+
return nil, status.Errorf(codes.InvalidArgument, "%v: %v", grpcRQLInvalidError, err)
126+
}
127+
128+
prospects, err := h.prospectService.List(ctx, requestQuery)
115129
if err != nil {
116130
return nil, grpcInternalServerError
117131
}
118132

119133
var transformedProspects []*frontierv1beta1.Prospect
120-
for _, val := range prospects {
134+
for _, val := range prospects.Prospects {
121135
transformedProspect, err := transformProspectToPB(val)
122136
if err != nil {
123137
return nil, err
124138
}
125139
transformedProspects = append(transformedProspects, transformedProspect)
126140
}
127-
return &frontierv1beta1.ListProspectsResponse{Prospects: transformedProspects}, nil
141+
142+
var transformedGroups *frontierv1beta1.RQLQueryGroupResponse
143+
144+
if len(prospects.Group.Data) > 0 {
145+
groupResponse := make([]*frontierv1beta1.RQLQueryGroupData, 0)
146+
for _, groupItem := range prospects.Group.Data {
147+
groupResponse = append(groupResponse, &frontierv1beta1.RQLQueryGroupData{
148+
Name: groupItem.Name,
149+
Count: uint32(groupItem.Count),
150+
})
151+
}
152+
transformedGroups = &frontierv1beta1.RQLQueryGroupResponse{
153+
Name: prospects.Group.Name,
154+
Data: groupResponse,
155+
}
156+
} else {
157+
transformedGroups = nil
158+
}
159+
160+
pagination := &frontierv1beta1.RQLQueryPaginationResponse{
161+
Offset: uint32(prospects.Page.Offset),
162+
Limit: uint32(prospects.Page.Limit),
163+
TotalCount: uint32(prospects.Page.TotalCount),
164+
}
165+
166+
return &frontierv1beta1.ListProspectsResponse{Prospects: transformedProspects, Group: transformedGroups, Pagination: pagination}, nil
128167
}
129168

130169
func (h Handler) GetProspect(ctx context.Context, request *frontierv1beta1.GetProspectRequest) (*frontierv1beta1.GetProspectResponse, error) {

internal/store/postgres/prospect_repository.go

Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,38 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"strings"
910

1011
"github.com/doug-martin/goqu/v9"
1112
"github.com/raystack/frontier/core/prospect"
1213
"github.com/raystack/frontier/pkg/db"
14+
"github.com/raystack/frontier/pkg/utils"
15+
"github.com/raystack/salt/rql"
1316
)
1417

18+
var (
19+
rqlFilerSupportedColumns = []string{"id", "name", "email", "phone", "activity", "status", "changed_at", "source", "verified", "created_at", "updated_at"}
20+
rqlSearchSupportedColumns = []string{"id", "name", "email", "phone", "activity", "status", "source", "verified"}
21+
rqlGroupSupportedColumns = []string{"activity", "status", "source", "verified"}
22+
)
23+
24+
type ProspectBillingGroup struct {
25+
Name sql.NullString `db:"name"`
26+
Data []ProspectBillingGroupData `db:"data"`
27+
}
28+
29+
type ProspectBillingGroupData struct {
30+
Name string `db:"values"`
31+
Count int `db:"count"`
32+
}
33+
34+
func (p ProspectBillingGroupData) toGroupData() utils.GroupData {
35+
return utils.GroupData{
36+
Name: p.Name,
37+
Count: p.Count,
38+
}
39+
}
40+
1541
type ProspectRepository struct {
1642
dbc *db.Client
1743
}
@@ -99,11 +125,72 @@ func (r ProspectRepository) Get(ctx context.Context, id string) (prospect.Prospe
99125
return transformedProspect, nil
100126
}
101127

102-
func (r ProspectRepository) List(ctx context.Context, filters prospect.Filter) ([]prospect.Prospect, error) {
103-
stmt := dialect.From(TABLE_PROSPECTS)
104-
query, params, err := stmt.ToSQL()
128+
func (r ProspectRepository) List(ctx context.Context, rqlQuery *rql.Query) (prospect.ListProspects, error) {
129+
baseStmt := dialect.From(TABLE_PROSPECTS)
130+
131+
// apply filters
132+
baseStmt, err := utils.AddRQLFiltersInQuery(baseStmt, rqlQuery, rqlFilerSupportedColumns, prospect.Prospect{})
133+
if err != nil {
134+
return prospect.ListProspects{}, fmt.Errorf("%w: %w", queryErr, err)
135+
}
136+
137+
// apply search
138+
baseStmt, err = utils.AddRQLSearchInQuery(baseStmt, rqlQuery, rqlSearchSupportedColumns)
139+
if err != nil {
140+
return prospect.ListProspects{}, fmt.Errorf("%w: %w", queryErr, err)
141+
}
142+
143+
listStmt := baseStmt
144+
countStmt := baseStmt
145+
groupStmt := baseStmt
146+
147+
// Get total row count
148+
countQuery, countParams, err := countStmt.Select(goqu.L("COUNT(*) as total")).ToSQL()
149+
if err != nil {
150+
return prospect.ListProspects{}, fmt.Errorf("%w: %w", queryErr, err)
151+
}
152+
var totalCount int64
153+
if err = r.dbc.WithTimeout(ctx, TABLE_PROSPECTS, "Count", func(ctx context.Context) error {
154+
return r.dbc.GetContext(ctx, &totalCount, countQuery, countParams...)
155+
}); err != nil {
156+
return prospect.ListProspects{}, err
157+
}
158+
159+
// Get group by results
160+
var groupResults []ProspectBillingGroupData
161+
162+
if len(rqlQuery.GroupBy) > 0 {
163+
groupStmt, err = utils.AddGroupInQuery(groupStmt, rqlQuery, rqlGroupSupportedColumns)
164+
if err != nil {
165+
return prospect.ListProspects{}, fmt.Errorf("%w: %w", queryErr, err)
166+
}
167+
168+
query, params, err := groupStmt.ToSQL()
169+
if err != nil {
170+
return prospect.ListProspects{}, fmt.Errorf("%w: %w", queryErr, err)
171+
}
172+
173+
if err = r.dbc.WithTimeout(ctx, TABLE_PROSPECTS, "groupCount", func(ctx context.Context) error {
174+
return r.dbc.SelectContext(ctx, &groupResults, query, params...)
175+
}); err != nil {
176+
err = checkPostgresError(err)
177+
if errors.Is(err, sql.ErrNoRows) {
178+
return prospect.ListProspects{}, prospect.ErrNotExist
179+
}
180+
return prospect.ListProspects{}, err
181+
}
182+
}
183+
184+
// List prospects with pagination and sorting
185+
listStmt, err = utils.AddRQLSortInQuery(listStmt, rqlQuery)
186+
if err != nil {
187+
return prospect.ListProspects{}, fmt.Errorf("%w: %w", queryErr, err)
188+
}
189+
listStmt, pagination := utils.AddRQLPaginationInQuery(listStmt, rqlQuery)
190+
191+
query, params, err := listStmt.ToSQL()
105192
if err != nil {
106-
return []prospect.Prospect{}, fmt.Errorf("%w: %w", queryErr, err)
193+
return prospect.ListProspects{}, fmt.Errorf("%w: %w", queryErr, err)
107194
}
108195

109196
var prospectModel []Prospect
@@ -112,20 +199,39 @@ func (r ProspectRepository) List(ctx context.Context, filters prospect.Filter) (
112199
}); err != nil {
113200
err = checkPostgresError(err)
114201
if errors.Is(err, sql.ErrNoRows) {
115-
return []prospect.Prospect{}, prospect.ErrNotExist
202+
return prospect.ListProspects{}, prospect.ErrNotExist
116203
}
117-
return []prospect.Prospect{}, err
204+
return prospect.ListProspects{}, err
205+
}
206+
207+
page := utils.Page{
208+
Limit: pagination.Limit,
209+
Offset: pagination.Offset,
210+
TotalCount: totalCount,
118211
}
119212

120213
var transformedProspects []prospect.Prospect
121-
for _, a := range prospectModel {
122-
transformedProspect, err := a.transformToProspect()
214+
for _, p := range prospectModel {
215+
transformedProspect, err := p.transformToProspect()
123216
if err != nil {
124-
return []prospect.Prospect{}, fmt.Errorf("%w: %w", parseErr, err)
217+
return prospect.ListProspects{}, fmt.Errorf("%w: %w", parseErr, err)
125218
}
126219
transformedProspects = append(transformedProspects, transformedProspect)
127220
}
128-
return transformedProspects, nil
221+
222+
groupData := make([]utils.GroupData, len(groupResults))
223+
for i, result := range groupResults {
224+
groupData[i] = result.toGroupData()
225+
}
226+
227+
return prospect.ListProspects{
228+
Prospects: transformedProspects,
229+
Group: &utils.Group{
230+
Name: strings.Join(rqlQuery.GroupBy, ","),
231+
Data: groupData,
232+
},
233+
Page: page,
234+
}, nil
129235
}
130236

131237
func (r ProspectRepository) Update(ctx context.Context, prspct prospect.Prospect) (prospect.Prospect, error) {

0 commit comments

Comments
 (0)