Automate counting of live services and orgs

Returns the data calculated by the API. Stored in Redis against a
hardcoded key so that no-one hammering the home page is directly hitting
the database.
This commit is contained in:
Chris Hill-Scott
2019-04-10 17:20:51 +01:00
parent 55eaa066f1
commit 9863aa3c48
10 changed files with 61 additions and 15 deletions

View File

@@ -12,7 +12,7 @@ from notifications_utils.international_billing_rates import (
) )
from notifications_utils.template import HTMLEmailTemplate, LetterImageTemplate from notifications_utils.template import HTMLEmailTemplate, LetterImageTemplate
from app import email_branding_client, letter_branding_client from app import email_branding_client, letter_branding_client, status_api_client
from app.main import main from app.main import main
from app.main.forms import FieldWithNoneOption, SearchByNameForm from app.main.forms import FieldWithNoneOption, SearchByNameForm
from app.main.views.sub_navigation_dictionaries import features_nav from app.main.views.sub_navigation_dictionaries import features_nav
@@ -21,9 +21,14 @@ from app.utils import AgreementInfo, get_logo_cdn_domain
@main.route('/') @main.route('/')
def index(): def index():
if current_user and current_user.is_authenticated: if current_user and current_user.is_authenticated:
return redirect(url_for('main.choose_account')) return redirect(url_for('main.choose_account'))
return render_template('views/signedout.html')
return render_template(
'views/signedout.html',
counts=status_api_client.get_count_of_live_services_and_organisations(),
)
@main.route('/robots.txt') @main.route('/robots.txt')

View File

