Merge pull request #2264 from knadh/edit-campaign

Refactor campaign editing and status management.

- Add `Unschedule` button.
- Add `Start` button on paused campaigns.
- Make paused campaigns editable.
- Remove redundant API and query logic.
This commit is contained in:
Kailash Nadh 2025-01-19 16:42:16 +05:30 committed by GitHub
commit 0e49a4bb7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 77 additions and 30 deletions

View file

@ -24,9 +24,6 @@ import (
type campaignReq struct {
models.Campaign
// Indicates if the "send_at" date should be written or set to null.
SendLater bool `json:"send_later"`
// This overrides Campaign.Lists to receive and
// write a list of int IDs during creation and updation.
// Campaign.Lists is JSONText for sending lists children
@ -251,7 +248,7 @@ func handleUpdateCampaign(c echo.Context) error {
return err
}
if isCampaignalMutable(cm.Status) {
if !canEditCampaign(cm.Status) {
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("campaigns.cantUpdate"))
}
@ -269,7 +266,7 @@ func handleUpdateCampaign(c echo.Context) error {
o = c
}
out, err := app.core.UpdateCampaign(id, o.Campaign, o.ListIDs, o.MediaIDs, o.SendLater)
out, err := app.core.UpdateCampaign(id, o.Campaign, o.ListIDs, o.MediaIDs)
if err != nil {
return err
}
@ -600,12 +597,12 @@ func validateCampaignFields(c campaignReq, app *App) (campaignReq, error) {
return c, nil
}
// isCampaignalMutable tells if a campaign's in a state where it's
// properties can be mutated.
func isCampaignalMutable(status string) bool {
return status == models.CampaignStatusRunning ||
status == models.CampaignStatusCancelled ||
status == models.CampaignStatusFinished
// canEditCampaign returns true if a campaign is in a status where updating
// its properties is allowed.
func canEditCampaign(status string) bool {
return status == models.CampaignStatusDraft ||
status == models.CampaignStatusPaused ||
status == models.CampaignStatusScheduled
}
// makeOptinCampaignMessage makes a default opt-in campaign message body.

View file

@ -43,6 +43,12 @@
{{ $t('campaigns.schedule') }}
</b-button>
</b-field>
<b-field expanded v-if="canUnSchedule">
<b-button expanded @click="unscheduleCampaign" :loading="loading.campaigns" type="is-primary"
icon-left="clock-start" data-cy="btn-unschedule">
{{ $t('campaigns.unSchedule') }}
</b-button>
</b-field>
</b-field>
</div>
</div>
@ -497,7 +503,6 @@ export default Vue.extend({
messenger: this.form.messenger,
type: 'regular',
tags: this.form.tags,
send_later: this.form.sendLater,
send_at: this.form.sendLater ? this.form.sendAtDate : null,
headers: this.form.headers,
template_id: this.form.templateId,
@ -521,7 +526,6 @@ export default Vue.extend({
messenger: this.form.messenger,
type: 'regular',
tags: this.form.tags,
send_later: this.form.sendLater,
send_at: this.form.sendLater ? this.form.sendAtDate : null,
headers: this.form.headers,
template_id: this.form.templateId,
@ -595,6 +599,13 @@ export default Vue.extend({
},
);
},
unscheduleCampaign() {
this.$api.changeCampaignStatus(this.data.id, 'draft').then((d) => {
this.data = d;
this.form.archiveSlug = d.archiveSlug;
});
},
},
computed: {
@ -602,15 +613,19 @@ export default Vue.extend({
canEdit() {
return this.isNew
|| this.data.status === 'draft' || this.data.status === 'scheduled';
|| this.data.status === 'draft' || this.data.status === 'scheduled' || this.data.status === 'paused';
},
canSchedule() {
return this.data.status === 'draft' && this.data.sendAt;
},
canUnSchedule() {
return this.data.status === 'scheduled' && this.data.sendAt;
},
canStart() {
return this.data.status === 'draft' && !this.data.sendAt;
return this.data.status === 'draft' || this.data.status === 'paused';
},
canArchive() {

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "S'ha enviat el missatge de prova",
"campaigns.timestamps": "Segells de temps",
"campaigns.trackLink": "Enllaç de seguiment",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Visualitzacions",
"dashboard.campaignViews": "Visualitzacions de la campanya",
"dashboard.linkClicks": "Clics a enllaços",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Testovací zpráva odeslána",
"campaigns.timestamps": "Časová razítka",
"campaigns.trackLink": "Sledovací odkaz",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Pohledy",
"dashboard.campaignViews": "Pohledy na kampaň",
"dashboard.linkClicks": "Klepnutí na odkaz",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Wedi anfon neges brawf",
"campaigns.timestamps": "Stamp amser",
"campaigns.trackLink": "Olrhain dolen",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Nifer y bobl sydd wedi'i gweld",
"dashboard.campaignViews": "Nifer y bobl sydd wedi gweld yr ymgyrch",
"dashboard.linkClicks": "Nifer y bobl sydd wedi clicio'r ddolen",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Testmeddelelse sendt",
"campaigns.timestamps": "Tidsstempler",
"campaigns.trackLink": "Link til spor",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Udsigt over",
"dashboard.campaignViews": "Kampagnevisninger",
"dashboard.linkClicks": "Klik på link",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Testnachricht gesendet",
"campaigns.timestamps": "Zeitstempel",
"campaigns.trackLink": "Track Link",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Ansichten",
"dashboard.campaignViews": "Kampagnenansichten",
"dashboard.linkClicks": "Linkklicks",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Το δοκιμαστικό μήνυμα στάλθηκε",
"campaigns.timestamps": "Χρονοσήματα",
"campaigns.trackLink": "Σύνδεσμος παρακολούθησης",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Προβολές",
"dashboard.campaignViews": "Προβολές εκστρατειών",
"dashboard.linkClicks": "Κλικ συνδέσμων",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Test message sent",
"campaigns.timestamps": "Timestamps",
"campaigns.trackLink": "Track link",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Views",
"dashboard.campaignViews": "Campaign views",
"dashboard.linkClicks": "Link clicks",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "S'ha enviat el missatge de prova",
"campaigns.timestamps": "Segells de temps",
"campaigns.trackLink": "Enllaç de seguiment",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Visualitzacions",
"dashboard.campaignViews": "Visualitzacions de la campanya",
"dashboard.linkClicks": "Clics a enllaços",

View file

@ -99,6 +99,7 @@
"campaigns.testSent": "Mensaje de prueba enviado",
"campaigns.timestamps": "Marcas de tiempo",
"campaigns.trackLink": "Enlace de rastreo (Track link)",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Vistas",
"dashboard.campaignViews": "Vista de campaña",
"dashboard.linkClicks": "Enlaces cliqueados",

View file

@ -99,6 +99,7 @@
"campaigns.testSent": "Testiviesti lähetetty",
"campaigns.timestamps": "Aikaleimat",
"campaigns.trackLink": "Seuraa linkkejä",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Katselukerrat",
"dashboard.campaignViews": "Kampanjan katselukerrat",
"dashboard.linkClicks": "Linkkiklikkaukset",

View file

@ -99,6 +99,7 @@
"campaigns.testSent": "Message de test envoyé",
"campaigns.timestamps": "Horodatages",
"campaigns.trackLink": "Lien de suivi",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Vues",
"dashboard.campaignViews": "vues de campagne",
"dashboard.linkClicks": "clics sur liens",

View file

@ -99,6 +99,7 @@
"campaigns.testSent": "Message de test envoyé",
"campaigns.timestamps": "Horodatages",
"campaigns.trackLink": "Lien de suivi",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Vues",
"dashboard.campaignViews": "vues de campagne",
"dashboard.linkClicks": "clics sur liens",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "הודעת בדיקה נשלחה",
"campaigns.timestamps": "חותמות זמן",
"campaigns.trackLink": "קישור מעקב",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "צפיות",
"dashboard.campaignViews": "צפיות בקמפיין",
"dashboard.linkClicks": "לחיצות על קישורים",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Tesztüzenet elküldve",
"campaigns.timestamps": "Időbélyegek",
"campaigns.trackLink": "Nyomkövetőhivatkozás",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Megtekintések",
"dashboard.campaignViews": "Megtekintések",
"dashboard.linkClicks": "Kattintások",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Messaggio di prova inviato",
"campaigns.timestamps": "Marcatura temporale ",
"campaigns.trackLink": "Link di tracciamento",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Visualizzazioni",
"dashboard.campaignViews": "Visualizzazioni della campagna",
"dashboard.linkClicks": "Clic sui link",

View file

@ -99,6 +99,7 @@
"campaigns.testSent": "テストメッセージ送信済み",
"campaigns.timestamps": "タイムスタンプ",
"campaigns.trackLink": "リンクの追跡",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "ビュー",
"dashboard.campaignViews": "キャンペーンビュー",
"dashboard.linkClicks": "リンクのクリック",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "പരീക്ഷണ സന്ദേശം അയച്ചു",
"campaigns.timestamps": "ടൈംസ്റ്റാമ്പുകൾ",
"campaigns.trackLink": "ട്രാക്ക് ലിങ്ക്",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "കാഴ്ചകൾ",
"dashboard.campaignViews": "ക്യാമ്പേയ്ൻ കാഴ്ചകൾ",
"dashboard.linkClicks": "ലിങ്ക് ക്ലിക്കുകൾ",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Testbericht verzonden",
"campaigns.timestamps": "Tijdstippen",
"campaigns.trackLink": "Traceerbare link",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Bekeken",
"dashboard.campaignViews": "Campagneviews",
"dashboard.linkClicks": "Linkkliks",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Wiadomość testowa wysłana",
"campaigns.timestamps": "Sygnatury czasowe",
"campaigns.trackLink": "Link śledzący",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Wyświetlenia",
"dashboard.campaignViews": "Wyświetlenia kampanii",
"dashboard.linkClicks": "Kliknięcia linków",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Mensagem de teste enviada",
"campaigns.timestamps": "Data e hora",
"campaigns.trackLink": "Link de rastreamento",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Visualizações",
"dashboard.campaignViews": "Visualizações da campanha",
"dashboard.linkClicks": "Links clicados",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Mensagem de teste enviada",
"campaigns.timestamps": "Carimbo de hora",
"campaigns.trackLink": "Link de rastreamento",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Visualizações",
"dashboard.campaignViews": "Vista de campanhas",
"dashboard.linkClicks": "Cliques nos links",

View file

@ -99,6 +99,7 @@
"campaigns.testSent": "Mesaj de testare trimis",
"campaigns.timestamps": "Marcajele",
"campaigns.trackLink": "Track link-ul",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Vizualizări",
"dashboard.campaignViews": "Vizualizările campaniei",
"dashboard.linkClicks": "Clicuri pe link",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Тестовое сообщение отправлено",
"campaigns.timestamps": "Метки времени",
"campaigns.trackLink": "Ссылка на трек",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Просмотры",
"dashboard.campaignViews": "Просмотров кампаний",
"dashboard.linkClicks": "Кликов по ссылкам",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Testmeddelande skickat",
"campaigns.timestamps": "Tidsstämplar",
"campaigns.trackLink": "Spåra länk",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Visningar",
"dashboard.campaignViews": "Visningar av kampanjer",
"dashboard.linkClicks": "Länkklickar",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Testovacia správa odoslaná",
"campaigns.timestamps": "Časové razítka",
"campaigns.trackLink": "Sledovací odkaz",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Zobrazenia",
"dashboard.campaignViews": "Zobrazenia kampane",
"dashboard.linkClicks": "Kliknutia na odkaz",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Poslano testno sporočilo",
"campaigns.timestamps": "Časovni žigi",
"campaigns.trackLink": "Sledenje povezavi",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Ogledi",
"dashboard.campaignViews": "Ogledi oglaševalske akcije",
"dashboard.linkClicks": "Kliki povezav",

View file

@ -99,6 +99,7 @@
"campaigns.testSent": "Test mesajı gönderildi",
"campaigns.timestamps": "Zaman etiketi",
"campaigns.trackLink": "İzleme bağlantısı",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Görüntülenme",
"dashboard.campaignViews": "Kampanya görüntülenme Sayısı",
"dashboard.linkClicks": "Linklerin tıklanması",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "Пробний лист надіслано",
"campaigns.timestamps": "Історія",
"campaigns.trackLink": "Відстежувати посилання",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Перегляди",
"dashboard.campaignViews": "Перегляди кампаній",
"dashboard.linkClicks": "Переходи за посиланнями",

View file

@ -99,6 +99,7 @@
"campaigns.testSent": "Gửi tin nhắn thử",
"campaigns.timestamps": "Dấu thời gian",
"campaigns.trackLink": "Theo dõi liên kết",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "Lượt xem",
"dashboard.campaignViews": "Chế độ xem chiến dịch",
"dashboard.linkClicks": "Liên kết nhấp chuột",

View file

@ -98,6 +98,7 @@
"campaigns.testSent": "已发送测试消息",
"campaigns.timestamps": "时间戳",
"campaigns.trackLink": "跟踪链接",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "视图",
"dashboard.campaignViews": "广告系列视图",
"dashboard.linkClicks": "链接点击次数",

View file

@ -99,6 +99,7 @@
"campaigns.testSent": "測試電子郵件已寄送",
"campaigns.timestamps": "時間戳記",
"campaigns.trackLink": "追蹤連結",
"campaigns.unSchedule": "Unschedule",
"campaigns.views": "開信",
"dashboard.campaignViews": "活動開信",
"dashboard.linkClicks": "連結點擊次數",

View file

@ -208,7 +208,7 @@ func (c *Core) CreateCampaign(o models.Campaign, listIDs []int, mediaIDs []int)
}
// UpdateCampaign updates a campaign.
func (c *Core) UpdateCampaign(id int, o models.Campaign, listIDs []int, mediaIDs []int, sendLater bool) (models.Campaign, error) {
func (c *Core) UpdateCampaign(id int, o models.Campaign, listIDs []int, mediaIDs []int) (models.Campaign, error) {
_, err := c.q.UpdateCampaign.Exec(id,
o.Name,
o.Subject,
@ -217,7 +217,6 @@ func (c *Core) UpdateCampaign(id int, o models.Campaign, listIDs []int, mediaIDs
o.AltBody,
o.ContentType,
o.SendAt,
sendLater,
o.Headers,
pq.StringArray(normalizeTags(o.Tags)),
o.Messenger,

View file

@ -830,33 +830,38 @@ WITH camp AS (
altbody=(CASE WHEN $6 = '' THEN NULL ELSE $6 END),
content_type=$7::content_type,
send_at=$8::TIMESTAMP WITH TIME ZONE,
status=(CASE WHEN NOT $9 THEN 'draft' ELSE status END),
headers=$10,
tags=$11::VARCHAR(100)[],
messenger=$12,
template_id=$13,
archive=$15,
archive_slug=$16,
archive_template_id=$17,
archive_meta=$18,
status=(
CASE
WHEN status = 'scheduled' AND $8 IS NULL THEN 'draft'
ELSE status
END
),
headers=$9,
tags=$10::VARCHAR(100)[],
messenger=$11,
template_id=$12,
archive=$14,
archive_slug=$15,
archive_template_id=$16,
archive_meta=$17,
updated_at=NOW()
WHERE id = $1 RETURNING id
),
clists AS (
-- Reset list relationships
DELETE FROM campaign_lists WHERE campaign_id = $1 AND NOT(list_id = ANY($14))
DELETE FROM campaign_lists WHERE campaign_id = $1 AND NOT(list_id = ANY($13))
),
med AS (
DELETE FROM campaign_media WHERE campaign_id = $1
AND ( media_id IS NULL or NOT(media_id = ANY($19))) RETURNING media_id
AND ( media_id IS NULL or NOT(media_id = ANY($18))) RETURNING media_id
),
medi AS (
INSERT INTO campaign_media (campaign_id, media_id, filename)
(SELECT $1 AS campaign_id, id, filename FROM media WHERE id=ANY($19::INT[]))
(SELECT $1 AS campaign_id, id, filename FROM media WHERE id=ANY($18::INT[]))
ON CONFLICT (campaign_id, media_id) DO NOTHING
)
INSERT INTO campaign_lists (campaign_id, list_id, list_name)
(SELECT $1 as campaign_id, id, name FROM lists WHERE id=ANY($14::INT[]))
(SELECT $1 as campaign_id, id, name FROM lists WHERE id=ANY($13::INT[]))
ON CONFLICT (campaign_id, list_id) DO UPDATE SET list_name = EXCLUDED.list_name;
-- name: update-campaign-counts