mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-11 10:28:41 -04:00
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:
@@ -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,
|
||||||
|
|||||||
@@ -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")],
|
||||||
|
)
|
||||||
|
|||||||
@@ -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']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 haven’t 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': {}})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user