@@ -262,12 +262,7 @@ def service_switch_live(service_id):
) )
if form.validate_on_submit(): if form.validate_on_submit():
current_service.update( current_service.update_status(live=form.enabled.data)
# TODO This limit should be set depending on the agreement signed by
# with Notify.
message_limit=250000 if form.enabled.data else 50,
restricted=(not form.enabled.data)
)
return redirect(url_for('.service_settings', service_id=service_id)) return redirect(url_for('.service_settings', service_id=service_id))
return render_template( return render_template(

View File

@@ -72,6 +72,9 @@ class Service():
def update(self, **kwargs): def update(self, **kwargs):
return service_api_client.update_service(self.id, **kwargs) return service_api_client.update_service(self.id, **kwargs)
def update_status(self, live):
return service_api_client.update_status(self.id, live=live)
def switch_permission(self, permission): def switch_permission(self, permission):
return self.force_permission( return self.force_permission(
permission, permission,

View File

@@ -25,6 +25,7 @@ class OrganisationsClient(NotifyAdminAPIClient):
return self.get(url="/service/{}/organisation".format(service_id)) return self.get(url="/service/{}/organisation".format(service_id))
@cache.delete('service-{service_id}') @cache.delete('service-{service_id}')
@cache.delete('live-service-and-organisation-counts')
def update_service_organisation(self, service_id, org_id): def update_service_organisation(self, service_id, org_id):
data = { data = {
'service_id': service_id 'service_id': service_id

View File

@@ -98,6 +98,14 @@ class ServiceAPIClient(NotifyAdminAPIClient):
endpoint = "/service/{0}".format(service_id) endpoint = "/service/{0}".format(service_id)
return self.post(endpoint, data) return self.post(endpoint, data)
@cache.delete('live-service-and-organisation-counts')
def update_status(self, service_id, live):
return self.update_service(
service_id,
message_limit=250000 if live else 50,
restricted=(not live),
)
# This method is not cached because it calls through to one which is # This method is not cached because it calls through to one which is
def update_service_with_properties(self, service_id, properties): def update_service_with_properties(self, service_id, properties):
return self.update_service(service_id, **properties) return self.update_service(service_id, **properties)

View File

@@ -1,5 +1,4 @@
from app.notify_client import NotifyAdminAPIClient, cache
from app.notify_client import NotifyAdminAPIClient
class StatusApiClient(NotifyAdminAPIClient): class StatusApiClient(NotifyAdminAPIClient):
@@ -7,5 +6,9 @@ class StatusApiClient(NotifyAdminAPIClient):
def get_status(self, *params): def get_status(self, *params):
return self.get(url='/_status', *params) return self.get(url='/_status', *params)
@cache.set('live-service-and-organisation-counts')
def get_count_of_live_services_and_organisations(self):
return self.get(url='/_status/live-service-and-organisation-counts')
status_api_client = StatusApiClient() status_api_client = StatusApiClient()

View File

@@ -115,17 +115,17 @@
</div> </div>
</div> </div>
<div class="product-page-section"> <div class="product-page-section">
<div class="with-keyline bottom-gutter-2"> <div class="with-keyline bottom-gutter-2" id="whos-using-notify">
<h2>Whos using GOV.UK Notify</h2> <h2>Whos using GOV.UK Notify</h2>
<div class="grid-row bottom-gutter"> <div class="grid-row bottom-gutter">
<div class="column-half"> <div class="column-half">
<h3 class="visually-hidden">Services</h3> <h3 class="visually-hidden">Services</h3>
<div class="product-page-big-number">745</div> <div class="product-page-big-number">{{ counts.services }}</div>
services services
</div> </div>
<div class="column-half"> <div class="column-half">
<h3 class="visually-hidden">Organisations</h3> <h3 class="visually-hidden">Organisations</h3>
<div class="product-page-big-number">233</div> <div class="product-page-big-number">{{ counts.organisations }}</div>
organisations organisations
</div> </div>
</div> </div>

View File

@@ -1,4 +1,10 @@
def test_owasp_useful_headers_set(client, mocker):
def test_owasp_useful_headers_set(
client,
mocker,
mock_get_service_and_organisation_counts,
):
mocker.patch('app.get_logo_cdn_domain', return_value='static-logos.test.com') mocker.patch('app.get_logo_cdn_domain', return_value='static-logos.test.com')
response = client.get('/') response = client.get('/')
@@ -19,7 +25,11 @@ def test_owasp_useful_headers_set(client, mocker):
) )
def test_headers_non_ascii_characters_are_replaced(client, mocker): def test_headers_non_ascii_characters_are_replaced(
client,
mocker,
mock_get_service_and_organisation_counts,
):
mocker.patch('app.get_logo_cdn_domain', return_value='static-logos۾.test.com') mocker.patch('app.get_logo_cdn_domain', return_value='static-logos۾.test.com')
response = client.get('/') response = client.get('/')

View File

@@ -14,6 +14,7 @@ from tests.conftest import (
def test_non_logged_in_user_can_see_homepage( def test_non_logged_in_user_can_see_homepage(
client, client,
mock_get_service_and_organisation_counts,
): ):
response = client.get(url_for('main.index')) response = client.get(url_for('main.index'))
assert response.status_code == 200 assert response.status_code == 200
@@ -30,6 +31,18 @@ def test_non_logged_in_user_can_see_homepage(
'local authority, or the NHS.' 'local authority, or the NHS.'
) )
assert normalize_spaces(page.select_one('#whos-using-notify').text) == (
'Whos using GOV.UK Notify '
'Services '
'9999 services '
'Organisations '
'111 organisations '
'See the list of services and organisations.'
)
assert page.select_one('#whos-using-notify a')['href'] == (
'https://www.gov.uk/performance/govuk-notify/government-services'
)
def test_logged_in_user_redirects_to_choose_account( def test_logged_in_user_redirects_to_choose_account(
client_request, client_request,

View File

@@ -3351,3 +3351,11 @@ def mock_move_to_template_folder(mocker):
@pytest.fixture @pytest.fixture
def mock_create_template_folder(mocker): def mock_create_template_folder(mocker):
return mocker.patch('app.template_folder_api_client.create_template_folder', return_value=sample_uuid()) return mocker.patch('app.template_folder_api_client.create_template_folder', return_value=sample_uuid())
@pytest.fixture(scope='function')
def mock_get_service_and_organisation_counts(mocker):
return mocker.patch('app.status_api_client.get_count_of_live_services_and_organisations', return_value={
'organisations': 111,
'services': 9999,
})