-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support per-campaign autosend limit #1473
Merged
Merged
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
5ee50f0
feat(autosend): wip add per campaign autosend limit
ajohn25 970189d
chore: update migration date
bchrobot 2b81635
style: tidy
bchrobot a67a732
fix: update campaign view
bchrobot 0166777
chore: finish updateCampaignAutosendingLimit implementation
bchrobot c6bddab
feat: auto-save autosending limit
bchrobot 7b887fe
test: pull up setup steps
bchrobot 3a2c809
test: pull up checking task count
bchrobot 3c402c8
test: add tests for per-campaign limits
bchrobot a0dde07
fix: restrict sending if limit is set
bchrobot d6418ef
fix: pause campaigns when they reach their limit
bchrobot fe8f9d6
Merge branch 'main' into feat-autosend-limit
bchrobot d9fbb76
fix: use helper
bchrobot 7e449e7
feat: add feedback for mutation error
bchrobot 712c3b0
chore: tidy up
bchrobot 2aac286
chore: split query for readability
bchrobot a799bb9
chore: add safety check
bchrobot f524015
perf: use partial index
bchrobot ceedc4c
fix: return campaign record
bchrobot 583579a
fix: set lower bound on autosending limit
bchrobot 14691ab
fix: set field value correctly
bchrobot 2972d99
chore: use knex query builder for simple query
bchrobot e20eb3f
fix: fix return types
bchrobot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,295 @@ | ||
exports.up = function up(knex) { | ||
return knex.schema | ||
.alterTable("all_campaign", (table) => { | ||
table.integer("autosend_limit"); | ||
table.integer("autosend_limit_max_contact_id"); | ||
table | ||
.foreign("autosend_limit_max_contact_id") | ||
.references("campaign_contact.id"); | ||
}) | ||
.then(() => { | ||
return knex.schema.raw(` | ||
create or replace view campaign as | ||
select | ||
id, | ||
organization_id, | ||
title, | ||
description, | ||
is_started, | ||
due_by, | ||
created_at, | ||
is_archived, | ||
use_dynamic_assignment, | ||
logo_image_url, | ||
intro_html, | ||
primary_color, | ||
texting_hours_start, | ||
texting_hours_end, | ||
timezone, | ||
creator_id, | ||
is_autoassign_enabled, | ||
limit_assignment_to_teams, | ||
updated_at, | ||
replies_stale_after_minutes, | ||
landlines_filtered, | ||
external_system_id, | ||
is_approved, | ||
autosend_status, | ||
autosend_user_id, | ||
messaging_service_sid, | ||
autosend_limit, | ||
autosend_limit_max_contact_id | ||
from all_campaign | ||
where is_template = false; | ||
`); | ||
}); | ||
}; | ||
|
||
exports.down = function down(knex) { | ||
return knex.schema | ||
.raw( | ||
` | ||
drop view campaign cascade; | ||
create view campaign as | ||
select | ||
id, | ||
organization_id, | ||
title, | ||
description, | ||
is_started, | ||
due_by, | ||
created_at, | ||
is_archived, | ||
use_dynamic_assignment, | ||
logo_image_url, | ||
intro_html, | ||
primary_color, | ||
texting_hours_start, | ||
texting_hours_end, | ||
timezone, | ||
creator_id, | ||
is_autoassign_enabled, | ||
limit_assignment_to_teams, | ||
updated_at, | ||
replies_stale_after_minutes, | ||
landlines_filtered, | ||
external_system_id, | ||
is_approved, | ||
autosend_status, | ||
autosend_user_id, | ||
messaging_service_sid | ||
from all_campaign | ||
where is_template = false; | ||
|
||
create or replace view assignable_campaigns as ( | ||
select id, title, organization_id, limit_assignment_to_teams | ||
from campaign | ||
where is_started = true | ||
and is_archived = false | ||
and is_autoassign_enabled = true | ||
); | ||
|
||
create or replace view assignable_campaign_contacts as ( | ||
select | ||
campaign_contact.id, campaign_contact.campaign_id, | ||
campaign_contact.message_status, campaign.texting_hours_end, | ||
campaign_contact.timezone::text as contact_timezone | ||
from campaign_contact | ||
join campaign on campaign_contact.campaign_id = campaign.id | ||
where assignment_id is null | ||
and is_opted_out = false | ||
and archived = false | ||
and not exists ( | ||
select 1 | ||
from campaign_contact_tag | ||
join tag on campaign_contact_tag.tag_id = tag.id | ||
where tag.is_assignable = false | ||
and campaign_contact_tag.campaign_contact_id = campaign_contact.id | ||
) | ||
); | ||
|
||
create or replace view assignable_needs_message as ( | ||
select acc.id, acc.campaign_id, acc.message_status | ||
from assignable_campaign_contacts as acc | ||
join campaign on campaign.id = acc.campaign_id | ||
where message_status = 'needsMessage' | ||
and ( | ||
( acc.contact_timezone is null | ||
and extract(hour from CURRENT_TIMESTAMP at time zone campaign.timezone) < campaign.texting_hours_end | ||
and extract(hour from CURRENT_TIMESTAMP at time zone campaign.timezone) >= campaign.texting_hours_start | ||
) | ||
or | ||
( campaign.texting_hours_end > extract(hour from (CURRENT_TIMESTAMP at time zone acc.contact_timezone) + interval '10 minutes') | ||
and campaign.texting_hours_start <= extract(hour from (CURRENT_TIMESTAMP at time zone acc.contact_timezone)) | ||
) | ||
) | ||
); | ||
|
||
create or replace view assignable_campaigns_with_needs_message as ( | ||
select * | ||
from assignable_campaigns | ||
where | ||
exists ( | ||
select 1 | ||
from assignable_needs_message | ||
where campaign_id = assignable_campaigns.id | ||
) | ||
and not exists ( | ||
select 1 | ||
from campaign | ||
where campaign.id = assignable_campaigns.id | ||
and now() > date_trunc('day', (due_by + interval '24 hours') at time zone campaign.timezone) | ||
) | ||
); | ||
|
||
create or replace view assignable_needs_reply as ( | ||
select acc.id, acc.campaign_id, acc.message_status | ||
from assignable_campaign_contacts as acc | ||
join campaign on campaign.id = acc.campaign_id | ||
where message_status = 'needsResponse' | ||
and ( | ||
( acc.contact_timezone is null | ||
and extract(hour from CURRENT_TIMESTAMP at time zone campaign.timezone) < campaign.texting_hours_end | ||
and extract(hour from CURRENT_TIMESTAMP at time zone campaign.timezone) >= campaign.texting_hours_start | ||
) | ||
or | ||
( campaign.texting_hours_end > extract(hour from (CURRENT_TIMESTAMP at time zone acc.contact_timezone) + interval '2 minutes') | ||
and campaign.texting_hours_start <= extract(hour from (CURRENT_TIMESTAMP at time zone acc.contact_timezone)) | ||
) | ||
) | ||
); | ||
|
||
create or replace view assignable_campaigns_with_needs_reply as ( | ||
select * | ||
from assignable_campaigns | ||
where exists ( | ||
select 1 | ||
from assignable_needs_reply | ||
where campaign_id = assignable_campaigns.id | ||
) | ||
); | ||
|
||
create or replace view assignable_needs_reply_with_escalation_tags as ( | ||
select acc.id, acc.campaign_id, acc.message_status, acc.applied_escalation_tags | ||
from assignable_campaign_contacts_with_escalation_tags as acc | ||
join campaign on campaign.id = acc.campaign_id | ||
where message_status = 'needsResponse' | ||
and ( | ||
( acc.contact_timezone is null | ||
and extract(hour from CURRENT_TIMESTAMP at time zone campaign.timezone) < campaign.texting_hours_end | ||
and extract(hour from CURRENT_TIMESTAMP at time zone campaign.timezone) >= campaign.texting_hours_start | ||
) | ||
or | ||
( campaign.texting_hours_end > extract(hour from (CURRENT_TIMESTAMP at time zone acc.contact_timezone) + interval '2 minutes') | ||
and campaign.texting_hours_start <= extract(hour from (CURRENT_TIMESTAMP at time zone acc.contact_timezone)) | ||
) | ||
) | ||
); | ||
|
||
create or replace view public.missing_external_sync_question_response_configuration as | ||
select | ||
all_values.*, | ||
external_system.id as system_id | ||
from ( | ||
select | ||
istep.campaign_id, | ||
istep.parent_interaction_id as interaction_step_id, | ||
istep.answer_option as value, | ||
exists ( | ||
select 1 | ||
from public.question_response as istep_qr | ||
where | ||
istep_qr.interaction_step_id = istep.parent_interaction_id | ||
and istep_qr.value = istep.answer_option | ||
) as is_required | ||
from public.interaction_step istep | ||
where istep.parent_interaction_id is not null | ||
union | ||
select | ||
qr_istep.campaign_id, | ||
qr.interaction_step_id, | ||
qr.value, | ||
true as is_required | ||
from public.question_response as qr | ||
join public.interaction_step qr_istep on qr_istep.id = qr.interaction_step_id | ||
) all_values | ||
join campaign on campaign.id = all_values.campaign_id | ||
join external_system | ||
on external_system.organization_id = campaign.organization_id | ||
where | ||
not exists ( | ||
select 1 | ||
from public.all_external_sync_question_response_configuration aqrc | ||
where | ||
all_values.campaign_id = aqrc.campaign_id | ||
and external_system.id = aqrc.system_id | ||
and all_values.interaction_step_id = aqrc.interaction_step_id | ||
and all_values.value = aqrc.question_response_value | ||
); | ||
|
||
create or replace view sendable_campaigns as ( | ||
select id, title, organization_id, limit_assignment_to_teams, autosend_status, is_autoassign_enabled | ||
from campaign | ||
where is_started and not is_archived | ||
); | ||
|
||
create or replace view assignable_campaigns as ( | ||
select id, title, organization_id, limit_assignment_to_teams, autosend_status | ||
from sendable_campaigns | ||
where is_autoassign_enabled | ||
); | ||
|
||
create or replace view assignable_campaigns_with_needs_message as ( | ||
select * | ||
from assignable_campaigns | ||
where | ||
exists ( | ||
select 1 | ||
from assignable_needs_message | ||
where campaign_id = assignable_campaigns.id | ||
) | ||
and not exists ( | ||
select 1 | ||
from campaign | ||
where campaign.id = assignable_campaigns.id | ||
and now() > date_trunc('day', (due_by + interval '24 hours') at time zone campaign.timezone) | ||
) | ||
and autosend_status <> 'sending' | ||
); | ||
|
||
create or replace view assignable_campaigns_with_needs_reply as ( | ||
select * | ||
from assignable_campaigns | ||
where exists ( | ||
select 1 | ||
from assignable_needs_reply | ||
where campaign_id = assignable_campaigns.id | ||
) | ||
); | ||
|
||
create or replace view autosend_campaigns_to_send as ( | ||
select * | ||
from sendable_campaigns | ||
where | ||
exists ( -- assignable contacts are valid for both autoassign and autosending | ||
select 1 | ||
from assignable_needs_message | ||
where campaign_id = sendable_campaigns.id | ||
) | ||
and not exists ( | ||
select 1 | ||
from campaign | ||
where campaign.id = sendable_campaigns.id | ||
and now() > date_trunc('day', (due_by + interval '24 hours') at time zone campaign.timezone) | ||
) | ||
and autosend_status = 'sending' | ||
); | ||
` | ||
) | ||
.then(() => | ||
knex.schema.alterTable("all_campaign", (table) => { | ||
table.dropColumn("autosend_limit"); | ||
table.dropColumn("autosend_limit_max_contact_id"); | ||
}) | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment: I hate how much work this requires. abysmal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep :(
I would back a proposal for Rewired to sponsor development of an
alter view drop column
. There's been discussion about this since 2008.