Highlight selected navigation item

In research I’ve sometimes seen people click the wrong nav item. I
reckon that people’s concept of which pages live behind which navigation
items isn’t very strong.

We can reinforce this relationship by showing, for every page, which is
the corresponding nav item. The conventional way of doing this is either
with some kind of emphasis, typically colour or bold. I’ve gone for bold
because colour would be weird.

---

The implementation of this is quite loosely coupled to our application
code because:
- our application code is not well structured (eg we don’t make any use
  of blueprints)
- spreading this change across lots of files in our application would
  make it harder to test without actually hitting each endpoints; such
  tests would be slow and verbose

So I’ve gone for more of a meta approach. Rather than testing that each
endpoint has a specific navigation item selected, I’ve gone for
validating that:
- all endpoints being mapped to are real
- all endpoints have _a_ selected navigation item (or are specifically
  excluded)

This means that it’s impossible to add, change or remove an endpoint
without also updating which navigation item should be selected. And the
actual mapping is so declarative that it testing it would be redundant.
This commit is contained in:
Chris Hill-Scott
2018-04-24 12:48:05 +01:00
parent 63061b1bab
commit 1fba5d186d
5 changed files with 312 additions and 8 deletions

238
app/navigation.py Normal file
View File

@@ -0,0 +1,238 @@
from itertools import chain
from flask import request
mapping = {
'dashboard': {
'conversation',
'inbox',
'monthly',
'service_dashboard',
'template_usage',
'view_job',
'view_jobs',
'view_notification',
'view_notifications',
},
'templates': {
'action_blocked',
'add_service_template',
'add_template_by_type',
'check_messages',
'check_notification',
'choose_template',
'confirm_redact_template',
'conversation_reply',
'delete_service_template',
'edit_service_template',
'send_messages',
'send_one_off',
'send_one_off_step',
'send_test',
'send_test_preview',
'send_test_step',
'set_sender',
'set_template_sender',
'view_template',
'view_template_version',
'view_template_versions',
},
'team-members': {
'edit_user_permissions',
'invite_user',
'manage_users',
'remove_user_from_service',
},
'usage': {
'usage',
},
'settings': {
'link_service_to_organisation',
'request_to_go_live',
'service_add_email_reply_to',
'service_add_letter_contact',
'service_add_sms_sender',
'service_edit_email_reply_to',
'service_edit_letter_contact',
'service_edit_sms_sender',
'service_email_reply_to',
'service_letter_contact_details',
'service_name_change',
'service_name_change_confirm',
'service_set_auth_type',
'service_set_email',
'service_set_email_branding',
'service_set_inbound_number',
'service_set_inbound_sms',
'service_set_international_sms',
'service_set_letter_contact_block',
'service_set_letters',
'service_set_reply_to_email',
'service_set_sms',
'service_set_sms_prefix',
'service_settings',
'service_sms_senders',
'set_free_sms_allowance',
'set_letter_branding',
'set_organisation_type',
'submit_request_to_go_live',
},
'api-integration': {
'api_callbacks',
'api_documentation',
'api_integration',
'api_keys',
'create_api_key',
'delivery_status_callback',
'received_text_messages_callback',
'revoke_api_key',
'whitelist',
},
}
exclude = {
'accept_invite',
'accept_org_invite',
'add_organisation',
'add_service',
'agreement',
'archive_service',
'bat_phone',
'callbacks',
'cancel_invited_org_user',
'cancel_invited_user',
'cancel_job',
'check_and_resend_text_code',
'check_and_resend_verification_code',
'check_messages_preview',
'choose_account',
'choose_service',
'confirm_edit_organisation_name',
'conversation_reply_with_template',
'conversation_updates',
'cookies',
'create_email_branding',
'delivery_and_failure',
'design_content',
'documentation',
'download_agreement',
'download_notifications_csv',
'edit_organisation_name',
'edit_provider',
'edit_user_org_permissions',
'email_branding',
'email_not_received',
'email_template',
'error',
'features',
'feedback',
'forgot_password',
'get_example_csv',
'get_notifications_as_json',
'go_to_dashboard_after_tour',
'inbound_sms_admin',
'inbox_download',
'inbox_updates',
'index',
'information_risk_management',
'information_security',
'integration_testing',
'invite_org_user',
'letter_jobs',
'live_services',
'manage_org_users',
'new_password',
'old_feedback',
'old_integration_testing',
'old_roadmap',
'old_service_dashboard',
'old_submit_feedback',
'old_terms',
'old_using_notify',
'organisation_dashboard',
'organisation_settings',
'organisations',
'platform_admin',
'pricing',
'redact_template',
'register',
'register_from_invite',
'register_from_org_invite',
'registration_continue',
'remove_user_from_organisation',
'resend_email_link',
'resend_email_verification',
'resume_service',
'roadmap',
'security',
'send_notification',
'service_dashboard_updates',
'service_switch_can_send_email',
'service_switch_can_send_precompiled_letter',
'service_switch_can_send_sms',
'service_switch_email_auth',
'service_switch_live',
'service_switch_research_mode',
'services_or_dashboard',
'show_accounts_or_dashboard',
'sign_in',
'sign_out',
'start_job',
'start_tour',
'styleguide',
'support',
'suspend_service',
'temp_service_history',
'template_history',
'terms',
'thanks',
'triage',
'trial_mode',
'trial_services',
'two_factor',
'two_factor_email',
'two_factor_email_sent',
'update_email_branding',
'user_profile',
'user_profile_email',
'user_profile_email_authenticate',
'user_profile_email_confirm',
'user_profile_mobile_number',
'user_profile_mobile_number_authenticate',
'user_profile_mobile_number_confirm',
'user_profile_name',
'user_profile_password',
'using_notify',
'verify',
'verify_email',
'verify_mobile',
'view_job_csv',
'view_job_updates',
'view_letter_notification_as_preview',
'view_letter_template_preview',
'view_notification_updates',
'view_notifications_csv',
'view_provider',
'view_providers',
'view_template_version_preview',
}
mapping = {
navigation: {
'main.{}'.format(endpoint) for endpoint in endpoints
} for navigation, endpoints in mapping.items()
}
endpoints_with_navigation = tuple(chain.from_iterable((
endpoints for navigation_item, endpoints in mapping.items()
)))
endpoints_without_navigation = tuple(
'main.{}'.format(endpoint) for endpoint in exclude
) + ('static', 'status.show_status')
def nav_selected(navigation_item):
if request.endpoint in mapping[navigation_item]:
return "class=selected"
return ''