mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-11 10:28:41 -04:00
Add a component for picking the time to send a job
Users need to pick a time in the next 24hrs, or send a file immediately. Rationale for this is a bit lost in time-before-holiday, but generally: ‘Now’ and ‘later’ as the inital choices makes it really clear what this feature is about conceptually. The choice of times is absolute, eg ‘1pm’ not ‘in 3 hours’
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
(function(Modules) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var render = ($options, $button) => (
|
||||||
|
filterOptionVisibility($options) && setButtonState($options, $button)
|
||||||
|
);
|
||||||
|
|
||||||
|
var filterOptionVisibility = $options => $options
|
||||||
|
.removeClass('js-visible')
|
||||||
|
.filter(
|
||||||
|
(index, element) => (index === 0 || $(element).has(':checked').length)
|
||||||
|
)
|
||||||
|
.addClass('js-visible');
|
||||||
|
|
||||||
|
var setButtonState = ($options, $button) => $button
|
||||||
|
.addClass('js-visible')
|
||||||
|
.prop(
|
||||||
|
'value',
|
||||||
|
$options.has(':checked').find('input').attr('id') === $options.eq(0).find('input').attr('id') ?
|
||||||
|
'Later' : 'Choose a different time'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Workaround because GOV.UK SelectionButtons doesn’t deselect in this case
|
||||||
|
var deselectUnchecked = $options => $options
|
||||||
|
.filter(
|
||||||
|
(index, element) => $(element).not(':has(:checked)')
|
||||||
|
).removeClass('selected');
|
||||||
|
|
||||||
|
var refocus = $element => setTimeout(
|
||||||
|
() => $element.blur().trigger('focus'),
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
var renderIfComponentLosesFocus = ($options, $button, $focused) => () =>
|
||||||
|
($focused.attr('type') !== 'radio') &&
|
||||||
|
render($options, $button) &&
|
||||||
|
refocus($focused); // Make sure that window scrolls to focused element
|
||||||
|
|
||||||
|
Modules.RadioSelect = function() {
|
||||||
|
|
||||||
|
this.start = function(component) {
|
||||||
|
|
||||||
|
let $component = $(component);
|
||||||
|
let $options = $('label', $component);
|
||||||
|
|
||||||
|
$component.append(
|
||||||
|
$button = $('<input type="button" value="Later" class="tertiary-button" />')
|
||||||
|
);
|
||||||
|
|
||||||
|
$button.on('click', () =>
|
||||||
|
$options.addClass('js-visible').has(':checked').focus() &&
|
||||||
|
$button.removeClass('js-visible')
|
||||||
|
);
|
||||||
|
|
||||||
|
$component.on('keydown', 'input[type=radio]', function() {
|
||||||
|
|
||||||
|
// intercept keypresses which aren’t enter or space
|
||||||
|
if (event.which !== 13 && event.which !== 32) {
|
||||||
|
setTimeout(
|
||||||
|
renderIfComponentLosesFocus($options, $button, $(document.activeElement)),
|
||||||
|
200
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
render($options, $button);
|
||||||
|
refocus($(this));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$component.on('click', 'input[type=radio]', function(event) {
|
||||||
|
|
||||||
|
deselectUnchecked($options);
|
||||||
|
|
||||||
|
// stop click being triggered by keyboard events
|
||||||
|
if (!event.pageX) return true;
|
||||||
|
|
||||||
|
render($options, $button);
|
||||||
|
refocus($(this));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
render($options, $button);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
})(window.GOVUK.Modules);
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
.radio-select {
|
||||||
|
|
||||||
|
&-column {
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
.block-label {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tertiary-button {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
width: auto;
|
||||||
|
padding: 20px 30px 15px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-enabled & {
|
||||||
|
|
||||||
|
|
||||||
|
.block-label {
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-label,
|
||||||
|
.tertiary-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-visible {
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&.tertiary-button {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
.sms-message-recipient {
|
.sms-message-recipient {
|
||||||
@include copy-19;
|
@include copy-19;
|
||||||
color: $secondary-text-colour;
|
color: $secondary-text-colour;
|
||||||
margin: 10px 0 5px 0;
|
margin: 10px 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sms-message-name {
|
.sms-message-name {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ $path: '/static/images/';
|
|||||||
@import 'components/email-message';
|
@import 'components/email-message';
|
||||||
@import 'components/api-key';
|
@import 'components/api-key';
|
||||||
@import 'components/vendor/previous-next-navigation';
|
@import 'components/vendor/previous-next-navigation';
|
||||||
|
@import 'components/radio-select';
|
||||||
@import 'components/pill';
|
@import 'components/pill';
|
||||||
@import 'components/secondary-button';
|
@import 'components/secondary-button';
|
||||||
@import 'components/show-more';
|
@import 'components/show-more';
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import pytz
|
||||||
from flask_wtf import Form
|
from flask_wtf import Form
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from notifications_utils.recipients import (
|
from notifications_utils.recipients import (
|
||||||
validate_phone_number,
|
validate_phone_number,
|
||||||
InvalidPhoneError
|
InvalidPhoneError
|
||||||
@@ -22,6 +24,30 @@ from app.main.validators import (Blacklist, CsvFileValidator, ValidEmailDomainRe
|
|||||||
from app.notify_client.api_key_api_client import KEY_TYPE_NORMAL, KEY_TYPE_TEST, KEY_TYPE_TEAM
|
from app.notify_client.api_key_api_client import KEY_TYPE_NORMAL, KEY_TYPE_TEST, KEY_TYPE_TEAM
|
||||||
|
|
||||||
|
|
||||||
|
def get_time_value_and_label(future_time):
|
||||||
|
return (
|
||||||
|
future_time.replace(tzinfo=None).isoformat(),
|
||||||
|
get_human_time(future_time.astimezone(pytz.timezone('Europe/London')))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_human_time(time):
|
||||||
|
return {
|
||||||
|
'0': 'Midnight',
|
||||||
|
'12': 'Midday'
|
||||||
|
}.get(
|
||||||
|
time.strftime('%-H'),
|
||||||
|
time.strftime('%-I%p').lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_next_hours_from(now, hours=23):
|
||||||
|
return [
|
||||||
|
(now + timedelta(hours=i)).replace(minute=0, second=0).replace(tzinfo=pytz.utc)
|
||||||
|
for i in range(1, hours + 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def email_address(label='Email address'):
|
def email_address(label='Email address'):
|
||||||
return EmailField(label, validators=[
|
return EmailField(label, validators=[
|
||||||
Length(min=5, max=255),
|
Length(min=5, max=255),
|
||||||
@@ -288,6 +314,23 @@ class ConfirmMobileNumberForm(Form):
|
|||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class ChooseTimeForm(Form):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ChooseTimeForm, self).__init__(*args, **kwargs)
|
||||||
|
self.scheduled_for.choices = [('', 'Now')] + [
|
||||||
|
get_time_value_and_label(hour) for hour in get_next_hours_from(datetime.utcnow())
|
||||||
|
]
|
||||||
|
|
||||||
|
scheduled_for = RadioField(
|
||||||
|
'When should Notify send these messages?',
|
||||||
|
default='',
|
||||||
|
validators=[
|
||||||
|
DataRequired()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CreateKeyForm(Form):
|
class CreateKeyForm(Form):
|
||||||
def __init__(self, existing_key_names=[], *args, **kwargs):
|
def __init__(self, existing_key_names=[], *args, **kwargs):
|
||||||
self.existing_key_names = [x.lower() for x in existing_key_names]
|
self.existing_key_names = [x.lower() for x in existing_key_names]
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ def cookies():
|
|||||||
|
|
||||||
@main.route('/trial-mode')
|
@main.route('/trial-mode')
|
||||||
def trial_mode():
|
def trial_mode():
|
||||||
return render_template('views/trial-mode.html')
|
return render_template('views/trial-mode.html', hours=hours)
|
||||||
|
|
||||||
|
|
||||||
@main.route('/pricing')
|
@main.route('/pricing')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import itertools
|
import itertools
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from string import ascii_uppercase
|
from string import ascii_uppercase
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
@@ -23,7 +23,7 @@ from notifications_utils.template import Template
|
|||||||
from notifications_utils.recipients import RecipientCSV, first_column_heading, validate_and_format_phone_number
|
from notifications_utils.recipients import RecipientCSV, first_column_heading, validate_and_format_phone_number
|
||||||
|
|
||||||
from app.main import main
|
from app.main import main
|
||||||
from app.main.forms import CsvUploadForm
|
from app.main.forms import CsvUploadForm, ChooseTimeForm
|
||||||
from app.main.uploader import (
|
from app.main.uploader import (
|
||||||
s3upload,
|
s3upload,
|
||||||
s3download
|
s3download
|
||||||
@@ -276,6 +276,7 @@ def check_messages(service_id, template_type, upload_id):
|
|||||||
upload_id=upload_id,
|
upload_id=upload_id,
|
||||||
form=CsvUploadForm(),
|
form=CsvUploadForm(),
|
||||||
remaining_messages=remaining_messages,
|
remaining_messages=remaining_messages,
|
||||||
|
choose_time_form=ChooseTimeForm(),
|
||||||
back_link=back_link,
|
back_link=back_link,
|
||||||
help=get_help_argument()
|
help=get_help_argument()
|
||||||
)
|
)
|
||||||
@@ -310,7 +311,8 @@ def start_job(service_id, upload_id):
|
|||||||
service_id,
|
service_id,
|
||||||
upload_data.get('template_id'),
|
upload_data.get('template_id'),
|
||||||
upload_data.get('original_file_name'),
|
upload_data.get('original_file_name'),
|
||||||
upload_data.get('notification_count')
|
upload_data.get('notification_count'),
|
||||||
|
scheduled_for=request.form.get('scheduled_for', '')
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect(
|
return redirect(
|
||||||
|
|||||||
@@ -58,13 +58,15 @@ class JobApiClient(BaseAPIClient):
|
|||||||
|
|
||||||
return jobs
|
return jobs
|
||||||
|
|
||||||
def create_job(self, job_id, service_id, template_id, original_file_name, notification_count):
|
def create_job(self, job_id, service_id, template_id, original_file_name, notification_count, scheduled_for=None):
|
||||||
data = {
|
data = {
|
||||||
"id": job_id,
|
"id": job_id,
|
||||||
"template": template_id,
|
"template": template_id,
|
||||||
"original_file_name": original_file_name,
|
"original_file_name": original_file_name,
|
||||||
"notification_count": notification_count
|
"notification_count": notification_count
|
||||||
}
|
}
|
||||||
|
if scheduled_for:
|
||||||
|
data.update({'scheduled_for': scheduled_for})
|
||||||
data = _attach_current_user(data)
|
data = _attach_current_user(data)
|
||||||
job = self.post(url='/service/{}/job'.format(service_id), data=data)
|
job = self.post(url='/service/{}/job'.format(service_id), data=data)
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,40 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{% macro radio_select(
|
||||||
|
field,
|
||||||
|
hint=None,
|
||||||
|
wrapping_class='form-group'
|
||||||
|
) %}
|
||||||
|
<div class="{{ wrapping_class }} {% if field.errors %} error{% endif %}">
|
||||||
|
<fieldset>
|
||||||
|
<legend class="form-label">
|
||||||
|
{{ field.label }}
|
||||||
|
{% if field.errors %}
|
||||||
|
<span class="error-message">
|
||||||
|
{{ field.errors[0] }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</legend>
|
||||||
|
<div class="radio-select" data-module="radio-select">
|
||||||
|
<div class="radio-select-column">
|
||||||
|
{% for option in field %}
|
||||||
|
<label class="block-label" for="{{ option.id }}">
|
||||||
|
{{ option }}
|
||||||
|
{{ option.label.text }}
|
||||||
|
</label>
|
||||||
|
{% if loop.first %}
|
||||||
|
</div>
|
||||||
|
<div class="radio-select-column">
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
{% macro branding_radios(
|
{% macro branding_radios(
|
||||||
field,
|
field,
|
||||||
hint=None,
|
hint=None,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{% extends "withnav_template.html" %}
|
{% extends "withnav_template.html" %}
|
||||||
{% from "components/banner.html" import banner_wrapper %}
|
{% from "components/banner.html" import banner_wrapper %}
|
||||||
{% from "components/email-message.html" import email_message %}
|
{% from "components/email-message.html" import email_message %}
|
||||||
|
{% from "components/radios.html" import radio_select %}
|
||||||
{% from "components/sms-message.html" import sms_message %}
|
{% from "components/sms-message.html" import sms_message %}
|
||||||
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
|
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
|
||||||
{% from "components/file-upload.html" import file_upload %}
|
{% from "components/file-upload.html" import file_upload %}
|
||||||
@@ -144,6 +145,10 @@
|
|||||||
<form method="post" enctype="multipart/form-data" action="{{url_for('main.start_job', service_id=current_service.id, upload_id=upload_id)}}" class='page-footer'>
|
<form method="post" enctype="multipart/form-data" action="{{url_for('main.start_job', service_id=current_service.id, upload_id=upload_id)}}" class='page-footer'>
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<input type="hidden" name="help" value="{{ '3' if help else 0 }}" />
|
<input type="hidden" name="help" value="{{ '3' if help else 0 }}" />
|
||||||
|
{{ radio_select(
|
||||||
|
choose_time_form.scheduled_for,
|
||||||
|
wrapping_class='bottom-gutter-2-3'
|
||||||
|
) }}
|
||||||
<input type="submit" class="button" value="Send {{ count_of_recipients }} {{ message_count_label(count_of_recipients, template.template_type, suffix='') }}" />
|
<input type="submit" class="button" value="Send {{ count_of_recipients }} {{ message_count_label(count_of_recipients, template.template_type, suffix='') }}" />
|
||||||
<a href="{{ back_link }}" class="page-footer-back-link">Back</a>
|
<a href="{{ back_link }}" class="page-footer-back-link">Back</a>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
<div class="grid-row">
|
<div class="grid-row">
|
||||||
<div class="column-two-thirds">
|
<div class="column-two-thirds">
|
||||||
<h1 class="heading-large">Trial mode</h1>
|
<h1 class="heading-large">Trial mode</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
All new accounts on Notify start off in trial mode.
|
All new accounts on Notify start off in trial mode.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
+2
-1
@@ -60,8 +60,9 @@ gulp.task('javascripts', () => gulp
|
|||||||
paths.src + 'javascripts/autofocus.js',
|
paths.src + 'javascripts/autofocus.js',
|
||||||
paths.src + 'javascripts/highlightTags.js',
|
paths.src + 'javascripts/highlightTags.js',
|
||||||
paths.src + 'javascripts/fileUpload.js',
|
paths.src + 'javascripts/fileUpload.js',
|
||||||
paths.src + 'javascripts/updateContent.js',
|
|
||||||
paths.src + 'javascripts/expandCollapse.js',
|
paths.src + 'javascripts/expandCollapse.js',
|
||||||
|
paths.src + 'javascripts/radioSelect.js',
|
||||||
|
paths.src + 'javascripts/updateContent.js',
|
||||||
paths.src + 'javascripts/main.js'
|
paths.src + 'javascripts/main.js'
|
||||||
])
|
])
|
||||||
.pipe(plugins.babel({
|
.pipe(plugins.babel({
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.main.forms import ChooseTimeForm
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2016-01-01 11:09:00.061258")
|
||||||
|
def test_form_contains_next_24h(app_):
|
||||||
|
|
||||||
|
choices = ChooseTimeForm().scheduled_for.choices
|
||||||
|
|
||||||
|
assert choices[0] == ('', 'Now')
|
||||||
|
assert choices[1] == ('2016-01-01T12:00:00.061258', 'Midday')
|
||||||
|
assert choices[23] == ('2016-01-02T10:00:00.061258', '10am')
|
||||||
|
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
assert choices[24]
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2016-01-01 11:09:00.061258")
|
||||||
|
def test_form_defaults_to_now(app_):
|
||||||
|
assert ChooseTimeForm().scheduled_for.data == ''
|
||||||
@@ -322,6 +322,11 @@ def test_upload_csvfile_with_valid_phone_shows_all_numbers(
|
|||||||
mock_get_detailed_service_for_today.assert_called_once_with(fake_uuid)
|
mock_get_detailed_service_for_today.assert_called_once_with(fake_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'when', [
|
||||||
|
'', '2016-08-25T13:04:21.767198'
|
||||||
|
]
|
||||||
|
)
|
||||||
def test_create_job_should_call_api(
|
def test_create_job_should_call_api(
|
||||||
app_,
|
app_,
|
||||||
service_one,
|
service_one,
|
||||||
@@ -331,7 +336,8 @@ def test_create_job_should_call_api(
|
|||||||
mock_get_notifications,
|
mock_get_notifications,
|
||||||
mock_get_service_template,
|
mock_get_service_template,
|
||||||
mocker,
|
mocker,
|
||||||
fake_uuid
|
fake_uuid,
|
||||||
|
when
|
||||||
):
|
):
|
||||||
service_id = service_one['id']
|
service_id = service_one['id']
|
||||||
data = mock_get_job(service_one['id'], fake_uuid)['data']
|
data = mock_get_job(service_one['id'], fake_uuid)['data']
|
||||||
@@ -348,11 +354,18 @@ def test_create_job_should_call_api(
|
|||||||
'notification_count': notification_count,
|
'notification_count': notification_count,
|
||||||
'valid': True}
|
'valid': True}
|
||||||
url = url_for('main.start_job', service_id=service_one['id'], upload_id=job_id)
|
url = url_for('main.start_job', service_id=service_one['id'], upload_id=job_id)
|
||||||
response = client.post(url, data={}, follow_redirects=True)
|
response = client.post(url, data={'scheduled_for': when}, follow_redirects=True)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert original_file_name in response.get_data(as_text=True)
|
assert original_file_name in response.get_data(as_text=True)
|
||||||
mock_create_job.assert_called_with(job_id, service_id, template_id, original_file_name, notification_count)
|
mock_create_job.assert_called_with(
|
||||||
|
job_id,
|
||||||
|
service_id,
|
||||||
|
template_id,
|
||||||
|
original_file_name,
|
||||||
|
notification_count,
|
||||||
|
scheduled_for=when
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_check_messages_should_revalidate_file_when_uploading_file(
|
def test_check_messages_should_revalidate_file_when_uploading_file(
|
||||||
|
|||||||
@@ -27,6 +27,21 @@ def test_client_creates_job_data_correctly(mocker, fake_uuid):
|
|||||||
mock_post.assert_called_once_with(url=expected_url, data=expected_data)
|
mock_post.assert_called_once_with(url=expected_url, data=expected_data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_schedules_job(mocker, fake_uuid):
|
||||||
|
|
||||||
|
mocker.patch('app.notify_client.current_user', id='1')
|
||||||
|
|
||||||
|
mock_post = mocker.patch('app.notify_client.job_api_client.JobApiClient.post')
|
||||||
|
|
||||||
|
when = '2016-08-25T13:04:21.767198'
|
||||||
|
|
||||||
|
JobApiClient().create_job(
|
||||||
|
fake_uuid, fake_uuid, fake_uuid, fake_uuid, 1, scheduled_for=when
|
||||||
|
)
|
||||||
|
|
||||||
|
assert mock_post.call_args[1]['data']['scheduled_for'] == when
|
||||||
|
|
||||||
|
|
||||||
def test_client_gets_job_by_service_and_job(mocker):
|
def test_client_gets_job_by_service_and_job(mocker):
|
||||||
mocker.patch('app.notify_client.current_user', id='1')
|
mocker.patch('app.notify_client.current_user', id='1')
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -837,7 +837,7 @@ def mock_check_verify_code_code_expired(mocker):
|
|||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def mock_create_job(mocker, api_user_active):
|
def mock_create_job(mocker, api_user_active):
|
||||||
def _create(job_id, service_id, template_id, file_name, notification_count):
|
def _create(job_id, service_id, template_id, file_name, notification_count, scheduled_for=None):
|
||||||
return job_json(
|
return job_json(
|
||||||
service_id,
|
service_id,
|
||||||
api_user_active,
|
api_user_active,
|
||||||
|
|||||||
Reference in New Issue
Block a user