Fix broken sorting (lists -> subcount, subscribers -> status) in queries. Closes #1076.
This commit is contained in:
parent
06b4494200
commit
c59825f3a5
6 changed files with 31 additions and 19 deletions
|
@ -234,9 +234,9 @@ describe('Subscribers', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Sorts subscribers', () => {
|
it('Sorts subscribers', () => {
|
||||||
const asc = [3, 4, 5, 6, 7, 8];
|
let asc = [3, 4, 5, 6, 7, 8];
|
||||||
const desc = [8, 7, 6, 5, 4, 3];
|
let desc = [8, 7, 6, 5, 4, 3];
|
||||||
const cases = ['cy-status', 'cy-email', 'cy-name', 'cy-created_at', 'cy-updated_at'];
|
let cases = ['cy-email', 'cy-name', 'cy-created_at', 'cy-updated_at'];
|
||||||
|
|
||||||
cases.forEach((c) => {
|
cases.forEach((c) => {
|
||||||
cy.sortTable(`thead th.${c}`, asc);
|
cy.sortTable(`thead th.${c}`, asc);
|
||||||
|
@ -244,6 +244,19 @@ describe('Subscribers', () => {
|
||||||
cy.sortTable(`thead th.${c}`, desc);
|
cy.sortTable(`thead th.${c}`, desc);
|
||||||
cy.wait(250);
|
cy.wait(250);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
asc = [4, 6, 8, 3, 5, 7];
|
||||||
|
desc = [7, 5, 3, 8, 6, 4];
|
||||||
|
cases = ['cy-status'];
|
||||||
|
|
||||||
|
cases.forEach((c) => {
|
||||||
|
cy.sortTable(`thead th.${c}`, asc);
|
||||||
|
cy.wait(250);
|
||||||
|
cy.sortTable(`thead th.${c}`, desc);
|
||||||
|
cy.wait(250);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -339,7 +352,7 @@ describe('Domain blocklist', () => {
|
||||||
cy.get('.b-tabs nav a').eq(2).click();
|
cy.get('.b-tabs nav a').eq(2).click();
|
||||||
cy.get('textarea[name="privacy.domain_blocklist"]').clear();
|
cy.get('textarea[name="privacy.domain_blocklist"]').clear();
|
||||||
cy.get('[data-cy=btn-save]').click();
|
cy.get('[data-cy=btn-save]').click();
|
||||||
cy.wait(1000);
|
cy.wait(3000);
|
||||||
|
|
||||||
// Add banned domain.
|
// Add banned domain.
|
||||||
cy.request({
|
cy.request({
|
||||||
|
|
|
@ -24,7 +24,7 @@ const (
|
||||||
// QueryCampaigns retrieves paginated campaigns optionally filtering them by the given arbitrary
|
// QueryCampaigns retrieves paginated campaigns optionally filtering them by the given arbitrary
|
||||||
// query expression. It also returns the total number of records in the DB.
|
// query expression. It also returns the total number of records in the DB.
|
||||||
func (c *Core) QueryCampaigns(searchStr string, statuses []string, orderBy, order string, offset, limit int) (models.Campaigns, int, error) {
|
func (c *Core) QueryCampaigns(searchStr string, statuses []string, orderBy, order string, offset, limit int) (models.Campaigns, int, error) {
|
||||||
queryStr, stmt := makeSearchQuery(searchStr, orderBy, order, c.q.QueryCampaigns)
|
queryStr, stmt := makeSearchQuery(searchStr, orderBy, order, c.q.QueryCampaigns, campQuerySortFields)
|
||||||
|
|
||||||
if statuses == nil {
|
if statuses == nil {
|
||||||
statuses = []string{}
|
statuses = []string{}
|
||||||
|
|
|
@ -57,9 +57,11 @@ type Opt struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
regexFullTextQuery = regexp.MustCompile(`\s+`)
|
regexFullTextQuery = regexp.MustCompile(`\s+`)
|
||||||
regexpSpaces = regexp.MustCompile(`[\s]+`)
|
regexpSpaces = regexp.MustCompile(`[\s]+`)
|
||||||
querySortFields = []string{"name", "status", "created_at", "updated_at"}
|
campQuerySortFields = []string{"name", "status", "created_at", "updated_at"}
|
||||||
|
subQuerySortFields = []string{"email", "status", "name", "created_at", "updated_at"}
|
||||||
|
listQuerySortFields = []string{"name", "status", "created_at", "updated_at", "subscriber_count"}
|
||||||
)
|
)
|
||||||
|
|
||||||
// New returns a new instance of the core.
|
// New returns a new instance of the core.
|
||||||
|
@ -88,7 +90,7 @@ func pqErrMsg(err error) string {
|
||||||
// makeSearchQuery cleans an optional search string and prepares the
|
// makeSearchQuery cleans an optional search string and prepares the
|
||||||
// query SQL statement (string interpolated) and returns the
|
// query SQL statement (string interpolated) and returns the
|
||||||
// search query string along with the SQL expression.
|
// search query string along with the SQL expression.
|
||||||
func makeSearchQuery(searchStr, orderBy, order, query string) (string, string) {
|
func makeSearchQuery(searchStr, orderBy, order, query string, querySortFields []string) (string, string) {
|
||||||
if searchStr != "" {
|
if searchStr != "" {
|
||||||
searchStr = `%` + string(regexFullTextQuery.ReplaceAll([]byte(searchStr), []byte("&"))) + `%`
|
searchStr = `%` + string(regexFullTextQuery.ReplaceAll([]byte(searchStr), []byte("&"))) + `%`
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (c *Core) GetLists(typ string) ([]models.List, error) {
|
||||||
func (c *Core) QueryLists(searchStr, orderBy, order string, offset, limit int) ([]models.List, int, error) {
|
func (c *Core) QueryLists(searchStr, orderBy, order string, offset, limit int) ([]models.List, int, error) {
|
||||||
out := []models.List{}
|
out := []models.List{}
|
||||||
|
|
||||||
queryStr, stmt := makeSearchQuery(searchStr, orderBy, order, c.q.QueryLists)
|
queryStr, stmt := makeSearchQuery(searchStr, orderBy, order, c.q.QueryLists, listQuerySortFields)
|
||||||
|
|
||||||
if err := c.db.Select(&out, stmt, 0, "", queryStr, offset, limit); err != nil {
|
if err := c.db.Select(&out, stmt, 0, "", queryStr, offset, limit); err != nil {
|
||||||
c.log.Printf("error fetching lists: %v", err)
|
c.log.Printf("error fetching lists: %v", err)
|
||||||
|
@ -75,7 +75,7 @@ func (c *Core) GetList(id int, uuid string) (models.List, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var res []models.List
|
var res []models.List
|
||||||
queryStr, stmt := makeSearchQuery("", "", "", c.q.QueryLists)
|
queryStr, stmt := makeSearchQuery("", "", "", c.q.QueryLists, nil)
|
||||||
if err := c.db.Select(&res, stmt, id, uu, queryStr, 0, 1); err != nil {
|
if err := c.db.Select(&res, stmt, id, uu, queryStr, 0, 1); err != nil {
|
||||||
c.log.Printf("error fetching lists: %v", err)
|
c.log.Printf("error fetching lists: %v", err)
|
||||||
return models.List{}, echo.NewHTTPError(http.StatusInternalServerError,
|
return models.List{}, echo.NewHTTPError(http.StatusInternalServerError,
|
||||||
|
|
|
@ -14,10 +14,6 @@ import (
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
subQuerySortFields = []string{"email", "name", "created_at", "updated_at"}
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetSubscriber fetches a subscriber by one of the given params.
|
// GetSubscriber fetches a subscriber by one of the given params.
|
||||||
func (c *Core) GetSubscriber(id int, uuid, email string) (models.Subscriber, error) {
|
func (c *Core) GetSubscriber(id int, uuid, email string) (models.Subscriber, error) {
|
||||||
var uu interface{}
|
var uu interface{}
|
||||||
|
|
|
@ -416,15 +416,16 @@ WITH ls AS (
|
||||||
WHEN $3 != '' THEN to_tsvector(name) @@ to_tsquery ($3)
|
WHEN $3 != '' THEN to_tsvector(name) @@ to_tsquery ($3)
|
||||||
ELSE true
|
ELSE true
|
||||||
END
|
END
|
||||||
ORDER BY %order%
|
|
||||||
OFFSET $4 LIMIT (CASE WHEN $5 < 1 THEN NULL ELSE $5 END)
|
OFFSET $4 LIMIT (CASE WHEN $5 < 1 THEN NULL ELSE $5 END)
|
||||||
),
|
),
|
||||||
counts AS (
|
counts AS (
|
||||||
SELECT list_id, JSON_OBJECT_AGG(status, subscriber_count) AS subscriber_statuses FROM (
|
SELECT list_id, JSON_OBJECT_AGG(status, num) AS subscriber_statuses, SUM(num) AS subscriber_count
|
||||||
SELECT COUNT(*) as subscriber_count, list_id, status FROM subscriber_lists
|
FROM (
|
||||||
|
SELECT list_id, status, COUNT(*) as num
|
||||||
|
FROM subscriber_lists
|
||||||
WHERE ($1 = 0 OR list_id = $1)
|
WHERE ($1 = 0 OR list_id = $1)
|
||||||
GROUP BY list_id, status
|
GROUP BY list_id, status
|
||||||
) row GROUP BY list_id
|
) AS subquery GROUP BY list_id
|
||||||
)
|
)
|
||||||
SELECT ls.*, subscriber_statuses FROM ls
|
SELECT ls.*, subscriber_statuses FROM ls
|
||||||
LEFT JOIN counts ON (counts.list_id = ls.id) ORDER BY %order%;
|
LEFT JOIN counts ON (counts.list_id = ls.id) ORDER BY %order%;
|
||||||
|
|
Loading…
Reference in a new issue