+ ),
render: (text, record) => {
const out = []
out.push(
@@ -86,6 +124,14 @@ class Campaigns extends React.PureComponent {
dataIndex: "status",
className: "status",
width: "10%",
+ filters: [
+ { text: "Draft", value: "draft" },
+ { text: "Running", value: "running" },
+ { text: "Scheduled", value: "scheduled" },
+ { text: "Paused", value: "paused" },
+ { text: "Cancelled", value: "cancelled" },
+ { text: "Finished", value: "finished" }
+ ],
render: (status, record) => {
let color = cs.CampaignStatusColors.hasOwnProperty(status)
? cs.CampaignStatusColors[status]
@@ -415,14 +461,17 @@ class Campaigns extends React.PureComponent {
}
fetchRecords = params => {
+ if (!params) {
+ params = {}
+ }
let qParams = {
page: this.state.queryParams.page,
per_page: this.state.queryParams.per_page
}
- // The records are for a specific list.
- if (this.state.queryParams.listID) {
- qParams.listID = this.state.queryParams.listID
+ // Avoid sending blank string where the enum check will fail.
+ if (!params.status) {
+ delete params.status
}
if (params) {
@@ -437,6 +486,17 @@ class Campaigns extends React.PureComponent {
qParams
)
.then(r => {
+ this.setState({
+ queryParams: {
+ ...this.state.queryParams,
+ total: this.props.data[cs.ModelCampaigns].total,
+ per_page: this.props.data[cs.ModelCampaigns].per_page,
+ page: this.props.data[cs.ModelCampaigns].page,
+ query: this.props.data[cs.ModelCampaigns].query,
+ status: params.status
+ }
+ })
+
this.startStatsPoll()
})
}
@@ -447,7 +507,7 @@ class Campaigns extends React.PureComponent {
// If there's at least one running campaign, start polling.
let hasRunning = false
- this.props.data[cs.ModelCampaigns].forEach(c => {
+ this.props.data[cs.ModelCampaigns].results.forEach(c => {
if (c.status === cs.CampaignStatusRunning) {
hasRunning = true
return
@@ -605,12 +665,32 @@ class Campaigns extends React.PureComponent {
record.uuid}
- dataSource={this.props.data[cs.ModelCampaigns]}
+ dataSource={(() => {
+ if (
+ !this.props.data[cs.ModelCampaigns] ||
+ !this.props.data[cs.ModelCampaigns].hasOwnProperty("results")
+ ) {
+ return []
+ }
+ return this.props.data[cs.ModelCampaigns].results
+ })()}
loading={this.props.reqStates[cs.ModelCampaigns] !== cs.StateDone}
pagination={pagination}
+ onChange={(pagination, filters, sorter, records) => {
+ this.fetchRecords({
+ per_page: pagination.pageSize,
+ page: pagination.current,
+ status:
+ filters.status && filters.status.length > 0
+ ? filters.status
+ : "",
+ query:
+ filters.name && filters.name.length > 0 ? filters.name[0] : ""
+ })
+ }}
/>
{this.state.previewRecord && (
diff --git a/models/models.go b/models/models.go
index 53e1798..f008365 100644
--- a/models/models.go
+++ b/models/models.go
@@ -141,6 +141,10 @@ type Campaign struct {
// TemplateBody is joined in from templates by the next-campaigns query.
TemplateBody string `db:"template_body" json:"-"`
Tpl *template.Template `json:"-"`
+
+ // Pseudofield for getting the total number of subscribers
+ // in searches and queries.
+ Total int `db:"total" json:"-"`
}
// CampaignMeta contains fields tracking a campaign's progress.
diff --git a/queries.sql b/queries.sql
index 9edfbe3..288152f 100644
--- a/queries.sql
+++ b/queries.sql
@@ -255,8 +255,12 @@ INSERT INTO campaign_lists (campaign_id, list_id, list_name)
-- name: get-campaigns
-- Here, 'lists' is returned as an aggregated JSON array from campaign_lists because
-- the list reference may have been deleted.
+-- While the results are sliced using offset+limit,
+-- there's a COUNT() OVER() that still returns the total result count
+-- for pagination in the frontend, albeit being a field that'll repeat
+-- with every resultant row.
WITH camps AS (
- SELECT campaigns.*, (
+ SELECT COUNT(*) OVER () AS total, campaigns.*, (
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(l)), '[]') FROM (
SELECT COALESCE(campaign_lists.list_id, 0) AS id,
campaign_lists.list_name AS name
@@ -264,8 +268,10 @@ WITH camps AS (
) l
) AS lists
FROM campaigns
- WHERE ($1 = 0 OR id = $1) AND status=(CASE WHEN $2 != '' THEN $2::campaign_status ELSE status END)
- ORDER BY created_at DESC OFFSET $3 LIMIT $4
+ WHERE ($1 = 0 OR id = $1)
+ AND status=ANY(CASE WHEN ARRAY_LENGTH($2::campaign_status[], 1) != 0 THEN $2::campaign_status[] ELSE ARRAY[status] END)
+ AND ($3 = '' OR (to_tsvector(name || subject) @@ to_tsquery($3)))
+ ORDER BY created_at DESC OFFSET $4 LIMIT $5
), views AS (
SELECT campaign_id, COUNT(campaign_id) as num FROM campaign_views
WHERE campaign_id = ANY(SELECT id FROM camps)
@@ -281,7 +287,8 @@ SELECT *,
COALESCE(c.num, 0) AS clicks
FROM camps
LEFT JOIN views AS v ON (v.campaign_id = camps.id)
-LEFT JOIN clicks AS c ON (c.campaign_id = camps.id);
+LEFT JOIN clicks AS c ON (c.campaign_id = camps.id)
+ORDER BY camps.created_at DESC;
-- name: get-campaign-for-preview
SELECT campaigns.*, COALESCE(templates.body, (SELECT body FROM templates WHERE is_default = true LIMIT 1)) AS template_body,