Adding views to view, add and edit service data retention policies.

Only visible to a platform admin.
A service can have a custom number of days to retain the notification data for each notification type.
This commit is contained in:
Rebecca Law
2018-07-17 14:39:04 +01:00
parent 13d05c5461
commit 4b8b571a87
12 changed files with 193 additions and 35 deletions
+9
View File
@@ -338,6 +338,14 @@ def valid_phone_number(phone_number):
return False return False
def format_notification_type(notification_type):
return {
'email': 'Email',
'sms': 'SMS',
'letter': 'Letter'
}[notification_type]
def format_notification_status(status, template_type): def format_notification_status(status, template_type):
return { return {
'email': { 'email': {
@@ -640,6 +648,7 @@ def add_template_filters(application):
format_datetime_relative, format_datetime_relative,
format_delta, format_delta,
format_notification_status, format_notification_status,
format_notification_type,
format_notification_status_as_time, format_notification_status_as_time,
format_notification_status_as_field_status, format_notification_status_as_field_status,
format_notification_status_as_url, format_notification_status_as_url,
+24
View File
@@ -1024,3 +1024,27 @@ class BrandingOptionsEmail(StripWhitespaceForm):
DataRequired() DataRequired()
], ],
) )
class ServiceDataRetentionForm(StripWhitespaceForm):
notification_type = RadioField(
'What notification type?',
choices=[
('email', 'Email'),
('sms', 'SMS'),
('letter', 'Letter'),
],
validators=[DataRequired()],
)
days_of_retention = IntegerField(label="Days of retention",
validators=[validators.NumberRange(min=3, max=90,
message="Must be between 3 and 90")],
)
class ServiceDataRetentionEditForm(StripWhitespaceForm):
days_of_retention = IntegerField(label="Days of retention",
validators=[validators.NumberRange(min=3, max=90,
message="Must be between 3 and 90")],
)
+39 -5
View File
@@ -36,6 +36,8 @@ from app.main.forms import (
RequestToGoLiveForm, RequestToGoLiveForm,
ServiceBasicViewForm, ServiceBasicViewForm,
ServiceContactLinkForm, ServiceContactLinkForm,
ServiceDataRetentionEditForm,
ServiceDataRetentionForm,
ServiceEditInboundNumberForm, ServiceEditInboundNumberForm,
ServiceInboundNumberForm, ServiceInboundNumberForm,
ServiceLetterContactBlockForm, ServiceLetterContactBlockForm,
@@ -1010,15 +1012,47 @@ def branding_request(service_id):
) )
@main.route("/services/<service_id>/set-data-retention", methods=['GET', 'POST']) @main.route("/services/<service_id>/data-retention", methods=['GET'])
@login_required @login_required
@user_is_platform_admin @user_is_platform_admin
def set_data_retention(service_id): def data_retention(service_id):
results = service_api_client.get_service_data_retention(service_id) results = service_api_client.get_service_data_retention(service_id)
print("DATA RETENTION: ", results[0]) return render_template('views/service-settings/data-retention.html',
data_retention_settings=results)
@main.route("/services/<service_id>/data-retention/add", methods=['GET', 'POST'])
@login_required
@user_is_platform_admin
def add_data_retention(service_id):
form = ServiceDataRetentionForm()
if form.validate_on_submit():
service_api_client.create_service_data_retention(service_id,
form.notification_type.data,
form.days_of_retention.data)
return redirect(url_for('.data_retention', service_id=service_id))
return render_template( return render_template(
'views/service-settings/set-data-retention.html', 'views/service-settings/data-retention/add.html',
data_retentions=results form=form
)
@main.route("/services/<service_id>/data-retention/<data_retention_id>/edit", methods=['GET', 'POST'])
@login_required
@user_is_platform_admin
def edit_data_retention(service_id, data_retention_id):
form = ServiceDataRetentionEditForm()
if request.method == 'GET':
data_retention_item = service_api_client.get_service_data_retention_by_id(service_id, data_retention_id)
if form.validate_on_submit():
service_api_client.update_service_data_retention(service_id, data_retention_id, form.days_of_retention.data)
return redirect(url_for('.data_retention', service_id=service_id))
return render_template(
'views/service-settings/data-retention/edit.html',
form=form,
data_retention_id=data_retention_id,
notification_type=data_retention_item['notification_type'],
days_of_retention=data_retention_item['days_of_retention']
) )
+12
View File
@@ -101,6 +101,7 @@ class HeaderNavigation(Navigation):
'accept_invite', 'accept_invite',
'accept_org_invite', 'accept_org_invite',
'action_blocked', 'action_blocked',
'add_data_retention',
'add_service', 'add_service',
'add_service_template', 'add_service_template',
'add_template_by_type', 'add_template_by_type',
@@ -131,12 +132,14 @@ class HeaderNavigation(Navigation):
'conversation_updates', 'conversation_updates',
'cookies', 'cookies',
'create_api_key', 'create_api_key',
'data_retention',
'delete_service_template', 'delete_service_template',
'delivery_and_failure', 'delivery_and_failure',
'delivery_status_callback', 'delivery_status_callback',
'design_content', 'design_content',
'download_agreement', 'download_agreement',
'download_notifications_csv', 'download_notifications_csv',
'edit_data_retention',
'edit_organisation_name', 'edit_organisation_name',
'edit_provider', 'edit_provider',
'edit_service_template', 'edit_service_template',
@@ -366,6 +369,7 @@ class MainNavigation(Navigation):
exclude = { exclude = {
'accept_invite', 'accept_invite',
'accept_org_invite', 'accept_org_invite',
'add_data_retention',
'add_organisation', 'add_organisation',
'add_service', 'add_service',
'agreement', 'agreement',
@@ -385,11 +389,13 @@ class MainNavigation(Navigation):
'conversation_updates', 'conversation_updates',
'cookies', 'cookies',
'create_email_branding', 'create_email_branding',
'data_retention',
'delivery_and_failure', 'delivery_and_failure',
'design_content', 'design_content',
'documentation', 'documentation',
'download_agreement', 'download_agreement',
'download_notifications_csv', 'download_notifications_csv',
'edit_data_retention',
'edit_organisation_name', 'edit_organisation_name',
'edit_provider', 'edit_provider',
'edit_user_org_permissions', 'edit_user_org_permissions',
@@ -517,6 +523,7 @@ class CaseworkNavigation(Navigation):
'accept_invite', 'accept_invite',
'accept_org_invite', 'accept_org_invite',
'action_blocked', 'action_blocked',
'add_data_retention',
'add_organisation', 'add_organisation',
'add_service', 'add_service',
'add_service_template', 'add_service_template',
@@ -549,6 +556,7 @@ class CaseworkNavigation(Navigation):
'cookies', 'cookies',
'create_api_key', 'create_api_key',
'create_email_branding', 'create_email_branding',
'data_retention',
'delete_service_template', 'delete_service_template',
'delivery_and_failure', 'delivery_and_failure',
'delivery_status_callback', 'delivery_status_callback',
@@ -556,6 +564,7 @@ class CaseworkNavigation(Navigation):
'documentation', 'documentation',
'download_agreement', 'download_agreement',
'download_notifications_csv', 'download_notifications_csv',
'edit_data_retention',
'edit_organisation_name', 'edit_organisation_name',
'edit_provider', 'edit_provider',
'edit_service_template', 'edit_service_template',
@@ -741,6 +750,7 @@ class OrgNavigation(Navigation):
'accept_invite', 'accept_invite',
'accept_org_invite', 'accept_org_invite',
'action_blocked', 'action_blocked',
'add_data_retention',
'add_organisation', 'add_organisation',
'add_service', 'add_service',
'add_service_template', 'add_service_template',
@@ -773,6 +783,7 @@ class OrgNavigation(Navigation):
'cookies', 'cookies',
'create_api_key', 'create_api_key',
'create_email_branding', 'create_email_branding',
'data_retention',
'delete_service_template', 'delete_service_template',
'delivery_and_failure', 'delivery_and_failure',
'delivery_status_callback', 'delivery_status_callback',
@@ -780,6 +791,7 @@ class OrgNavigation(Navigation):
'documentation', 'documentation',
'download_agreement', 'download_agreement',
'download_notifications_csv', 'download_notifications_csv',
'edit_data_retention',
'edit_provider', 'edit_provider',
'edit_service_template', 'edit_service_template',
'edit_user_permissions', 'edit_user_permissions',
+4 -2
View File
@@ -322,8 +322,10 @@
{% endcall %} {% endcall %}
{% call row() %} {% call row() %}
{{ text_field('Data Retention')}} {{ text_field('Data Retention')}}
{{ text_field(data_retention[0]['notification_type']) }} {% call field() %}
{{ edit_field('Change', url_for('.set_data_retention', service_id=current_service.id)) }} {{ data_retention | join(', ', attribute='notification_type') }}
{% endcall %}
{{ edit_field('Change', url_for('.data_retention', service_id=current_service.id)) }}
{% endcall %} {% endcall %}
{% endcall %} {% endcall %}
@@ -0,0 +1,44 @@
{% extends "withnav_template.html" %}
{% from "components/api-key.html" import api_key %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/table.html" import row_group, row, text_field, edit_field, field, boolean_field, list_table with context %}
{% block service_page_title %}
Email reply to addresses
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row bottom-gutter">
<div class="column-two-thirds">
<h1 class="heading-large">
Data retention
</h1>
</div>
<div class="column-one-third">
<a href="{{ url_for('.add_data_retention', service_id=current_service.id) }}" class="button align-with-heading">Add data retention</a>
</div>
</div>
<div class="grid-row bottom-gutter">
<div class="column-full">
By default data is kept for 7 days
</div>
</div>
<div class="user-list">
{% if not data_retention_settings %}
<div class="user-list-item">
<span class="hint">You havent added any data retention</span>
</div>
{% endif %}
{% for item in data_retention_settings %}
<div class="user-list-item">
<p>
{{ item.notification_type | format_notification_type }} notifications will be kept for {{ item.days_of_retention }} days
<a class="user-list-edit-link" href="{{ url_for('.edit_data_retention', service_id =current_service.id, data_retention_id = item.id) }}">Change</a>
</p>
</div>
{% endfor %}
</div>
{% endblock %}
@@ -0,0 +1,25 @@
{% extends "withnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/radios.html" import radios %}
{% block service_page_title %}
Data retention
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-large">Set data retention</h1>
<form method="post">
{{ radios(form.notification_type) }}
{{ textbox(form.days_of_retention) }}
{{ page_footer(
'Add',
back_link=url_for('.add_data_retention', service_id=current_service.id),
back_link_text='Back'
) }}
</form>
{% endblock %}
@@ -0,0 +1,25 @@
{% extends "withnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/radios.html" import radios %}
{% block service_page_title %}
Data retention
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-large">Set data retention</h1>
<form method="post">
{{ notification_type | capitalize}}
{{ textbox(form.days_of_retention) }}
{{ page_footer(
'Save',
back_link=url_for('.edit_data_retention', service_id=current_service.id, data_retention_id=data_retention_id),
back_link_text='Back'
) }}
</form>
{% endblock %}
@@ -1,28 +0,0 @@
{% extends "withnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import page_footer %}
{% block service_page_title %}
Data retention
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="column-five-sixths">
<h1 class="heading-large">Emails</h1>
<p>
Add new data retention here
</p>
{% for item in data_retentions %}
{{ item.notification_type }}
{{ item.days_of_retention }}
{% endfor %}
{{ page_footer(
back_link=url_for('.service_settings', service_id=current_service.id),
back_link_text='Back to settings'
) }}
</div>
</div>
{% endblock %}
@@ -16,6 +16,7 @@ def get_service_settings_page(
no_reply_to_email_addresses, no_reply_to_email_addresses,
no_letter_contact_blocks, no_letter_contact_blocks,
single_sms_sender, single_sms_sender,
mock_get_service_data_retention,
): ):
client_request.login(platform_admin_user) client_request.login(platform_admin_user)
return functools.partial(client_request.get, 'main.service_settings', service_id=service_one['id']) return functools.partial(client_request.get, 'main.service_settings', service_id=service_one['id'])
@@ -103,6 +104,7 @@ def test_normal_user_doesnt_see_any_toggle_buttons(
mock_get_letter_email_branding, mock_get_letter_email_branding,
mock_get_inbound_number_for_service, mock_get_inbound_number_for_service,
mock_get_free_sms_fragment_limit, mock_get_free_sms_fragment_limit,
mock_get_service_data_retention
): ):
page = client_request.get('main.service_settings', service_id=service_one['id']) page = client_request.get('main.service_settings', service_id=service_one['id'])
toggles = page.find('a', {'class': 'button'}) toggles = page.find('a', {'class': 'button'})
@@ -40,6 +40,7 @@ def mock_get_service_settings_page_common(
mock_get_letter_email_branding, mock_get_letter_email_branding,
mock_get_inbound_number_for_service, mock_get_inbound_number_for_service,
mock_get_free_sms_fragment_limit, mock_get_free_sms_fragment_limit,
mock_get_service_data_retention,
): ):
return return
@@ -94,6 +95,7 @@ def mock_get_service_settings_page_common(
'Free text message allowance 250,000 Change', 'Free text message allowance 250,000 Change',
'Email branding GOV.UK Change', 'Email branding GOV.UK Change',
'Letter branding HM Government Change', 'Letter branding HM Government Change',
'Data Retention Change'
]), ]),
]) ])
@@ -2527,6 +2529,7 @@ def test_service_settings_when_inbound_number_is_not_set(
mocker, mocker,
mock_get_letter_email_branding, mock_get_letter_email_branding,
mock_get_free_sms_fragment_limit, mock_get_free_sms_fragment_limit,
mock_get_service_data_retention,
): ):
mocker.patch('app.inbound_number_client.get_inbound_sms_number_for_service', mocker.patch('app.inbound_number_client.get_inbound_sms_number_for_service',
return_value={'data': {}}) return_value={'data': {}})
+6
View File
@@ -2670,6 +2670,12 @@ def normalize_spaces(input):
return normalize_spaces(' '.join(item.text for item in input)) return normalize_spaces(' '.join(item.text for item in input))
@pytest.fixture(scope='function')
def mock_get_service_data_retention(mocker):
return mocker.patch('app.service_api_client.get_service_data_retention',
return_value={})
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def mock_get_free_sms_fragment_limit(mocker): def mock_get_free_sms_fragment_limit(mocker):
sample_limit = 250000 sample_limit = 250000