diff --git a/.gitignore b/.gitignore index 869589c17..57a363cd0 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,6 @@ playwright/ # Pyenv .python-version + +# Nodenv +.node-version diff --git a/app/__init__.py b/app/__init__.py index c8224e21e..4e53bbb76 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -93,7 +93,6 @@ from app.notify_client import InviteTokenError from app.notify_client.api_key_api_client import api_key_api_client from app.notify_client.billing_api_client import billing_api_client from app.notify_client.complaint_api_client import complaint_api_client -from app.notify_client.email_branding_client import email_branding_client from app.notify_client.events_api_client import events_api_client from app.notify_client.inbound_number_client import inbound_number_client from app.notify_client.invite_api_client import invite_api_client @@ -111,11 +110,7 @@ from app.notify_client.template_folder_api_client import template_folder_api_cli from app.notify_client.template_statistics_api_client import template_statistics_client from app.notify_client.upload_api_client import upload_api_client from app.notify_client.user_api_client import user_api_client -from app.url_converters import ( - SimpleDateTypeConverter, - TemplateTypeConverter, - TicketTypeConverter, -) +from app.url_converters import SimpleDateTypeConverter, TemplateTypeConverter login_manager = LoginManager() csrf = CSRFProtect() @@ -186,7 +181,6 @@ def create_app(application): api_key_api_client, billing_api_client, complaint_api_client, - email_branding_client, events_api_client, inbound_number_client, invite_api_client, @@ -328,7 +322,6 @@ def init_app(application): application.url_map.converters["uuid"].to_python = lambda self, value: value application.url_map.converters["template_type"] = TemplateTypeConverter - application.url_map.converters["ticket_type"] = TicketTypeConverter application.url_map.converters["simple_date"] = SimpleDateTypeConverter diff --git a/app/assets/javascripts/consent.js b/app/assets/javascripts/consent.js deleted file mode 100644 index fab23e895..000000000 --- a/app/assets/javascripts/consent.js +++ /dev/null @@ -1,15 +0,0 @@ -(function (window) { - "use strict"; - - function hasConsentFor (cookieCategory, consentCookie) { - if (consentCookie === undefined) { consentCookie = window.GOVUK.getConsentCookie(); } - - if (consentCookie === null) { return false; } - - if (!(cookieCategory in consentCookie)) { return false; } - - return consentCookie[cookieCategory]; - } - - window.GOVUK.hasConsentFor = hasConsentFor; -})(window); diff --git a/app/assets/javascripts/cookieMessage.js b/app/assets/javascripts/cookieMessage.js deleted file mode 100644 index ffd906d34..000000000 --- a/app/assets/javascripts/cookieMessage.js +++ /dev/null @@ -1,104 +0,0 @@ -window.GOVUK = window.GOVUK || {}; -window.GOVUK.Modules = window.GOVUK.Modules || {}; - -(function (Modules) { - function CookieBanner () { } - - CookieBanner.clearOldCookies = function (consent) { - var gaCookies = ['_ga', '_gid']; - - // clear old cookie set by our previous JS, set on the www domain - if (window.GOVUK.cookie('seen_cookie_message')) { - document.cookie = 'seen_cookie_message=;expires=' + new Date().toGMTString() + ';path=/'; - } - - if (consent === null) { - for (var i = 0; i < gaCookies.length; i++) { - if (window.GOVUK.cookie(gaCookies[i])) { - // GA cookies are set on the base domain so need the www stripping - var cookieString = gaCookies[i] + '=;expires=' + new Date().toGMTString() + ';domain=' + window.location.hostname.replace(/^www\./, '.') + ';path=/'; - document.cookie = cookieString; - } - } - } - }; - - CookieBanner.prototype.start = function ($module) { - this.$module = $module[0]; - this.$module.hideCookieMessage = this.hideCookieMessage.bind(this); - this.$module.showConfirmationMessage = this.showConfirmationMessage.bind(this); - this.$module.setCookieConsent = this.setCookieConsent.bind(this); - - this.$module.cookieBanner = document.querySelector('.notify-cookie-banner'); - this.$module.cookieBannerConfirmationMessage = this.$module.querySelector('.notify-cookie-banner__confirmation'); - - this.setupCookieMessage(); - }; - - CookieBanner.prototype.setupCookieMessage = function () { - this.$hideLink = this.$module.querySelector('button[data-hide-cookie-banner]'); - if (this.$hideLink) { - this.$hideLink.addEventListener('click', this.$module.hideCookieMessage); - } - - this.$acceptCookiesLink = this.$module.querySelector('button[data-accept-cookies=true]'); - if (this.$acceptCookiesLink) { - this.$acceptCookiesLink.addEventListener('click', () => this.$module.setCookieConsent(true)); - } - - this.$rejectCookiesLink = this.$module.querySelector('button[data-accept-cookies=false]'); - if (this.$rejectCookiesLink) { - this.$rejectCookiesLink.addEventListener('click', () => this.$module.setCookieConsent(false)); - } - - this.showCookieMessage(); - }; - - CookieBanner.prototype.showCookieMessage = function () { - // Show the cookie banner if not in the cookie settings page - if (!this.isInCookiesPage()) { - var hasCookiesPolicy = window.GOVUK.cookie('cookies_policy'); - - if (this.$module && !hasCookiesPolicy) { - this.$module.style.display = 'block'; - } - } - }; - - CookieBanner.prototype.hideCookieMessage = function (event) { - if (this.$module) { - this.$module.style.display = 'none'; - } - - if (event.target) { - event.preventDefault(); - } - }; - - CookieBanner.prototype.setCookieConsent = function (analyticsConsent) { - window.GOVUK.setConsentCookie({ 'analytics': analyticsConsent }); - - this.$module.showConfirmationMessage(analyticsConsent); - this.$module.cookieBannerConfirmationMessage.focus(); - - if (analyticsConsent) { window.GOVUK.initAnalytics(); } - }; - - CookieBanner.prototype.showConfirmationMessage = function (analyticsConsent) { - var messagePrefix = analyticsConsent ? 'You’ve accepted analytics cookies.' : 'You told us not to use analytics cookies.'; - - this.$cookieBannerMainContent = document.querySelector('.notify-cookie-banner__wrapper'); - this.$cookieBannerConfirmationMessage = document.querySelector('.notify-cookie-banner__confirmation-message'); - - this.$cookieBannerConfirmationMessage.insertAdjacentText('afterbegin', messagePrefix); - this.$cookieBannerMainContent.style.display = 'none'; - this.$module.cookieBannerConfirmationMessage.style.display = 'block'; - }; - - CookieBanner.prototype.isInCookiesPage = function () { - return window.location.pathname === '/cookies'; - }; - - Modules.CookieBanner = CookieBanner; -})(window.GOVUK.Modules); - diff --git a/app/assets/javascripts/govuk/cookie-functions.js b/app/assets/javascripts/govuk/cookie-functions.js deleted file mode 100644 index 5fa15bee7..000000000 --- a/app/assets/javascripts/govuk/cookie-functions.js +++ /dev/null @@ -1,163 +0,0 @@ -// used by the cookie banner component - -(function (root) { - 'use strict'; - window.GOVUK = window.GOVUK || {}; - - var DEFAULT_COOKIE_CONSENT = { - 'analytics': false - }; - - var COOKIE_CATEGORIES = { - '_ga': 'analytics', - '_gid': 'analytics' - }; - - /* - Cookie methods - ============== - - Usage: - - Setting a cookie: - GOVUK.cookie('hobnob', 'tasty', { days: 30 }); - - Reading a cookie: - GOVUK.cookie('hobnob'); - - Deleting a cookie: - GOVUK.cookie('hobnob', null); - */ - window.GOVUK.cookie = function (name, value, options) { - if (typeof value !== 'undefined') { - if (value === false || value === null) { - return window.GOVUK.setCookie(name, '', { days: -1 }); - } else { - // Default expiry date of 30 days - if (typeof options === 'undefined') { - options = { days: 30 }; - } - return window.GOVUK.setCookie(name, value, options); - } - } else { - return window.GOVUK.getCookie(name); - } - }; - - window.GOVUK.getConsentCookie = function () { - var consentCookie = window.GOVUK.cookie('cookies_policy'); - var consentCookieObj; - - if (consentCookie) { - try { - consentCookieObj = JSON.parse(consentCookie); - } catch (err) { - return null; - } - - if (typeof consentCookieObj !== 'object' && consentCookieObj !== null) { - consentCookieObj = JSON.parse(consentCookieObj); - } - } else { - return null; - } - - return consentCookieObj; - }; - - window.GOVUK.setConsentCookie = function (options) { - var cookieConsent = window.GOVUK.getConsentCookie(); - - if (!cookieConsent) { - cookieConsent = JSON.parse(JSON.stringify(DEFAULT_COOKIE_CONSENT)); - } - - for (var cookieType in options) { - cookieConsent[cookieType] = options[cookieType]; - - // Delete cookies of that type if consent being set to false - if (!options[cookieType]) { - for (var cookie in COOKIE_CATEGORIES) { - if (COOKIE_CATEGORIES[cookie] === cookieType) { - window.GOVUK.cookie(cookie, null); - - if (window.GOVUK.cookie(cookie)) { - document.cookie = cookie + '=;expires=' + new Date() + ';domain=' + window.location.hostname.replace(/^www\./, '.') + ';path=/'; - } - } - } - } - } - - window.GOVUK.setCookie('cookies_policy', JSON.stringify(cookieConsent), { days: 365 }); - }; - - window.GOVUK.checkConsentCookieCategory = function (cookieName, cookieCategory) { - var currentConsentCookie = window.GOVUK.getConsentCookie(); - - // If the consent cookie doesn't exist, but the cookie is in our known list, return true - if (!currentConsentCookie && COOKIE_CATEGORIES[cookieName]) { - return true; - } - - currentConsentCookie = window.GOVUK.getConsentCookie(); - - // Sometimes currentConsentCookie is malformed in some of the tests, so we need to handle these - try { - return currentConsentCookie[cookieCategory]; - } catch (e) { - console.error(e); - return false; - } - }; - - window.GOVUK.checkConsentCookie = function (cookieName, cookieValue) { - // If we're setting the consent cookie OR deleting a cookie, allow by default - if (cookieName === 'cookies_policy' || (cookieValue === null || cookieValue === false)) { - return true; - } - - if (COOKIE_CATEGORIES[cookieName]) { - var cookieCategory = COOKIE_CATEGORIES[cookieName]; - - return window.GOVUK.checkConsentCookieCategory(cookieName, cookieCategory); - } else { - // Deny the cookie if it is not known to us - return false; - } - }; - - window.GOVUK.setCookie = function (name, value, options) { - if (window.GOVUK.checkConsentCookie(name, value)) { - if (typeof options === 'undefined') { - options = {}; - } - var cookieString = name + '=' + value + '; path=/'; - if (options.days) { - var date = new Date(); - date.setTime(date.getTime() + (options.days * 24 * 60 * 60 * 1000)); - cookieString = cookieString + '; expires=' + date.toGMTString(); - } - if (document.location.protocol === 'https:') { - cookieString = cookieString + '; Secure'; - } - document.cookie = cookieString; - } - }; - - window.GOVUK.getCookie = function (name) { - var nameEQ = name + '='; - var cookies = document.cookie.split(';'); - for (var i = 0, len = cookies.length; i < len; i++) { - var cookie = cookies[i]; - while (cookie.charAt(0) === ' ') { - cookie = cookie.substring(1, cookie.length); - } - if (cookie.indexOf(nameEQ) === 0) { - return decodeURIComponent(cookie.substring(nameEQ.length)); - } - } - return null; - }; -}(window)); - diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 3cc17c20d..e48485471 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -1,12 +1,5 @@ window.GOVUK.Frontend.initAll(); -var consentData = window.GOVUK.getConsentCookie(); -window.GOVUK.Modules.CookieBanner.clearOldCookies(consentData); - -if (window.GOVUK.hasConsentFor('analytics', consentData)) { - window.GOVUK.initAnalytics(); -} - $(() => $("time.timeago").timeago()); var showHideContent = new GOVUK.ShowHideContent(); diff --git a/app/assets/javascripts/previewPane.js b/app/assets/javascripts/previewPane.js deleted file mode 100644 index 4266b8590..000000000 --- a/app/assets/javascripts/previewPane.js +++ /dev/null @@ -1,36 +0,0 @@ -(function (global) { - - 'use strict'; - - $ = global.jQuery; - - let branding_style = $('.usa-radio input[name="branding_style"]:checked'); - - if (!branding_style.length) { return; } - - branding_style = branding_style.val(); - - const $paneWrapper = $('
'); - const $form = $('form'); - const previewType = $form.data('previewType'); - const $previewPane = $(``); - - function buildQueryString () { - return $.map(arguments, (val, idx) => encodeURI(val[0]) + '=' + encodeURI(val[1])).join('&'); - } - - function setPreviewPane (e) { - const $target = $(e.target); - if ($target.attr('name') == 'branding_style') { - branding_style = $target.val(); - } - $previewPane.attr('src', `/_${previewType}?${buildQueryString(['branding_style', branding_style])}`); - } - - $paneWrapper.append($previewPane); - $form.find('.govuk-grid-row').eq(0).prepend($paneWrapper); - $form.attr('action', location.pathname.replace(new RegExp(`set-${previewType}-branding$`), `preview-${previewType}-branding`)); - $form.find('button[type="submit"]').text('Save'); - - $('fieldset').on('change', 'input[name="branding_style"]', setPreviewPane); -})(window); diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index 9b88d44a7..341ee6d9a 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -45,12 +45,12 @@ i.e. font-size: size("body", 4); } &> .usa-nav__primary-item:last-child { - margin-left: auto; + margin-left: auto; @include u-margin-right(-4); } } -.usa-nav__primary +.usa-nav__primary h1 { @@ -132,11 +132,15 @@ td.table-empty-message { box-shadow: none; } +.template-list-item-hidden-by-default { + display: none +} + .folder-heading a.folder-heading-folder, .template-list-folder { background: url(../img/material-icons/folder.svg) no-repeat; padding-left: units(4); - display: inline-block; + display: inline-block; background-position: 0; } @@ -166,7 +170,7 @@ td.table-empty-message { .template-list-template { background: url(../img/material-icons/description.svg) no-repeat; padding-left: units(4); - display: inline-flex; + display: inline-flex; } .js-enabled .live-search { @@ -214,7 +218,7 @@ td.table-empty-message { } .usa-hero { - background-image: none; + background-image: none; } .usa-prose { @@ -232,7 +236,7 @@ td.table-empty-message { } .navigation-service.usa-breadcrumb { - -bottom: 0; + -bottom: 0; } // Dashboard @@ -246,13 +250,13 @@ td.table-empty-message { text-decoration: none; &:hover{ background: color("blue-warm-70v"); - } + } } span { - color: white; + color: white; } .big-number-smaller { - display: flex; + display: flex; flex-direction: column; .big-number-number { font-size: units(5); @@ -264,15 +268,15 @@ td.table-empty-message { } .big-number-status { background: color("green-cool-40v"); - display: flex; + display: flex; padding: units(1) units(2); &--failing { - padding: 0; + padding: 0; a.usa-link { color: white; background: color("red-warm-50v"); padding: units(1) units(2); - margin: 0; + margin: 0; width: 100%; &:hover { background: color("red-warm-60v"); @@ -282,12 +286,12 @@ td.table-empty-message { } } .usa-table { - width: 100%; + width: 100%; caption { - margin-bottom: 0; + margin-bottom: 0; } .table-field-center-aligned { - text-align: center; + text-align: center; } .template-statistics-table-template-name { padding-left: units(4); @@ -300,32 +304,32 @@ td.table-empty-message { .dashboard-table { table { - width: 100%; + width: 100%; } .file-list-filename { font-weight: bold; } .file-list-hint { - margin: 0; + margin: 0; } .table-field, .table-field-right-aligned { - width: 50%; + width: 50%; } &.usage-table { .table-field, .table-field-left-aligned, .table-field-right-aligned { - width: auto; + width: auto; } } } .usage-table { ul { - list-style: none; - padding: 0; - margin: 0; + list-style: none; + padding: 0; + margin: 0; } .big-number-smallest { - display: flex; + display: flex; flex-direction: column; } } @@ -336,27 +340,27 @@ td.table-empty-message { padding: units(1) 0 units(1) units(1); margin: units(2) 0 units(5); ul { - padding: 0; - margin: 0; - list-style: none; - } + padding: 0; + margin: 0; + list-style: none; + } } // Tabs .tabs { .pill { - display: flex; + display: flex; list-style: none; - padding: 0; + padding: 0; .pill-item__container { border: 1px solid color("gray-cool-10"); - flex: 1; - display: flex; - flex-direction: column; - text-align: center; + flex: 1; + display: flex; + flex-direction: column; + text-align: center; font-size: units(2); - a { + a { padding: units(4); .big-number-smaller { font-size: units(5); @@ -367,7 +371,7 @@ td.table-empty-message { } &:not(.pill-item--selected):hover { background: color("blue-warm-70v"); - } + } &.pill-item--selected:hover { color: color("blue-60v"); } @@ -379,12 +383,12 @@ td.table-empty-message { // Etc .email-brand, .browse-list { - padding: 0; - margin: 0; - list-style: none; + padding: 0; + margin: 0; + list-style: none; margin-bottom: units(2); li { - padding: 8px 0; + padding: 8px 0; } } @@ -392,7 +396,7 @@ details form { box-sizing:border-box; } -// Textbox highlight +// Textbox highlight .textbox-highlight { @@ -440,7 +444,7 @@ details form { .placeholder, .placeholder-conditional { background-color: #fff; - position: relative; + position: relative; &:after { content: ""; background-color: color("yellow-20v"); @@ -449,9 +453,9 @@ details form { position: absolute; top: 0; left: 5px; - right: 6px; + right: 6px; height: 100%; - border-radius: 7px; + border-radius: 7px; } } @@ -471,3 +475,7 @@ details form { padding-left: 5px; letter-spacing: 0.04em; } + +.edit-textbox-error-mt { + margin-top: 1.5rem; +} diff --git a/app/event_handlers.py b/app/event_handlers.py index 7e534a76d..629f566cb 100644 --- a/app/event_handlers.py +++ b/app/event_handlers.py @@ -24,6 +24,7 @@ EVENT_SCHEMAS = { "service_id", "ui_permissions", }, + "resend_user_invite_to_service": {"email_address", "resent_by_id", "service_id"}, "cancel_user_invite_to_service": {"email_address", "canceled_by_id", "service_id"}, "set_user_permissions": { "user_id", @@ -63,6 +64,10 @@ def create_cancel_user_invite_to_service_event(**kwargs): _send_event("cancel_user_invite_to_service", **kwargs) +def create_resend_user_invite_to_service_event(**kwargs): + _send_event("resend_user_invite_to_service", **kwargs) + + def create_add_user_to_service_event(**kwargs): _send_event("add_user_to_service", **kwargs) diff --git a/app/main/__init__.py b/app/main/__init__.py index 325a82e93..8626582f2 100644 --- a/app/main/__init__.py +++ b/app/main/__init__.py @@ -9,7 +9,6 @@ from app.main.views import ( # noqa isort:skip code_not_received, conversation, dashboard, - email_branding, feedback, find_services, find_users, diff --git a/app/main/forms.py b/app/main/forms.py index 2f18b3b88..2520474fc 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -61,9 +61,8 @@ from app.main.validators import ( ValidEmail, ValidGovEmail, ) -from app.models.feedback import PROBLEM_TICKET_TYPE, QUESTION_TICKET_TYPE from app.models.organization import Organization -from app.utils import branding, merge_jsonlike +from app.utils import merge_jsonlike from app.utils.csv import get_user_preferred_timezone from app.utils.user_permissions import all_ui_permissions, permission_options @@ -815,49 +814,6 @@ class GovukCheckboxField(BooleanField): ) -class GovukTextareaField(TextAreaField): - def __init__(self, label="", validators=None, param_extensions=None, **kwargs): - super(TextAreaField, self).__init__(label, validators, **kwargs) - self.param_extensions = param_extensions - - # self.__call__ renders the HTML for the field by: - # 1. delegating to self.meta.render_field which - # 2. calls field.widget - # this bypasses that by making self.widget a method with the same interface as widget.__call__ - def widget(self, field, param_extensions=None, **kwargs): - # error messages - error_message = None - if field.errors: - error_message = {"text": field.errors[0]} - - params = { - "name": field.name, - "id": field.id, - "rows": 8, - "label": { - "text": field.label.text, - "classes": None, - "isPageHeading": False, - }, - "hint": {"text": None}, - "errorMessage": error_message, - } - - # extend default params with any sent in during instantiation - if self.param_extensions: - merge_jsonlike(params, self.param_extensions) - - # add any sent in though use in templates - if param_extensions: - merge_jsonlike(params, param_extensions) - - return Markup( - render_template( - "components/components/textarea/template.njk", params=params - ) - ) - - # based on work done by @richardjpope: https://github.com/richardjpope/recourse/blob/master/recourse/forms.py#L6 class GovukCheckboxesField(SelectMultipleField): render_as_list = False @@ -1362,49 +1318,6 @@ class CreateKeyForm(StripWhitespaceForm): raise ValidationError("A key with this name already exists") -class SupportType(StripWhitespaceForm): - support_type = GovukRadiosField( - "How can we help you?", - choices=[ - (PROBLEM_TICKET_TYPE, "Report a problem"), - (QUESTION_TICKET_TYPE, "Ask a question or give feedback"), - ], - ) - - -class SupportRedirect(StripWhitespaceForm): - who = GovukRadiosField( - "What do you need help with?", - choices=[ - ( - "public-sector", - "I work in the public sector and need to send emails or text messages", - ), - ("public", "I’m a member of the public with a question for the government"), - ], - param_extensions={"fieldset": {"legend": {"classes": "usa-sr-only"}}}, - ) - - -class FeedbackOrProblem(StripWhitespaceForm): - name = GovukTextInputField("Name (optional)") - email_address = email_address(label="Email address", gov_user=False, required=True) - feedback = TextAreaField( - "Your message", validators=[DataRequired(message="Cannot be empty")] - ) - - -class Triage(StripWhitespaceForm): - severe = GovukRadiosField( - "Is it an emergency?", - choices=[ - ("yes", "Yes"), - ("no", "No"), - ], - thing="yes or no", - ) - - class EstimateUsageForm(StripWhitespaceForm): volume_email = ForgivingIntegerField( "How many emails do you expect to send in the next year?", @@ -1543,65 +1456,6 @@ class ServiceSwitchChannelForm(ServiceOnOffSettingForm): super().__init__(name, *args, **kwargs) -class AdminSetEmailBrandingForm(StripWhitespaceForm): - branding_style = GovukRadiosFieldWithNoneOption( - "Branding style", - param_extensions={"fieldset": {"legend": {"classes": "usa-sr-only"}}}, - thing="a branding style", - ) - - DEFAULT = (FieldWithNoneOption.NONE_OPTION_VALUE, "GOV.UK") - - def __init__(self, all_branding_options, current_branding): - super().__init__(branding_style=current_branding) - - self.branding_style.choices = sorted( - all_branding_options + [self.DEFAULT], - key=lambda branding: ( - branding[0] != current_branding, - branding[0] is not self.DEFAULT[0], - branding[1].lower(), - ), - ) - - -class AdminPreviewBrandingForm(StripWhitespaceForm): - branding_style = HiddenFieldWithNoneOption("branding_style") - - -class AdminEditEmailBrandingForm(StripWhitespaceForm): - name = GovukTextInputField("Name of brand") - text = GovukTextInputField("Text") - colour = GovukTextInputField( - "Colour", - validators=[ - Regexp( - regex="^$|^#(?:[0-9a-fA-F]{3}){1,2}$", - message="Must be a valid color hex code (starting with #)", - ) - ], - param_extensions={ - "attributes": {"data-module": "colour-preview"}, - }, - ) - file = FileField_wtf( - "Upload a PNG logo", validators=[FileAllowed(["png"], "PNG Images only!")] - ) - brand_type = GovukRadiosField( - "Brand type", - choices=[ - ("both", "GOV.UK and branding"), - ("org", "Branding only"), - ("org_banner", "Branding banner"), - ], - ) - - def validate_name(self, name): - op = request.form.get("operation") - if op == "email-branding-details" and not self.name.data: - raise ValidationError("This field is required") - - class SVGFileUpload(StripWhitespaceForm): file = FileField_wtf( "Upload an SVG logo", @@ -1816,42 +1670,6 @@ class AdminSetOrganizationForm(StripWhitespaceForm): ) -class ChooseBrandingForm(StripWhitespaceForm): - FALLBACK_OPTION_VALUE = "something_else" - FALLBACK_OPTION = (FALLBACK_OPTION_VALUE, "Something else") - - @property - def something_else_is_only_option(self): - return self.options.choices == (self.FALLBACK_OPTION,) - - -class ChooseEmailBrandingForm(ChooseBrandingForm): - options = RadioField("Choose your new email branding") - - def __init__(self, service): - super().__init__() - - self.options.choices = tuple( - list(branding.get_email_choices(service)) + [self.FALLBACK_OPTION] - ) - - -class SomethingElseBrandingForm(StripWhitespaceForm): - something_else = GovukTextareaField( - "Describe the branding you want", - validators=[DataRequired("Cannot be empty")], - param_extensions={ - "label": { - "isPageHeading": True, - "classes": "font-body-xl", - }, - "hint": { - "text": "Include links to your brand guidelines or examples of how to use your branding." - }, - }, - ) - - class AdminServiceAddDataRetentionForm(StripWhitespaceForm): notification_type = GovukRadiosField( "What notification type?", @@ -2043,13 +1861,6 @@ class AdminClearCacheForm(StripWhitespaceForm): raise ValidationError("Select at least one option") -class AdminOrganizationGoLiveNotesForm(StripWhitespaceForm): - request_to_go_live_notes = TextAreaField( - "Go live notes", - filters=[lambda x: x or None], - ) - - class ChangeSecurityKeyNameForm(StripWhitespaceForm): security_key_name = GovukTextInputField( "Name of key", diff --git a/app/main/views/email_branding.py b/app/main/views/email_branding.py deleted file mode 100644 index 4a050f849..000000000 --- a/app/main/views/email_branding.py +++ /dev/null @@ -1,133 +0,0 @@ -from flask import current_app, redirect, render_template, session, url_for - -from app import email_branding_client -from app.main import main -from app.main.forms import AdminEditEmailBrandingForm, SearchByNameForm -from app.s3_client.s3_logo_client import ( - TEMP_TAG, - delete_email_temp_file, - delete_email_temp_files_created_by, - permanent_email_logo_name, - persist_logo, - upload_email_logo, -) -from app.utils.user import user_is_platform_admin - - -@main.route("/email-branding", methods=["GET", "POST"]) -@user_is_platform_admin -def email_branding(): - brandings = email_branding_client.get_all_email_branding(sort_key="name") - - return render_template( - "views/email-branding/select-branding.html", - email_brandings=brandings, - search_form=SearchByNameForm(), - ) - - -@main.route("/email-branding//edit", methods=["GET", "POST"]) -@main.route("/email-branding//edit/", methods=["GET", "POST"]) -@user_is_platform_admin -def update_email_branding(branding_id, logo=None): - email_branding = email_branding_client.get_email_branding(branding_id)[ - "email_branding" - ] - - form = AdminEditEmailBrandingForm( - name=email_branding["name"], - text=email_branding["text"], - colour=email_branding["colour"], - brand_type=email_branding["brand_type"], - ) - - logo = logo if logo else email_branding.get("logo") if email_branding else None - - if form.validate_on_submit(): - if form.file.data: - upload_filename = upload_email_logo( - form.file.data.filename, form.file.data, user_id=session["user_id"] - ) - - if logo and logo.startswith(TEMP_TAG.format(user_id=session["user_id"])): - delete_email_temp_file(logo) - - return redirect( - url_for( - ".update_email_branding", - branding_id=branding_id, - logo=upload_filename, - ) - ) - - updated_logo_name = ( - permanent_email_logo_name(logo, session["user_id"]) if logo else None - ) - - email_branding_client.update_email_branding( - branding_id=branding_id, - logo=updated_logo_name, - name=form.name.data, - text=form.text.data, - colour=form.colour.data, - brand_type=form.brand_type.data, - ) - - if logo: - persist_logo(logo, updated_logo_name) - - delete_email_temp_files_created_by(session["user_id"]) - - return redirect(url_for(".email_branding", branding_id=branding_id)) - - return render_template( - "views/email-branding/manage-branding.html", - form=form, - email_branding=email_branding, - cdn_url=current_app.config["LOGO_CDN_DOMAIN"], - logo=logo, - ) - - -@main.route("/email-branding/create", methods=["GET", "POST"]) -@main.route("/email-branding/create/", methods=["GET", "POST"]) -@user_is_platform_admin -def create_email_branding(logo=None): - form = AdminEditEmailBrandingForm(brand_type="org") - - if form.validate_on_submit(): - if form.file.data: - upload_filename = upload_email_logo( - form.file.data.filename, form.file.data, user_id=session["user_id"] - ) - - if logo and logo.startswith(TEMP_TAG.format(user_id=session["user_id"])): - delete_email_temp_file(logo) - - return redirect(url_for(".create_email_branding", logo=upload_filename)) - - updated_logo_name = ( - permanent_email_logo_name(logo, session["user_id"]) if logo else None - ) - - email_branding_client.create_email_branding( - logo=updated_logo_name, - name=form.name.data, - text=form.text.data, - colour=form.colour.data, - brand_type=form.brand_type.data, - ) - - if logo: - persist_logo(logo, updated_logo_name) - - delete_email_temp_files_created_by(session["user_id"]) - - return redirect(url_for(".email_branding")) - - return render_template( - "views/email-branding/manage-branding.html", - form=form, - cdn_url=current_app.config["LOGO_CDN_DOMAIN"], - logo=logo, - ) diff --git a/app/main/views/feedback.py b/app/main/views/feedback.py index a68cc798a..26399f9db 100644 --- a/app/main/views/feedback.py +++ b/app/main/views/feedback.py @@ -1,239 +1,10 @@ -from datetime import datetime +from flask import render_template -import pytz -from flask import redirect, render_template, request, session, url_for -from flask_login import current_user -from govuk_bank_holidays.bank_holidays import BankHolidays -from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket - -from app import convert_to_boolean, current_service -from app.extensions import zendesk_client from app.main import main -from app.main.forms import FeedbackOrProblem, SupportRedirect, SupportType, Triage -from app.models.feedback import ( - GENERAL_TICKET_TYPE, - PROBLEM_TICKET_TYPE, - QUESTION_TICKET_TYPE, -) -from app.utils import hide_from_search_engines - -bank_holidays = BankHolidays(use_cached_holidays=True) +from app.utils.user import user_is_logged_in -@main.route("/support", methods=["GET", "POST"]) -@hide_from_search_engines +@main.route("/support", methods=["GET"]) +@user_is_logged_in def support(): - if current_user.is_authenticated: - form = SupportType() - if form.validate_on_submit(): - return redirect( - url_for( - ".feedback", - ticket_type=form.support_type.data, - ) - ) - else: - form = SupportRedirect() - if form.validate_on_submit(): - if form.who.data == "public": - return redirect(url_for(".support_public")) - else: - return redirect( - url_for( - ".feedback", - ticket_type=GENERAL_TICKET_TYPE, - ) - ) - - return render_template("views/support/index.html", form=form) - - -@main.route("/support/public") -@hide_from_search_engines -def support_public(): - return render_template("views/support/public.html") - - -@main.route("/support/triage", methods=["GET", "POST"]) -@main.route("/support/triage/", methods=["GET", "POST"]) -@hide_from_search_engines -def triage(ticket_type=PROBLEM_TICKET_TYPE): - form = Triage() - if form.validate_on_submit(): - return redirect( - url_for(".feedback", ticket_type=ticket_type, severe=form.severe.data) - ) - return render_template( - "views/support/triage.html", - form=form, - page_title={ - PROBLEM_TICKET_TYPE: "Report a problem", - GENERAL_TICKET_TYPE: "Contact Notify.gov support", - }.get(ticket_type), - ) - - -@main.route("/support/", methods=["GET", "POST"]) -@hide_from_search_engines -def feedback(ticket_type): - form = FeedbackOrProblem() - - if not form.feedback.data: - form.feedback.data = session.pop("feedback_message", "") - - if request.args.get("severe") in ["yes", "no"]: - severe = convert_to_boolean(request.args.get("severe")) - else: - severe = None - - out_of_hours_emergency = all( - ( - ticket_type != QUESTION_TICKET_TYPE, - not in_business_hours(), - severe, - ) - ) - - if needs_triage(ticket_type, severe): - session["feedback_message"] = form.feedback.data - return redirect(url_for(".triage", ticket_type=ticket_type)) - - if needs_escalation(ticket_type, severe): - return redirect(url_for(".bat_phone")) - - if current_user.is_authenticated: - form.email_address.data = current_user.email_address - form.name.data = current_user.name - - if form.validate_on_submit(): - user_email = form.email_address.data - user_name = form.name.data or None - - feedback_msg = render_template( - "support-tickets/support-ticket.txt", - content=form.feedback.data, - ) - - ticket = NotifySupportTicket( - subject="Notify feedback", - message=feedback_msg, - ticket_type=get_zendesk_ticket_type(ticket_type), - p1=out_of_hours_emergency, - user_name=user_name, - user_email=user_email, - org_id=current_service.organization_id if current_service else None, - org_type=current_service.organization_type if current_service else None, - service_id=current_service.id if current_service else None, - ) - zendesk_client.send_ticket_to_zendesk(ticket) - - return redirect( - url_for( - ".thanks", - out_of_hours_emergency=out_of_hours_emergency, - email_address_provided=( - current_user.is_authenticated or bool(form.email_address.data) - ), - ) - ) - - return render_template( - "views/support/form.html", - form=form, - back_link=( - url_for(".support") - if severe is None - else url_for(".triage", ticket_type=ticket_type) - ), - show_status_page_banner=(ticket_type == PROBLEM_TICKET_TYPE), - page_title={ - GENERAL_TICKET_TYPE: "Contact Notify.gov support", - PROBLEM_TICKET_TYPE: "Report a problem", - QUESTION_TICKET_TYPE: "Ask a question or give feedback", - }.get(ticket_type), - ) - - -@main.route("/support/escalate", methods=["GET", "POST"]) -@hide_from_search_engines -def bat_phone(): - if current_user.is_authenticated: - return redirect(url_for("main.feedback", ticket_type=PROBLEM_TICKET_TYPE)) - - return render_template("views/support/bat-phone.html") - - -@main.route("/support/thanks", methods=["GET", "POST"]) -@hide_from_search_engines -def thanks(): - return render_template( - "views/support/thanks.html", - out_of_hours_emergency=convert_to_boolean( - request.args.get("out_of_hours_emergency") - ), - email_address_provided=convert_to_boolean( - request.args.get("email_address_provided") - ), - out_of_hours=not in_business_hours(), - ) - - -def in_business_hours(): - now = datetime.utcnow().replace(tzinfo=pytz.utc) - - if is_weekend(now) or is_bank_holiday(now): - return False - - return london_time_today_as_utc(9, 30) <= now < london_time_today_as_utc(17, 30) - - -def london_time_today_as_utc(hour, minute): - return ( - pytz.timezone("Europe/London") - .localize(datetime.now().replace(hour=hour, minute=minute)) - .astimezone(pytz.utc) - ) - - -def is_weekend(time): - return time.strftime("%A") in { - "Saturday", - "Sunday", - } - - -def is_bank_holiday(time): - return bank_holidays.is_holiday(time.date()) - - -def needs_triage(ticket_type, severe): - return all( - ( - ticket_type != QUESTION_TICKET_TYPE, - severe is None, - (not current_user.is_authenticated or current_user.live_services), - not in_business_hours(), - ) - ) - - -def needs_escalation(ticket_type, severe): - return all( - ( - ticket_type != QUESTION_TICKET_TYPE, - severe, - not current_user.is_authenticated, - not in_business_hours(), - ) - ) - - -def get_zendesk_ticket_type(ticket_type): - # Zendesk has 4 ticket types - "problem", "incident", "task" and "question". - # We don't want to use a Zendesk "problem" ticket type when someone reports a - # Notify problem because they are designed to group multiple incident tickets together, - # allowing them to be solved as a group. - if ticket_type == PROBLEM_TICKET_TYPE: - return NotifySupportTicket.TYPE_INCIDENT - - return NotifySupportTicket.TYPE_QUESTION + return render_template("views/support/index.html") diff --git a/app/main/views/index.py b/app/main/views/index.py index 69934c893..d6d08150d 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -1,19 +1,9 @@ -from flask import ( - abort, - current_app, - make_response, - redirect, - render_template, - request, - url_for, -) +from flask import abort, redirect, render_template, request, url_for from flask_login import current_user -from notifications_utils.template import HTMLEmailTemplate -from app import email_branding_client, status_api_client +from app import status_api_client from app.formatters import convert_markdown_template from app.main import main -from app.main.forms import FieldWithNoneOption from app.main.views.pricing import CURRENT_SMS_RATE from app.main.views.sub_navigation_dictionaries import features_nav, using_notify_nav from app.utils.user import user_is_logged_in @@ -64,105 +54,6 @@ def design_content(): ) -@main.route("/_email") -@user_is_logged_in -def email_template(): - branding_type = "govuk" - branding_style = request.args.get("branding_style", None) - - if branding_style == FieldWithNoneOption.NONE_OPTION_VALUE: - branding_style = None - - if branding_style is not None: - email_branding = email_branding_client.get_email_branding(branding_style)[ - "email_branding" - ] - branding_type = email_branding["brand_type"] - - if branding_type == "govuk": - brand_text = None - brand_colour = None - brand_logo = None - govuk_banner = True - brand_banner = False - brand_name = None - else: - colour = email_branding["colour"] - brand_text = email_branding["text"] - brand_colour = colour - brand_logo = ( - f"https://{current_app.config['LOGO_CDN_DOMAIN']}/{email_branding['logo']}" - if email_branding["logo"] - else None - ) - govuk_banner = branding_type in ["govuk", "both"] - brand_banner = branding_type == "org_banner" - brand_name = email_branding["name"] - - template = { - "template_type": "email", - "subject": "Email branding preview", - "content": ( - "Lorem Ipsum is simply dummy text of the printing and typesetting " - "industry.\n\nLorem Ipsum has been the industry’s standard dummy " - "text ever since the 1500s, when an unknown printer took a galley " - "of type and scrambled it to make a type specimen book. " - "\n\n" - "# History" - "\n\n" - "It has " - "survived not only" - "\n\n" - "* five centuries" - "\n" - "* but also the leap into electronic typesetting" - "\n\n" - "It was " - "popularised in the 1960s with the release of Letraset sheets " - "containing Lorem Ipsum passages, and more recently with desktop " - "publishing software like Aldus PageMaker including versions of " - "Lorem Ipsum." - "\n\n" - "^ It is a long established fact that a reader will be distracted " - "by the readable content of a page when looking at its layout." - "\n\n" - "The point of using Lorem Ipsum is that it has a more-or-less " - "normal distribution of letters, as opposed to using ‘Content " - "here, content here’, making it look like readable English." - "\n\n\n" - "1. One" - "\n" - "2. Two" - "\n" - "10. Three" - "\n\n" - "This is an example of an email sent using Notify.gov." - "\n\n" - "https://www.notifications.service.gov.uk" - ), - } - - if not bool(request.args): - resp = make_response(str(HTMLEmailTemplate(template))) - else: - resp = make_response( - str( - HTMLEmailTemplate( - template, - govuk_banner=govuk_banner, - brand_text=brand_text, - brand_colour=brand_colour, - brand_logo=brand_logo, - brand_banner=brand_banner, - brand_name=brand_name, - ) - ) - ) - - resp.headers["X-Frame-Options"] = "SAMEORIGIN" - return resp - - @main.route("/documentation") @user_is_logged_in def documentation(): @@ -197,14 +88,6 @@ def roadmap(): return render_template("views/roadmap.html", navigation_links=features_nav()) -@main.route("/features/email") -@user_is_logged_in -def features_email(): - return render_template( - "views/features/emails.html", navigation_links=features_nav() - ) - - @main.route("/features/sms") @user_is_logged_in def features_sms(): @@ -290,15 +173,6 @@ def guidance_index(): ) -@main.route("/using-notify/guidance/branding-and-customisation") -@user_is_logged_in -def branding_and_customisation(): - return render_template( - "views/guidance/branding-and-customisation.html", - navigation_links=using_notify_nav(), - ) - - @main.route("/using-notify/guidance/create-and-send-messages") @user_is_logged_in def create_and_send_messages(): diff --git a/app/main/views/manage_users.py b/app/main/views/manage_users.py index 3daaa38d2..4ab20f363 100644 --- a/app/main/views/manage_users.py +++ b/app/main/views/manage_users.py @@ -9,6 +9,7 @@ from app.event_handlers import ( create_invite_user_to_service_event, create_mobile_number_change_event, create_remove_user_from_service_event, + create_resend_user_invite_to_service_event, ) from app.formatters import redact_mobile_number from app.main import main @@ -331,3 +332,22 @@ def cancel_invited_user(service_id, invited_user_id): flash(f"Invitation cancelled for {invited_user.email_address}", "default_with_tick") return redirect(url_for("main.manage_users", service_id=service_id)) + + +@main.route( + "/services//resend-invite/", + methods=["GET"], +) +@user_has_permissions("manage_service") +def resend_invite(service_id, invited_user_id): + current_service.resend_invite(invited_user_id) + + invited_user = InvitedUser.by_id_and_service_id(service_id, invited_user_id) + create_resend_user_invite_to_service_event( + email_address=invited_user.email_address, + resent_by_id=current_user.id, + service_id=service_id, + ) + + flash(f"Invitation resent for {invited_user.email_address}", "default_with_tick") + return redirect(url_for("main.manage_users", service_id=service_id)) diff --git a/app/main/views/notifications.py b/app/main/views/notifications.py index f64fdbdd4..d62fded65 100644 --- a/app/main/views/notifications.py +++ b/app/main/views/notifications.py @@ -3,6 +3,7 @@ from datetime import datetime from flask import ( Response, + flash, jsonify, render_template, request, @@ -32,14 +33,16 @@ from app.utils.user import user_has_permissions @main.route("/services//notification/") @user_has_permissions("view_activity", "send_messages") -def view_notification(service_id, notification_id): +def view_notification(service_id, notification_id, error_message=None): + if error_message: + flash(error_message) + notification = notification_api_client.get_notification( service_id, str(notification_id) ) notification["template"].update({"reply_to_text": notification["reply_to_text"]}) personalisation = get_all_personalisation_from_notification(notification) - error_message = None template = get_template( notification["template"], diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index 1a1ba44f2..14ccc9de2 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -6,21 +6,13 @@ from flask import flash, redirect, render_template, request, url_for from flask_login import current_user from notifications_python_client.errors import HTTPError -from app import ( - current_organization, - email_branding_client, - org_invite_api_client, - organizations_client, -) +from app import current_organization, org_invite_api_client, organizations_client from app.main import main from app.main.forms import ( AdminBillingDetailsForm, AdminNewOrganizationForm, AdminNotesForm, AdminOrganizationDomainsForm, - AdminOrganizationGoLiveNotesForm, - AdminPreviewBrandingForm, - AdminSetEmailBrandingForm, InviteOrgUserForm, OrganizationOrganizationTypeForm, RenameOrganizationForm, @@ -31,7 +23,6 @@ from app.main.views.dashboard import ( get_tuples_of_financial_years, requested_and_current_financial_year, ) -from app.main.views.service_settings import get_branding_as_value_and_label from app.models.organization import AllOrganizations, Organization from app.models.user import InvitedOrgUser, User from app.utils.csv import Spreadsheet @@ -283,58 +274,6 @@ def edit_organization_type(org_id): ) -@main.route( - "/organizations//settings/set-email-branding", methods=["GET", "POST"] -) -@user_is_platform_admin -def edit_organization_email_branding(org_id): - email_branding = email_branding_client.get_all_email_branding() - - form = AdminSetEmailBrandingForm( - all_branding_options=get_branding_as_value_and_label(email_branding), - current_branding=current_organization.email_branding_id, - ) - - if form.validate_on_submit(): - return redirect( - url_for( - ".organization_preview_email_branding", - org_id=org_id, - branding_style=form.branding_style.data, - ) - ) - - return render_template( - "views/organizations/organization/settings/set-email-branding.html", - form=form, - search_form=SearchByNameForm(), - ) - - -@main.route( - "/organizations//settings/preview-email-branding", - methods=["GET", "POST"], -) -@user_is_platform_admin -def organization_preview_email_branding(org_id): - branding_style = request.args.get("branding_style", None) - - form = AdminPreviewBrandingForm(branding_style=branding_style) - - if form.validate_on_submit(): - current_organization.update( - email_branding_id=form.branding_style.data, - delete_services_cache=True, - ) - return redirect(url_for(".organization_settings", org_id=org_id)) - - return render_template( - "views/organizations/organization/settings/preview-email-branding.html", - form=form, - action=url_for("main.organization_preview_email_branding", org_id=org_id), - ) - - @main.route( "/organizations//settings/edit-organization-domains", methods=["GET", "POST"], @@ -373,28 +312,6 @@ def edit_organization_domains(org_id): ) -@main.route( - "/organizations//settings/edit-go-live-notes", methods=["GET", "POST"] -) -@user_is_platform_admin -def edit_organization_go_live_notes(org_id): - form = AdminOrganizationGoLiveNotesForm() - - if form.validate_on_submit(): - organizations_client.update_organization( - org_id, request_to_go_live_notes=form.request_to_go_live_notes.data - ) - return redirect(url_for(".organization_settings", org_id=org_id)) - - org = organizations_client.get_organization(org_id) - form.request_to_go_live_notes.data = org["request_to_go_live_notes"] - - return render_template( - "views/organizations/organization/settings/edit-go-live-notes.html", - form=form, - ) - - @main.route("/organizations//settings/notes", methods=["GET", "POST"]) @user_is_platform_admin def edit_organization_notes(org_id): diff --git a/app/main/views/platform_admin.py b/app/main/views/platform_admin.py index f953d1e58..802ed5b18 100644 --- a/app/main/views/platform_admin.py +++ b/app/main/views/platform_admin.py @@ -596,13 +596,6 @@ def clear_cache(): "service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-versions", # noqa ], ), - ( - "email_branding", - [ - "email_branding", - "email_branding-????????-????-????-????-????????????", - ], - ), ( "organization", [ diff --git a/app/main/views/send.py b/app/main/views/send.py index 2ba60de23..fe36c961b 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -1,17 +1,10 @@ import itertools +import time +import uuid from string import ascii_uppercase from zipfile import BadZipFile -from flask import ( - abort, - current_app, - flash, - redirect, - render_template, - request, - session, - url_for, -) +from flask import abort, flash, redirect, render_template, request, session, url_for from flask_login import current_user from notifications_python_client.errors import HTTPError from notifications_utils import SMS_CHAR_COUNT_LIMIT @@ -495,7 +488,6 @@ def _check_messages(service_id, template_id, upload_id, preview_row): remaining_messages = current_service.message_limit - notification_count contents = s3download(service_id, upload_id) - db_template = current_service.get_template_with_user_permission_or_403( template_id, current_user ) @@ -836,7 +828,6 @@ def get_template_error_dict(exception): @user_has_permissions("send_messages", restrict_admin_usage=True) def send_notification(service_id, template_id): recipient = get_recipient() - if not recipient: return redirect( url_for( @@ -846,38 +837,69 @@ def send_notification(service_id, template_id): ) ) - db_template = current_service.get_template_with_user_permission_or_403( - template_id, current_user + keys = [] + values = [] + for k, v in session["placeholders"].items(): + keys.append(k) + values.append(v) + + data = ",".join(keys) + vals = ",".join(values) + data = f"{data}\r\n{vals}" + + filename = f"one-off-{current_user.name}-{uuid.uuid4()}.csv" + my_data = {"filename": filename, "template_id": template_id, "data": data} + upload_id = s3upload(service_id, my_data) + form = CsvUploadForm() + form.file.data = my_data + form.file.name = filename + + check_message_output = check_messages(service_id, template_id, upload_id, 2) + if "You cannot send to" in check_message_output: + return check_messages(service_id, template_id, upload_id, 2) + + job_api_client.create_job( + upload_id, + service_id, + scheduled_for="", + template_id=template_id, + original_file_name=filename, + notification_count=1, + valid="True", ) - try: - noti = notification_api_client.send_notification( - service_id, - template_id=db_template["id"], - recipient=recipient, - personalisation=session["placeholders"], - sender_id=session.get("sender_id", None), + session.pop("recipient") + session.pop("placeholders") + + # We have to wait for the job to run and create the notification in the database + time.sleep(0.1) + notifications = notification_api_client.get_notifications_for_service( + service_id, job_id=upload_id, include_one_off=True + ) + attempts = 0 + while notifications["total"] == 0 and attempts < 5: + notifications = notification_api_client.get_notifications_for_service( + service_id, job_id=upload_id, include_one_off=True ) - except HTTPError as exception: - current_app.logger.error( - 'Service {} could not send notification: "{}"'.format( - current_service.id, exception.message + time.sleep(0.1) + attempts = attempts + 1 + + if notifications["total"] == 0 and attempts == 5: + # This shows the job we auto-generated for the user + return redirect( + url_for( + "main.view_job", + service_id=service_id, + job_id=upload_id, ) ) - return render_template( - "views/notifications/check.html", - **_check_notification(service_id, template_id, exception), - ) - - session.pop("placeholders") - session.pop("recipient") - session.pop("sender_id", None) return redirect( url_for( ".view_notification", service_id=service_id, - notification_id=noti["id"], + from_job=upload_id, + notification_id=notifications["notifications"][0]["id"], # used to show the final step of the tour (help=3) or not show # a back link on a just sent one off notification (help=0) help=request.args.get("help"), diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index 70ad3ef92..feb1e4388 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -13,12 +13,10 @@ from flask import ( ) from flask_login import current_user from notifications_python_client.errors import HTTPError -from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket from app import ( billing_api_client, current_service, - email_branding_client, inbound_number_client, notification_api_client, organizations_client, @@ -29,23 +27,18 @@ from app.event_handlers import ( create_resume_service_event, create_suspend_service_event, ) -from app.extensions import zendesk_client from app.formatters import email_safe from app.main import main from app.main.forms import ( AdminBillingDetailsForm, AdminNotesForm, - AdminPreviewBrandingForm, AdminServiceAddDataRetentionForm, AdminServiceEditDataRetentionForm, AdminServiceInboundNumberForm, AdminServiceMessageLimitForm, AdminServiceRateLimitForm, AdminServiceSMSAllowanceForm, - AdminSetEmailBrandingForm, AdminSetOrganizationForm, - ChooseEmailBrandingForm, - EstimateUsageForm, RenameServiceForm, SearchByNameForm, ServiceContactDetailsForm, @@ -55,16 +48,10 @@ from app.main.forms import ( ServiceSmsSenderForm, ServiceSwitchChannelForm, SMSPrefixForm, - SomethingElseBrandingForm, ) from app.utils import DELIVERED_STATUSES, FAILURE_STATUSES, SENDING_STATUSES -from app.utils.branding import get_email_choices as get_email_branding_choices from app.utils.time import parse_naive_dt -from app.utils.user import ( - user_has_permissions, - user_is_gov_user, - user_is_platform_admin, -) +from app.utils.user import user_has_permissions, user_is_platform_admin PLATFORM_ADMIN_SERVICE_PERMISSIONS = OrderedDict( [ @@ -87,7 +74,6 @@ def service_settings(service_id): return render_template( "views/service-settings.html", service_permissions=PLATFORM_ADMIN_SERVICE_PERMISSIONS, - email_branding_options=ChooseEmailBrandingForm(current_service), ) @@ -127,81 +113,6 @@ def service_name_change(service_id): ) -@main.route( - "/services//service-settings/request-to-go-live/estimate-usage", - methods=["GET", "POST"], -) -@user_has_permissions("manage_service") -def estimate_usage(service_id): - form = EstimateUsageForm( - volume_email=current_service.volume_email, - volume_sms=current_service.volume_sms, - consent_to_research={ - True: "yes", - False: "no", - }.get(current_service.consent_to_research), - ) - - if form.validate_on_submit(): - current_service.update( - volume_email=form.volume_email.data, - volume_sms=form.volume_sms.data, - consent_to_research=(form.consent_to_research.data == "yes"), - ) - return redirect( - url_for( - "main.request_to_go_live", - service_id=service_id, - ) - ) - - return render_template( - "views/service-settings/estimate-usage.html", - form=form, - ) - - -@main.route( - "/services//service-settings/request-to-go-live", methods=["GET"] -) -@user_has_permissions("manage_service") -def request_to_go_live(service_id): - if current_service.live: - return render_template("views/service-settings/service-already-live.html") - - return render_template("views/service-settings/request-to-go-live.html") - - -@main.route( - "/services//service-settings/request-to-go-live", methods=["POST"] -) -@user_has_permissions("manage_service") -@user_is_gov_user -def submit_request_to_go_live(service_id): - ticket_message = render_template("support-tickets/go-live-request.txt") + "\n" - - ticket = NotifySupportTicket( - subject=f"Request to go live - {current_service.name}", - message=ticket_message, - ticket_type=NotifySupportTicket.TYPE_QUESTION, - user_name=current_user.name, - user_email=current_user.email_address, - requester_sees_message_content=False, - org_id=current_service.organization_id, - org_type=current_service.organization_type, - service_id=current_service.id, - ) - zendesk_client.send_ticket_to_zendesk(ticket) - - current_service.update(go_live_user=current_user.id) - - flash( - "Thanks for your request to go live. We’ll get back to you within one working day.", - "default", - ) - return redirect(url_for(".service_settings", service_id=service_id)) - - @main.route( "/services//service-settings/switch-live", methods=["GET", "POST"] ) @@ -876,57 +787,6 @@ def set_rate_limit(service_id): ) -@main.route( - "/services//service-settings/set-email-branding", - methods=["GET", "POST"], -) -@user_is_platform_admin -def service_set_email_branding(service_id): - email_branding = email_branding_client.get_all_email_branding() - - form = AdminSetEmailBrandingForm( - all_branding_options=get_branding_as_value_and_label(email_branding), - current_branding=current_service.email_branding_id, - ) - - if form.validate_on_submit(): - return redirect( - url_for( - ".service_preview_email_branding", - service_id=service_id, - branding_style=form.branding_style.data, - ) - ) - - return render_template( - "views/service-settings/set-email-branding.html", - form=form, - search_form=SearchByNameForm(), - ) - - -@main.route( - "/services//service-settings/preview-email-branding", - methods=["GET", "POST"], -) -@user_is_platform_admin -def service_preview_email_branding(service_id): - branding_style = request.args.get("branding_style", None) - - form = AdminPreviewBrandingForm(branding_style=branding_style) - - if form.validate_on_submit(): - current_service.update(email_branding=form.branding_style.data) - return redirect(url_for(".service_settings", service_id=service_id)) - - return render_template( - "views/service-settings/preview-email-branding.html", - form=form, - service_id=service_id, - action=url_for("main.service_preview_email_branding", service_id=service_id), - ) - - @main.route( "/services//service-settings/link-service-to-organization", methods=["GET", "POST"], @@ -957,145 +817,6 @@ def link_service_to_organization(service_id): ) -def create_email_branding_zendesk_ticket(form_option_selected, detail=None): - form = ChooseEmailBrandingForm(current_service) - - ticket_message = render_template( - "support-tickets/branding-request.txt", - current_branding=current_service.email_branding_name, - branding_requested=dict(form.options.choices)[form_option_selected], - detail=detail, - ) - ticket = NotifySupportTicket( - subject=f"Email branding request - {current_service.name}", - message=ticket_message, - ticket_type=NotifySupportTicket.TYPE_QUESTION, - user_name=current_user.name, - user_email=current_user.email_address, - org_id=current_service.organization_id, - org_type=current_service.organization_type, - service_id=current_service.id, - ) - zendesk_client.send_ticket_to_zendesk(ticket) - - -@main.route( - "/services//service-settings/email-branding", - methods=["GET", "POST"], -) -@user_has_permissions("manage_service") -def email_branding_request(service_id): - form = ChooseEmailBrandingForm(current_service) - branding_name = current_service.email_branding_name - if form.validate_on_submit(): - return redirect( - url_for( - f".email_branding_{form.options.data}", - service_id=current_service.id, - ) - ) - - return render_template( - "views/service-settings/branding/email-branding-options.html", - form=form, - branding_name=branding_name, - ) - - -def check_email_branding_allowed_for_service(branding): - allowed_branding_for_service = dict(get_email_branding_choices(current_service)) - - if branding not in allowed_branding_for_service: - abort(404) - - -@main.route( - "/services//service-settings/email-branding/govuk", - methods=["GET", "POST"], -) -@user_has_permissions("manage_service") -def email_branding_govuk(service_id): - check_email_branding_allowed_for_service("govuk") - - if request.method == "POST": - current_service.update(email_branding=None) - - flash("You’ve updated your email branding", "default") - return redirect(url_for(".service_settings", service_id=current_service.id)) - - return render_template("views/service-settings/branding/email-branding-govuk.html") - - -@main.route( - "/services//service-settings/email-branding/govuk-and-org", - methods=["GET", "POST"], -) -@user_has_permissions("manage_service") -def email_branding_govuk_and_org(service_id): - check_email_branding_allowed_for_service("govuk_and_org") - - if request.method == "POST": - create_email_branding_zendesk_ticket("govuk_and_org") - - flash( - "Thanks for your branding request. We’ll get back to you within one working day.", - "default", - ) - return redirect(url_for(".service_settings", service_id=current_service.id)) - - return render_template( - "views/service-settings/branding/email-branding-govuk-org.html" - ) - - -@main.route( - "/services//service-settings/email-branding/organization", - methods=["GET", "POST"], -) -@user_has_permissions("manage_service") -def email_branding_organization(service_id): - check_email_branding_allowed_for_service("organization") - - if request.method == "POST": - create_email_branding_zendesk_ticket("organization") - - flash( - "Thanks for your branding request. We’ll get back to you within one working day.", - "default", - ) - return redirect(url_for(".service_settings", service_id=current_service.id)) - - return render_template( - "views/service-settings/branding/email-branding-organization.html" - ) - - -@main.route( - "/services//service-settings/email-branding/something-else", - methods=["GET", "POST"], -) -@user_has_permissions("manage_service") -def email_branding_something_else(service_id): - form = SomethingElseBrandingForm() - - if form.validate_on_submit(): - create_email_branding_zendesk_ticket( - "something_else", detail=form.something_else.data - ) - - flash( - "Thanks for your branding request. We’ll get back to you within one working day.", - "default", - ) - return redirect(url_for(".service_settings", service_id=current_service.id)) - - return render_template( - "views/service-settings/branding/email-branding-something-else.html", - form=form, - branding_options=ChooseEmailBrandingForm(current_service), - ) - - @main.route("/services//data-retention", methods=["GET"]) @user_is_platform_admin def data_retention(service_id): @@ -1184,10 +905,6 @@ def edit_service_billing_details(service_id): ) -def get_branding_as_value_and_label(email_branding): - return [(branding["id"], branding["name"]) for branding in email_branding] - - def convert_dictionary_to_wtforms_choices_format(dictionary, value, label): return [(item[value], item[label]) for item in dictionary] diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index 609e74814..f76d69c14 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -4,10 +4,6 @@ def features_nav(): "name": "Features", "link": "main.features", "sub_navigation_items": [ - # { - # "name": "Emails", - # "link": "main.features_email", - # }, # { # "name": "Text messages", # "link": "main.features_sms", @@ -56,10 +52,6 @@ def using_notify_nav(): # "link": "main.edit_and_format_messages", # }, # { - # "name": "Branding", - # "link": "main.branding_and_customisation", - # }, - # { # "name": "Send files by email", # "link": "main.send_files_by_email", # }, diff --git a/app/models/event.py b/app/models/event.py index 548123b58..3af502ea7 100644 --- a/app/models/event.py +++ b/app/models/event.py @@ -66,9 +66,6 @@ class ServiceEvent(Event): def format_contact_link(self): return "Set the contact details for this service to ‘{}’".format(self.value_to) - def format_email_branding(self): - return "Updated this service’s email branding" - def format_inbound_api(self): return "Updated the callback for received text messages" @@ -116,9 +113,6 @@ class ServiceEvent(Event): def format_service_callback_api(self): return "Updated the callback for delivery receipts" - def format_go_live_user(self): - return "Requested for this service to go live" - class APIKeyEvent(Event): relevant = True diff --git a/app/models/feedback.py b/app/models/feedback.py deleted file mode 100644 index 31a669ac2..000000000 --- a/app/models/feedback.py +++ /dev/null @@ -1,3 +0,0 @@ -QUESTION_TICKET_TYPE = "ask-question-give-feedback" -PROBLEM_TICKET_TYPE = "report-problem" -GENERAL_TICKET_TYPE = "general" diff --git a/app/models/organization.py b/app/models/organization.py index 8be65a662..e9e30f460 100644 --- a/app/models/organization.py +++ b/app/models/organization.py @@ -3,7 +3,6 @@ from collections import OrderedDict from werkzeug.utils import cached_property from app.models import JSONModel, ModelList, SerialisedModelCollection, SortByNameMixin -from app.notify_client.email_branding_client import email_branding_client from app.notify_client.organizations_api_client import organizations_client @@ -25,9 +24,7 @@ class Organization(JSONModel, SortByNameMixin): "name", "active", "organization_type", - "email_branding_id", "domains", - "request_to_go_live_notes", "count_of_live_services", "billing_contact_email_addresses", "billing_contact_names", @@ -73,8 +70,6 @@ class Organization(JSONModel, SortByNameMixin): self.name = None self.domains = [] self.organization_type = None - self.request_to_go_live_notes = None - self.email_branding_id = None @property def organization_type_label(self): @@ -128,19 +123,6 @@ class Organization(JSONModel, SortByNameMixin): key=lambda user: user.email_address.lower(), ) - @cached_property - def email_branding(self): - if self.email_branding_id: - return email_branding_client.get_email_branding(self.email_branding_id)[ - "email_branding" - ] - - @property - def email_branding_name(self): - if self.email_branding_id: - return self.email_branding["name"] - return "GOV.UK" - def update(self, delete_services_cache=False, **kwargs): response = organizations_client.update_organization( self.id, diff --git a/app/models/service.py b/app/models/service.py index 9a0cdd1e7..e06a1b16d 100644 --- a/app/models/service.py +++ b/app/models/service.py @@ -8,7 +8,6 @@ from app.models.organization import Organization from app.models.user import InvitedUsers, User, Users from app.notify_client.api_key_api_client import api_key_api_client from app.notify_client.billing_api_client import billing_api_client -from app.notify_client.email_branding_client import email_branding_client from app.notify_client.inbound_number_client import inbound_number_client from app.notify_client.invite_api_client import invite_api_client from app.notify_client.job_api_client import job_api_client @@ -191,6 +190,15 @@ class Service(JSONModel, SortByNameMixin): invited_user_id=str(invited_user_id), ) + def resend_invite(self, invited_user_id): + if str(invited_user_id) not in {user.id for user in self.invited_users}: + abort(404) + + return invite_api_client.resend_invite( + service_id=self.id, + invited_user_id=str(invited_user_id), + ) + def get_team_member(self, user_id): if str(user_id) not in {user.id for user in self.active_users}: abort(404) @@ -371,22 +379,6 @@ class Service(JSONModel, SortByNameMixin): ) ) - @property - def go_live_checklist_completed(self): - return all( - ( - bool(self.volumes), - self.has_team_members, - self.has_templates, - not self.needs_to_add_email_reply_to_address, - not self.needs_to_change_sms_sender, - ) - ) - - @property - def go_live_checklist_completed_as_yes_no(self): - return "Yes" if self.go_live_checklist_completed else "No" - @cached_property def free_sms_fragment_limit(self): return billing_api_client.get_free_sms_fragment_limit_for_year(self.id) or 0 @@ -408,31 +400,6 @@ class Service(JSONModel, SortByNameMixin): {}, ).get("days_of_retention", current_app.config["ACTIVITY_STATS_LIMIT_DAYS"]) - @property - def email_branding_id(self): - return self._dict["email_branding"] - - @cached_property - def email_branding(self): - if self.email_branding_id: - return email_branding_client.get_email_branding(self.email_branding_id)[ - "email_branding" - ] - return None - - @cached_property - def email_branding_name(self): - if self.email_branding is None: - return "GOV.UK" - return self.email_branding["name"] - - @property - def needs_to_change_email_branding(self): - return ( - self.email_branding_id is None - and self.organization_type != Organization.TYPE_CENTRAL - ) - @cached_property def organization(self): return Organization.from_id(self.organization_id) diff --git a/app/navigation.py b/app/navigation.py index d5af628ab..e824d6de2 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -38,16 +38,10 @@ class Navigation: class HeaderNavigation(Navigation): mapping = { "support": { - "bat_phone", - "feedback", "support", - "support_public", - "thanks", - "triage", }, "features": { "features", - "features_email", "features_sms", "roadmap", "security", @@ -102,14 +96,7 @@ class HeaderNavigation(Navigation): "manage_users", "remove_user_from_service", "usage", - "email_branding_govuk", - "email_branding_govuk_and_org", - "email_branding_organization", - "email_branding_request", - "email_branding_something_else", - "estimate_usage", "link_service_to_organization", - "request_to_go_live", "service_add_email_reply_to", "service_add_sms_sender", "service_confirm_delete_email_reply_to", @@ -118,11 +105,9 @@ class HeaderNavigation(Navigation): "service_edit_sms_sender", "service_email_reply_to", "service_name_change", - "service_preview_email_branding", "service_set_auth_type", "service_set_channel", "send_files_by_email_contact_details", - "service_set_email_branding", "service_set_inbound_number", "service_set_inbound_sms", "service_set_international_sms", @@ -135,7 +120,6 @@ class HeaderNavigation(Navigation): "set_free_sms_allowance", "set_message_limit", "set_rate_limit", - "submit_request_to_go_live", }, "pricing": { "how_to_pay", @@ -164,8 +148,6 @@ class HeaderNavigation(Navigation): "archive_user", "change_user_auth", "clear_cache", - "create_email_branding", - "email_branding", "find_services_by_name", "find_users_by_email", "live_services", @@ -183,7 +165,6 @@ class HeaderNavigation(Navigation): "platform_admin_splash_page", "suspend_service", "trial_services", - "update_email_branding", "user_information", }, "sign-in": { @@ -256,15 +237,7 @@ class MainNavigation(Navigation): "usage", }, "settings": { - # 'add_organization_from_gp_service', - "email_branding_govuk", - "email_branding_govuk_and_org", - "email_branding_organization", - "email_branding_request", - "email_branding_something_else", - "estimate_usage", "link_service_to_organization", - "request_to_go_live", "service_add_email_reply_to", "service_add_sms_sender", "service_confirm_delete_email_reply_to", @@ -273,11 +246,9 @@ class MainNavigation(Navigation): "service_edit_sms_sender", "service_email_reply_to", "service_name_change", - "service_preview_email_branding", "service_set_auth_type", "service_set_channel", "send_files_by_email_contact_details", - "service_set_email_branding", "service_set_inbound_number", "service_set_inbound_sms", "service_set_international_sms", @@ -290,7 +261,6 @@ class MainNavigation(Navigation): "set_free_sms_allowance", "set_message_limit", "set_rate_limit", - "submit_request_to_go_live", }, "api-integration": { "api_callbacks", @@ -335,12 +305,9 @@ class OrgNavigation(Navigation): "settings": { "edit_organization_billing_details", "edit_organization_domains", - "edit_organization_email_branding", - "edit_organization_go_live_notes", "edit_organization_name", "edit_organization_notes", "edit_organization_type", - "organization_preview_email_branding", "organization_settings", }, "team-members": { diff --git a/app/notify_client/email_branding_client.py b/app/notify_client/email_branding_client.py deleted file mode 100644 index fe939cfd8..000000000 --- a/app/notify_client/email_branding_client.py +++ /dev/null @@ -1,40 +0,0 @@ -from app.notify_client import NotifyAdminAPIClient, cache - - -class EmailBrandingClient(NotifyAdminAPIClient): - @cache.set("email_branding-{branding_id}") - def get_email_branding(self, branding_id): - return self.get(url="/email-branding/{}".format(branding_id)) - - @cache.set("email_branding") - def get_all_email_branding(self, sort_key=None): - brandings = self.get(url="/email-branding")["email_branding"] - if sort_key and sort_key in brandings[0]: - brandings.sort(key=lambda branding: branding[sort_key].lower()) - return brandings - - @cache.delete("email_branding") - def create_email_branding(self, logo, name, text, colour, brand_type): - data = { - "logo": logo, - "name": name, - "text": text, - "colour": colour, - "brand_type": brand_type, - } - return self.post(url="/email-branding", data=data) - - @cache.delete("email_branding") - @cache.delete("email_branding-{branding_id}") - def update_email_branding(self, branding_id, logo, name, text, colour, brand_type): - data = { - "logo": logo, - "name": name, - "text": text, - "colour": colour, - "brand_type": brand_type, - } - return self.post(url="/email-branding/{}".format(branding_id), data=data) - - -email_branding_client = EmailBrandingClient() diff --git a/app/notify_client/invite_api_client.py b/app/notify_client/invite_api_client.py index 1debeaea2..d410ceec5 100644 --- a/app/notify_client/invite_api_client.py +++ b/app/notify_client/invite_api_client.py @@ -32,11 +32,11 @@ class InviteApiClient(NotifyAdminAPIClient): "folder_permissions": folder_permissions, } data = _attach_current_user(data) - resp = self.post(url="/service/{}/invite".format(service_id), data=data) + resp = self.post(url=f"/service/{service_id}/invite", data=data) return resp["data"] def get_invites_for_service(self, service_id): - return self.get("/service/{}/invite".format(service_id))["data"] + return self.get(f"/service/{service_id}/invite")["data"] def get_invited_user(self, invited_user_id): return self.get(f"/invite/service/{invited_user_id}")["data"] @@ -46,7 +46,7 @@ class InviteApiClient(NotifyAdminAPIClient): def get_count_of_invites_with_permission(self, service_id, permission): if permission not in all_ui_permissions: - raise TypeError("{} is not a valid permission".format(permission)) + raise TypeError(f"{permission} is not a valid permission") return len( [ invited_user @@ -56,22 +56,21 @@ class InviteApiClient(NotifyAdminAPIClient): ) def check_token(self, token): - return self.get(url="/invite/service/check/{}".format(token))["data"] + return self.get(url=f"/invite/service/check/{token}")["data"] def cancel_invited_user(self, service_id, invited_user_id): data = {"status": "cancelled"} data = _attach_current_user(data) - self.post( - url="/service/{0}/invite/{1}".format(service_id, invited_user_id), data=data - ) + self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data) + + def resend_invite(self, service_id, invited_user_id): + self.post(url=f"/service/{service_id}/invite/{invited_user_id}/resend", data={}) @cache.delete("service-{service_id}") @cache.delete("user-{invited_user_id}") def accept_invite(self, service_id, invited_user_id): data = {"status": "accepted"} - self.post( - url="/service/{0}/invite/{1}".format(service_id, invited_user_id), data=data - ) + self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data) invite_api_client = InviteApiClient() diff --git a/app/notify_client/job_api_client.py b/app/notify_client/job_api_client.py index 5ea333993..538bdd370 100644 --- a/app/notify_client/job_api_client.py +++ b/app/notify_client/job_api_client.py @@ -103,14 +103,32 @@ class JobApiClient(NotifyAdminAPIClient): return scheduled_for - def create_job(self, job_id, service_id, scheduled_for=None): + def create_job( + self, + job_id, + service_id, + scheduled_for=None, + template_id=None, + original_file_name=None, + notification_count=None, + valid=None, + ): data = {"id": job_id} # make a datetime object in the user's preferred timezone if scheduled_for: scheduled_for = JobApiClient.convert_user_time_to_utc(scheduled_for) - data.update({"scheduled_for": scheduled_for}) + data["scheduled_for"] = scheduled_for + + if template_id: + data["template_id"] = template_id + if original_file_name: + data["original_file_name"] = original_file_name + if notification_count: + data["notification_count"] = notification_count + if valid: + data["valid"] = valid data = _attach_current_user(data) job = self.post(url="/service/{}/job".format(service_id), data=data) diff --git a/app/notify_client/service_api_client.py b/app/notify_client/service_api_client.py index 434bf8b3b..fe1a6aff5 100644 --- a/app/notify_client/service_api_client.py +++ b/app/notify_client/service_api_client.py @@ -82,7 +82,6 @@ class ServiceAPIClient(NotifyAdminAPIClient): "contact_link", "created_by", "count_as_live", - "email_branding", "email_from", "free_sms_fragment_limit", "go_live_at", diff --git a/app/templates/components/big-number.html b/app/templates/components/big-number.html index 52b0dadbe..fcb89b7e9 100644 --- a/app/templates/components/big-number.html +++ b/app/templates/components/big-number.html @@ -22,54 +22,3 @@ {% endif %} {% endmacro %} - - -{% macro big_number_with_status( - number, - label, - failures, - failure_percentage, - danger_zone=False, - failure_link=None, - link=None, - show_failures=True, - smaller=False, - smallest=False -) %} - - {{ big_number(number, label, link=link, smaller=smaller, smallest=smallest) }} - {% if show_failures %} - - {% if failures %} - {% if failure_link %} - - {{ "{:,}".format(failures) }} - failed – {{ failure_percentage }}% - - {% else %} - {{ "{:,}".format(failures) }} - failed – {{ failure_percentage }}% - {% endif %} - {% else %} - No failures - {% endif %} - - {% endif %} - -{% endmacro %} - - -{% macro big_number_simple(number, label) %} - - - {% if number is number %} - {{ "{:,}".format(number) }} - {% else %} - {{ number }} - {% endif %} - - {% if label %} - {{ label }} - {% endif %} - -{% endmacro %} diff --git a/app/templates/components/branding-preview.html b/app/templates/components/branding-preview.html deleted file mode 100644 index db3e3001d..000000000 --- a/app/templates/components/branding-preview.html +++ /dev/null @@ -1,7 +0,0 @@ -{% macro branding_preview(branding_style, endpoint) %} - -{% endmacro %} - -{% macro email_branding_preview(branding_style) %} - {{ branding_preview(branding_style, 'main.email_template') }} -{% endmacro %} diff --git a/app/templates/components/components/alert/README.md b/app/templates/components/components/alert/README.md deleted file mode 100644 index 8e601db74..000000000 --- a/app/templates/components/components/alert/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Error message - -## Installation - -See the [main README quick start guide](https://github.com/alphagov/govuk-frontend#quick-start) for how to install this component. - -## Guidance and Examples - -Find out when to use the error message component in your service in the [GOV.UK Design System](https://design-system.service.gov.uk/components/error-message). - -## Component options - -Use options to customize the appearance, content and behavior of a component when using a macro, for example, changing the text. - -See [options table](https://design-system.service.gov.uk/components/error-message/#options-example-default) for details. \ No newline at end of file diff --git a/app/templates/components/components/alert/macro-options.json b/app/templates/components/components/alert/macro-options.json deleted file mode 100644 index eddcab0d2..000000000 --- a/app/templates/components/components/alert/macro-options.json +++ /dev/null @@ -1,37 +0,0 @@ -[ - { - "name": "text", - "type": "string", - "required": true, - "description": "If `html` is set, this is not required. Text to use within the error message. If `html` is provided, the `text` argument will be ignored." - }, - { - "name": "html", - "type": "string", - "required": true, - "description": "If `text` is set, this is not required. HTML to use within the error message. If `html` is provided, the `text` argument will be ignored." - }, - { - "name": "id", - "type": "string", - "required": false, - "description": "Id attribute to add to the error message span tag." - }, - { - "name": "classes", - "type": "string", - "required": false, - "description": "Classes to add to the error message span tag." - }, - { - "name": "attributes", - "type": "object", - "required": false, - "description": "HTML attributes (for example data attributes) to add to the error message span tag" - }, - { - "name": "visuallyHiddenText", - "type": "string", - "description": "A visually hidden prefix used before the error message. Defaults to \"Error\"." - } -] \ No newline at end of file diff --git a/app/templates/components/components/alert/macro.njk b/app/templates/components/components/alert/macro.njk deleted file mode 100644 index 65a977566..000000000 --- a/app/templates/components/components/alert/macro.njk +++ /dev/null @@ -1,3 +0,0 @@ -{% macro usaAlert(params) %} - {%- include "./template.njk" -%} -{% endmacro %} diff --git a/app/templates/components/components/alert/template.njk b/app/templates/components/components/alert/template.njk deleted file mode 100644 index d89405e64..000000000 --- a/app/templates/components/components/alert/template.njk +++ /dev/null @@ -1,18 +0,0 @@ -{%- if params.slim %} -
-
-

- {{params.text}} -

-
-
-{% else %} -
-
- {{params.heading}} -

- {{params.text | safe }} -

-
-
-{% endif %} diff --git a/app/templates/components/components/textarea/README.md b/app/templates/components/components/textarea/README.md deleted file mode 100644 index b8a8e0b3e..000000000 --- a/app/templates/components/components/textarea/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Textarea - -## Installation - -See the [main README quick start guide](https://github.com/alphagov/govuk-frontend#quick-start) for how to install this component. - -## Guidance and Examples - -Find out when to use the textarea component in your service in the [GOV.UK Design System](https://design-system.service.gov.uk/components/textarea). - -## Component options - -Use options to customize the appearance, content and behavior of a component when using a macro, for example, changing the text. - -See [options table](https://design-system.service.gov.uk/components/textarea/#options-example-default) for details. \ No newline at end of file diff --git a/app/templates/components/components/textarea/macro-options.json b/app/templates/components/components/textarea/macro-options.json deleted file mode 100644 index ea2eefa9f..000000000 --- a/app/templates/components/components/textarea/macro-options.json +++ /dev/null @@ -1,85 +0,0 @@ -[ - { - "name": "id", - "type": "string", - "required": true, - "description": "The id of the textarea." - }, - { - "name": "name", - "type": "string", - "required": true, - "description": "The name of the textarea, which is submitted with the form data." - }, - { - "name": "rows", - "type": "string", - "required": false, - "description": "Optional number of textarea rows (default is 5 rows)." - }, - { - "name": "value", - "type": "string", - "required": false, - "description": "Optional initial value of the textarea." - }, - { - "name": "describedBy", - "type": "string", - "required": false, - "description": "One or more element IDs to add to the `aria-describedby` attribute, used to provide additional descriptive information for screenreader users." - }, - { - "name": "label", - "type": "object", - "required": true, - "description": "Options for the label component.", - "isComponent": true - }, - { - "name": "hint", - "type": "object", - "required": false, - "description": "Options for the hint component.", - "isComponent": true - }, - { - "name": "errorMessage", - "type": "object", - "required": false, - "description": "Options for the errorMessage component (e.g. text).", - "isComponent": true - }, - { - "name": "formGroup", - "type": "object", - "required": false, - "description": "Options for the form-group wrapper", - "params": [ - { - "name": "classes", - "type": "string", - "required": false, - "description": "Classes to add to the form group (e.g. to show error state for the whole group)" - } - ] - }, - { - "name": "classes", - "type": "string", - "required": false, - "description": "Classes to add to the textarea." - }, - { - "name": "autocomplete", - "type": "string", - "required": false, - "description": "Attribute to [identify input purpose](https://www.w3.org/WAI/WCAG21/Understanding/identify-input-purpose.html), for instance \"postal-code\" or \"username\". See [autofill](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill) for full list of attributes that can be used." - }, - { - "name": "attributes", - "type": "object", - "required": false, - "description": "HTML attributes (for example data attributes) to add to the textarea." - } -] \ No newline at end of file diff --git a/app/templates/components/components/textarea/macro.njk b/app/templates/components/components/textarea/macro.njk deleted file mode 100644 index 36a1c4ee7..000000000 --- a/app/templates/components/components/textarea/macro.njk +++ /dev/null @@ -1,3 +0,0 @@ -{% macro govukTextarea(params) %} - {%- include "./template.njk" -%} -{% endmacro %} diff --git a/app/templates/components/components/textarea/template.njk b/app/templates/components/components/textarea/template.njk deleted file mode 100644 index 82c3ac751..000000000 --- a/app/templates/components/components/textarea/template.njk +++ /dev/null @@ -1,44 +0,0 @@ -{% from "../error-message/macro.njk" import usaErrorMessage -%} -{% from "../hint/macro.njk" import usaHint %} -{% from "../label/macro.njk" import usaLabel %} - -{#- a record of other elements that we need to associate with the input using - aria-describedby – for example hints or error messages -#} -{% set describedBy = params.describedBy if params.describedBy else "" %} -
- {{ usaLabel({ - html: params.label.html, - text: params.label.text, - classes: params.label.classes, - isPageHeading: params.label.isPageHeading, - attributes: params.label.attributes, - for: params.id - }) | indent(2) | trim }} -{% if params.hint %} - {% set hintId = params.id + '-hint' %} - {% set describedBy = describedBy + ' ' + hintId if describedBy else hintId %} - {{ usaHint({ - id: hintId, - classes: params.hint.classes, - attributes: params.hint.attributes, - html: params.hint.html, - text: params.hint.text - }) | indent(2) | trim }} -{% endif %} -{% if params.errorMessage %} - {% set errorId = params.id + '-error' %} - {% set describedBy = describedBy + ' ' + errorId if describedBy else errorId %} - {{ usaErrorMessage({ - id: errorId, - classes: params.errorMessage.classes, - attributes: params.errorMessage.attributes, - html: params.errorMessage.html, - text: params.errorMessage.text, - visuallyHiddenText: params.errorMessage.visuallyHiddenText - }) | indent(2) | trim }} -{% endif %} - -
diff --git a/app/templates/components/show-more.html b/app/templates/components/show-more.html deleted file mode 100644 index 710d2c647..000000000 --- a/app/templates/components/show-more.html +++ /dev/null @@ -1,6 +0,0 @@ -{% macro show_more(url, label, with_border=True) %} - {{ label }} -{% endmacro %} diff --git a/app/templates/components/textbox.html b/app/templates/components/textbox.html index c7bc28d45..3e479cbce 100644 --- a/app/templates/components/textbox.html +++ b/app/templates/components/textbox.html @@ -19,17 +19,22 @@ class="form-group{% if field.errors %} form-group-error{% endif %} {{ extra_form_group_classes }}" data-module="{% if autofocus %}autofocus{% elif colour_preview %}colour-preview{% endif %}" > + {% if field.errors %} + + {% endif %} {% if hint %}
diff --git a/app/templates/partials/count.html b/app/templates/partials/count.html index 7915b31e1..17321d67f 100644 --- a/app/templates/partials/count.html +++ b/app/templates/partials/count.html @@ -1,4 +1,3 @@ -{% from "components/big-number.html" import big_number %} {% from "components/pill.html" import pill %}
@@ -6,11 +5,40 @@
{% for label, query_param, url, count in counts %} {% if query_param == 'pending' %} -
{{ big_number(count, query_param, smaller=True) }}
+
+ + + {% if count is number %} + {% if currency %} + {{ "{}{:,.2f}".format(currency, count) }} + {% else %} + {{ "{:,}".format(count) }} + {% endif %} + {% else %} + {{ count }} + {% endif %} + + {{ query_param }} + +
{% else %} -
{{ big_number(count, label, smaller=True) }}
+
+ + + {% if count is number %} + {% if currency %} + {{ "{}{:,.2f}".format(currency, count) }} + {% else %} + {{ "{:,}".format(count) }} + {% endif %} + {% else %} + {{ count }} + {% endif %} + + {{ label }} + +
{% endif %} - {% endfor %}
{% else %} diff --git a/app/templates/support-tickets/branding-request.txt b/app/templates/support-tickets/branding-request.txt deleted file mode 100644 index 7c80176e8..000000000 --- a/app/templates/support-tickets/branding-request.txt +++ /dev/null @@ -1,14 +0,0 @@ -Organization: {% if current_service.organization -%} - {{ current_service.organization.name }} -{%- else -%} - Can’t tell (domain is {{ current_user.email_domain }}) -{%- endif %} -Service: {{ current_service.name }} -{{ url_for('main.service_dashboard', service_id=current_service.id, _external=True) }} - ---- -Current branding: {{ current_branding }} -Branding requested: {{ branding_requested }} -{% if detail %} -{{ detail }} -{% endif %} diff --git a/app/templates/support-tickets/go-live-request.txt b/app/templates/support-tickets/go-live-request.txt deleted file mode 100644 index 996234f35..000000000 --- a/app/templates/support-tickets/go-live-request.txt +++ /dev/null @@ -1,31 +0,0 @@ -{% set service = current_service -%} -{% set organization = service.organization -%} -{% set user = current_user -%} - -Service: {{ service.name }} -{{ url_for('main.service_dashboard', service_id=service.id, _external=True) }} - ---- -Organization type: {{ service.organization_type_label }} -{%- if organization.name %} (organization is {{ organization.name }}) -{%- else %} (domain is {{ user.email_domain }}) -{%- endif %}. -{%- if organization.request_to_go_live_notes %} {{ organization.request_to_go_live_notes }}{% endif %} -{%- if organization.agreement_signed_by %} -Agreement signed by: {{ organization.agreement_signed_by.email_address }} -{% endif -%} -{%- if organization.agreement_signed_on_behalf_of_email_address -%} -Agreement signed on behalf of: {{ organization.agreement_signed_on_behalf_of_email_address }} -{%- endif %} - -Emails in next year: {{ service.volume_email|format_thousands }} -Text messages in next year: {{ service.volume_sms|format_thousands }} - -Consent to research: {{ service.consent_to_research|format_yes_no }} -Other live services for that user: {{ user.live_services|format_yes_no }} - -Service reply-to address: {{ service.default_email_reply_to_address or "not set" }} - ---- -Request sent by {{ user.email_address }} -Requester’s user page: {{ url_for('main.user_information', user_id=user.id, _external=True) }} diff --git a/app/templates/support-tickets/support-ticket.txt b/app/templates/support-tickets/support-ticket.txt deleted file mode 100644 index 08fd2629d..000000000 --- a/app/templates/support-tickets/support-ticket.txt +++ /dev/null @@ -1,5 +0,0 @@ -{{ content }} -{% if current_service -%} -Service: "{{ current_service.name }}" -{{ url_for('main.service_dashboard', service_id=current_service.id, _external=True) }} -{% endif %} diff --git a/app/templates/views/check/column-errors.html b/app/templates/views/check/column-errors.html index 404302ffe..a24b5de3e 100644 --- a/app/templates/views/check/column-errors.html +++ b/app/templates/views/check/column-errors.html @@ -10,7 +10,12 @@ Error {% endblock %} {% block backLink %} -{{ usaBackLink({ "href": back_link }) }} + +{% if recipients|length == 1 and not recipients.allowed_to_send_to and not recipients.missing_column_headers %} + +{% else %} + {{ usaBackLink({ "href": back_link }) }} +{% endif %} {% endblock %} {% block maincolumn_content %} @@ -130,7 +135,10 @@ Error {% endcall %}
- + +{% if recipients|length == 1 and not recipients.allowed_to_send_to and not recipients.missing_column_headers %} + +{% else %}
{% if not request.args.from_test %} @@ -144,6 +152,7 @@ Error
Back to top
+{% endif %} {% if not request.args.from_test %} @@ -210,4 +219,4 @@ recipients.column_headers %}

Preview of {{ template.name }}

{{ template|string }} - {% endblock %} \ No newline at end of file + {% endblock %} diff --git a/app/templates/views/check/row-errors.html b/app/templates/views/check/row-errors.html index aadbad7c2..9d9a2edc1 100644 --- a/app/templates/views/check/row-errors.html +++ b/app/templates/views/check/row-errors.html @@ -4,7 +4,6 @@ {% from "components/table.html" import mapping_table, row, field, text_field, index_field, hidden_field_heading %} {% from "components/file-upload.html" import file_upload %} {% from "components/components/back-link/macro.njk" import usaBackLink %} -{% from "components/components/alert/macro.njk" import usaAlert %} {% block service_page_title %} Error diff --git a/app/templates/views/cookies.html b/app/templates/views/cookies.html deleted file mode 100644 index 1af35b4ec..000000000 --- a/app/templates/views/cookies.html +++ /dev/null @@ -1,151 +0,0 @@ -{% extends "withoutnav_template.html" %} -{% from "components/banner.html" import banner %} - -{% block per_page_title %} - Cookies -{% endblock %} - -{% block cookie_message %}{% endblock %} - -{% block maincolumn_content %} - -
-
- -

Cookies

-

- Cookies are small files saved on your phone, tablet, or computer when you visit a website. -

-

We use cookies to make Notify.gov work and collect information about how you use our service.

- -

Essential cookies

-

- Essential cookies keep your information secure while you use Notify. We do not need to ask permission to use them. -

- - - - - - - - - - - - - - - - - - - - - -
Essential cookies
NamePurposeExpires
- notify_admin_session - - Used to keep you signed in - - 20 hours -
- cookie_policy - - Saves your cookie consent settings - - 1 year -
- -

Analytics cookies (optional)

-

- With your permission, we use Google Analytics to collect data about how you use Notify. This information helps us to improve our service. -

-

- Google is not allowed to use or share our analytics data with anyone. -

-

- Google Analytics stores anonymized information about: -

-
    -
  • how you got to Notify.gov
  • -
  • the pages you visit on Notify and how long you spend on them
  • -
  • any errors you see while using Notify
  • -
- - - - - - - - - - - - - - - - - - - - - -
Google Analytics cookies
NamePurposeExpires
- _ga - - Checks if you’ve visited Notify before. This helps us count how many people visit our site. - - 2 years -
- _gid - - Checks if you’ve visited Notify before. This helps us count how many people visit our site. - - 24 hours -
- - -
-
- -{% endblock %} diff --git a/app/templates/views/dashboard/_jobs.html b/app/templates/views/dashboard/_jobs.html index c5dbab9bf..3a7d93a80 100644 --- a/app/templates/views/dashboard/_jobs.html +++ b/app/templates/views/dashboard/_jobs.html @@ -1,5 +1,4 @@ {% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading %} -{% from "components/big-number.html" import big_number -%}
{% call(item, row_number) list_table( @@ -36,28 +35,78 @@ {% endcall %} {% call field() %} {% if item.scheduled %} - {{ big_number( - item.notification_count, - smallest=True, - label=item.notification_count|message_count_label( - item.template_type, - suffix='waiting to send' - ) - ) }} + {% if link %} + + {% endif %} + + + {% if item.notification_count is number %} + {% if currency %} + {{ "{}{:,.2f}".format(currency, item.notification_count) }} + {% else %} + {{ "{:,}".format(item.notification_count) }} + {% endif %} + {% else %} + {{ item.notification_count }} + {% endif %} + + {% if item.notification_count %} + {{ item.notification_count|message_count_label(item.template_type,suffix='waiting to send') }} + {% endif %} + + {% if link %} + + {% endif %} {% else %}
- {{ big_number( - item.notifications_sending, - smallest=True, - label='pending', - ) }} + {% if link %} + + {% endif %} + + + {{ "{:,}".format(item.notifications_sending) }} + + pending + + {% if link %} + + {% endif %}
- {{ big_number(item.notifications_delivered, smallest=True, label='delivered') }} + + + {% if item.notifications_delivered is number %} + {{ "{:,}".format(item.notifications_delivered) }} + {% else %} + {{ item.notifications_delivered }} + {% endif %} + + delivered +
{% endif %} diff --git a/app/templates/views/dashboard/_totals.html b/app/templates/views/dashboard/_totals.html index ea8fd03cf..20c1d3a81 100644 --- a/app/templates/views/dashboard/_totals.html +++ b/app/templates/views/dashboard/_totals.html @@ -1,18 +1,32 @@ -{% from "components/big-number.html" import big_number_with_status %} -
- {{ big_number_with_status( - statistics['sms']['requested'], - statistics['sms']['requested']|message_count_label('sms', suffix='sent'), - statistics['sms']['failed'], - statistics['sms']['failed_percentage'], - statistics['sms']['show_warning'], - failure_link=url_for(".view_notifications", service_id=service_id, message_type='sms', status='failed'), - link=url_for(".view_notifications", service_id=service_id, message_type='sms', status='sending,delivered,failed'), - smaller=True, - ) }} + + + + + {% if statistics['sms']['requested'] is number %} + {{ "{:,}".format(statistics['sms']['requested']) }} + {% else %} + {{ statistics['sms']['requested'] }} + {% endif %} + + {{ statistics['sms']['requested']|message_count_label('sms', suffix='sent') }} + + + {% if show_failures %} + + {% if statistics['sms']['failed'] %} + + {{ "{:,}".format(statistics['sms']['failed']) }} + failed – {{ statistics['sms']['failed_percentage'] }}% + + {% else %} + No failures + {% endif %} + + {% endif %} +

You do not need any technical knowledge to use Notify.

diff --git a/app/templates/views/features/emails.html b/app/templates/views/features/emails.html deleted file mode 100644 index 88731639f..000000000 --- a/app/templates/views/features/emails.html +++ /dev/null @@ -1,49 +0,0 @@ -{% extends "content_template.html" %} -{% from "components/table.html" import mapping_table, row, text_field, edit_field, field with context %} - -{% block per_page_title %} - Emails -{% endblock %} - -{% block content_column_content %} - -

Emails

-

Send an unlimited number of emails for free with Notify.gov.

- {% if not current_user.is_authenticated %} -

Create an account and try Notify for yourself.

- {% endif %} - -

Features

-

Notify makes it easy to:

-
    -
  • create reusable email templates
  • -
  • personalize the content of your emails
  • -
  • send and schedule bulk messages
  • -
-

You can also integrate with our API to send emails automatically.

- -

Email branding

-

Add your organization’s logo and brand color to email templates.

-

See how to change your email branding.

- -

Send files by email

-

Notify offers a safe and reliable way to send files by email.

-

Upload a file using our API, then send your users an email with a link to download it.

-

Notify uses encrypted links instead of email attachments because:

-
    -
  • they’re more secure
  • - -
  • email attachments are often marked as spam
  • -
-

Read our API documentation for more information.

- -

Add a reply-to address

-

Notify lets you choose the email address that users reply to.

-

Emails with a reply-to address seem more trustworthy and are less likely to be labelled as spam.

-

See how to add a reply-to address.

- -

Pricing

-

It’s free to send emails through Notify.

-

See pricing for more details.

- -{% endblock %} diff --git a/app/templates/views/guidance/branding-and-customisation.html b/app/templates/views/guidance/branding-and-customisation.html deleted file mode 100644 index 2e3939a5b..000000000 --- a/app/templates/views/guidance/branding-and-customisation.html +++ /dev/null @@ -1,55 +0,0 @@ -{% extends "content_template.html" %} -{% from "components/service-link.html" import service_link %} - -{% block per_page_title %} - Branding and customization -{% endblock %} - -{% block content_column_content %} - -

Branding and customization

- - - -

Change the text message sender

- -

The text message sender tells your users who the message is from.

- -

To change the text message sender from the default of ‘Notify.gov’:

- -
    -
  1. Go to the Text message settings section of the {{ service_link(current_service, 'main.service_settings', 'settings') }} page.
  2. -
  3. Select Manage on the Text message senders row.
  4. -
  5. Select Change or Add text message sender.
  6. -
- -{% endblock %} diff --git a/app/templates/views/guidance/index.html b/app/templates/views/guidance/index.html index e73cd5cd5..7d43f81c3 100644 --- a/app/templates/views/guidance/index.html +++ b/app/templates/views/guidance/index.html @@ -17,12 +17,12 @@

Edit and format messages

- +

This section explains how to:

- +

Format your content

- +

You can see a list of formatting instructions on the edit template page:

- +
  1. Go to the {{ service_link(current_service, 'main.choose_template', 'templates') }} page.
  2. Add a new template or choose an existing template and select Edit.
- + - +

When composing a text message, write URLs in full and Notify will convert them into links for you.

- +

You cannot convert text into a link.

- +

We do not recommend using a third-party link shortening service because:

- +
  • your users cannot see where the link will take them
  • your link might stop working if there’s a service outage
  • you can no longer control where the redirect goes
- +

Personalize your content

- +

To personalize the content of your messages, add a placeholder to the template.

- +

Placeholders are filled in with details, like a name or reference number, each time you send a message.

- +

To add a placeholder to the template:

- +
  1. Go to the {{ service_link(current_service, 'main.choose_template', 'templates') }} page.
  2. Add a new template or choose an existing template and select Edit.
  3. @@ -73,20 +73,20 @@ ((ref number)).
  4. Select Save.
- +

When you send a message you can either:

- +
  • manually fill in the placeholders yourself
  • upload a list of personal details and let Notify do it for you
- +

If you upload a list, the column names need to match the placeholders in your template.

- +

Add optional content

- +

To add optional content to your messages:

- +
  1. Go to the {{ service_link(current_service, 'main.choose_template', 'templates') }} page.
  2. Add a new template or choose an existing template and select Edit.
  3. @@ -94,25 +94,25 @@ who are under 18: ((under18??Please get your application signed by a parent or guardian.))
  4. Select Save.
- +

For each person you send this message to, specify ‘yes’ or ‘no’ to show or hide this content. You can either:

- +
  • do this yourself
  • upload a list of personal details and let Notify do it for you
- +

If you upload a list, the column names need to match the optional content in your template.

-

Branding and customization

- +

Message customization

+

Change the text message sender

- +

The text message sender tells your users who the message is from.

- +

To change the text message sender from the default of ‘Notify.gov’:

- +
  1. Go to the Text message settings section of the {{ service_link(current_service, 'main.service_settings', 'settings') }} page.
  2. diff --git a/app/templates/views/jobs/job.html b/app/templates/views/jobs/job.html index 331f693f6..6001ae3b6 100644 --- a/app/templates/views/jobs/job.html +++ b/app/templates/views/jobs/job.html @@ -1,6 +1,5 @@ {% extends "withnav_template.html" %} {% from "components/banner.html" import banner %} -{% from "components/ajax-block.html" import ajax_block %} {% from "components/page-footer.html" import page_footer %} {% from "components/page-header.html" import page_header %} {% from "components/components/back-link/macro.njk" import usaBackLink %} @@ -13,9 +12,44 @@ {{ page_header(job.original_file_name) }} - {{ ajax_block(partials, updates_url, 'status', finished=job.processing_finished) }} - {{ ajax_block(partials, updates_url, 'counts', finished=job.processing_finished) }} - {{ ajax_block(partials, updates_url, 'notifications', finished=job.processing_finished) }} + {% if not job.processing_finished %} +
    + {% endif %} + {{ partials['status']|safe }} + {% if not job.processing_finished %} +
    + {% endif %} + + {% if not finished %} +
    + {% endif %} + {{ partials['counts']|safe }} + {% if not finished %} +
    + {% endif %} + + {% if not job.processing_finished %} +
    + {% endif %} + {{ partials['notifications']|safe }} + {% if not job.processing_finished %} +
    + {% endif %}
    diff --git a/app/templates/views/manage-users.html b/app/templates/views/manage-users.html index f8585870e..cab732a47 100644 --- a/app/templates/views/manage-users.html +++ b/app/templates/views/manage-users.html @@ -43,6 +43,8 @@ {{ user.email_address }} (invited) {%- elif user.status == 'cancelled' -%} {{ user.email_address }} (cancelled invite) + {%- elif user.status == 'expired' -%} + {{ user.email_address }} (expired invite) {%- elif user.id == current_user.id -%} (you) {% else %} @@ -84,6 +86,8 @@ {% if current_user.has_permissions('manage_service') %} {% if user.status == 'pending' %} Cancel invitation for {{ user.email_address }} + {% elif user.status == 'expired' %} + Resend invite for {{ user.email_address }} {% elif user.is_editable_by(current_user) %} Change details for {{ user.name }} {{ user.email_address }} {% endif %} diff --git a/app/templates/views/organizations/add-gp-organization.html b/app/templates/views/organizations/add-gp-organization.html deleted file mode 100644 index f6854ecde..000000000 --- a/app/templates/views/organizations/add-gp-organization.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/page-header.html" import page_header %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/radios.html" import radio, conditional_radio_panel %} -{% from "components/select-input.html" import select_wrapper %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - - -{% block backLink %} - {{ usaBackLink({ "href": url_for('main.request_to_go_live', service_id=current_service.id) }) }} -{% endblock %} - -{% block maincolumn_content %} - {% call form_wrapper() %} - {% call select_wrapper(form.same_as_service_name) %} - {% for option in form.same_as_service_name %} - {{ radio(option, data_target='custom-organization-name' if option.data == False else '') }} - {% endfor %} - {% endcall %} - {% call conditional_radio_panel('custom-organization-name') %} - {{ form.name }} - {% endcall %} - {{ page_footer('Continue') }} - {% endcall %} -{% endblock %} diff --git a/app/templates/views/organizations/add-nhs-local-organization.html b/app/templates/views/organizations/add-nhs-local-organization.html deleted file mode 100644 index 5006407bc..000000000 --- a/app/templates/views/organizations/add-nhs-local-organization.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/page-header.html" import page_header %} -{% from "components/page-footer.html" import sticky_page_footer %} -{% from "components/live-search.html" import live_search %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% block per_page_title %} - {{ page_title }} -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ "href": url_for('main.request_to_go_live', service_id=current_service.id) }) }} -{% endblock %} - -{% block maincolumn_content %} - {{ page_header(page_title) }} - {% call form_wrapper() %} -

    - {{ form.organizations.label.text }} -

    - {{ live_search( - target_selector='.usa-radio', - show=True, - form=search_form, - label='Search by name', - autofocus=True) - }} - {{ form.organizations }} - {{ sticky_page_footer('Continue') }} - {% endcall %} -{% endblock %} diff --git a/app/templates/views/organizations/organization/settings/edit-go-live-notes.html b/app/templates/views/organizations/organization/settings/edit-go-live-notes.html deleted file mode 100644 index c7955f616..000000000 --- a/app/templates/views/organizations/organization/settings/edit-go-live-notes.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "org_template.html" %} -{% from "components/form.html" import form_wrapper %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/page-header.html" import page_header %} -{% from "components/textbox.html" import textbox %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% block org_page_title %} - Edit request to go live notes -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ "href": url_for('.organization_settings', org_id=current_org.id) }) }} -{% endblock %} - -{% block maincolumn_content %} - {{ page_header("Edit request to go live notes") }} -
    -
    -

    - Text entered here will be displayed in the Zendesk ticket when a service - belonging to this organization requests to go live. -

    - {% call form_wrapper() %} - {{ textbox(form.request_to_go_live_notes, width='1-1', rows=3, autosize=True) }} - {{ page_footer('Save') }} - {% endcall %} -
    -
    -{% endblock %} diff --git a/app/templates/views/organizations/organization/settings/index.html b/app/templates/views/organizations/organization/settings/index.html index 50bee3109..ddd60bc64 100644 --- a/app/templates/views/organizations/organization/settings/index.html +++ b/app/templates/views/organizations/organization/settings/index.html @@ -34,16 +34,6 @@ ) }} {% endcall %} - {% call row() %} - {{ text_field('Request to go live notes') }} - {{ optional_text_field(current_org.request_to_go_live_notes, default='None') }} - {{ edit_field( - 'Change', - url_for('.edit_organization_go_live_notes', org_id=current_org.id), - suffix='go live notes for the organization' - ) - }} - {% endcall %} {% call row() %} {{ text_field('Billing details')}} @@ -67,16 +57,6 @@ }} {% endcall %} - {% call row() %} - {{ text_field('Default email branding') }} - {{ text_field(current_org.email_branding_name) }} - {{ edit_field( - 'Change', - url_for('.edit_organization_email_branding', org_id=current_org.id), - suffix='default email branding for the organization' - ) - }} - {% endcall %} {% call row() %} {{ text_field('Known email domains') }} {{ optional_text_field(current_org.domains or None, default='None') }} diff --git a/app/templates/views/organizations/organization/settings/preview-email-branding.html b/app/templates/views/organizations/organization/settings/preview-email-branding.html deleted file mode 100644 index 45feae8c5..000000000 --- a/app/templates/views/organizations/organization/settings/preview-email-branding.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "org_template.html" %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/button/macro.njk" import usaButton %} -{% from "components/branding-preview.html" import email_branding_preview %} - -{% block org_page_title %} - Preview email branding -{% endblock %} - -{% block maincolumn_content %} - -

    Preview email branding

    -
    -
    - {{ email_branding_preview(form.branding_style.data) }} - {% call form_wrapper(action=action) %} -
    - {{ form.hidden_tag() }} - -
    - {% endcall %} -
    -
    -{% endblock %} diff --git a/app/templates/views/organizations/organization/settings/set-email-branding.html b/app/templates/views/organizations/organization/settings/set-email-branding.html deleted file mode 100644 index 30df51d10..000000000 --- a/app/templates/views/organizations/organization/settings/set-email-branding.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends "org_template.html" %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/page-header.html" import page_header %} -{% from "components/live-search.html" import live_search %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% set page_title = "Default email branding" %} - -{% block per_page_title %} - {{ page_title }} -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ "href": url_for('.organization_settings', org_id=current_org.id) }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header(page_title) }} - {% call form_wrapper(data_kwargs={'preview-type': 'email'}) %} -
    -
    -
    -
    -
    -
    - {{ live_search( - target_selector='.usa-radio', - show=True, - form=search_form, - label='Search branding styles by name', - autofocus=True - ) }} - {{ form.branding_style }} -
    -
    -
    - {{ page_footer('Preview') }} -
    - {% endcall %} - -{% endblock %} diff --git a/app/templates/views/platform-admin/_base_template.html b/app/templates/views/platform-admin/_base_template.html index 777d7b9bd..99d788dda 100644 --- a/app/templates/views/platform-admin/_base_template.html +++ b/app/templates/views/platform-admin/_base_template.html @@ -26,7 +26,6 @@ ('Trial mode services', ('main.trial_services')), ('Organizations', ('main.organizations')), ('Reports', ('main.platform_admin_reports')), - ('Email branding', ('main.email_branding')), ('Inbound SMS numbers', ('main.inbound_sms_admin')), ('Find services by name', ('main.find_services_by_name')), ('Find users by email', ('main.find_users_by_email')), diff --git a/app/templates/views/platform-admin/_global_stats.html b/app/templates/views/platform-admin/_global_stats.html index bef4adeed..aaf9b41ef 100644 --- a/app/templates/views/platform-admin/_global_stats.html +++ b/app/templates/views/platform-admin/_global_stats.html @@ -1,24 +1,64 @@ -{% from "components/big-number.html" import big_number_with_status %} -
    - {{ big_number_with_status( - global_stats.email.delivered + global_stats.email.failed, - global_stats.email.delivered|message_count_label('email'), - global_stats.email.failed, - global_stats.email.failure_rate, - global_stats.email.failure_rate|float > 3, - smaller=True - ) }} + + {% if link %} + + {% endif %} + + + {{ "{:,}".format(global_stats.email.delivered + global_stats.email.failed) }} + + {{ global_stats.email.delivered|message_count_label('email') }} + + {% if link %} + + {% endif %} + + {% if global_stats.email.failed %} + {% if failure_link %} + + {{ "{:,}".format(global_stats.email.failed) }} + failed – {{ global_stats.email.failure_rate }}% + + {% else %} + {{ "{:,}".format(global_stats.email.failed) }} + failed – {{ global_stats.email.failure_rate }}% + {% endif %} + {% else %} + No failures + {% endif %} + +
    - {{ big_number_with_status( - global_stats.sms.delivered + global_stats.sms.failed, - global_stats.sms.delivered|message_count_label('sms'), - global_stats.sms.failed, - global_stats.sms.failure_rate, - global_stats.sms.failure_rate|float > 3, - smaller=True - ) }} + + {% if link %} + + {% endif %} + + + {{ "{:,}".format( global_stats.sms.delivered + global_stats.sms.failed ) }} + + {{ global_stats.sms.delivered|message_count_label('sms') }} + + {% if link %} + + {% endif %} + + {% if global_stats.sms.failed %} + {% if failure_link %} + + {{ "{:,}".format(global_stats.sms.failed) }} + failed – {{ global_stats.sms.failure_rate }}% + + {% else %} + {{ "{:,}".format(global_stats.sms.failed) }} + failed – {{ global_stats.sms.failure_rate }}% + {% endif %} + {% else %} + No failures + {% endif %} + +
    diff --git a/app/templates/views/platform-admin/index.html b/app/templates/views/platform-admin/index.html index 7d12873fa..a90da7224 100644 --- a/app/templates/views/platform-admin/index.html +++ b/app/templates/views/platform-admin/index.html @@ -1,5 +1,4 @@ {% extends "views/platform-admin/_base_template.html" %} -{% from "components/big-number.html" import big_number_simple %} {% from "components/status-box.html" import status_box %} {% from "components/form.html" import form_wrapper %} {% from "components/components/details/macro.njk" import usaDetails %} @@ -33,10 +32,14 @@
    {% for noti_type in global_stats %}
    - {{ big_number_simple( - noti_type.black_box.number, - noti_type.black_box.number|message_count_label(noti_type.black_box.notification_type) - ) }} + + + {{ "{:,}".format(noti_type.black_box.number) }} + + + {{ noti_type.black_box.number|message_count_label(noti_type.black_box.notification_type) }} + + {% for item in noti_type.other_data %} {{ status_box( diff --git a/app/templates/views/platform-admin/services.html b/app/templates/views/platform-admin/services.html index 02507d02e..d56e3390e 100644 --- a/app/templates/views/platform-admin/services.html +++ b/app/templates/views/platform-admin/services.html @@ -1,6 +1,6 @@ {% extends "views/platform-admin/_base_template.html" %} {% from "components/page-footer.html" import page_footer %} -{% from "components/big-number.html" import big_number, big_number_with_status %} +{% from "components/big-number.html" import big_number %} {% from "components/table.html" import mapping_table, field, stats_fields, row_group, row, right_aligned_field_heading, hidden_field_heading, text_field %} {% from "components/form.html" import form_wrapper %} {% from "components/components/button/macro.njk" import usaButton %} diff --git a/app/templates/views/service-settings.html b/app/templates/views/service-settings.html index a1dd3232e..0bb3da6e4 100644 --- a/app/templates/views/service-settings.html +++ b/app/templates/views/service-settings.html @@ -162,23 +162,6 @@ }} {% endcall %} - {% if email_branding_options.something_else_is_only_option %} - {% set email_request_url = url_for('.email_branding_something_else', service_id=current_service.id) %} - {% else %} - {% set email_request_url = url_for('.email_branding_request', service_id=current_service.id) %} - {% endif %} - - {% call settings_row(if_has_permission='email') %} - {{ text_field('Email branding') }} - {{ text_field(current_service.email_branding_name) }} - {{ edit_field( - 'Change', - email_request_url, - permissions=['manage_service'], - suffix='email branding', - )}} - {% endcall %} - {% call settings_row(if_has_permission='email') %} {{ text_field('Send files by email') }} {{ optional_text_field(current_service.contact_link, default="Not set up", truncate=true) }} @@ -223,7 +206,7 @@

    Problems or comments? - Give feedback. + Contact us.

    {% endif %} @@ -303,11 +286,6 @@ {{ text_field('{:,} per year'.format(current_service.free_sms_fragment_limit)) }} {{ edit_field('Change', url_for('.set_free_sms_allowance', service_id=current_service.id), suffix='free text message allowance') }} {% endcall %} - {% call row() %} - {{ text_field('Email branding' )}} - {{ text_field(current_service.email_branding_name) }} - {{ edit_field('Change', url_for('.service_set_email_branding', service_id=current_service.id), suffix='email branding (admin view)') }} - {% endcall %} {% call row() %} {{ text_field('Custom data retention')}} {% call field() %} diff --git a/app/templates/views/service-settings/branding/email-branding-govuk-org.html b/app/templates/views/service-settings/branding/email-branding-govuk-org.html deleted file mode 100644 index ba3043073..000000000 --- a/app/templates/views/service-settings/branding/email-branding-govuk-org.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/page-header.html" import page_header %} - -{% block service_page_title %} - Before you request new branding -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ - "href": url_for('.email_branding_request', service_id=current_service.id) - }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header('Before you request new branding') }} - -

    You can only use Notify.gov branding if people go to Notify.gov to access your service.

    - -

    - You cannot use Notify.gov branding if your organization is - independent - from government. -

    - -

    We’ll email you once your branding’s ready to use, or if we need any more information.

    - - {% call form_wrapper() %} - {{ page_footer('Request new branding') }} - {% endcall %} - -{% endblock %} diff --git a/app/templates/views/service-settings/branding/email-branding-govuk.html b/app/templates/views/service-settings/branding/email-branding-govuk.html deleted file mode 100644 index e58231326..000000000 --- a/app/templates/views/service-settings/branding/email-branding-govuk.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/page-header.html" import page_header %} -{% from "components/branding-preview.html" import email_branding_preview %} - -{% block service_page_title %} - Check your new branding -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ - "href": url_for('.email_branding_request', service_id=current_service.id) - }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header('Check your new branding') }} - -

    - Emails from {{ current_service.name }} will look like this. -

    - - {{ email_branding_preview('__NONE__') }} - -

    Before you continue

    - -

    You can only use Notify.gov branding if people go to Notify.gov to access your service.

    - -

    - You cannot use Notify.gov branding if your organization is - independent - from government. -

    - - {% call form_wrapper() %} - {{ page_footer('Use this branding') }} - {% endcall %} - -{% endblock %} diff --git a/app/templates/views/service-settings/branding/email-branding-options.html b/app/templates/views/service-settings/branding/email-branding-options.html deleted file mode 100644 index acaf348b0..000000000 --- a/app/templates/views/service-settings/branding/email-branding-options.html +++ /dev/null @@ -1,48 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/radios.html" import radio %} -{% from "components/select-input.html" import select_wrapper %} -{% from "components/textbox.html" import textbox %} -{% from "components/page-header.html" import page_header %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} -{% from "components/branding-preview.html" import email_branding_preview %} - -{% block service_page_title %} - Change email branding -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ - "href": url_for('.service_settings', service_id=current_service.id) - }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header('Change email branding') }} - -

    - Your emails currently have {{ branding_name }} branding. -

    - - {{ email_branding_preview( - current_service.email_branding_id if current_service.email_branding else '__NONE__' - ) }} - - {% if current_service.needs_to_change_email_branding %} -

    - You should be using your own branding instead. -

    - {% endif %} - - {% call form_wrapper() %} - {% call select_wrapper(form.options) %} - {% for option in form.options %} - {{ radio(option) }} - {% endfor %} - {% endcall %} - {{ page_footer('Continue') }} - {% endcall %} - -{% endblock %} diff --git a/app/templates/views/service-settings/branding/email-branding-organization.html b/app/templates/views/service-settings/branding/email-branding-organization.html deleted file mode 100644 index 83b6eb8c2..000000000 --- a/app/templates/views/service-settings/branding/email-branding-organization.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/page-header.html" import page_header %} - -{% block service_page_title %} - When you request new branding -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ - "href": url_for('.email_branding_request', service_id=current_service.id) - }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header('When you request new branding') }} - -

    We’ll check if we already have the {{organization}} logo.

    - -

    If we do, we’ll let you know when your new branding is ready to use.

    - -

    If we don’t, we’ll email you to ask for more information.

    - - {% call form_wrapper() %} - {{ page_footer('Request new branding') }} - {% endcall %} - -{% endblock %} diff --git a/app/templates/views/service-settings/branding/email-branding-something-else.html b/app/templates/views/service-settings/branding/email-branding-something-else.html deleted file mode 100644 index bd61df811..000000000 --- a/app/templates/views/service-settings/branding/email-branding-something-else.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/textbox.html" import textbox %} -{% from "components/components/textarea/macro.njk" import govukTextarea %} - -{% block service_page_title %} - Describe the branding you want -{% endblock %} - -{% if branding_options.something_else_is_only_option %} - {% set back_url = url_for('.service_settings', service_id=current_service.id) %} -{% else %} - {% set back_url = url_for('.email_branding_request', service_id=current_service.id) %} -{% endif %} - -{% block backLink %} - {{ usaBackLink({"href": back_url}) }} -{% endblock %} - -{% block maincolumn_content %} - - {% call form_wrapper() %} - {{ form.something_else }} -

    We’ll email you when your branding is ready, or if we need any more information.

    - {{ page_footer('Request new branding') }} - {% endcall %} - -{% endblock %} diff --git a/app/templates/views/service-settings/estimate-usage.html b/app/templates/views/service-settings/estimate-usage.html deleted file mode 100644 index 2b0643cfe..000000000 --- a/app/templates/views/service-settings/estimate-usage.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/banner.html" import banner_wrapper %} -{% from "components/form.html" import form_wrapper %} -{% from "components/page-header.html" import page_header %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% block service_page_title %} - Tell us how many messages you expect to send -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ "href": url_for('main.request_to_go_live', service_id=current_service.id) }) }} -{% endblock %} - -{% block maincolumn_content %} -
    -
    - {% if not form.at_least_one_volume_filled %} - {% call banner_wrapper(type='dangerous') %} -

    - Enter the number of messages you expect to send in the next year -

    - {% endcall %} - {% else %} - {{ page_header('Tell us how many messages you expect to send') }} - {% endif %} - {% call form_wrapper() %} -
    - {{ form.volume_email(param_extensions={ - "hint": {"text": "For example, 50,000"}, - }) }} - {{ form.volume_sms(param_extensions={ - "hint": {"text": "For example, 50,000"}, - }) }} -
    - {{ form.consent_to_research }} - {{ page_footer('Continue') }} - {% endcall %} -
    -
    -{% endblock %} diff --git a/app/templates/views/service-settings/preview-email-branding.html b/app/templates/views/service-settings/preview-email-branding.html deleted file mode 100644 index c575f4acc..000000000 --- a/app/templates/views/service-settings/preview-email-branding.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/button/macro.njk" import usaButton %} -{% from "components/branding-preview.html" import email_branding_preview %} - -{% block service_page_title %} - Preview email branding -{% endblock %} - -{% block maincolumn_content %} - -

    Preview email branding

    -
    -
    - {{ email_branding_preview(form.branding_style.data) }} - {% call form_wrapper(action=action) %} -
    - {{ form.hidden_tag() }} - -
    - {% endcall %} -
    -
    -{% endblock %} diff --git a/app/templates/views/service-settings/request-to-go-live.html b/app/templates/views/service-settings/request-to-go-live.html deleted file mode 100644 index 5a5d89cdc..000000000 --- a/app/templates/views/service-settings/request-to-go-live.html +++ /dev/null @@ -1,75 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/form.html" import form_wrapper %} -{% from "components/page-header.html" import page_header %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/task-list.html" import task_list_wrapper, task_list_item %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% block service_page_title %} - Before you request to go live -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ "href": url_for('main.service_settings', service_id=current_service.id) }) }} -{% endblock %} - -{% block maincolumn_content %} -
    -
    - {{ page_header('Before you request to go live') }} - {% call task_list_wrapper() %} - {{ task_list_item( - current_service.has_estimated_usage, - 'Tell us how many messages you expect to send', - url_for('main.estimate_usage', service_id=current_service.id), - ) }} - {{ task_list_item( - current_service.has_team_members, - 'Add a team member who can manage settings, team and usage', - url_for('main.manage_users', service_id=current_service.id), - ) }} - {{ task_list_item( - current_service.has_templates, - 'Add templates with examples of the content you plan to send', - url_for('main.choose_template', service_id=current_service.id), - ) }} - {% if current_service.intending_to_send_email %} - {{ task_list_item( - current_service.has_email_reply_to_address, - 'Add a reply-to email address', - url_for('main.service_email_reply_to', service_id=current_service.id), - ) }} - {% endif %} - {% if ( - current_service.intending_to_send_sms - and current_service.shouldnt_use_govuk_as_sms_sender - ) %} - {{ task_list_item( - not current_service.sms_sender_is_govuk, - 'Change your text message sender name', - url_for('main.service_sms_senders', service_id=current_service.id), - ) }} - {% endif %} - {% endcall %} - {% if not current_user.is_gov_user %} -

    - Only team members with a government email address can request to go live. -

    - {% elif (not current_service.go_live_checklist_completed) %} -

    - You must complete these steps before you can request to go live. -

    - {% else %} -

    - When we receive your request we’ll get back to you within one working day. -

    -

    - By requesting to go live you’re agreeing to our terms of use. -

    - {% call form_wrapper() %} - {{ page_footer('Request to go live') }} - {% endcall %} - {% endif %} -
    -
    -{% endblock %} diff --git a/app/templates/views/service-settings/service-already-live.html b/app/templates/views/service-settings/service-already-live.html deleted file mode 100644 index a42a87618..000000000 --- a/app/templates/views/service-settings/service-already-live.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/page-header.html" import page_header %} - -{% block service_page_title %} - Your service is already live -{% endblock %} - -{% block maincolumn_content %} -
    -
    - {{ page_header('Your service is already live') }} - -

    - {% if current_service.go_live_at %} - ‘{{ current_service.name }}’ went live on {{ current_service.go_live_at | format_date_normal }}. - {% else %} - ‘{{ current_service.name }}’ is already live. - {% endif %} -

    - -

    - Switch service - if you want to make a different service live. -

    - -
    -
    -{% endblock %} diff --git a/app/templates/views/service-settings/set-email-branding.html b/app/templates/views/service-settings/set-email-branding.html deleted file mode 100644 index 131e8d73a..000000000 --- a/app/templates/views/service-settings/set-email-branding.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/page-header.html" import page_header %} -{% from "components/page-footer.html" import page_footer, sticky_page_footer %} -{% from "components/live-search.html" import live_search %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% set page_title = "Set email branding" %} - -{% block backLink %} - {{ usaBackLink({ "href": url_for('.service_settings', service_id=current_service.id) }) }} -{% endblock %} - -{% block service_page_title %} - {{ page_title }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header(page_title) }} - {% call form_wrapper(data_kwargs={'preview-type': 'email'}) %} -
    -
    -
    -
    -
    -
    - {{ live_search( - target_selector='.usa-radio', - show=True, - form=search_form, - label='Search branding styles by name', - autofocus=True - ) }} - {{ form.branding_style }} -
    -
    - {{ sticky_page_footer('Preview') }} - {% endcall %} - -{% endblock %} diff --git a/app/templates/views/support/bat-phone.html b/app/templates/views/support/bat-phone.html deleted file mode 100644 index 46e947f44..000000000 --- a/app/templates/views/support/bat-phone.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends "withoutnav_template.html" %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/page-header.html" import page_header %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% block per_page_title %} - Out of hours emergencies -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ "href": url_for('.support') }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header('Out of hours emergencies')}} -
    -
    -

    - First, check the - system status page. - You do not need to contact us if - the problem you’re having is listed on that page. -

    -

    - Otherwise, contact us using the emergency email address we - gave you or your service manager when we made your service live. -

    -

    - We’ll reply within 30 minutes and give you hourly updates - until the problem’s fixed. -

    -

    - We do not offer out of hours support if your service is in - trial mode. -

    -

    Any other problems

    -

    - Fill in this form - and we’ll get back to you by the next working day. -

    -
    -
    - - -{% endblock %} diff --git a/app/templates/views/support/form.html b/app/templates/views/support/form.html deleted file mode 100644 index 904bc9485..000000000 --- a/app/templates/views/support/form.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "withoutnav_template.html" %} -{% from "components/textbox.html" import textbox %} -{% from "components/page-footer.html" import sticky_page_footer %} -{% from "components/page-header.html" import page_header %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% block per_page_title %} - {{ page_title }} -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ "href": back_link }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header(page_title) }} -
    -
    - {% if show_status_page_banner %} -
    -

    - Check our system status - page to see if there are any known issues with Notify.gov. -

    -
    - {% endif %} - {% call form_wrapper() %} - {{ textbox(form.feedback, width='1-1', hint='', rows=10, autosize=True) }} - {% if not current_user.is_authenticated %} - {{ form.name(param_extensions={"classes": ""}) }} - {{ form.email_address(param_extensions={"classes": ""}) }} - {% else %} -

    We’ll reply to {{ current_user.email_address }}

    - {% endif %} - {{ sticky_page_footer('Send') }} - {% endcall %} -
    -
    - -{% endblock %} diff --git a/app/templates/views/support/public.html b/app/templates/views/support/public.html deleted file mode 100644 index 344d0961a..000000000 --- a/app/templates/views/support/public.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends "withoutnav_template.html" %} - -{% from "components/page-header.html" import page_header %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% block per_page_title %} - The Notify.gov service is for people who work in the government -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ "href": url_for('.support') }) }} -{% endblock %} - -{% block maincolumn_content %} - -
    -
    - - {{ page_header('The Notify.gov service is for people who work in the government') }} - -

    - We cannot give advice to the public. We do not have access to information about you held by government departments. -

    - -

    - There are other pages on Notify.gov where you can get help: -

    - -

    - Coronavirus (COVID-19) -

    -

    - Find guidance and support. -

    -

    - Contact the government -

    -

    - Ask about benefits, driving, transport, tax, and more. -

    -

    - Report internet scams and phishing -

    -

    - Advice on suspicious emails and text messages. -

    -
    -
    - -{% endblock %} diff --git a/app/templates/views/support/thanks.html b/app/templates/views/support/thanks.html deleted file mode 100644 index f695b2d3b..000000000 --- a/app/templates/views/support/thanks.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "withoutnav_template.html" %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/page-header.html" import page_header %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% block per_page_title %} - Thanks for contacting us -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ "href": url_for('.support') }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header('Thanks for contacting us') }} -

    - {% if out_of_hours_emergency %} - We’ll reply in the next 30 minutes. - {% else %} - {% if email_address_provided %} - {% if out_of_hours %} - We’ll reply within one working day. - {% else %} - We’ll aim to read your message in the next 30 minutes and we’ll reply within one - working day. - {% endif %} - {% else %} - {% if out_of_hours %} - We’ll read your message when we’re back in the office. - {% else %} - We’ll aim to read your message in the next 30 minutes. - {% endif %} - {% endif %} - {% endif %} -

    - -{% endblock %} diff --git a/app/templates/views/support/triage.html b/app/templates/views/support/triage.html deleted file mode 100644 index 447e1a8e0..000000000 --- a/app/templates/views/support/triage.html +++ /dev/null @@ -1,62 +0,0 @@ -{% extends "withoutnav_template.html" %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/page-header.html" import page_header %} -{% from "components/form.html" import form_wrapper %} -{% from "components/components/back-link/macro.njk" import usaBackLink %} - -{% block per_page_title %} - {{ page_title }} -{% endblock %} - -{% block backLink %} - {{ usaBackLink({ "href": url_for('.support') }) }} -{% endblock %} - -{% block maincolumn_content %} - -
    -
    - {{ page_header(page_title) }} - {% call form_wrapper() %} - {{ form.severe }} - {{ page_footer('Continue') }} - {% endcall %} -

    - It’s only an emergency if: -

    -
      -
    • - no one in your team can log in -
    • -
    • - you get a ‘technical difficulties’ error message when you try - to upload a file -
    • -
    • - you get a 500 response code when you try to send messages - using the API -
    • -
    -

    - It’s not an emergency if: -

    -
      -
    • - all your messages stay in ‘sending’ for a few hours -
    • -
    • - you send the wrong message by accident -
    • -
    • - a team member uses Notify.gov to send an - inappropriate message -
    • -
    • - your system is telling the Notify.gov API to send the wrong - message -
    • -
    -
    -
    - -{% endblock %} diff --git a/app/templates/views/templates/choose.html b/app/templates/views/templates/choose.html index 70d7cbca1..e1e99f527 100644 --- a/app/templates/views/templates/choose.html +++ b/app/templates/views/templates/choose.html @@ -4,7 +4,6 @@ {% from "components/form.html" import form_wrapper %} {% from "components/page-header.html" import page_header %} {% from "components/page-footer.html" import page_footer %} -{% from "components/components/alert/macro.njk" import usaAlert %} {% extends "withnav_template.html" %} @@ -23,7 +22,7 @@

    Every message starts with a template. You can change it later. - + {% if current_user.has_permissions('manage_templates') %} You need a template before you can {% else %} @@ -36,11 +35,13 @@

    - {{ usaAlert({ - "text": "Every message starts with a template. To send, choose or create a template.", - "slim": true, - "type": "info", - }) }} +
    +
    +

    + Every message starts with a template. To send, choose or create a template. +

    +
    +
    {{ folder_path( folders=template_folder_path, service_id=current_service.id, diff --git a/app/templates/views/trial-mode.html b/app/templates/views/trial-mode.html index 6c4d9b8db..72b37170d 100644 --- a/app/templates/views/trial-mode.html +++ b/app/templates/views/trial-mode.html @@ -17,7 +17,7 @@ {% if current_service and current_service.trial_mode %}

    - To remove these restrictions, you can request to go live.

    + To remove these restrictions, you can request to go live.

    {% else %}

    To remove these restrictions: @@ -38,6 +38,6 @@

  3. update your settings so you’re ready to send and receive messages
  4. accept our terms of use
  5. - + {% endblock %} diff --git a/app/templates/views/using-notify.html b/app/templates/views/using-notify.html index 7d2701777..3b11fa271 100644 --- a/app/templates/views/using-notify.html +++ b/app/templates/views/using-notify.html @@ -21,7 +21,6 @@ diff --git a/app/url_converters.py b/app/url_converters.py index 15d3f3b73..8c9500d36 100644 --- a/app/url_converters.py +++ b/app/url_converters.py @@ -1,10 +1,5 @@ from werkzeug.routing import BaseConverter -from app.models.feedback import ( - GENERAL_TICKET_TYPE, - PROBLEM_TICKET_TYPE, - QUESTION_TICKET_TYPE, -) from app.models.service import Service @@ -12,9 +7,5 @@ class TemplateTypeConverter(BaseConverter): regex = "(?:{})".format("|".join(Service.TEMPLATE_TYPES)) -class TicketTypeConverter(BaseConverter): - regex = f"(?:{PROBLEM_TICKET_TYPE}|{QUESTION_TICKET_TYPE}|{GENERAL_TICKET_TYPE})" - - class SimpleDateTypeConverter(BaseConverter): regex = r"([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))" diff --git a/app/utils/branding.py b/app/utils/branding.py deleted file mode 100644 index dd35fea30..000000000 --- a/app/utils/branding.py +++ /dev/null @@ -1,23 +0,0 @@ -from app.models.organization import Organization - - -def get_email_choices(service): - organization_branding_id = ( - service.organization.email_branding_id if service.organization else None - ) - - if ( - service.organization_type == Organization.TYPE_FEDERAL - and service.email_branding_id is not None # GOV.UK is not current branding - and organization_branding_id is None # no default to supersede it (GOV.UK) - ): - yield ("govuk", "GOV.UK") - - if ( - service.organization_type == Organization.TYPE_FEDERAL - and service.organization - and organization_branding_id is None # don't offer both if org has default - and service.email_branding_name.lower() - != f"GOV.UK and {service.organization.name}".lower() - ): - yield ("govuk_and_org", f"GOV.UK and {service.organization.name}") diff --git a/get_zendesk_tickets.py b/get_zendesk_tickets.py deleted file mode 100644 index 6d32d484d..000000000 --- a/get_zendesk_tickets.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -This script can be used to retrieve Zendesk tickets. -This can be run locally if you set the ZENDESK_API_KEY. Or the script can be run from a flask shell from a ssh session. -""" -# flake8: noqa: T001 (print) - -import csv -import os -import urllib.parse - -import requests - -# Group: 3rd Line--Notify Support -NOTIFY_GROUP_ID = 360000036529 - -# Organization: GDS -NOTIFY_ORG_ID = 21891972 - -# the account used to authenticate with. If no requester is provided, the ticket will come from this account. -NOTIFY_ZENDESK_EMAIL = "zd-api-notify@digital.cabinet-office.gov.uk" -ZENDESK_API_KEY = os.environ.get("ZENDESK_API_KEY") - - -def get_tickets(): - ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}" - query_params = "type:ticket group:{}".format(NOTIFY_GROUP_ID) - query_params = urllib.parse.quote(query_params) - - next_page = ZENDESK_TICKET_URL.format(query_params) - - with open("zendesk_ticket_data.csv", "w") as csvfile: - fieldnames = [ - "Service id", - "Ticket id", - "Subject line", - "Date ticket created", - "Tags", - ] - writer = csv.DictWriter(csvfile, fieldnames=fieldnames) - writer.writeheader() - while next_page: - print(next_page) - response = requests.get( - next_page, - headers={"Content-type": "application/json"}, - auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY), - ) - data = response.json() - print(data) - for row in data["results"]: - service_url = [ - x - for x in row["description"].split("\n") - if x.startswith( - "https://www.notifications.service.gov.uk/services/" - ) - ] - service_url = service_url[0][50:] if len(service_url) > 0 else None - if service_url: - writer.writerow( - { - "Service id": service_url, - "Ticket id": row["id"], - "Subject line": row["subject"], - "Date ticket created": row["created_at"], - "Tags": row.get("tags", ""), - } - ) - next_page = data["next_page"] - - -def get_tickets_without_service_id(): - ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}" - query_params = "type:ticket group:{}".format(NOTIFY_GROUP_ID) - query_params = urllib.parse.quote(query_params) - - next_page = ZENDESK_TICKET_URL.format(query_params) - with open("zendesk_ticket_data_without_service.csv", "w") as csvfile: - fieldnames = [ - "Ticket id", - "Subject line", - "Date ticket created", - "Tags", - ] - writer = csv.DictWriter(csvfile, fieldnames=fieldnames) - writer.writeheader() - while next_page: - print(next_page) - response = requests.get( - next_page, - headers={"Content-type": "application/json"}, - auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY), - ) - data = response.json() - print(data) - for row in data["results"]: - service_url = [ - x - for x in row["description"].split("\n") - if x.startswith( - "https://www.notifications.service.gov.uk/services/" - ) - ] - service_url = service_url[0][50:] if len(service_url) > 0 else None - if not service_url: - writer.writerow( - { - "Ticket id": row["id"], - "Subject line": row["subject"], - "Date ticket created": row["created_at"], - "Tags": row.get("tags", ""), - } - ) - next_page = data["next_page"] - - -def get_tickets_with_description(): - ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}" - query_params = "type:ticket group:{}, created>2019-07-01".format(NOTIFY_GROUP_ID) - query_params = urllib.parse.quote(query_params) - - next_page = ZENDESK_TICKET_URL.format(query_params) - with open("zendesk_ticket.csv", "w") as csvfile: - fieldnames = [ - "Ticket id", - "Subject line", - "Description", - "Date ticket created", - "Tags", - ] - writer = csv.DictWriter(csvfile, fieldnames=fieldnames) - writer.writeheader() - while next_page: - print(next_page) - response = requests.get( - next_page, - headers={"Content-type": "application/json"}, - auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY), - ) - data = response.json() - print(data) - for row in data["results"]: - writer.writerow( - { - "Ticket id": row["id"], - "Subject line": row["subject"], - "Description": row["description"], - "Date ticket created": row["created_at"], - "Tags": row.get("tags", ""), - } - ) - next_page = data["next_page"] diff --git a/gulpfile.js b/gulpfile.js index 541c39cf4..6828bcaf1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -103,9 +103,6 @@ const javascripts = () => { const local = src([ paths.toolkit + 'javascripts/govuk/modules.js', paths.toolkit + 'javascripts/govuk/show-hide-content.js', - paths.src + 'javascripts/govuk/cookie-functions.js', - paths.src + 'javascripts/consent.js', - paths.src + 'javascripts/cookieMessage.js', paths.src + 'javascripts/copyToClipboard.js', paths.src + 'javascripts/autofocus.js', paths.src + 'javascripts/enhancedTextbox.js', @@ -117,7 +114,6 @@ const javascripts = () => { paths.src + 'javascripts/errorTracking.js', paths.src + 'javascripts/preventDuplicateFormSubmissions.js', paths.src + 'javascripts/fullscreenTable.js', - paths.src + 'javascripts/previewPane.js', paths.src + 'javascripts/colourPreview.js', paths.src + 'javascripts/templateFolderForm.js', paths.src + 'javascripts/collapsibleCheckboxes.js', diff --git a/paas-failwhale/static_503/stylesheets/main.css b/paas-failwhale/static_503/stylesheets/main.css index 92ac258b8..50a467879 100644 --- a/paas-failwhale/static_503/stylesheets/main.css +++ b/paas-failwhale/static_503/stylesheets/main.css @@ -8903,14 +8903,6 @@ only screen and (min-resolution: 2dppx) { padding-bottom: 75% } -.branding-preview { - width: 100%; - box-sizing: border-box; - border: solid 1px #bfc1c3; - min-height: 200px; - margin-bottom: 30px -} - #logo-img { background-color: #f8f8f8; background-image: linear-gradient(45deg, #dee0e2 25%, transparent 25%), linear-gradient(-45deg, #dee0e2 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #dee0e2 75%), linear-gradient(-45deg, transparent 75%, #dee0e2 75%); @@ -9444,56 +9436,6 @@ only screen and (min-resolution: 2dppx) { position: relative } -.edit-template-link-letter-contact, -.edit-template-link-letter-address, -.edit-template-link-letter-body, -.edit-template-link-letter-branding, -.edit-template-link { - font-family: "nta", Arial, sans-serif; - font-weight: 400; - text-transform: none; - font-size: 16px; - line-height: 1.25; - position: absolute; - background: #005ea5; - color: #fff; - padding: 10px 15px; - z-index: 10000 -} - -@media (min-width: 641px) { - - .edit-template-link-letter-contact, - .edit-template-link-letter-address, - .edit-template-link-letter-body, - .edit-template-link-letter-branding, - .edit-template-link { - font-size: 19px; - line-height: 1.31579 - } -} - -.edit-template-link-letter-contact:link, -.edit-template-link-letter-address:link, -.edit-template-link-letter-body:link, -.edit-template-link-letter-branding:link, -.edit-template-link-letter-contact:visited, -.edit-template-link-letter-address:visited, -.edit-template-link-letter-body:visited, -.edit-template-link-letter-branding:visited, -.edit-template-link:link, -.edit-template-link:visited { - color: #fff -} - -.edit-template-link-letter-contact:hover, -.edit-template-link-letter-address:hover, -.edit-template-link-letter-body:hover, -.edit-template-link-letter-branding:hover, -.edit-template-link:hover { - color: #d5e8f3 -} - .notification-status { font-family: "nta", Arial, sans-serif; font-weight: 400; @@ -9817,4 +9759,4 @@ details .arrow { .heading-upcoming-jobs { margin-top: 15px -} \ No newline at end of file +} diff --git a/poetry.lock b/poetry.lock index 4871b2259..abf5e58b9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -462,63 +462,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.3" +version = "7.3.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d874434e0cb7b90f7af2b6e3309b0733cde8ec1476eb47db148ed7deeb2a9494"}, - {file = "coverage-7.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee6621dccce8af666b8c4651f9f43467bfbf409607c604b840b78f4ff3619aeb"}, - {file = "coverage-7.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1367aa411afb4431ab58fd7ee102adb2665894d047c490649e86219327183134"}, - {file = "coverage-7.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f0f8f0c497eb9c9f18f21de0750c8d8b4b9c7000b43996a094290b59d0e7523"}, - {file = "coverage-7.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db0338c4b0951d93d547e0ff8d8ea340fecf5885f5b00b23be5aa99549e14cfd"}, - {file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d31650d313bd90d027f4be7663dfa2241079edd780b56ac416b56eebe0a21aab"}, - {file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9437a4074b43c177c92c96d051957592afd85ba00d3e92002c8ef45ee75df438"}, - {file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9e17d9cb06c13b4f2ef570355fa45797d10f19ca71395910b249e3f77942a837"}, - {file = "coverage-7.3.3-cp310-cp310-win32.whl", hash = "sha256:eee5e741b43ea1b49d98ab6e40f7e299e97715af2488d1c77a90de4a663a86e2"}, - {file = "coverage-7.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:593efa42160c15c59ee9b66c5f27a453ed3968718e6e58431cdfb2d50d5ad284"}, - {file = "coverage-7.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c944cf1775235c0857829c275c777a2c3e33032e544bcef614036f337ac37bb"}, - {file = "coverage-7.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eda7f6e92358ac9e1717ce1f0377ed2b9320cea070906ece4e5c11d172a45a39"}, - {file = "coverage-7.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c854c1d2c7d3e47f7120b560d1a30c1ca221e207439608d27bc4d08fd4aeae8"}, - {file = "coverage-7.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:222b038f08a7ebed1e4e78ccf3c09a1ca4ac3da16de983e66520973443b546bc"}, - {file = "coverage-7.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff4800783d85bff132f2cc7d007426ec698cdce08c3062c8d501ad3f4ea3d16c"}, - {file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fc200cec654311ca2c3f5ab3ce2220521b3d4732f68e1b1e79bef8fcfc1f2b97"}, - {file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:307aecb65bb77cbfebf2eb6e12009e9034d050c6c69d8a5f3f737b329f4f15fb"}, - {file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ffb0eacbadb705c0a6969b0adf468f126b064f3362411df95f6d4f31c40d31c1"}, - {file = "coverage-7.3.3-cp311-cp311-win32.whl", hash = "sha256:79c32f875fd7c0ed8d642b221cf81feba98183d2ff14d1f37a1bbce6b0347d9f"}, - {file = "coverage-7.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:243576944f7c1a1205e5cd658533a50eba662c74f9be4c050d51c69bd4532936"}, - {file = "coverage-7.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a2ac4245f18057dfec3b0074c4eb366953bca6787f1ec397c004c78176a23d56"}, - {file = "coverage-7.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9191be7af41f0b54324ded600e8ddbcabea23e1e8ba419d9a53b241dece821d"}, - {file = "coverage-7.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c0b1b8b5a4aebf8fcd227237fc4263aa7fa0ddcd4d288d42f50eff18b0bac4"}, - {file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee453085279df1bac0996bc97004771a4a052b1f1e23f6101213e3796ff3cb85"}, - {file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1191270b06ecd68b1d00897b2daddb98e1719f63750969614ceb3438228c088e"}, - {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:007a7e49831cfe387473e92e9ff07377f6121120669ddc39674e7244350a6a29"}, - {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:af75cf83c2d57717a8493ed2246d34b1f3398cb8a92b10fd7a1858cad8e78f59"}, - {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:811ca7373da32f1ccee2927dc27dc523462fd30674a80102f86c6753d6681bc6"}, - {file = "coverage-7.3.3-cp312-cp312-win32.whl", hash = "sha256:733537a182b5d62184f2a72796eb6901299898231a8e4f84c858c68684b25a70"}, - {file = "coverage-7.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:e995efb191f04b01ced307dbd7407ebf6e6dc209b528d75583277b10fd1800ee"}, - {file = "coverage-7.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbd8a5fe6c893de21a3c6835071ec116d79334fbdf641743332e442a3466f7ea"}, - {file = "coverage-7.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:50c472c1916540f8b2deef10cdc736cd2b3d1464d3945e4da0333862270dcb15"}, - {file = "coverage-7.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e9223a18f51d00d3ce239c39fc41410489ec7a248a84fab443fbb39c943616c"}, - {file = "coverage-7.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f501e36ac428c1b334c41e196ff6bd550c0353c7314716e80055b1f0a32ba394"}, - {file = "coverage-7.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:475de8213ed95a6b6283056d180b2442eee38d5948d735cd3d3b52b86dd65b92"}, - {file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:afdcc10c01d0db217fc0a64f58c7edd635b8f27787fea0a3054b856a6dff8717"}, - {file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fff0b2f249ac642fd735f009b8363c2b46cf406d3caec00e4deeb79b5ff39b40"}, - {file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a1f76cfc122c9e0f62dbe0460ec9cc7696fc9a0293931a33b8870f78cf83a327"}, - {file = "coverage-7.3.3-cp38-cp38-win32.whl", hash = "sha256:757453848c18d7ab5d5b5f1827293d580f156f1c2c8cef45bfc21f37d8681069"}, - {file = "coverage-7.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad2453b852a1316c8a103c9c970db8fbc262f4f6b930aa6c606df9b2766eee06"}, - {file = "coverage-7.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b15e03b8ee6a908db48eccf4e4e42397f146ab1e91c6324da44197a45cb9132"}, - {file = "coverage-7.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:89400aa1752e09f666cc48708eaa171eef0ebe3d5f74044b614729231763ae69"}, - {file = "coverage-7.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c59a3e59fb95e6d72e71dc915e6d7fa568863fad0a80b33bc7b82d6e9f844973"}, - {file = "coverage-7.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ede881c7618f9cf93e2df0421ee127afdfd267d1b5d0c59bcea771cf160ea4a"}, - {file = "coverage-7.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3bfd2c2f0e5384276e12b14882bf2c7621f97c35320c3e7132c156ce18436a1"}, - {file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7f3bad1a9313401ff2964e411ab7d57fb700a2d5478b727e13f156c8f89774a0"}, - {file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:65d716b736f16e250435473c5ca01285d73c29f20097decdbb12571d5dfb2c94"}, - {file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a702e66483b1fe602717020a0e90506e759c84a71dbc1616dd55d29d86a9b91f"}, - {file = "coverage-7.3.3-cp39-cp39-win32.whl", hash = "sha256:7fbf3f5756e7955174a31fb579307d69ffca91ad163467ed123858ce0f3fd4aa"}, - {file = "coverage-7.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cad9afc1644b979211989ec3ff7d82110b2ed52995c2f7263e7841c846a75348"}, - {file = "coverage-7.3.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:d299d379b676812e142fb57662a8d0d810b859421412b4d7af996154c00c31bb"}, - {file = "coverage-7.3.3.tar.gz", hash = "sha256:df04c64e58df96b4427db8d0559e95e2df3138c9916c96f9f6a4dd220db2fdb7"}, + {file = "coverage-7.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aff2bd3d585969cc4486bfc69655e862028b689404563e6b549e6a8244f226df"}, + {file = "coverage-7.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4353923f38d752ecfbd3f1f20bf7a3546993ae5ecd7c07fd2f25d40b4e54571"}, + {file = "coverage-7.3.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea473c37872f0159294f7073f3fa72f68b03a129799f3533b2bb44d5e9fa4f82"}, + {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5214362abf26e254d749fc0c18af4c57b532a4bfde1a057565616dd3b8d7cc94"}, + {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f99b7d3f7a7adfa3d11e3a48d1a91bb65739555dd6a0d3fa68aa5852d962e5b1"}, + {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:74397a1263275bea9d736572d4cf338efaade2de9ff759f9c26bcdceb383bb49"}, + {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f154bd866318185ef5865ace5be3ac047b6d1cc0aeecf53bf83fe846f4384d5d"}, + {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e0d84099ea7cba9ff467f9c6f747e3fc3906e2aadac1ce7b41add72e8d0a3712"}, + {file = "coverage-7.3.4-cp310-cp310-win32.whl", hash = "sha256:3f477fb8a56e0c603587b8278d9dbd32e54bcc2922d62405f65574bd76eba78a"}, + {file = "coverage-7.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:c75738ce13d257efbb6633a049fb2ed8e87e2e6c2e906c52d1093a4d08d67c6b"}, + {file = "coverage-7.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:997aa14b3e014339d8101b9886063c5d06238848905d9ad6c6eabe533440a9a7"}, + {file = "coverage-7.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a9c5bc5db3eb4cd55ecb8397d8e9b70247904f8eca718cc53c12dcc98e59fc8"}, + {file = "coverage-7.3.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27ee94f088397d1feea3cb524e4313ff0410ead7d968029ecc4bc5a7e1d34fbf"}, + {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ce03e25e18dd9bf44723e83bc202114817f3367789052dc9e5b5c79f40cf59d"}, + {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85072e99474d894e5df582faec04abe137b28972d5e466999bc64fc37f564a03"}, + {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a877810ef918d0d345b783fc569608804f3ed2507bf32f14f652e4eaf5d8f8d0"}, + {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9ac17b94ab4ca66cf803f2b22d47e392f0977f9da838bf71d1f0db6c32893cb9"}, + {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36d75ef2acab74dc948d0b537ef021306796da551e8ac8b467810911000af66a"}, + {file = "coverage-7.3.4-cp311-cp311-win32.whl", hash = "sha256:47ee56c2cd445ea35a8cc3ad5c8134cb9bece3a5cb50bb8265514208d0a65928"}, + {file = "coverage-7.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:11ab62d0ce5d9324915726f611f511a761efcca970bd49d876cf831b4de65be5"}, + {file = "coverage-7.3.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:33e63c578f4acce1b6cd292a66bc30164495010f1091d4b7529d014845cd9bee"}, + {file = "coverage-7.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:782693b817218169bfeb9b9ba7f4a9f242764e180ac9589b45112571f32a0ba6"}, + {file = "coverage-7.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4277ddaad9293454da19121c59f2d850f16bcb27f71f89a5c4836906eb35ef"}, + {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d892a19ae24b9801771a5a989fb3e850bd1ad2e2b6e83e949c65e8f37bc67a1"}, + {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3024ec1b3a221bd10b5d87337d0373c2bcaf7afd86d42081afe39b3e1820323b"}, + {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1c3e9d2bbd6f3f79cfecd6f20854f4dc0c6e0ec317df2b265266d0dc06535f1"}, + {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e91029d7f151d8bf5ab7d8bfe2c3dbefd239759d642b211a677bc0709c9fdb96"}, + {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6879fe41c60080aa4bb59703a526c54e0412b77e649a0d06a61782ecf0853ee1"}, + {file = "coverage-7.3.4-cp312-cp312-win32.whl", hash = "sha256:fd2f8a641f8f193968afdc8fd1697e602e199931012b574194052d132a79be13"}, + {file = "coverage-7.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:d1d0ce6c6947a3a4aa5479bebceff2c807b9f3b529b637e2b33dea4468d75fc7"}, + {file = "coverage-7.3.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36797b3625d1da885b369bdaaa3b0d9fb8865caed3c2b8230afaa6005434aa2f"}, + {file = "coverage-7.3.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfed0ec4b419fbc807dec417c401499ea869436910e1ca524cfb4f81cf3f60e7"}, + {file = "coverage-7.3.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f97ff5a9fc2ca47f3383482858dd2cb8ddbf7514427eecf5aa5f7992d0571429"}, + {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607b6c6b35aa49defaebf4526729bd5238bc36fe3ef1a417d9839e1d96ee1e4c"}, + {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8e258dcc335055ab59fe79f1dec217d9fb0cdace103d6b5c6df6b75915e7959"}, + {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a02ac7c51819702b384fea5ee033a7c202f732a2a2f1fe6c41e3d4019828c8d3"}, + {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b710869a15b8caf02e31d16487a931dbe78335462a122c8603bb9bd401ff6fb2"}, + {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c6a23ae9348a7a92e7f750f9b7e828448e428e99c24616dec93a0720342f241d"}, + {file = "coverage-7.3.4-cp38-cp38-win32.whl", hash = "sha256:758ebaf74578b73f727acc4e8ab4b16ab6f22a5ffd7dd254e5946aba42a4ce76"}, + {file = "coverage-7.3.4-cp38-cp38-win_amd64.whl", hash = "sha256:309ed6a559bc942b7cc721f2976326efbfe81fc2b8f601c722bff927328507dc"}, + {file = "coverage-7.3.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aefbb29dc56317a4fcb2f3857d5bce9b881038ed7e5aa5d3bcab25bd23f57328"}, + {file = "coverage-7.3.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:183c16173a70caf92e2dfcfe7c7a576de6fa9edc4119b8e13f91db7ca33a7923"}, + {file = "coverage-7.3.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a4184dcbe4f98d86470273e758f1d24191ca095412e4335ff27b417291f5964"}, + {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93698ac0995516ccdca55342599a1463ed2e2d8942316da31686d4d614597ef9"}, + {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb220b3596358a86361139edce40d97da7458412d412e1e10c8e1970ee8c09ab"}, + {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5b14abde6f8d969e6b9dd8c7a013d9a2b52af1235fe7bebef25ad5c8f47fa18"}, + {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:610afaf929dc0e09a5eef6981edb6a57a46b7eceff151947b836d869d6d567c1"}, + {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed790728fb71e6b8247bd28e77e99d0c276dff952389b5388169b8ca7b1c28"}, + {file = "coverage-7.3.4-cp39-cp39-win32.whl", hash = "sha256:c15fdfb141fcf6a900e68bfa35689e1256a670db32b96e7a931cab4a0e1600e5"}, + {file = "coverage-7.3.4-cp39-cp39-win_amd64.whl", hash = "sha256:38d0b307c4d99a7aca4e00cad4311b7c51b7ac38fb7dea2abe0d182dd4008e05"}, + {file = "coverage-7.3.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b1e0f25ae99cf247abfb3f0fac7ae25739e4cd96bf1afa3537827c576b4847e5"}, + {file = "coverage-7.3.4.tar.gz", hash = "sha256:020d56d2da5bc22a0e00a5b0d54597ee91ad72446fa4cf1b97c35022f6b6dbf0"}, ] [package.extras] @@ -884,13 +884,13 @@ email = ["email-validator"] [[package]] name = "freezegun" -version = "1.3.1" +version = "1.4.0" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" files = [ - {file = "freezegun-1.3.1-py3-none-any.whl", hash = "sha256:065e77a12624d05531afa87ade12a0b9bdb53495c4573893252a055b545ce3ea"}, - {file = "freezegun-1.3.1.tar.gz", hash = "sha256:48984397b3b58ef5dfc645d6a304b0060f612bcecfdaaf45ce8aff0077a6cb6a"}, + {file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"}, + {file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"}, ] [package.dependencies] @@ -1528,13 +1528,13 @@ files = [ [[package]] name = "moto" -version = "4.2.11" +version = "4.2.12" description = "" optional = false python-versions = ">=3.7" files = [ - {file = "moto-4.2.11-py2.py3-none-any.whl", hash = "sha256:58c12ab9ee69b6a5d1cddf83611ba4071508f07894317c57844b3ae6dc5bcd38"}, - {file = "moto-4.2.11.tar.gz", hash = "sha256:2da62d52eaa765dfe2762c920f0a88a58f3a09e04581c91db967d92faec848f1"}, + {file = "moto-4.2.12-py2.py3-none-any.whl", hash = "sha256:bdcad46e066a55b7d308a786e5dca863b3cba04c6239c6974135a48d1198b3ab"}, + {file = "moto-4.2.12.tar.gz", hash = "sha256:7c4d37f47becb4a0526b64df54484e988c10fde26861fc3b5c065bc78800cb59"}, ] [package.dependencies] @@ -1549,29 +1549,29 @@ werkzeug = ">=0.5,<2.2.0 || >2.2.0,<2.2.1 || >2.2.1" xmltodict = "*" [package.extras] -all = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.4.2)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +all = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] apigateway = ["PyYAML (>=5.1)", "ecdsa (!=0.15)", "openapi-spec-validator (>=0.5.0)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"] apigatewayv2 = ["PyYAML (>=5.1)"] appsync = ["graphql-core"] awslambda = ["docker (>=3.0.0)"] batch = ["docker (>=3.0.0)"] -cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.4.2)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] cognitoidp = ["ecdsa (!=0.15)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"] ds = ["sshpubkeys (>=3.1.0)"] -dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.4.2)"] -dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.4.2)"] +dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.0)"] +dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.0)"] ebs = ["sshpubkeys (>=3.1.0)"] ec2 = ["sshpubkeys (>=3.1.0)"] efs = ["sshpubkeys (>=3.1.0)"] eks = ["sshpubkeys (>=3.1.0)"] glue = ["pyparsing (>=3.0.7)"] iotdata = ["jsondiff (>=1.1.2)"] -proxy = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.4.2)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] -resourcegroupstaggingapi = ["PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.4.2)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "sshpubkeys (>=3.1.0)"] +proxy = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +resourcegroupstaggingapi = ["PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "sshpubkeys (>=3.1.0)"] route53resolver = ["sshpubkeys (>=3.1.0)"] -s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.4.2)"] -s3crc32c = ["PyYAML (>=5.1)", "crc32c", "py-partiql-parser (==0.4.2)"] -server = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.4.2)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] +s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.5.0)"] +s3crc32c = ["PyYAML (>=5.1)", "crc32c", "py-partiql-parser (==0.5.0)"] +server = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] ssm = ["PyYAML (>=5.1)"] xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] @@ -1920,18 +1920,18 @@ pip = "*" [[package]] name = "pip-audit" -version = "2.6.1" +version = "2.6.2" description = "A tool for scanning Python environments for known vulnerabilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pip_audit-2.6.1-py3-none-any.whl", hash = "sha256:8a32bb67dca6a76c244bbccebed562c0f6957b1fc9d34d59a9ec0fbff0672ae0"}, - {file = "pip_audit-2.6.1.tar.gz", hash = "sha256:55c9bd18b0fe3959f73397db08d257c6012ad1826825e3d74cb6c3f79e95c245"}, + {file = "pip_audit-2.6.2-py3-none-any.whl", hash = "sha256:ac3a4b6e977ef2c574aa8d19a5d71d12201bdb65bba2d67d9df49f53f0be5e7d"}, + {file = "pip_audit-2.6.2.tar.gz", hash = "sha256:0bbd023a199a104b29f949f063a872d41113b5a9048285666820fa35a76a7794"}, ] [package.dependencies] CacheControl = {version = ">=0.13.0", extras = ["filecache"]} -cyclonedx-python-lib = ">=4.0,<5.0" +cyclonedx-python-lib = ">=4,<6" html5lib = ">=1.1" packaging = ">=23.0.0" pip-api = ">=0.0.28" @@ -1943,8 +1943,8 @@ toml = ">=0.10" [package.extras] dev = ["build", "bump (>=1.3.2)", "pip-audit[doc,lint,test]"] doc = ["pdoc"] -lint = ["black (>=22.3.0)", "interrogate", "isort", "mypy", "ruff (<0.0.281)", "types-html5lib", "types-requests", "types-toml"] -test = ["coverage[toml]", "pretend", "pytest", "pytest-cov"] +lint = ["interrogate", "mypy", "ruff (<0.1.9)", "types-html5lib", "types-requests", "types-toml"] +test = ["coverage[toml] (>=7.0,!=7.3.3,<8.0)", "pretend", "pytest", "pytest-cov"] [[package]] name = "pip-requirements-parser" @@ -3107,4 +3107,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "06cc93098e0294887fdce7d80fd03cbb0117c9546244a340de589fa682f11799" +content-hash = "f06451d8cf0d8f4d59b67f06d2ede8153a19055403a81b90afc700dcf60d139b" diff --git a/pyproject.toml b/pyproject.toml index c0146d1f5..3007cfbf4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ bandit = "*" beautifulsoup4 = "^4.12.2" black = "^23.12.0" coverage = "*" -freezegun = "^1.3.1" +freezegun = "^1.4.0" flake8 = "^6.1.0" flake8-bugbear = "^23.12.2" flake8-print = "^5.0.0" diff --git a/tests/__init__.py b/tests/__init__.py index bf6527147..0f2827511 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -222,7 +222,6 @@ def organization_json( agreement_signed_on_behalf_of_name=None, agreement_signed_on_behalf_of_email_address=None, organization_type="federal", - request_to_go_live_notes=None, notes=None, billing_contact_email_addresses=None, billing_contact_names=None, @@ -248,7 +247,6 @@ def organization_json( "agreement_signed_on_behalf_of_name": agreement_signed_on_behalf_of_name, "agreement_signed_on_behalf_of_email_address": agreement_signed_on_behalf_of_email_address, "domains": domains or [], - "request_to_go_live_notes": request_to_go_live_notes, "count_of_live_services": len(services), "notes": notes, "billing_contact_email_addresses": billing_contact_email_addresses, diff --git a/tests/app/main/views/organizations/test_organizations.py b/tests/app/main/views/organizations/test_organizations.py index 0b330b5a9..7679e1429 100644 --- a/tests/app/main/views/organizations/test_organizations.py +++ b/tests/app/main/views/organizations/test_organizations.py @@ -928,10 +928,8 @@ def test_organization_settings_for_platform_admin( "Label Value Action", "Name Test organization Change organization name", "Sector Federal government Change sector for the organization", - "Request to go live notes None Change go live notes for the organization", "Billing details None Change billing details for the organization", "Notes None Change the notes for the organization", - "Default email branding GOV.UK Change default email branding for the organization", "Known email domains None Change known email domains for the organization", ] @@ -1347,48 +1345,6 @@ def test_update_organization_with_non_unique_name( ) -def test_get_edit_organization_go_live_notes_page( - client_request, - platform_admin_user, - mock_get_organization, - organization_one, -): - client_request.login(platform_admin_user) - page = client_request.get( - ".edit_organization_go_live_notes", - org_id=organization_one["id"], - ) - assert page.find("textarea", id="request_to_go_live_notes") - - -@pytest.mark.parametrize( - ("input_note", "saved_note"), - [("Needs permission", "Needs permission"), (" ", None)], -) -def test_post_edit_organization_go_live_notes_updates_go_live_notes( - client_request, - platform_admin_user, - mock_get_organization, - mock_update_organization, - organization_one, - input_note, - saved_note, -): - client_request.login(platform_admin_user) - client_request.post( - ".edit_organization_go_live_notes", - org_id=organization_one["id"], - _data={"request_to_go_live_notes": input_note}, - _expected_redirect=url_for( - ".organization_settings", - org_id=organization_one["id"], - ), - ) - mock_update_organization.assert_called_once_with( - organization_one["id"], request_to_go_live_notes=saved_note - ) - - def test_organization_settings_links_to_edit_organization_notes_page( mocker, mock_get_organization, diff --git a/tests/app/main/views/service_settings/test_email_branding_requests.py b/tests/app/main/views/service_settings/test_email_branding_requests.py deleted file mode 100644 index ccab8a87a..000000000 --- a/tests/app/main/views/service_settings/test_email_branding_requests.py +++ /dev/null @@ -1,519 +0,0 @@ -from unittest.mock import ANY, PropertyMock - -import pytest -from flask import url_for -from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket - -from tests import sample_uuid -from tests.conftest import ORGANISATION_ID, SERVICE_ONE_ID, normalize_spaces - - -@pytest.mark.parametrize( - ("organization_type", "expected_options"), - [ - ( - "other", - [ - ("something_else", "Something else"), - ], - ), - ], -) -def test_email_branding_request_page_when_no_branding_is_set( - service_one, - client_request, - mocker, - mock_get_email_branding, - organization_type, - expected_options, -): - service_one["email_branding"] = None - service_one["organization_type"] = organization_type - - mocker.patch( - "app.models.service.Service.email_branding_id", - new_callable=PropertyMock, - return_value=None, - ) - - page = client_request.get(".email_branding_request", service_id=SERVICE_ONE_ID) - - assert mock_get_email_branding.called is False - assert page.find_all("iframe")[1]["src"] == url_for( - "main.email_template", branding_style="__NONE__" - ) - - button_text = normalize_spaces(page.select_one(".page-footer button").text) - - assert [ - ( - radio["value"], - page.select_one("label[for={}]".format(radio["id"])).text.strip(), - ) - for radio in page.select("input[type=radio]") - ] == expected_options - - assert button_text == "Continue" - - -def test_email_branding_request_page_shows_branding_if_set( - mocker, - service_one, - client_request, - mock_get_email_branding, - mock_get_service_organization, -): - mocker.patch( - "app.models.service.Service.email_branding_id", - new_callable=PropertyMock, - return_value="some-random-branding", - ) - - page = client_request.get(".email_branding_request", service_id=SERVICE_ONE_ID) - assert page.find_all("iframe")[1]["src"] == url_for( - "main.email_template", branding_style="some-random-branding" - ) - - -def test_email_branding_request_page_back_link( - client_request, -): - page = client_request.get(".email_branding_request", service_id=SERVICE_ONE_ID) - - back_link = page.select_one("a.usa-back-link") - assert len(back_link) > 0, "No back link found on the page" - assert back_link["href"] == url_for(".service_settings", service_id=SERVICE_ONE_ID) - - -@pytest.mark.parametrize( - ("data", "org_type", "endpoint"), - [ - ( - { - "options": "govuk", - }, - "federal", - "main.email_branding_govuk", - ), - ( - { - "options": "govuk_and_org", - }, - "federal", - "main.email_branding_govuk_and_org", - ), - ( - { - "options": "something_else", - }, - "federal", - "main.email_branding_something_else", - ), - ], -) -def test_email_branding_request_submit( - client_request, - service_one, - mocker, - mock_get_email_branding, - organization_one, - data, - org_type, - endpoint, -): - organization_one["organization_type"] = org_type - service_one["email_branding"] = sample_uuid() - service_one["organization"] = organization_one - - mocker.patch( - "app.organizations_client.get_organization", - return_value=organization_one, - ) - - client_request.post( - ".email_branding_request", - service_id=SERVICE_ONE_ID, - _data=data, - _expected_status=302, - _expected_redirect=url_for( - endpoint, - service_id=SERVICE_ONE_ID, - ), - ) - - -def test_email_branding_request_submit_when_no_radio_button_is_selected( - client_request, - service_one, - mock_get_email_branding, -): - service_one["email_branding"] = sample_uuid() - - page = client_request.post( - ".email_branding_request", - service_id=SERVICE_ONE_ID, - _data={"options": ""}, - _follow_redirects=True, - ) - assert page.h1.text == "Change email branding" - assert ( - normalize_spaces(page.select_one(".error-message").text) == "Select an option" - ) - - -@pytest.mark.parametrize( - ("endpoint", "expected_heading"), - [ - ("main.email_branding_govuk_and_org", "Before you request new branding"), - ], -) -def test_email_branding_description_pages_for_org_branding( - client_request, - mocker, - service_one, - organization_one, - mock_get_email_branding, - endpoint, - expected_heading, -): - service_one["email_branding"] = sample_uuid() - service_one["organization"] = organization_one - - mocker.patch( - "app.organizations_client.get_organization", - return_value=organization_one, - ) - - page = client_request.get( - endpoint, - service_id=SERVICE_ONE_ID, - ) - assert page.h1.text == expected_heading - assert ( - normalize_spaces(page.select_one(".page-footer button").text) - == "Request new branding" - ) - - -@pytest.mark.parametrize( - ("endpoint", "service_org_type", "branding_preview_id"), - [("main.email_branding_govuk", "central", "__NONE__")], -) -@pytest.mark.skip(reason="Update for TTS") -def test_email_branding_govuk_and_nhs_pages( - client_request, - mocker, - service_one, - organization_one, - mock_get_email_branding, - endpoint, - service_org_type, - branding_preview_id, -): - organization_one["organization_type"] = service_org_type - service_one["email_branding"] = sample_uuid() - service_one["organization"] = organization_one - - mocker.patch( - "app.organizations_client.get_organization", - return_value=organization_one, - ) - - page = client_request.get( - endpoint, - service_id=SERVICE_ONE_ID, - ) - assert page.h1.text == "Check your new branding" - assert "Emails from service one will look like this" in normalize_spaces(page.text) - assert page.find("iframe")["src"] == url_for( - "main.email_template", branding_style=branding_preview_id - ) - assert ( - normalize_spaces(page.select_one(".page-footer button").text) - == "Use this branding" - ) - - -@pytest.mark.skip(reason="Update for TTS") -def test_email_branding_something_else_page(client_request, service_one): - # expect to have a "NHS" option as well as the - # fallback, so back button goes to choices page - service_one["organization_type"] = "nhs_central" - - page = client_request.get( - "main.email_branding_something_else", - service_id=SERVICE_ONE_ID, - ) - assert normalize_spaces(page.h1.text) == "Describe the branding you want" - assert page.select_one("textarea")["name"] == ("something_else") - assert ( - normalize_spaces(page.select_one(".page-footer button").text) - == "Request new branding" - ) - assert page.select_one(".usa-back-link")["href"] == url_for( - "main.email_branding_request", - service_id=SERVICE_ONE_ID, - ) - - -def test_get_email_branding_something_else_page_is_only_option( - client_request, service_one -): - # should only have a "something else" option - # so back button goes back to settings page - service_one["organization_type"] = "other" - - page = client_request.get( - "main.email_branding_something_else", - service_id=SERVICE_ONE_ID, - ) - assert page.select_one(".usa-back-link")["href"] == url_for( - "main.service_settings", - service_id=SERVICE_ONE_ID, - ) - - -@pytest.mark.parametrize( - "endpoint", - [ - ("main.email_branding_govuk"), - ("main.email_branding_govuk_and_org"), - ("main.email_branding_organization"), - ], -) -def test_email_branding_pages_give_404_if_selected_branding_not_allowed( - client_request, - endpoint, -): - # The only email branding allowed is 'something_else', so trying to visit any of the other - # endpoints gives a 404 status code. - client_request.get(endpoint, service_id=SERVICE_ONE_ID, _expected_status=404) - - -def test_email_branding_govuk_submit( - mocker, - client_request, - service_one, - organization_one, - no_reply_to_email_addresses, - mock_get_email_branding, - single_sms_sender, - mock_update_service, -): - mocker.patch( - "app.organizations_client.get_organization", - return_value=organization_one, - ) - mocker.patch( - "app.models.service.Service.organization_id", - new_callable=PropertyMock, - return_value=ORGANISATION_ID, - ) - service_one["email_branding"] = sample_uuid() - - page = client_request.post( - ".email_branding_govuk", - service_id=SERVICE_ONE_ID, - _follow_redirects=True, - ) - - mock_update_service.assert_called_once_with( - SERVICE_ONE_ID, - email_branding=None, - ) - assert page.h1.text == "Settings" - assert ( - normalize_spaces(page.select_one(".banner-default").text) - == "You’ve updated your email branding" - ) - - -@pytest.mark.skip(reason="Update for TTS") -def test_email_branding_govuk_and_org_submit( - mocker, - client_request, - service_one, - organization_one, - no_reply_to_email_addresses, - mock_get_email_branding, - single_sms_sender, -): - mocker.patch( - "app.organizations_client.get_organization", - return_value=organization_one, - ) - mocker.patch( - "app.models.service.Service.organization_id", - new_callable=PropertyMock, - return_value=ORGANISATION_ID, - ) - service_one["email_branding"] = sample_uuid() - - mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") - mock_send_ticket_to_zendesk = mocker.patch( - "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", - autospec=True, - ) - - page = client_request.post( - ".email_branding_govuk_and_org", - service_id=SERVICE_ONE_ID, - _follow_redirects=True, - ) - - mock_create_ticket.assert_called_once_with( - ANY, - message="\n".join( - [ - "Organization: organization one", - "Service: service one", - "http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb", - "", - "---", - "Current branding: Organization name", - "Branding requested: GOV.UK and organization one\n", - ] - ), - subject="Email branding request - service one", - ticket_type="question", - user_name="Test User", - user_email="test@user.gsa.gov", - org_id=ORGANISATION_ID, - org_type="central", - service_id=SERVICE_ONE_ID, - ) - mock_send_ticket_to_zendesk.assert_called_once() - assert normalize_spaces(page.select_one(".banner-default").text) == ( - "Thanks for your branding request. We’ll get back to you " - "within one working day." - ) - - -@pytest.mark.skip(reason="Update for TTS") -def test_email_branding_organization_submit( - mocker, - client_request, - service_one, - organization_one, - no_reply_to_email_addresses, - mock_get_email_branding, - single_sms_sender, -): - mocker.patch( - "app.organizations_client.get_organization", - return_value=organization_one, - ) - mocker.patch( - "app.models.service.Service.organization_id", - new_callable=PropertyMock, - return_value=ORGANISATION_ID, - ) - service_one["email_branding"] = sample_uuid() - - mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") - mock_send_ticket_to_zendesk = mocker.patch( - "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", - autospec=True, - ) - - page = client_request.post( - ".email_branding_organization", - service_id=SERVICE_ONE_ID, - _follow_redirects=True, - ) - - mock_create_ticket.assert_called_once_with( - ANY, - message="\n".join( - [ - "Organization: organization one", - "Service: service one", - "http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb", - "", - "---", - "Current branding: Organization name", - "Branding requested: organization one\n", - ] - ), - subject="Email branding request - service one", - ticket_type="question", - user_name="Test User", - user_email="test@user.gsa.gov", - org_id=ORGANISATION_ID, - org_type="central", - service_id=SERVICE_ONE_ID, - ) - mock_send_ticket_to_zendesk.assert_called_once() - assert normalize_spaces(page.select_one(".banner-default").text) == ( - "Thanks for your branding request. We’ll get back to you " - "within one working day." - ) - - -def test_email_branding_something_else_submit( - client_request, - mocker, - service_one, - no_reply_to_email_addresses, - mock_get_email_branding, - single_sms_sender, -): - service_one["email_branding"] = sample_uuid() - service_one["organization_type"] = "nhs_local" - - mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") - mock_send_ticket_to_zendesk = mocker.patch( - "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", - autospec=True, - ) - - page = client_request.post( - ".email_branding_something_else", - service_id=SERVICE_ONE_ID, - _data={"something_else": "Homer Simpson"}, - _follow_redirects=True, - ) - - mock_create_ticket.assert_called_once_with( - ANY, - message="\n".join( - [ - "Organization: Can’t tell (domain is user.gsa.gov)", - "Service: service one", - "http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb", - "", - "---", - "Current branding: Organization name", - "Branding requested: Something else\n", - "Homer Simpson\n", - ] - ), - subject="Email branding request - service one", - ticket_type="question", - user_name="Test User", - user_email="test@user.gsa.gov", - org_id=None, - org_type="nhs_local", - service_id=SERVICE_ONE_ID, - ) - mock_send_ticket_to_zendesk.assert_called_once() - assert normalize_spaces(page.select_one(".banner-default").text) == ( - "Thanks for your branding request. We’ll get back to you " - "within one working day." - ) - - -def test_email_branding_something_else_submit_shows_error_if_textbox_is_empty( - client_request, -): - page = client_request.post( - ".email_branding_something_else", - service_id=SERVICE_ONE_ID, - _data={"something_else": ""}, - _follow_redirects=True, - ) - assert normalize_spaces(page.h1.text) == "Describe the branding you want" - assert ( - normalize_spaces(page.select_one(".usa-error-message").text) - == "Error: Cannot be empty" - ) diff --git a/tests/app/main/views/service_settings/test_service_settings.py b/tests/app/main/views/service_settings/test_service_settings.py index ede9f1e92..a36d2c0ff 100644 --- a/tests/app/main/views/service_settings/test_service_settings.py +++ b/tests/app/main/views/service_settings/test_service_settings.py @@ -1,19 +1,15 @@ -from datetime import datetime from functools import partial -from unittest.mock import ANY, Mock, PropertyMock, call -from urllib.parse import parse_qs, urlparse +from unittest.mock import Mock, PropertyMock, call from uuid import uuid4 import pytest from flask import url_for from freezegun import freeze_time from notifications_python_client.errors import HTTPError -from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket import app from tests import ( find_element_by_tag_and_partial_text, - invite_json, organization_json, sample_uuid, service_json, @@ -31,7 +27,6 @@ from tests.conftest import ( create_platform_admin_user, create_reply_to_email_address, create_sms_sender, - create_template, normalize_spaces, ) @@ -83,7 +78,6 @@ def _mock_get_service_settings_page_common( "Rate limit 3,000 per minute Change rate limit", "Message batch limit 1,000 per send Change message batch limit", "Free text message allowance 250,000 per year Change free text message allowance", - "Email branding GOV.UK Change email branding (admin view)", "Custom data retention Email – 7 days Change data retention", "Receive inbound SMS Off Change your settings for Receive inbound SMS", "Email authentication Off Change your settings for Email authentication", @@ -121,39 +115,6 @@ def test_should_show_overview( app.service_api_client.get_service.assert_called_with(SERVICE_ONE_ID) -@pytest.mark.usefixtures("_mock_get_service_settings_page_common") -def test_no_go_live_link_for_service_without_organization( - client_request, - mocker, - no_reply_to_email_addresses, - single_sms_sender, - platform_admin_user, -): - mocker.patch("app.organizations_client.get_organization", return_value=None) - client_request.login(platform_admin_user) - page = client_request.get("main.service_settings", service_id=SERVICE_ONE_ID) - - assert page.find("h1").text == "Settings" - - is_live = find_element_by_tag_and_partial_text(page, tag="td", string="Live") - assert ( - normalize_spaces(is_live.find_next_sibling().text) - == "No (organization must be set first)" - ) - - organization = find_element_by_tag_and_partial_text( - page, tag="td", string="Organization" - ) - assert ( - normalize_spaces(organization.find_next_siblings()[0].text) - == "Not set Federal government" - ) - assert ( - normalize_spaces(organization.find_next_siblings()[1].text) - == "Change organization for service" - ) - - @pytest.mark.usefixtures("_mock_get_service_settings_page_common") def test_organization_name_links_to_org_dashboard( client_request, @@ -256,13 +217,11 @@ def test_should_show_overview_for_service_with_more_things_set( service_one, single_reply_to_email_address, single_sms_sender, - mock_get_email_branding, permissions, expected_rows, ): client_request.login(active_user_with_permissions) service_one["permissions"] = permissions - service_one["email_branding"] = uuid4() page = client_request.get("main.service_settings", service_id=service_one["id"]) for index, row in enumerate(expected_rows): assert row == " ".join(page.find_all("tr")[index + 1].text.split()) @@ -588,1120 +547,12 @@ def test_should_redirect_after_service_name_change( ) -@pytest.mark.parametrize( - ("volumes", "consent_to_research", "expected_estimated_volumes_item"), - [ - ((0, 0), None, "Tell us how many messages you expect to send Not completed"), - ((1, 0), None, "Tell us how many messages you expect to send Not completed"), - ((1, 0), False, "Tell us how many messages you expect to send Completed"), - ((1, 0), True, "Tell us how many messages you expect to send Completed"), - ((9, 99), True, "Tell us how many messages you expect to send Completed"), - ], -) -def test_should_check_if_estimated_volumes_provided( - client_request, - mocker, - single_sms_sender, - single_reply_to_email_address, - mock_get_service_templates, - mock_get_users_by_service, - mock_get_organization, - mock_get_invites_for_service, - volumes, - consent_to_research, - expected_estimated_volumes_item, -): - for volume, channel in zip(volumes, ("sms", "email")): - mocker.patch( - "app.models.service.Service.volume_{}".format(channel), - create=True, - new_callable=PropertyMock, - return_value=volume, - ) - - mocker.patch( - "app.models.service.Service.consent_to_research", - create=True, - new_callable=PropertyMock, - return_value=consent_to_research, - ) - - page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) - assert page.h1.text == "Before you request to go live" - - assert normalize_spaces(page.select_one(".task-list .task-list-item").text) == ( - expected_estimated_volumes_item - ) - - -@pytest.mark.parametrize( - ( - "volume_email", - "count_of_email_templates", - "reply_to_email_addresses", - "expected_reply_to_checklist_item", - ), - [ - (None, 1, [], "Add a reply-to email address Not completed"), - (None, 1, [{}], "Add a reply-to email address Completed"), - (1, 1, [], "Add a reply-to email address Not completed"), - (1, 1, [{}], "Add a reply-to email address Completed"), - (1, 0, [], "Add a reply-to email address Not completed"), - (1, 0, [{}], "Add a reply-to email address Completed"), - ], -) -def test_should_check_for_reply_to_on_go_live( - client_request, - mocker, - service_one, - fake_uuid, - single_sms_sender, - volume_email, - count_of_email_templates, - reply_to_email_addresses, - expected_reply_to_checklist_item, - mock_get_invites_for_service, - mock_get_users_by_service, -): - mocker.patch( - "app.service_api_client.get_service_templates", - return_value={ - "data": [ - create_template(template_type="email") - for _ in range(0, count_of_email_templates) - ] - }, - ) - - mock_get_reply_to_email_addresses = mocker.patch( - "app.main.views.service_settings.service_api_client.get_reply_to_email_addresses", - return_value=reply_to_email_addresses, - ) - - for channel, volume in (("email", volume_email), ("sms", 0)): - mocker.patch( - "app.models.service.Service.volume_{}".format(channel), - create=True, - new_callable=PropertyMock, - return_value=volume, - ) - - page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) - assert page.h1.text == "Before you request to go live" - - checklist_items = page.select(".task-list .task-list-item") - assert normalize_spaces(checklist_items[3].text) == expected_reply_to_checklist_item - - if count_of_email_templates: - mock_get_reply_to_email_addresses.assert_called_once_with(SERVICE_ONE_ID) - - -@pytest.mark.parametrize( - ( - "volume_email", - "count_of_email_templates", - "reply_to_email_addresses", - "expected_reply_to_checklist_item", - ), - [ - (None, 0, [], ""), - (0, 0, [], ""), - ], -) -def test_should_check_for_reply_to_on_go_live_index_error( - client_request, - mocker, - service_one, - fake_uuid, - single_sms_sender, - volume_email, - count_of_email_templates, - reply_to_email_addresses, - expected_reply_to_checklist_item, - mock_get_invites_for_service, - mock_get_users_by_service, -): - mocker.patch( - "app.service_api_client.get_service_templates", - return_value={ - "data": [ - create_template(template_type="email") - for _ in range(0, count_of_email_templates) - ] - }, - ) - - mocker.patch( - "app.main.views.service_settings.service_api_client.get_reply_to_email_addresses", - return_value=reply_to_email_addresses, - ) - - for channel, volume in (("email", volume_email), ("sms", 0)): - mocker.patch( - "app.models.service.Service.volume_{}".format(channel), - create=True, - new_callable=PropertyMock, - return_value=volume, - ) - - page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) - assert page.h1.text == "Before you request to go live" - checklist_items = page.select(".task-list .task-list-item") - - with pytest.raises(expected_exception=IndexError): - assert ( - normalize_spaces(checklist_items[3].text) - == expected_reply_to_checklist_item - ) - - -@pytest.mark.parametrize( - ( - "count_of_users_with_manage_service", - "count_of_invites_with_manage_service", - "expected_user_checklist_item", - ), - [ - ( - 1, - 0, - "Add a team member who can manage settings, team and usage Not completed", - ), - (2, 0, "Add a team member who can manage settings, team and usage Completed"), - (1, 1, "Add a team member who can manage settings, team and usage Completed"), - ], -) -@pytest.mark.parametrize( - ("count_of_templates", "expected_templates_checklist_item"), - [ - ( - 0, - "Add templates with examples of the content you plan to send Not completed", - ), - (1, "Add templates with examples of the content you plan to send Completed"), - (2, "Add templates with examples of the content you plan to send Completed"), - ], -) -def test_should_check_for_sending_things_right( - client_request, - mocker, - service_one, - fake_uuid, - single_sms_sender, - count_of_users_with_manage_service, - count_of_invites_with_manage_service, - expected_user_checklist_item, - count_of_templates, - expected_templates_checklist_item, - active_user_with_permissions, - active_user_no_settings_permission, - single_reply_to_email_address, -): - mocker.patch( - "app.service_api_client.get_service_templates", - return_value={ - "data": [ - create_template(template_type="sms") - for _ in range(0, count_of_templates) - ] - }, - ) - - mock_get_users = mocker.patch( - "app.models.user.Users.client_method", - return_value=( - [active_user_with_permissions] * count_of_users_with_manage_service - + [active_user_no_settings_permission] - ), - ) - invite_one = invite_json( - id_=uuid4(), - from_user=service_one["users"][0], - service_id=service_one["id"], - email_address="invited_user@test.gsa.gov", - permissions="view_activity,send_messages,manage_service,manage_api_keys", - created_at=datetime.utcnow(), - status="pending", - auth_type="sms_auth", - folder_permissions=[], - ) - - invite_two = invite_one.copy() - invite_two["permissions"] = "view_activity" - - mock_get_invites = mocker.patch( - "app.models.user.InvitedUsers.client_method", - return_value=( - ([invite_one] * count_of_invites_with_manage_service) + [invite_two] - ), - ) - - page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) - assert page.h1.text == "Before you request to go live" - - checklist_items = page.select(".task-list .task-list-item") - assert normalize_spaces(checklist_items[1].text) == expected_user_checklist_item - assert ( - normalize_spaces(checklist_items[2].text) == expected_templates_checklist_item - ) - - mock_get_users.assert_called_once_with(SERVICE_ONE_ID) - mock_get_invites.assert_called_once_with(SERVICE_ONE_ID) - - -@pytest.mark.parametrize( - ("checklist_completed", "expected_button"), - [ - (True, True), - (False, False), - ], -) -def test_should_not_show_go_live_button_if_checklist_not_complete( - client_request, - mocker, - mock_get_service_templates, - mock_get_users_by_service, - mock_get_service_organization, - mock_get_invites_for_service, - single_sms_sender, - checklist_completed, - expected_button, -): - mocker.patch( - "app.models.service.Service.go_live_checklist_completed", - new_callable=PropertyMock, - return_value=checklist_completed, - ) - - for channel in ("email", "sms"): - mocker.patch( - "app.models.service.Service.volume_{}".format(channel), - create=True, - new_callable=PropertyMock, - return_value=0, - ) - - page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) - assert page.h1.text == "Before you request to go live" - - if expected_button: - assert page.select_one("form")["method"] == "post" - assert "action" not in page.select_one("form") - assert normalize_spaces(page.select("main p")[0].text) == ( - "When we receive your request we’ll get back to you within one working day." - ) - assert normalize_spaces(page.select("main p")[1].text) == ( - "By requesting to go live you’re agreeing to our terms of use." - ) - assert page.select_one("main [type=submit]").text.strip() == ( - "Request to go live" - ) - else: - assert not page.select("form") - assert not page.select("main [type=submit]") - assert len(page.select("main p")) == 1 - assert normalize_spaces(page.select_one("main p").text) == ( - "You must complete these steps before you can request to go live." - ) - - -@pytest.mark.parametrize( - ("go_live_at", "message"), - [ - (None, "‘service one’ is already live."), - ("2020-10-09 13:55:20", "‘service one’ went live on 9 October 2020."), - ], -) -def test_request_to_go_live_redirects_if_service_already_live( - client_request, - service_one, - go_live_at, - message, -): - service_one["restricted"] = False - service_one["go_live_at"] = go_live_at - - page = client_request.get( - "main.request_to_go_live", - service_id=SERVICE_ONE_ID, - ) - - assert page.h1.text == "Your service is already live" - assert normalize_spaces(page.select_one("main p").text) == message - - -@pytest.mark.parametrize( - ( - "estimated_sms_volume", - "organization_type", - "count_of_sms_templates", - "sms_senders", - "expected_sms_sender_checklist_item", - ), - [ - ( - 0, - "state", - 0, - [], - "", - ), - ( - None, - "state", - 0, - [{"is_default": True, "sms_sender": "GOVUK"}], - "", - ), - ( - None, - "federal", - 99, - [{"is_default": True, "sms_sender": "GOVUK"}], - "", - ), - ( - 1, - "federal", - 99, - [{"is_default": True, "sms_sender": "GOVUK"}], - "", - ), - ( - 1, - "state", - 1, - [], - "Change your text message sender name Not completed", - ), - ( - 1, - "state", - 1, - [ - {"is_default": False, "sms_sender": "GOVUK"}, - {"is_default": True, "sms_sender": "KUVOG"}, - ], - "Change your text message sender name Completed", - ), - ], -) -def test_should_check_for_sms_sender_on_go_live( - client_request, - service_one, - mocker, - mock_get_organization, - mock_get_invites_for_service, - organization_type, - count_of_sms_templates, - sms_senders, - expected_sms_sender_checklist_item, - estimated_sms_volume, -): - service_one["organization_type"] = organization_type - - mocker.patch( - "app.service_api_client.get_service_templates", - return_value={ - "data": [ - create_template(template_type="sms") - for _ in range(0, count_of_sms_templates) - ] - }, - ) - - mocker.patch( - "app.models.service.Service.has_team_members", - return_value=True, - ) - - mock_get_sms_senders = mocker.patch( - "app.main.views.service_settings.service_api_client.get_sms_senders", - return_value=sms_senders, - ) - - for channel, volume in (("email", 0), ("sms", estimated_sms_volume)): - mocker.patch( - "app.models.service.Service.volume_{}".format(channel), - create=True, - new_callable=PropertyMock, - return_value=volume, - ) - - with pytest.raises(expected_exception=IndexError): - simple_statement_for_test_should_check_for_sms_sender_on_go_live( - client_request, expected_sms_sender_checklist_item, mock_get_sms_senders - ) - - -def simple_statement_for_test_should_check_for_sms_sender_on_go_live( - client_request, expected_sms_sender_checklist_item, mock_get_sms_senders -): - page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) - assert page.h1.text == "Before you request to go live" - checklist_items = page.select(".task-list .task-list-item") - - assert ( - normalize_spaces(checklist_items[3].text) == expected_sms_sender_checklist_item - ) - - mock_get_sms_senders.assert_called_once_with(SERVICE_ONE_ID) - - -def test_non_gov_user_is_told_they_cant_go_live( - client_request, - api_nongov_user_active, - mock_get_invites_for_service, - mocker, - mock_get_organizations, - mock_get_organization, -): - mocker.patch( - "app.models.service.Service.has_team_members", - return_value=False, - ) - mocker.patch( - "app.models.service.Service.all_templates", - new_callable=PropertyMock, - return_value=[], - ) - mocker.patch( - "app.main.views.service_settings.service_api_client.get_sms_senders", - return_value=[], - ) - mocker.patch( - "app.main.views.service_settings.service_api_client.get_reply_to_email_addresses", - return_value=[], - ) - client_request.login(api_nongov_user_active) - page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) - assert normalize_spaces(page.select_one("main p").text) == ( - "Only team members with a government email address can request to go live." - ) - assert len(page.select("main form")) == 0 - assert len(page.select("main button")) == 0 - - -@pytest.mark.parametrize( - ("consent_to_research", "displayed_consent"), - [ - (None, None), - (True, "yes"), - (False, "no"), - ], -) -@pytest.mark.parametrize( - ("volumes", "displayed_volumes"), - [ - ( - (("email", None), ("sms", None)), - (None, None), - ), - ( - (("email", 1234), ("sms", 0)), - ("1,234", "0"), - ), - ], -) -def test_should_show_estimate_volumes( - mocker, - client_request, - volumes, - displayed_volumes, - consent_to_research, - displayed_consent, -): - for channel, volume in volumes: - mocker.patch( - "app.models.service.Service.volume_{}".format(channel), - create=True, - new_callable=PropertyMock, - return_value=volume, - ) - mocker.patch( - "app.models.service.Service.consent_to_research", - create=True, - new_callable=PropertyMock, - return_value=consent_to_research, - ) - page = client_request.get("main.estimate_usage", service_id=SERVICE_ONE_ID) - assert page.h1.text == "Tell us how many messages you expect to send" - for channel, label, hint, value in ( - ( - "email", - "How many emails do you expect to send in the next year?", - "For example, 50,000", - displayed_volumes[0], - ), - ( - "sms", - "How many text messages do you expect to send in the next year?", - "For example, 50,000", - displayed_volumes[1], - ), - ): - assert ( - normalize_spaces( - page.select_one("label[for=volume_{}]".format(channel)).text - ) - == label - ) - assert ( - normalize_spaces(page.select_one("#volume_{}-hint".format(channel)).text) - == hint - ) - assert page.select_one("#volume_{}".format(channel)).get("value") == value - - assert len(page.select("input[type=radio]")) == 2 - - if displayed_consent is None: - assert len(page.select("input[checked]")) == 0 - else: - assert len(page.select("input[checked]")) == 1 - assert page.select_one("input[checked]")["value"] == displayed_consent - - -@pytest.mark.parametrize( - ("consent_to_research", "expected_persisted_consent_to_research"), - [ - ("yes", True), - ("no", False), - ], -) -def test_should_show_persist_estimated_volumes( - client_request, - mock_update_service, - consent_to_research, - expected_persisted_consent_to_research, -): - client_request.post( - "main.estimate_usage", - service_id=SERVICE_ONE_ID, - _data={ - "volume_email": "1,234,567", - "volume_sms": "", - "consent_to_research": consent_to_research, - }, - _expected_status=302, - _expected_redirect=url_for( - "main.request_to_go_live", - service_id=SERVICE_ONE_ID, - ), - ) - mock_update_service.assert_called_once_with( - SERVICE_ONE_ID, - volume_email=1234567, - volume_sms=0, - consent_to_research=expected_persisted_consent_to_research, - ) - - -@pytest.mark.parametrize( - ("data", "error_selector", "expected_error_message"), - [ - ( - { - "volume_email": "1234", - "volume_sms": "2000000001", - "consent_to_research": "yes", - }, - "#volume_sms-error", - "Number of text messages must be 2,000,000,000 or less", - ), - ( - { - "volume_email": "1 234", - "volume_sms": "0", - "consent_to_research": "", - }, - '[data-error-label="consent_to_research"]', - "Select yes or no", - ), - ], -) -def test_should_error_if_bad_estimations_given( - client_request, - mock_update_service, - data, - error_selector, - expected_error_message, -): - page = client_request.post( - "main.estimate_usage", - service_id=SERVICE_ONE_ID, - _data=data, - _expected_status=200, - ) - assert expected_error_message in page.select_one(error_selector).text - assert mock_update_service.called is False - - -def test_should_error_if_all_volumes_zero( - client_request, - mock_update_service, -): - page = client_request.post( - "main.estimate_usage", - service_id=SERVICE_ONE_ID, - _data={ - "volume_email": "", - "volume_sms": "0", - "consent_to_research": "yes", - }, - _expected_status=200, - ) - assert page.select("input[type=text]")[0].get("value") is None - assert page.select("input[type=text]")[1]["value"] == "0" - assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( - "Enter the number of messages you expect to send in the next year" - ) - assert mock_update_service.called is False - - -def test_should_not_default_to_zero_if_some_fields_dont_validate( - client_request, - mock_update_service, -): - page = client_request.post( - "main.estimate_usage", - service_id=SERVICE_ONE_ID, - _data={ - "volume_email": "aaaaaaaaaaaaa", - "volume_sms": "", - "consent_to_research": "yes", - }, - _expected_status=200, - ) - assert page.select("input[type=text]")[0]["value"] == "aaaaaaaaaaaaa" - assert page.select("input[type=text]")[1].get("value") is None - assert ( - normalize_spaces(page.select_one("#volume_email-error").text) - == "Error: Enter the number of emails you expect to send" - ) - assert mock_update_service.called is False - - -def test_non_gov_users_cant_request_to_go_live( - client_request, - api_nongov_user_active, - mock_get_organizations, -): - client_request.login(api_nongov_user_active) - client_request.post( - "main.request_to_go_live", - service_id=SERVICE_ONE_ID, - _expected_status=403, - ) - - -@pytest.mark.usefixtures("_mock_get_service_settings_page_common") -@pytest.mark.parametrize( - ("volumes", "displayed_volumes", "formatted_displayed_volumes"), - [ - ( - (("email", None), ("sms", None)), - ", ", - ("Emails in next year: \n" "Text messages in next year: \n"), - ), - ( - (("email", 1234), ("sms", 0)), - "0, 1234", # This is a different order to match the spreadsheet - ("Emails in next year: 1,234\n" "Text messages in next year: 0\n"), - ), - ], -) -@freeze_time("2012-12-21 13:12:12.12354") -def test_should_redirect_after_request_to_go_live( - client_request, - mocker, - active_user_with_permissions, - single_reply_to_email_address, - mock_get_organizations_and_services_for_user, - single_sms_sender, - mock_get_service_templates, - mock_get_users_by_service, - mock_update_service, - mock_get_invites_without_manage_permission, - volumes, - displayed_volumes, - formatted_displayed_volumes, -): - for channel, volume in volumes: - mocker.patch( - "app.models.service.Service.volume_{}".format(channel), - create=True, - new_callable=PropertyMock, - return_value=volume, - ) - mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") - mock_send_ticket_to_zendesk = mocker.patch( - "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", - autospec=True, - ) - page = client_request.post( - "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True - ) - - expected_message = ( - "Service: service one\n" - "http://localhost/services/{service_id}\n" - "\n" - "---\n" - "Organization type: Federal government (domain is user.gsa.gov).\n" - "\n" - "{formatted_displayed_volumes}" - "\n" - "Consent to research: Yes\n" - "Other live services for that user: No\n" - "\n" - "Service reply-to address: test@example.com\n" - "\n" - "---\n" - "Request sent by test@user.gsa.gov\n" - "Requester’s user page: http://localhost/users/{user_id}\n" - ).format( - service_id=SERVICE_ONE_ID, - formatted_displayed_volumes=formatted_displayed_volumes, - user_id=active_user_with_permissions["id"], - ) - mock_create_ticket.assert_called_once_with( - ANY, - subject="Request to go live - service one", - message=expected_message, - ticket_type="question", - user_name=active_user_with_permissions["name"], - user_email=active_user_with_permissions["email_address"], - requester_sees_message_content=False, - org_id=None, - org_type="federal", - service_id=SERVICE_ONE_ID, - ) - mock_send_ticket_to_zendesk.assert_called_once() - - assert normalize_spaces(page.select_one(".banner-default").text) == ( - "Thanks for your request to go live. We’ll get back to you within one working day." - ) - assert normalize_spaces(page.select_one("h1").text) == ("Settings") - mock_update_service.assert_called_once_with( - SERVICE_ONE_ID, go_live_user=active_user_with_permissions["id"] - ) - - -@pytest.mark.usefixtures("_mock_get_service_settings_page_common") -def test_request_to_go_live_displays_go_live_notes_in_zendesk_ticket( - client_request, - mocker, - active_user_with_permissions, - single_reply_to_email_address, - mock_get_organizations_and_services_for_user, - single_sms_sender, - mock_get_service_organization, - mock_get_service_templates, - mock_get_users_by_service, - mock_update_service, - mock_get_invites_without_manage_permission, -): - go_live_note = "This service is not allowed to go live" - - mocker.patch( - "app.organizations_client.get_organization", - side_effect=lambda org_id: organization_json( - ORGANISATION_ID, - "Org 1", - request_to_go_live_notes=go_live_note, - ), - ) - mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") - mock_send_ticket_to_zendesk = mocker.patch( - "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", - autospec=True, - ) - client_request.post( - "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True - ) - - expected_message = ( - "Service: service one\n" - "http://localhost/services/{service_id}\n" - "\n" - "---\n" - "Organization type: Federal government (organization is Org 1). {go_live_note}\n" - "\n" - "Emails in next year: 111,111\n" - "Text messages in next year: 222,222\n" - "\n" - "Consent to research: Yes\n" - "Other live services for that user: No\n" - "\n" - "Service reply-to address: test@example.com\n" - "\n" - "---\n" - "Request sent by test@user.gsa.gov\n" - "Requester’s user page: http://localhost/users/{user_id}\n" - ).format( - service_id=SERVICE_ONE_ID, - go_live_note=go_live_note, - user_id=active_user_with_permissions["id"], - ) - - mock_create_ticket.assert_called_once_with( - ANY, - subject="Request to go live - service one", - message=expected_message, - ticket_type="question", - user_name=active_user_with_permissions["name"], - user_email=active_user_with_permissions["email_address"], - requester_sees_message_content=False, - org_id=ORGANISATION_ID, - org_type="federal", - service_id=SERVICE_ONE_ID, - ) - mock_send_ticket_to_zendesk.assert_called_once() - - -@pytest.mark.usefixtures("_mock_get_service_settings_page_common") -def test_request_to_go_live_displays_mou_signatories( - client_request, - mocker, - fake_uuid, - active_user_with_permissions, - single_reply_to_email_address, - mock_get_organizations_and_services_for_user, - single_sms_sender, - mock_get_service_organization, - mock_get_service_templates, - mock_get_users_by_service, - mock_update_service, - mock_get_invites_without_manage_permission, -): - mocker.patch( - "app.organizations_client.get_organization", - side_effect=lambda org_id: organization_json( - ORGANISATION_ID, - "Org 1", - agreement_signed=True, - agreement_signed_by_id=fake_uuid, - agreement_signed_on_behalf_of_email_address="bigdog@example.gsa.gov", - ), - ) - mocker.patch( - "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", - autospec=True, - ) - mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") - client_request.post( - "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True - ) - - assert ("Organization type: Federal government") in mock_create_ticket.call_args[1][ - "message" - ] - - assert ("Emails in next year: 111,111\n") in mock_create_ticket.call_args[1][ - "message" - ] - - -@pytest.mark.usefixtures("_mock_get_service_settings_page_common") -def test_should_be_able_to_request_to_go_live_with_no_organization( - client_request, - mocker, - single_reply_to_email_address, - mock_get_organizations_and_services_for_user, - single_sms_sender, - mock_get_service_templates, - mock_get_users_by_service, - mock_update_service, - mock_get_invites_without_manage_permission, -): - for channel in {"email", "sms"}: - mocker.patch( - "app.models.service.Service.volume_{}".format(channel), - create=True, - new_callable=PropertyMock, - return_value=1, - ) - mock_post = mocker.patch( - "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", - autospec=True, - ) - - client_request.post( - "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True - ) - - assert mock_post.called is True - - -@pytest.mark.parametrize( - ( - "has_team_members", - "has_templates", - "has_email_templates", - "has_sms_templates", - "has_email_reply_to_address", - "shouldnt_use_govuk_as_sms_sender", - "sms_sender_is_govuk", - "volume_email", - "volume_sms", - "expected_readyness", - "agreement_signed", - ), - [ - ( # Just sending email - True, - True, - True, - False, - True, - True, - True, - 1, - 0, - "Yes", - True, - ), - ( # Needs to set reply to address - True, - True, - True, - False, - False, - True, - True, - 1, - 0, - "No", - True, - ), - ( # Just sending SMS - True, - True, - False, - True, - True, - True, - False, - 0, - 1, - "Yes", - True, - ), - ( # Needs to change SMS sender - True, - True, - False, - True, - True, - True, - True, - 0, - 1, - "No", - True, - ), - ( # Needs team members - False, - True, - False, - True, - True, - True, - False, - 1, - 0, - "No", - True, - ), - ( # Needs templates - True, - False, - False, - True, - True, - True, - False, - 0, - 1, - "No", - True, - ), - ( # Not done anything yet - False, - False, - False, - False, - False, - False, - True, - None, - None, - "No", - False, - ), - ], -) -def test_ready_to_go_live( - client_request, - mocker, - mock_get_service_organization, - has_team_members, - has_templates, - has_email_templates, - has_sms_templates, - has_email_reply_to_address, - shouldnt_use_govuk_as_sms_sender, - sms_sender_is_govuk, - volume_email, - volume_sms, - expected_readyness, - agreement_signed, -): - mocker.patch( - "app.organizations_client.get_organization", - return_value=organization_json(agreement_signed=agreement_signed), - ) - - for prop in { - "has_team_members", - "has_templates", - "has_email_templates", - "has_sms_templates", - "has_email_reply_to_address", - "shouldnt_use_govuk_as_sms_sender", - "sms_sender_is_govuk", - }: - mocker.patch( - "app.models.service.Service.{}".format(prop), new_callable=PropertyMock - ).return_value = locals()[prop] - - for channel, volume in ( - ("sms", volume_sms), - ("email", volume_email), - ): - mocker.patch( - "app.models.service.Service.volume_{}".format(channel), - create=True, - new_callable=PropertyMock, - return_value=volume, - ) - - assert ( - app.models.service.Service( - {"id": SERVICE_ONE_ID} - ).go_live_checklist_completed_as_yes_no - == expected_readyness - ) - - @pytest.mark.usefixtures("_mock_get_service_settings_page_common") @pytest.mark.parametrize( "route", [ "main.service_settings", "main.service_name_change", - "main.request_to_go_live", - "main.submit_request_to_go_live", "main.archive_service", ], ) @@ -1735,8 +586,6 @@ def test_route_permissions( [ "main.service_settings", "main.service_name_change", - "main.request_to_go_live", - "main.submit_request_to_go_live", "main.service_switch_live", "main.archive_service", ], @@ -1769,8 +618,6 @@ def test_route_invalid_permissions( [ "main.service_settings", "main.service_name_change", - "main.request_to_go_live", - "main.submit_request_to_go_live", ], ) def test_route_for_platform_admin( @@ -2823,247 +1670,6 @@ def test_does_not_show_research_mode_indicator( assert not element -@pytest.mark.parametrize( - ("current_branding", "expected_values", "expected_labels"), - [ - ( - None, - [ - "__NONE__", - "1", - "2", - "3", - "4", - "5", - ], - ["GOV.UK", "org 1", "org 2", "org 3", "org 4", "org 5"], - ), - ( - "5", - [ - "5", - "__NONE__", - "1", - "2", - "3", - "4", - ], - [ - "org 5", - "GOV.UK", - "org 1", - "org 2", - "org 3", - "org 4", - ], - ), - ], -) -@pytest.mark.parametrize( - ("endpoint", "extra_args"), - [ - ( - "main.service_set_email_branding", - {"service_id": SERVICE_ONE_ID}, - ), - ( - "main.edit_organization_email_branding", - {"org_id": ORGANISATION_ID}, - ), - ], -) -def test_should_show_branding_styles( - mocker, - client_request, - platform_admin_user, - service_one, - mock_get_all_email_branding, - current_branding, - expected_values, - expected_labels, - endpoint, - extra_args, -): - service_one["email_branding"] = current_branding - mocker.patch( - "app.organizations_client.get_organization", - side_effect=lambda org_id: organization_json( - org_id, - "Org 1", - email_branding_id=current_branding, - ), - ) - - client_request.login(platform_admin_user) - page = client_request.get(endpoint, **extra_args) - - branding_style_choices = page.find_all("input", attrs={"name": "branding_style"}) - - radio_labels = [ - page.find("label", attrs={"for": branding_style_choices[idx]["id"]}) - .get_text() - .strip() - for idx, element in enumerate(branding_style_choices) - ] - - assert len(branding_style_choices) == 6 - - for index, expected_value in enumerate(expected_values): - assert branding_style_choices[index]["value"] == expected_value - - # radios should be in alphabetical order, based on their labels - assert radio_labels == expected_labels - - assert "checked" in branding_style_choices[0].attrs - assert "checked" not in branding_style_choices[1].attrs - assert "checked" not in branding_style_choices[2].attrs - assert "checked" not in branding_style_choices[3].attrs - assert "checked" not in branding_style_choices[4].attrs - assert "checked" not in branding_style_choices[5].attrs - - app.email_branding_client.get_all_email_branding.assert_called_once_with() - app.service_api_client.get_service.assert_called_once_with(service_one["id"]) - - -@pytest.mark.parametrize( - ("endpoint", "extra_args", "expected_redirect"), - [ - ( - "main.service_set_email_branding", - {"service_id": SERVICE_ONE_ID}, - "main.service_preview_email_branding", - ), - ( - "main.edit_organization_email_branding", - {"org_id": ORGANISATION_ID}, - "main.organization_preview_email_branding", - ), - ], -) -def test_should_send_branding_and_organizations_to_preview( - client_request, - platform_admin_user, - service_one, - mock_get_organization, - mock_get_all_email_branding, - mock_update_service, - endpoint, - extra_args, - expected_redirect, -): - client_request.login(platform_admin_user) - client_request.post( - endpoint, - _data={"branding_type": "org", "branding_style": "1"}, - _expected_status=302, - _expected_location=url_for(expected_redirect, branding_style="1", **extra_args), - **extra_args, - ) - - mock_get_all_email_branding.assert_called_once_with() - - -@pytest.mark.parametrize( - ("endpoint", "extra_args"), - [ - ( - "main.service_preview_email_branding", - {"service_id": SERVICE_ONE_ID}, - ), - ( - "main.organization_preview_email_branding", - {"org_id": ORGANISATION_ID}, - ), - ], -) -def test_should_preview_email_branding( - client_request, - platform_admin_user, - mock_get_organization, - endpoint, - extra_args, -): - client_request.login(platform_admin_user) - page = client_request.get( - endpoint, branding_type="org", branding_style="1", **extra_args - ) - - iframe = page.find("iframe", attrs={"class": "branding-preview"}) - iframeURLComponents = urlparse(iframe["src"]) - iframeQString = parse_qs(iframeURLComponents.query) - - assert page.find("input", attrs={"id": "branding_style"})["value"] == "1" - assert iframeURLComponents.path == "/_email" - assert iframeQString["branding_style"] == ["1"] - - -@pytest.mark.parametrize( - ("posted_value", "submitted_value"), - [ - ("1", "1"), - ("__NONE__", None), - pytest.param("None", None, marks=pytest.mark.xfail(raises=AssertionError)), - ], -) -@pytest.mark.parametrize( - ("endpoint", "extra_args", "expected_redirect"), - [ - ( - "main.service_preview_email_branding", - {"service_id": SERVICE_ONE_ID}, - "main.service_settings", - ), - ( - "main.organization_preview_email_branding", - {"org_id": ORGANISATION_ID}, - "main.organization_settings", - ), - ], -) -def test_should_set_branding_and_organizations( - client_request, - platform_admin_user, - service_one, - mock_get_organization, - mock_get_organization_services, - mock_update_service, - mock_update_organization, - posted_value, - submitted_value, - endpoint, - extra_args, - expected_redirect, -): - client_request.login(platform_admin_user) - client_request.post( - endpoint, - _data={"branding_style": posted_value}, - _expected_status=302, - _expected_redirect=url_for(expected_redirect, **extra_args), - **extra_args, - ) - - if endpoint == "main.service_preview_email_branding": - mock_update_service.assert_called_once_with( - SERVICE_ONE_ID, - email_branding=submitted_value, - ) - assert mock_update_organization.called is False - elif endpoint == "main.organization_preview_email_branding": - mock_update_organization.assert_called_once_with( - ORGANISATION_ID, - email_branding_id=submitted_value, - cached_service_ids=[ - "12345", - "67890", - "596364a0-858e-42c8-9062-a8fe822260eb", - ], - ) - assert mock_update_service.called is False - else: - raise Exception - - @pytest.mark.parametrize("method", ["get", "post"]) @pytest.mark.parametrize( "endpoint", @@ -4170,33 +2776,6 @@ def test_update_service_organization_does_not_update_if_same_value( assert mock_update_service_organization.called is False -@pytest.mark.skip(reason="Email currently deactivated") -@pytest.mark.parametrize( - ("single_branding_option", "expected_href"), - [ - ( - True, - f"/services/{SERVICE_ONE_ID}/service-settings/email-branding/something-else", - ), - ], -) -def test_service_settings_links_to_branding_request_page_for_emails( - service_one, - client_request, - no_reply_to_email_addresses, - single_sms_sender, - single_branding_option, - expected_href, -): - if single_branding_option: - # should only have a "something else" option - # so we go straight to that form - service_one["organization_type"] = "other" - - page = client_request.get(".service_settings", service_id=SERVICE_ONE_ID) - assert len(page.find_all("a", attrs={"href": expected_href})) == 1 - - def test_show_service_data_retention( client_request, platform_admin_user, diff --git a/tests/app/main/views/test_add_service.py b/tests/app/main/views/test_add_service.py index 0d04715ac..4f2ab9964 100644 --- a/tests/app/main/views/test_add_service.py +++ b/tests/app/main/views/test_add_service.py @@ -97,7 +97,6 @@ def test_should_add_service_and_redirect_to_tour_when_no_services( mock_create_service_template, mock_get_services_with_no_services, api_user_active, - mock_get_all_email_branding, inherited, email_address, posted, @@ -153,7 +152,6 @@ def test_add_service_has_to_choose_org_type( mock_create_service_template, mock_get_services_with_no_services, api_user_active, - mock_get_all_email_branding, platform_admin_user, ): client_request.login(platform_admin_user) @@ -227,7 +225,6 @@ def test_should_add_service_and_redirect_to_dashboard_when_existing_service( api_user_active, organization_type, free_allowance, - mock_get_all_email_branding, platform_admin_user, ): client_request.login(platform_admin_user) diff --git a/tests/app/main/views/test_email_branding.py b/tests/app/main/views/test_email_branding.py deleted file mode 100644 index fe86c108c..000000000 --- a/tests/app/main/views/test_email_branding.py +++ /dev/null @@ -1,451 +0,0 @@ -from io import BytesIO -from unittest.mock import call - -import pytest -from flask import url_for -from notifications_python_client.errors import HTTPError - -from app.s3_client.s3_logo_client import EMAIL_LOGO_LOCATION_STRUCTURE, TEMP_TAG -from tests.conftest import create_email_branding, normalize_spaces - - -def test_email_branding_page_shows_full_branding_list( - client_request, platform_admin_user, mock_get_all_email_branding -): - client_request.login(platform_admin_user) - page = client_request.get(".email_branding") - - links = page.select(".message-name a") - brand_names = [normalize_spaces(link.text) for link in links] - hrefs = [link["href"] for link in links] - - assert normalize_spaces(page.select_one("h1").text) == "Email branding" - - assert page.select(".grid-col-9 a")[-1]["href"] == url_for( - "main.create_email_branding" - ) - - assert brand_names == [ - "org 1", - "org 2", - "org 3", - "org 4", - "org 5", - ] - assert hrefs == [ - url_for(".update_email_branding", branding_id=1), - url_for(".update_email_branding", branding_id=2), - url_for(".update_email_branding", branding_id=3), - url_for(".update_email_branding", branding_id=4), - url_for(".update_email_branding", branding_id=5), - ] - - -def test_edit_email_branding_shows_the_correct_branding_info( - client_request, platform_admin_user, mock_get_email_branding, fake_uuid -): - client_request.login(platform_admin_user) - page = client_request.get( - ".update_email_branding", - branding_id=fake_uuid, - _test_page_title=False, # TODO: Fix page titles - ) - - assert page.select_one("#logo-img > img")["src"].endswith("/example.png") - assert page.select_one("#name").attrs.get("value") == "Organization name" - assert page.select_one("#file").attrs.get("accept") == ".png" - assert page.select_one("#text").attrs.get("value") == "Organization text" - assert page.select_one("#colour").attrs.get("value") == "#f00" - - -def test_create_email_branding_does_not_show_any_branding_info( - client_request, platform_admin_user, mock_no_email_branding -): - client_request.login(platform_admin_user) - page = client_request.get( - ".create_email_branding", - _test_page_title=False, # TODO: Fix page titles - ) - - assert page.select_one("#logo-img > img") is None - assert page.select_one("#name").attrs.get("value") is None - assert page.select_one("#file").attrs.get("accept") == ".png" - assert page.select_one("#text").attrs.get("value") is None - assert page.select_one("#colour").attrs.get("value") is None - - -def test_create_new_email_branding_without_logo( - client_request, - platform_admin_user, - mocker, - fake_uuid, - mock_create_email_branding, -): - data = { - "logo": None, - "colour": "#ff0000", - "text": "new text", - "name": "new name", - "brand_type": "org", - } - - mock_persist = mocker.patch("app.main.views.email_branding.persist_logo") - mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by") - - client_request.login(platform_admin_user) - client_request.post( - ".create_email_branding", - _content_type="multipart/form-data", - _data=data, - ) - - assert mock_create_email_branding.called - assert mock_create_email_branding.call_args == call( - logo=data["logo"], - name=data["name"], - text=data["text"], - colour=data["colour"], - brand_type=data["brand_type"], - ) - assert mock_persist.call_args_list == [] - - -def test_create_email_branding_requires_a_name_when_submitting_logo_details( - client_request, - mocker, - mock_create_email_branding, - platform_admin_user, -): - mocker.patch("app.main.views.email_branding.persist_logo") - mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by") - data = { - "operation": "email-branding-details", - "logo": "", - "colour": "#ff0000", - "text": "new text", - "name": "", - "brand_type": "org", - } - client_request.login(platform_admin_user) - page = client_request.post( - ".create_email_branding", - _content_type="multipart/form-data", - _data=data, - _expected_status=200, - ) - - assert ( - page.select_one(".usa-error-message").text.strip() - == "Error: This field is required" - ) - assert mock_create_email_branding.called is False - - -def test_create_email_branding_does_not_require_a_name_when_uploading_a_file( - client_request, - mocker, - platform_admin_user, -): - mocker.patch( - "app.main.views.email_branding.upload_email_logo", return_value="temp_filename" - ) - data = { - "file": (BytesIO("".encode("utf-8")), "test.png"), - "colour": "", - "text": "", - "name": "", - "brand_type": "org", - } - client_request.login(platform_admin_user) - page = client_request.post( - ".create_email_branding", - _content_type="multipart/form-data", - _data=data, - _follow_redirects=True, - ) - - assert not page.find(".error-message") - - -def test_create_new_email_branding_when_branding_saved( - client_request, platform_admin_user, mocker, mock_create_email_branding, fake_uuid -): - with client_request.session_transaction() as session: - user_id = session["user_id"] - - data = { - "logo": "test.png", - "colour": "#ff0000", - "text": "new text", - "name": "new name", - "brand_type": "org_banner", - } - - temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), - unique_id=fake_uuid, - filename=data["logo"], - ) - - mocker.patch("app.main.views.email_branding.persist_logo") - mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by") - - client_request.login(platform_admin_user) - client_request.post( - ".create_email_branding", - logo=temp_filename, - _content_type="multipart/form-data", - _data={ - "colour": data["colour"], - "name": data["name"], - "text": data["text"], - "cdn_url": "https://static-logos.cdn.com", - "brand_type": data["brand_type"], - }, - ) - - updated_logo_name = "{}-{}".format(fake_uuid, data["logo"]) - - assert mock_create_email_branding.called - assert mock_create_email_branding.call_args == call( - logo=updated_logo_name, - name=data["name"], - text=data["text"], - colour=data["colour"], - brand_type=data["brand_type"], - ) - - -@pytest.mark.parametrize( - ("endpoint", "has_data"), - [ - ("main.create_email_branding", False), - ("main.update_email_branding", True), - ], -) -def test_deletes_previous_temp_logo_after_uploading_logo( - client_request, platform_admin_user, mocker, endpoint, has_data, fake_uuid -): - if has_data: - mocker.patch( - "app.email_branding_client.get_email_branding", - return_value=create_email_branding(fake_uuid), - ) - - with client_request.session_transaction() as session: - user_id = session["user_id"] - - temp_old_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), - unique_id=fake_uuid, - filename="old_test.png", - ) - - temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png" - ) - - mocked_upload_email_logo = mocker.patch( - "app.main.views.email_branding.upload_email_logo", return_value=temp_filename - ) - - mocked_delete_email_temp_file = mocker.patch( - "app.main.views.email_branding.delete_email_temp_file" - ) - - client_request.login(platform_admin_user) - client_request.post( - "main.create_email_branding", - logo=temp_old_filename, - branding_id=fake_uuid, - _data={"file": (BytesIO("".encode("utf-8")), "test.png")}, - _content_type="multipart/form-data", - ) - - assert mocked_upload_email_logo.called - assert mocked_delete_email_temp_file.called - assert mocked_delete_email_temp_file.call_args == call(temp_old_filename) - - -def test_update_existing_branding( - client_request, - platform_admin_user, - mocker, - fake_uuid, - mock_get_email_branding, - mock_update_email_branding, -): - with client_request.session_transaction() as session: - user_id = session["user_id"] - - data = { - "logo": "test.png", - "colour": "#0000ff", - "text": "new text", - "name": "new name", - "brand_type": "both", - } - - temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), - unique_id=fake_uuid, - filename=data["logo"], - ) - - mocker.patch("app.main.views.email_branding.persist_logo") - mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by") - - client_request.login(platform_admin_user) - client_request.post( - ".update_email_branding", - logo=temp_filename, - branding_id=fake_uuid, - _content_type="multipart/form-data", - _data={ - "colour": data["colour"], - "name": data["name"], - "text": data["text"], - "cdn_url": "https://static-logos.cdn.com", - "brand_type": data["brand_type"], - }, - ) - - updated_logo_name = "{}-{}".format(fake_uuid, data["logo"]) - - assert mock_update_email_branding.called - assert mock_update_email_branding.call_args == call( - branding_id=fake_uuid, - logo=updated_logo_name, - name=data["name"], - text=data["text"], - colour=data["colour"], - brand_type=data["brand_type"], - ) - - -def test_temp_logo_is_shown_after_uploading_logo( - client_request, - platform_admin_user, - mocker, - fake_uuid, -): - with client_request.session_transaction() as session: - user_id = session["user_id"] - - temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png" - ) - - mocker.patch( - "app.main.views.email_branding.upload_email_logo", return_value=temp_filename - ) - mocker.patch("app.main.views.email_branding.delete_email_temp_file") - - client_request.login(platform_admin_user) - page = client_request.post( - "main.create_email_branding", - _data={"file": (BytesIO("".encode("utf-8")), "test.png")}, - _content_type="multipart/form-data", - _follow_redirects=True, - ) - - assert page.select_one("#logo-img > img").attrs["src"].endswith(temp_filename) - - -def test_logo_persisted_when_organization_saved( - client_request, platform_admin_user, mock_create_email_branding, mocker, fake_uuid -): - with client_request.session_transaction() as session: - user_id = session["user_id"] - - temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png" - ) - - mocked_upload_email_logo = mocker.patch( - "app.main.views.email_branding.upload_email_logo" - ) - mocked_persist_logo = mocker.patch("app.main.views.email_branding.persist_logo") - mocked_delete_email_temp_files_by = mocker.patch( - "app.main.views.email_branding.delete_email_temp_files_created_by" - ) - - client_request.login(platform_admin_user) - client_request.post( - ".create_email_branding", - logo=temp_filename, - _content_type="multipart/form-data", - ) - - assert not mocked_upload_email_logo.called - assert mocked_persist_logo.called - assert mocked_delete_email_temp_files_by.called - assert mocked_delete_email_temp_files_by.call_args == call(user_id) - assert mock_create_email_branding.called - - -def test_logo_does_not_get_persisted_if_updating_email_branding_client_throws_an_error( - client_request, platform_admin_user, mock_create_email_branding, mocker, fake_uuid -): - with client_request.session_transaction() as session: - user_id = session["user_id"] - - temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png" - ) - - mocked_persist_logo = mocker.patch("app.main.views.email_branding.persist_logo") - mocked_delete_email_temp_files_by = mocker.patch( - "app.main.views.email_branding.delete_email_temp_files_created_by" - ) - mocker.patch( - "app.main.views.email_branding.email_branding_client.create_email_branding", - side_effect=HTTPError(), - ) - - client_request.login(platform_admin_user) - client_request.post( - ".create_email_branding", - logo=temp_filename, - _content_type="multipart/form-data", - _expected_status=500, - ) - - assert not mocked_persist_logo.called - assert not mocked_delete_email_temp_files_by.called - - -@pytest.mark.parametrize( - ("colour_hex", "expected_status_code"), - [ - ("#FF00FF", 302), - ("hello", 200), - ("", 302), - ], -) -def test_colour_regex_validation( - client_request, - platform_admin_user, - mocker, - fake_uuid, - colour_hex, - expected_status_code, - mock_create_email_branding, -): - data = { - "logo": None, - "colour": colour_hex, - "text": "new text", - "name": "new name", - "brand_type": "org", - } - - mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by") - - client_request.login(platform_admin_user) - client_request.post( - ".create_email_branding", - _content_type="multipart/form-data", - _data=data, - _expected_status=expected_status_code, - ) diff --git a/tests/app/main/views/test_email_preview.py b/tests/app/main/views/test_email_preview.py deleted file mode 100644 index 60604a9f3..000000000 --- a/tests/app/main/views/test_email_preview.py +++ /dev/null @@ -1,95 +0,0 @@ -import re - -import pytest - - -@pytest.mark.parametrize( - ("query_args", "result"), [({}, True), ({"govuk_banner": "false"}, "false")] -) -def test_renders(client_request, mocker, query_args, result): - mocker.patch( - "app.main.views.index.HTMLEmailTemplate.__str__", return_value="rendered" - ) - - response = client_request.get_response("main.email_template", **query_args) - - assert response.get_data(as_text=True) == "rendered" - - -def test_displays_both_branding( - client_request, mock_get_email_branding_with_both_brand_type -): - page = client_request.get( - "main.email_template", branding_style="1", _test_page_title=False - ) - - mock_get_email_branding_with_both_brand_type.assert_called_once_with("1") - - assert page.find("img", attrs={"src": re.compile("example.png$")}) - assert ( - page.select( - "body > table:nth-of-type(3) table > tr:nth-of-type(1) > td:nth-of-type(2)" - )[0] - .get_text() - .strip() - == "Organization text" - ) # brand text is set - - -def test_displays_org_branding(client_request, mock_get_email_branding): - # mock_get_email_branding has 'brand_type' of 'org' - page = client_request.get( - "main.email_template", branding_style="1", _test_page_title=False - ) - - mock_get_email_branding.assert_called_once_with("1") - - assert not page.find("a", attrs={"href": "https://www.gsa.gov"}) - assert page.find("img", attrs={"src": re.compile("example.png")}) - assert not page.select( - "body > table > tr > td[bgcolor='#f00']" - ) # banner colour is not set - assert ( - page.select( - "body > table:nth-of-type(1) > tr:nth-of-type(1) > td:nth-of-type(2)" - )[0] - .get_text() - .strip() - == "Organization text" - ) # brand text is set - - -def test_displays_org_branding_with_banner( - client_request, mock_get_email_branding_with_org_banner_brand_type -): - page = client_request.get( - "main.email_template", branding_style="1", _test_page_title=False - ) - - mock_get_email_branding_with_org_banner_brand_type.assert_called_once_with("1") - - assert not page.find("a", attrs={"href": "https://www.gsa.gov"}) - assert page.find("img", attrs={"src": re.compile("example.png")}) - assert page.select("body > table > tr > td[bgcolor='#f00']") # banner colour is set - assert ( - page.select("body > table table > tr > td > span")[0].get_text().strip() - == "Organization text" - ) # brand text is set - - -def test_displays_org_branding_with_banner_without_brand_text( - client_request, mock_get_email_branding_without_brand_text -): - # mock_get_email_branding_without_brand_text has 'brand_type' of 'org_banner' - page = client_request.get( - "main.email_template", branding_style="1", _test_page_title=False - ) - - mock_get_email_branding_without_brand_text.assert_called_once_with("1") - - assert not page.find("a", attrs={"href": "https://www.gsa.gov"}) - assert page.find("img", attrs={"src": re.compile("example.png")}) - assert page.select("body > table > tr > td[bgcolor='#f00']") # banner colour is set - assert ( - not page.select("body > table table > tr > td > span") == 0 - ) # brand text is not set diff --git a/tests/app/main/views/test_feedback.py b/tests/app/main/views/test_feedback.py index e3ab37e93..633ad54c4 100644 --- a/tests/app/main/views/test_feedback.py +++ b/tests/app/main/views/test_feedback.py @@ -1,812 +1,15 @@ -from functools import partial -from unittest.mock import ANY, PropertyMock - -import pytest -from flask import url_for from freezegun import freeze_time -from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket -from app.main.views.feedback import in_business_hours -from app.models.feedback import ( - GENERAL_TICKET_TYPE, - PROBLEM_TICKET_TYPE, - QUESTION_TICKET_TYPE, -) -from tests.conftest import SERVICE_ONE_ID, normalize_spaces - - -def no_redirect(): - return lambda: None - - -@pytest.mark.skip(reason="Not currently using Zendesk") -def test_get_support_index_page( - client_request, -): - page = client_request.get(".support") - assert page.select_one("form")["method"] == "post" - assert "action" not in page.select_one("form") - assert normalize_spaces(page.select_one("h1").text) == "Support" - assert ( - normalize_spaces(page.select_one("form label[for=support_type-0]").text) - == "Report a problem" - ) - assert page.select_one("form input#support_type-0")["value"] == "report-problem" - assert ( - normalize_spaces(page.select_one("form label[for=support_type-1]").text) - == "Ask a question or give feedback" - ) - assert ( - page.select_one("form input#support_type-1")["value"] - == "ask-question-give-feedback" - ) - assert ( - normalize_spaces(page.select_one("form button[type=submit]").text) == "Continue" - ) - - -@pytest.mark.skip(reason="Not currently using Zendesk") -def test_get_support_index_page_when_signed_out( - client_request, -): - client_request.logout() - page = client_request.get(".support") - assert page.select_one("form")["method"] == "post" - assert "action" not in page.select_one("form") - assert normalize_spaces(page.select_one("form label[for=who-0]").text) == ( - "I work in the public sector and need to send emails or text messages" - ) - assert page.select_one("form input#who-0")["value"] == "public-sector" - assert normalize_spaces(page.select_one("form label[for=who-1]").text) == ( - "I’m a member of the public with a question for the government" - ) - assert page.select_one("form input#who-1")["value"] == "public" - assert ( - normalize_spaces(page.select_one("form button[type=submit]").text) == "Continue" - ) - - -@freeze_time("2016-12-12 12:00:00.000000") -@pytest.mark.parametrize( - ("support_type", "expected_h1"), - [ - (PROBLEM_TICKET_TYPE, "Report a problem"), - (QUESTION_TICKET_TYPE, "Ask a question or give feedback"), - ], -) -def test_choose_support_type( - client_request, - mock_get_non_empty_organizations_and_services_for_user, - support_type, - expected_h1, -): - page = client_request.post( - "main.support", - _data={"support_type": support_type}, - _follow_redirects=True, - ) - assert page.h1.string.strip() == expected_h1 - assert not page.select_one("input[name=name]") - assert not page.select_one("input[name=email_address]") - assert page.find("form").find("p").text.strip() == ( - "We’ll reply to test@user.gsa.gov" - ) +from tests.conftest import normalize_spaces @freeze_time("2016-12-12 12:00:00.000000") def test_get_support_as_someone_in_the_public_sector( + mocker, + active_user_with_permissions, client_request, ): - client_request.logout() - page = client_request.post( + page = client_request.get( "main.support", - _data={"who": "public-sector"}, - _follow_redirects=True, ) - assert normalize_spaces(page.select("h1")) == ("Contact Notify.gov support") - assert page.select_one("form textarea[name=feedback]") - assert page.select_one("form input[name=name]") - assert page.select_one("form input[name=email_address]") - assert page.select_one("form button[type=submit]") - - -def test_get_support_as_member_of_public( - client_request, -): - client_request.logout() - page = client_request.post( - "main.support", - _data={"who": "public"}, - _follow_redirects=True, - ) - assert normalize_spaces(page.select("h1")) == ( - "The Notify.gov service is for people who work in the government" - ) - assert len(page.select("h2 a")) == 3 - assert not page.select("form") - assert not page.select("input") - assert not page.select("form [type=submit]") - - -@freeze_time("2016-12-12 12:00:00.000000") -@pytest.mark.parametrize( - ("ticket_type", "expected_status_code"), - [(PROBLEM_TICKET_TYPE, 200), (QUESTION_TICKET_TYPE, 200), ("gripe", 404)], -) -def test_get_feedback_page(client_request, ticket_type, expected_status_code): - client_request.logout() - client_request.get( - "main.feedback", - ticket_type=ticket_type, - _expected_status=expected_status_code, - ) - - -@freeze_time("2016-12-12 12:00:00.000000") -@pytest.mark.parametrize( - ("ticket_type", "zendesk_ticket_type"), - [ - (PROBLEM_TICKET_TYPE, "incident"), - (QUESTION_TICKET_TYPE, "question"), - (GENERAL_TICKET_TYPE, "question"), - ], -) -def test_passed_non_logged_in_user_details_through_flow( - client_request, mocker, ticket_type, zendesk_ticket_type -): - client_request.logout() - mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") - mock_send_ticket_to_zendesk = mocker.patch( - "app.main.views.feedback.zendesk_client.send_ticket_to_zendesk", - autospec=True, - ) - - data = { - "feedback": "blah", - "name": "Anne Example", - "email_address": "anne@example.com", - } - - client_request.post( - "main.feedback", - ticket_type=ticket_type, - _data=data, - _expected_redirect=url_for( - "main.thanks", - out_of_hours_emergency=False, - email_address_provided=True, - ), - ) - - mock_create_ticket.assert_called_once_with( - ANY, - subject="Notify feedback", - message="blah\n", - ticket_type=zendesk_ticket_type, - p1=False, - user_name="Anne Example", - user_email="anne@example.com", - org_id=None, - org_type=None, - service_id=None, - ) - mock_send_ticket_to_zendesk.assert_called_once() - - -@freeze_time("2016-12-12 12:00:00.000000") -@pytest.mark.parametrize( - "data", - [ - {"feedback": "blah"}, - {"feedback": "blah", "name": "Ignored", "email_address": "ignored@email.com"}, - ], -) -@pytest.mark.parametrize( - ("ticket_type", "zendesk_ticket_type"), - [ - (PROBLEM_TICKET_TYPE, "incident"), - (QUESTION_TICKET_TYPE, "question"), - (GENERAL_TICKET_TYPE, "question"), - ], -) -def test_passes_user_details_through_flow( - client_request, - mock_get_non_empty_organizations_and_services_for_user, - mocker, - ticket_type, - zendesk_ticket_type, - data, -): - mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") - mock_send_ticket_to_zendesk = mocker.patch( - "app.main.views.feedback.zendesk_client.send_ticket_to_zendesk", - autospec=True, - ) - - client_request.post( - "main.feedback", - ticket_type=ticket_type, - _data=data, - _expected_status=302, - _expected_redirect=url_for( - "main.thanks", - email_address_provided=True, - out_of_hours_emergency=False, - ), - ) - mock_create_ticket.assert_called_once_with( - ANY, - subject="Notify feedback", - message=ANY, - ticket_type=zendesk_ticket_type, - p1=False, - user_name="Test User", - user_email="test@user.gsa.gov", - org_id=None, - org_type="federal", - service_id=SERVICE_ONE_ID, - ) - - assert mock_create_ticket.call_args[1]["message"] == "\n".join( - [ - "blah", - 'Service: "service one"', - url_for( - "main.service_dashboard", - service_id=SERVICE_ONE_ID, - _external=True, - ), - "", - ] - ) - mock_send_ticket_to_zendesk.assert_called_once() - - -@freeze_time("2016-12-12 12:00:00.000000") -@pytest.mark.parametrize( - "data", - [ - {"feedback": "blah", "name": "Fred"}, - {"feedback": "blah"}, - ], -) -@pytest.mark.parametrize( - "ticket_type", - [ - PROBLEM_TICKET_TYPE, - QUESTION_TICKET_TYPE, - ], -) -def test_email_address_required_for_problems_and_questions( - client_request, - mocker, - data, - ticket_type, -): - mocker.patch("app.main.views.feedback.zendesk_client") - client_request.logout() - page = client_request.post( - "main.feedback", ticket_type=ticket_type, _data=data, _expected_status=200 - ) - assert normalize_spaces(page.select_one(".usa-error-message").text) == ( - "Error: Cannot be empty" - ) - - -@freeze_time("2016-12-12 12:00:00.000000") -@pytest.mark.parametrize("ticket_type", [PROBLEM_TICKET_TYPE, QUESTION_TICKET_TYPE]) -def test_email_address_must_be_valid_if_provided_to_support_form( - client_request, - mocker, - ticket_type, -): - client_request.logout() - page = client_request.post( - "main.feedback", - ticket_type=ticket_type, - _data={ - "feedback": "blah", - "email_address": "not valid", - }, - _expected_status=200, - ) - - assert normalize_spaces(page.select_one("span.usa-error-message").text) == ( - "Error: Enter a valid email address" - ) - - -@pytest.mark.parametrize( - ("ticket_type", "severe", "is_in_business_hours", "is_out_of_hours_emergency"), - [ - # business hours, never an emergency - (PROBLEM_TICKET_TYPE, "yes", True, False), - (QUESTION_TICKET_TYPE, "yes", True, False), - (PROBLEM_TICKET_TYPE, "no", True, False), - (QUESTION_TICKET_TYPE, "no", True, False), - # out of hours, if the user says it’s not an emergency - (PROBLEM_TICKET_TYPE, "no", False, False), - (QUESTION_TICKET_TYPE, "no", False, False), - # out of hours, only problems can be emergencies - (PROBLEM_TICKET_TYPE, "yes", False, True), - (QUESTION_TICKET_TYPE, "yes", False, False), - ], -) -def test_urgency( - client_request, - mock_get_non_empty_organizations_and_services_for_user, - mocker, - ticket_type, - severe, - is_in_business_hours, - is_out_of_hours_emergency, -): - mocker.patch( - "app.main.views.feedback.in_business_hours", return_value=is_in_business_hours - ) - - mock_ticket = mocker.patch("app.main.views.feedback.NotifySupportTicket") - mocker.patch( - "app.main.views.feedback.zendesk_client.send_ticket_to_zendesk", - autospec=True, - ) - - client_request.post( - "main.feedback", - ticket_type=ticket_type, - severe=severe, - _data={"feedback": "blah", "email_address": "test@example.com"}, - _expected_status=302, - _expected_redirect=url_for( - "main.thanks", - out_of_hours_emergency=is_out_of_hours_emergency, - email_address_provided=True, - ), - ) - assert mock_ticket.call_args[1]["p1"] == is_out_of_hours_emergency - - -ids, params = zip( - *[ - ( - "non-logged in users always have to triage", - ( - GENERAL_TICKET_TYPE, - False, - False, - True, - 302, - partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE), - ), - ), - ( - "trial services are never high priority", - (PROBLEM_TICKET_TYPE, False, True, False, 200, no_redirect()), - ), - ( - "we can triage in hours", - (PROBLEM_TICKET_TYPE, True, True, True, 200, no_redirect()), - ), - ( - "only problems are high priority", - (QUESTION_TICKET_TYPE, False, True, True, 200, no_redirect()), - ), - ( - "should triage out of hours", - ( - PROBLEM_TICKET_TYPE, - False, - True, - True, - 302, - partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), - ), - ), - ] -) - - -@pytest.mark.parametrize( - ( - "ticket_type", - "is_in_business_hours", - "logged_in", - "has_live_services", - "expected_status", - "expected_redirect", - ), - params, - ids=ids, -) -def test_redirects_to_triage( - client_request, - api_user_active, - mocker, - mock_get_user, - ticket_type, - is_in_business_hours, - logged_in, - has_live_services, - expected_status, - expected_redirect, -): - mocker.patch( - "app.models.user.User.live_services", - new_callable=PropertyMock, - return_value=[{}, {}] if has_live_services else [], - ) - mocker.patch( - "app.main.views.feedback.in_business_hours", return_value=is_in_business_hours - ) - if not logged_in: - client_request.logout() - - client_request.get( - "main.feedback", - ticket_type=ticket_type, - _expected_status=expected_status, - _expected_redirect=expected_redirect(), - ) - - -@pytest.mark.parametrize( - ("ticket_type", "expected_h1"), - [ - (PROBLEM_TICKET_TYPE, "Report a problem"), - (GENERAL_TICKET_TYPE, "Contact Notify.gov support"), - ], -) -def test_options_on_triage_page( - client_request, - ticket_type, - expected_h1, -): - page = client_request.get("main.triage", ticket_type=ticket_type) - assert normalize_spaces(page.select_one("h1").text) == expected_h1 - assert page.select("form input[type=radio]")[0]["value"] == "yes" - assert page.select("form input[type=radio]")[1]["value"] == "no" - - -def test_doesnt_lose_message_if_post_across_closing( - client_request, - mocker, -): - mocker.patch("app.models.user.User.live_services", return_value=True) - mocker.patch("app.main.views.feedback.in_business_hours", return_value=False) - - page = client_request.post( - "main.feedback", - ticket_type=PROBLEM_TICKET_TYPE, - _data={"feedback": "foo"}, - _expected_status=302, - _expected_redirect=url_for(".triage", ticket_type=PROBLEM_TICKET_TYPE), - ) - with client_request.session_transaction() as session: - assert session["feedback_message"] == "foo" - - page = client_request.get( - "main.feedback", - ticket_type=PROBLEM_TICKET_TYPE, - severe="yes", - ) - - with client_request.session_transaction() as session: - assert page.find("textarea", {"name": "feedback"}).text == "\r\nfoo" - assert "feedback_message" not in session - - -@pytest.mark.parametrize( - ("when", "is_in_business_hours"), - [ - ("2016-06-06 09:29:59+0100", False), # opening time, summer and winter - ("2016-12-12 09:29:59+0000", False), - ("2016-06-06 09:30:00+0100", True), - ("2016-12-12 09:30:00+0000", True), - ("2016-12-12 12:00:00+0000", True), # middle of the day - ("2016-12-12 17:29:59+0000", True), # closing time - ("2016-12-12 17:30:00+0000", False), - ("2016-12-10 12:00:00+0000", False), # Saturday - ("2016-12-11 12:00:00+0000", False), # Sunday - ("2016-01-01 12:00:00+0000", False), # Bank holiday - ], -) -def test_in_business_hours(when, is_in_business_hours): - with freeze_time(when): - assert in_business_hours() == is_in_business_hours - - -@pytest.mark.parametrize( - "ticket_type", - [ - GENERAL_TICKET_TYPE, - PROBLEM_TICKET_TYPE, - ], -) -@pytest.mark.parametrize( - ("choice", "expected_redirect_param"), - [ - ("yes", "yes"), - ("no", "no"), - ], -) -def test_triage_redirects_to_correct_url( - client_request, - ticket_type, - choice, - expected_redirect_param, -): - client_request.post( - "main.triage", - ticket_type=ticket_type, - _data={"severe": choice}, - _expected_status=302, - _expected_redirect=url_for( - "main.feedback", - ticket_type=ticket_type, - severe=expected_redirect_param, - ), - ) - - -@pytest.mark.parametrize( - ("extra_args", "expected_back_link"), - [ - ( - {"severe": "yes"}, - partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), - ), - ( - {"severe": "no"}, - partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), - ), - ({"severe": "foo"}, partial(url_for, "main.support")), # hacking the URL - ({}, partial(url_for, "main.support")), - ], -) -@freeze_time("2012-12-12 12:12") -def test_back_link_from_form( - client_request, - mock_get_non_empty_organizations_and_services_for_user, - extra_args, - expected_back_link, -): - page = client_request.get( - "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, **extra_args - ) - assert page.select_one(".usa-back-link")["href"] == expected_back_link() - assert normalize_spaces(page.select_one("h1").text) == "Report a problem" - - -@pytest.mark.parametrize( - ( - "is_in_business_hours", - "severe", - "expected_status_code", - "expected_redirect", - "expected_status_code_when_logged_in", - "expected_redirect_when_logged_in", - ), - [ - (True, "yes", 200, no_redirect(), 200, no_redirect()), - (True, "no", 200, no_redirect(), 200, no_redirect()), - ( - False, - "no", - 200, - no_redirect(), - 200, - no_redirect(), - ), - # Treat empty query param as mangled URL – ask question again - ( - False, - "", - 302, - partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), - 302, - partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), - ), - # User hasn’t answered the triage question - ( - False, - None, - 302, - partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), - 302, - partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), - ), - # Escalation is needed for non-logged-in users - ( - False, - "yes", - 302, - partial(url_for, "main.bat_phone"), - 200, - no_redirect(), - ), - ], -) -def test_should_be_shown_the_bat_email( - client_request, - active_user_with_permissions, - mocker, - service_one, - mock_get_non_empty_organizations_and_services_for_user, - is_in_business_hours, - severe, - expected_status_code, - expected_redirect, - expected_status_code_when_logged_in, - expected_redirect_when_logged_in, -): - mocker.patch( - "app.main.views.feedback.in_business_hours", return_value=is_in_business_hours - ) - - feedback_page = url_for( - "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, severe=severe - ) - - client_request.logout() - client_request.get_url( - feedback_page, - _expected_status=expected_status_code, - _expected_redirect=expected_redirect(), - ) - - # logged in users should never be redirected to the bat email page - client_request.login(active_user_with_permissions) - client_request.get_url( - feedback_page, - _expected_status=expected_status_code_when_logged_in, - _expected_redirect=expected_redirect_when_logged_in(), - ) - - -@pytest.mark.parametrize( - ( - "severe", - "expected_status_code", - "expected_redirect", - "expected_status_code_when_logged_in", - "expected_redirect_when_logged_in", - ), - [ - # User hasn’t answered the triage question - ( - None, - 302, - partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE), - 302, - partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE), - ), - # Escalation is needed for non-logged-in users - ( - "yes", - 302, - partial(url_for, "main.bat_phone"), - 200, - no_redirect(), - ), - ], -) -def test_should_be_shown_the_bat_email_for_general_questions( - client_request, - active_user_with_permissions, - mocker, - service_one, - mock_get_non_empty_organizations_and_services_for_user, - severe, - expected_status_code, - expected_redirect, - expected_status_code_when_logged_in, - expected_redirect_when_logged_in, -): - mocker.patch("app.main.views.feedback.in_business_hours", return_value=False) - - feedback_page = url_for( - "main.feedback", ticket_type=GENERAL_TICKET_TYPE, severe=severe - ) - - client_request.logout() - client_request.get_url( - feedback_page, - _expected_status=expected_status_code, - _expected_redirect=expected_redirect(), - ) - - # logged in users should never be redirected to the bat email page - client_request.login(active_user_with_permissions) - client_request.get_url( - feedback_page, - _expected_status=expected_status_code_when_logged_in, - _expected_redirect=expected_redirect_when_logged_in(), - ) - - -def test_bat_email_page( - client_request, - active_user_with_permissions, - mocker, - service_one, -): - bat_phone_page = "main.bat_phone" - - client_request.logout() - page = client_request.get(bat_phone_page) - - assert page.select_one(".usa-back-link").text == "Back" - assert page.select_one(".usa-back-link")["href"] == url_for("main.support") - assert page.select("main a")[1].text == "Fill in this form" - assert page.select("main a")[1]["href"] == url_for( - "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, severe="no" - ) - next_page = client_request.get_url(page.select("main a")[1]["href"]) - assert next_page.h1.text.strip() == "Report a problem" - - client_request.login(active_user_with_permissions) - client_request.get( - bat_phone_page, - _expected_redirect=url_for("main.feedback", ticket_type=PROBLEM_TICKET_TYPE), - ) - - -@pytest.mark.parametrize( - ("out_of_hours_emergency", "email_address_provided", "out_of_hours", "message"), - [ - # Out of hours emergencies trump everything else - ( - True, - True, - True, - "We’ll reply in the next 30 minutes.", - ), - ( - True, - False, - False, # Not a real scenario - "We’ll reply in the next 30 minutes.", - ), - # Anonymous tickets don’t promise a reply - ( - False, - False, - False, - "We’ll aim to read your message in the next 30 minutes.", - ), - ( - False, - False, - True, - "We’ll read your message when we’re back in the office.", - ), - # When we look at your ticket depends on whether we’re in normal - # business hours - ( - False, - True, - False, - "We’ll aim to read your message in the next 30 minutes and we’ll reply within one working day.", - ), - (False, True, True, "We’ll reply within one working day."), - ], -) -def test_thanks( - client_request, - mocker, - api_user_active, - mock_get_user, - out_of_hours_emergency, - email_address_provided, - out_of_hours, - message, -): - mocker.patch( - "app.main.views.feedback.in_business_hours", return_value=(not out_of_hours) - ) - page = client_request.get( - "main.thanks", - out_of_hours_emergency=out_of_hours_emergency, - email_address_provided=email_address_provided, - ) - assert normalize_spaces(page.find("main").find("p").text) == message + assert normalize_spaces(page.select("h1")) == ("Contact us") diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index 7311dbaaa..315ef635a 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -5,7 +5,7 @@ from bs4 import BeautifulSoup from flask import url_for from freezegun import freeze_time -from tests.conftest import SERVICE_ONE_ID, normalize_spaces, sample_uuid +from tests.conftest import SERVICE_ONE_ID, normalize_spaces def test_non_logged_in_user_can_see_homepage( @@ -65,14 +65,6 @@ def test_robots(client_request): ("endpoint", "kwargs"), [ ("sign_in", {}), - ("support", {}), - ("support_public", {}), - ("triage", {}), - ("feedback", {"ticket_type": "ask-question-give-feedback"}), - ("feedback", {"ticket_type": "general"}), - ("feedback", {"ticket_type": "report-problem"}), - ("bat_phone", {}), - ("thanks", {}), ("register", {}), pytest.param("index", {}, marks=pytest.mark.xfail(raises=AssertionError)), ], @@ -104,12 +96,10 @@ def test_hiding_pages_from_search_engines( "documentation", "security", "message_status", - "features_email", "features_sms", "how_to_pay", "get_started", "guidance_index", - "branding_and_customisation", "create_and_send_messages", "edit_and_format_messages", "send_files_by_email", @@ -265,36 +255,6 @@ def test_css_is_served_from_correct_path(client_request): # assert logo_svg_fallback['src'].startswith('https://static.example.com/images/us-notify-color.png') -@pytest.mark.parametrize( - ("extra_args", "email_branding_retrieved"), - [ - ( - {}, - False, - ), - ( - {"branding_style": "__NONE__"}, - False, - ), - ( - {"branding_style": sample_uuid()}, - True, - ), - ], -) -def test_email_branding_preview( - client_request, - mock_get_email_branding, - extra_args, - email_branding_retrieved, -): - page = client_request.get( - "main.email_template", _test_page_title=False, **extra_args - ) - assert page.title.text == "Email branding preview" - assert mock_get_email_branding.called is email_branding_retrieved - - @pytest.mark.parametrize( ("current_date", "expected_rate"), [ diff --git a/tests/app/main/views/test_manage_users.py b/tests/app/main/views/test_manage_users.py index c5d97bd03..a6877a142 100644 --- a/tests/app/main/views/test_manage_users.py +++ b/tests/app/main/views/test_manage_users.py @@ -1157,6 +1157,35 @@ def test_invite_user_with_email_auth_service( ) +def test_resend_expired_invitation( + client_request, + mock_get_invites_for_service, + expired_invite, + active_user_with_permissions, + mock_get_users_by_service, + mock_get_template_folders, + mocker, +): + mock_resend = mocker.patch("app.invite_api_client.resend_invite") + mocker.patch( + "app.invite_api_client.get_invited_user_for_service", + return_value=expired_invite, + ) + page = client_request.get( + "main.resend_invite", + service_id=SERVICE_ONE_ID, + invited_user_id=expired_invite["id"], + _follow_redirects=True, + ) + assert normalize_spaces(page.h1.text) == "Team members" + assert mock_resend.called + called_args = set(mock_resend.call_args.args) | set( + mock_resend.call_args.kwargs.values() + ) + assert SERVICE_ONE_ID in called_args + assert expired_invite["id"] in called_args + + def test_cancel_invited_user_cancels_user_invitations( client_request, mock_get_invites_for_service, @@ -1168,7 +1197,8 @@ def test_cancel_invited_user_cancels_user_invitations( ): mock_cancel = mocker.patch("app.invite_api_client.cancel_invited_user") mocker.patch( - "app.invite_api_client.get_invited_user_for_service", return_value=sample_invite + "app.invite_api_client.get_invited_user_for_service", + return_value=sample_invite, ) page = client_request.get( diff --git a/tests/app/main/views/test_platform_admin.py b/tests/app/main/views/test_platform_admin.py index 49206d873..f5e7862a7 100644 --- a/tests/app/main/views/test_platform_admin.py +++ b/tests/app/main/views/test_platform_admin.py @@ -757,7 +757,6 @@ def test_clear_cache_shows_form( "user", "service", "template", - "email_branding", "organization", } diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py index 0d9c896e4..7448c44cf 100644 --- a/tests/app/main/views/test_send.py +++ b/tests/app/main/views/test_send.py @@ -30,6 +30,63 @@ from tests.conftest import ( normalize_spaces, ) +FAKE_ONE_OFF_NOTIFICATION = { + "links": {}, + "notifications": [ + { + "api_key": None, + "billable_units": 0, + "carrier": None, + "client_reference": None, + "created_at": "2023-12-14T20:35:55+00:00", + "created_by": { + "email_address": "grsrbsrgsrf@fake.gov", + "id": "de059e0a-42e5-48bb-939e-4f76804ab739", + "name": "grsrbsrgsrf", + }, + "document_download_count": None, + "id": "a3442b43-0ba1-4854-9e0a-d2fba1cc9b81", + "international": False, + "job": { + "id": "55b242b5-9f62-4271-aff7-039e9c320578", + "original_file_name": "1127b78e-a4a8-4b70-8f4f-9f4fbf03ece2.csv", + }, + "job_row_number": 0, + "key_name": None, + "key_type": "normal", + "normalised_to": "+16615555555", + "notification_type": "sms", + "personalisation": { + "dayofweek": "2", + "favecolor": "3", + "phonenumber": "+16615555555", + }, + "phone_prefix": "1", + "provider_response": None, + "rate_multiplier": 1.0, + "reference": None, + "reply_to_text": "development", + "sent_at": None, + "sent_by": None, + "service": "f62d840f-8bcb-4b36-b959-4687e16dd1a1", + "status": "created", + "template": { + "content": "((day of week)) and ((fave color))", + "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73", + "name": "personalized", + "redact_personalisation": False, + "subject": None, + "template_type": "sms", + "version": 1, + }, + "to": "+16615555555", + "updated_at": None, + } + ], + "page_size": 50, + "total": 1, +} + template_types = ["email", "sms"] unchanging_fake_uuid = uuid.uuid4() @@ -2060,10 +2117,19 @@ def test_route_permissions_send_check_notifications( route, response_code, method, + mock_create_job, + mock_s3_upload, ): with client_request.session_transaction() as session: session["recipient"] = "2028675301" session["placeholders"] = {"name": "a"} + + mocker.patch("app.main.views.send.check_messages") + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) + validate_route_permission_with_client( mocker, client_request, @@ -2578,28 +2644,31 @@ def test_check_notification_shows_preview( def test_send_notification_submits_data( client_request, fake_uuid, - mock_send_notification, mock_get_service_template, template, recipient, placeholders, expected_personalisation, + mocker, + mock_create_job, + mock_s3_upload, ): with client_request.session_transaction() as session: session["recipient"] = recipient session["placeholders"] = placeholders + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) + + mocker.patch("app.main.views.send.check_messages", return_value="") + client_request.post( "main.send_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) - mock_send_notification.assert_called_once_with( - SERVICE_ONE_ID, - template_id=fake_uuid, - recipient=recipient, - personalisation=expected_personalisation, - sender_id=None, - ) + mock_create_job.assert_called_once() def test_send_notification_clears_session( @@ -2608,11 +2677,20 @@ def test_send_notification_clears_session( fake_uuid, mock_send_notification, mock_get_service_template, + mocker, + mock_create_job, + mock_s3_upload, ): with client_request.session_transaction() as session: session["recipient"] = "2028675301" session["placeholders"] = {"a": "b"} + mocker.patch("app.main.views.send.check_messages") + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) + client_request.post( "main.send_notification", service_id=service_one["id"], template_id=fake_uuid ) @@ -2661,22 +2739,26 @@ def test_send_notification_redirects_to_view_page( mock_get_service_template, extra_args, extra_redirect_args, + mocker, + mock_create_job, + mock_s3_upload, ): with client_request.session_transaction() as session: session["recipient"] = "2028675301" session["placeholders"] = {"a": "b"} + mocker.patch("app.main.views.send.check_messages") + + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) + client_request.post( "main.send_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, - _expected_redirect=url_for( - ".view_notification", - service_id=SERVICE_ONE_ID, - notification_id=fake_uuid, - **extra_redirect_args, - ), **extra_args, ) @@ -2715,13 +2797,24 @@ def test_send_notification_shows_error_if_400( fake_uuid, mocker, mock_get_service_template_with_placeholders, + mock_create_job, exception_msg, expected_h1, expected_err_details, + mock_s3_upload, ): class MockHTTPError(HTTPError): message = exception_msg + mocker.patch( + "app.main.views.send.check_messages", + ) + + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) + mocker.patch( "app.notification_api_client.send_notification", side_effect=MockHTTPError(), @@ -2730,18 +2823,14 @@ def test_send_notification_shows_error_if_400( session["recipient"] = "2028675301" session["placeholders"] = {"name": "a" * 900} + # This now redirects to the jobs results page page = client_request.post( "main.send_notification", service_id=service_one["id"], template_id=fake_uuid, - _expected_status=200, + _expected_status=302, ) - assert normalize_spaces(page.select(".banner-dangerous h1")[0].text) == expected_h1 - assert ( - normalize_spaces(page.select(".banner-dangerous p")[0].text) - == expected_err_details - ) assert not page.find("input[type=submit]") @@ -2750,11 +2839,19 @@ def test_send_notification_shows_email_error_in_trial_mode( fake_uuid, mocker, mock_get_service_email_template, + mock_create_job, + mock_s3_upload, ): class MockHTTPError(HTTPError): message = TRIAL_MODE_MSG status_code = 400 + mocker.patch("app.main.views.send.check_messages") + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) + mocker.patch( "app.notification_api_client.send_notification", side_effect=MockHTTPError(), @@ -2763,18 +2860,12 @@ def test_send_notification_shows_email_error_in_trial_mode( session["recipient"] = "test@example.com" session["placeholders"] = {"date": "foo", "thing": "bar"} - page = client_request.post( + # Calling this means we successful ran a job so we will be redirect to the jobs page + client_request.post( "main.send_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - _expected_status=200, - ) - - assert normalize_spaces(page.select(".banner-dangerous h1")[0].text) == ( - "You cannot send to this email address" - ) - assert normalize_spaces(page.select(".banner-dangerous p")[0].text) == ( - "In trial mode you can only send to yourself and members of your team" + _expected_status=302, ) diff --git a/tests/app/models/test_event.py b/tests/app/models/test_event.py index 4f64f7a32..bbff4fe91 100644 --- a/tests/app/models/test_event.py +++ b/tests/app/models/test_event.py @@ -12,7 +12,6 @@ from tests.conftest import sample_uuid ("active", False, True, ("Unsuspended this service")), ("active", True, False, ("Deleted this service")), ("contact_link", "x", "y", ("Set the contact details for this service to ‘y’")), - ("email_branding", "foo", "bar", ("Updated this service’s email branding")), ( "inbound_api", "foo", diff --git a/tests/app/models/test_user.py b/tests/app/models/test_user.py index 4611d5ea6..8c4c26907 100644 --- a/tests/app/models/test_user.py +++ b/tests/app/models/test_user.py @@ -10,7 +10,6 @@ def test_anonymous_user(notify_admin): assert AnonymousUser().default_organization.name is None assert AnonymousUser().default_organization.domains == [] assert AnonymousUser().default_organization.organization_type is None - assert AnonymousUser().default_organization.request_to_go_live_notes is None def test_user(notify_admin): diff --git a/tests/app/notify_client/test_email_branding_client.py b/tests/app/notify_client/test_email_branding_client.py deleted file mode 100644 index 85b2c12c9..000000000 --- a/tests/app/notify_client/test_email_branding_client.py +++ /dev/null @@ -1,104 +0,0 @@ -from unittest.mock import call - -from app.notify_client.email_branding_client import EmailBrandingClient - - -def test_get_email_branding(mocker, fake_uuid): - mock_get = mocker.patch( - "app.notify_client.email_branding_client.EmailBrandingClient.get", - return_value={"foo": "bar"}, - ) - mock_redis_get = mocker.patch( - "app.extensions.RedisClient.get", - return_value=None, - ) - mock_redis_set = mocker.patch( - "app.extensions.RedisClient.set", - ) - EmailBrandingClient().get_email_branding(fake_uuid) - mock_get.assert_called_once_with(url="/email-branding/{}".format(fake_uuid)) - mock_redis_get.assert_called_once_with("email_branding-{}".format(fake_uuid)) - mock_redis_set.assert_called_once_with( - "email_branding-{}".format(fake_uuid), - '{"foo": "bar"}', - ex=604800, - ) - - -def test_get_all_email_branding(mocker): - mock_get = mocker.patch( - "app.notify_client.email_branding_client.EmailBrandingClient.get", - return_value={"email_branding": [1, 2, 3]}, - ) - mock_redis_get = mocker.patch( - "app.extensions.RedisClient.get", - return_value=None, - ) - mock_redis_set = mocker.patch( - "app.extensions.RedisClient.set", - ) - EmailBrandingClient().get_all_email_branding() - mock_get.assert_called_once_with(url="/email-branding") - mock_redis_get.assert_called_once_with("email_branding") - mock_redis_set.assert_called_once_with( - "email_branding", - "[1, 2, 3]", - ex=604800, - ) - - -def test_create_email_branding(mocker): - org_data = { - "logo": "test.png", - "name": "test name", - "text": "test name", - "colour": "red", - "brand_type": "org", - } - - mock_post = mocker.patch( - "app.notify_client.email_branding_client.EmailBrandingClient.post" - ) - mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") - EmailBrandingClient().create_email_branding( - logo=org_data["logo"], - name=org_data["name"], - text=org_data["text"], - colour=org_data["colour"], - brand_type="org", - ) - - mock_post.assert_called_once_with(url="/email-branding", data=org_data) - - mock_redis_delete.assert_called_once_with("email_branding") - - -def test_update_email_branding(mocker, fake_uuid): - org_data = { - "logo": "test.png", - "name": "test name", - "text": "test name", - "colour": "red", - "brand_type": "org", - } - - mock_post = mocker.patch( - "app.notify_client.email_branding_client.EmailBrandingClient.post" - ) - mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") - EmailBrandingClient().update_email_branding( - branding_id=fake_uuid, - logo=org_data["logo"], - name=org_data["name"], - text=org_data["text"], - colour=org_data["colour"], - brand_type="org", - ) - - mock_post.assert_called_once_with( - url="/email-branding/{}".format(fake_uuid), data=org_data - ) - assert mock_redis_delete.call_args_list == [ - call("email_branding-{}".format(fake_uuid)), - call("email_branding"), - ] diff --git a/tests/app/notify_client/test_service_api_client.py b/tests/app/notify_client/test_service_api_client.py index acffa3f8a..815f3e5e2 100644 --- a/tests/app/notify_client/test_service_api_client.py +++ b/tests/app/notify_client/test_service_api_client.py @@ -632,7 +632,6 @@ def test_client_updates_service_with_allowed_attributes( "consent_to_research", "contact_link", "count_as_live", - "email_branding", "email_from", "free_sms_fragment_limit", "go_live_at", diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py index 9873d6422..b139eb9be 100644 --- a/tests/app/test_navigation.py +++ b/tests/app/test_navigation.py @@ -31,13 +31,12 @@ EXCLUDED_ENDPOINTS = tuple( "api_keys", "archive_service", "archive_user", - "bat_phone", "begin_tour", "billing_details", - "branding_and_customisation", "callbacks", "cancel_invited_org_user", "cancel_invited_user", + "resend_invite", "cancel_job", "change_user_auth", "check_and_resend_text_code", @@ -61,7 +60,6 @@ EXCLUDED_ENDPOINTS = tuple( "count_content_length", "create_and_send_messages", "create_api_key", - "create_email_branding", "data_retention", "delete_service_template", "delete_template_folder", @@ -75,8 +73,6 @@ EXCLUDED_ENDPOINTS = tuple( "edit_data_retention", "edit_organization_billing_details", "edit_organization_domains", - "edit_organization_email_branding", - "edit_organization_go_live_notes", "edit_organization_name", "edit_organization_notes", "edit_organization_type", @@ -87,20 +83,10 @@ EXCLUDED_ENDPOINTS = tuple( "edit_user_email", "edit_user_mobile_number", "edit_user_permissions", - "email_branding", - "email_branding_govuk", - "email_branding_govuk_and_org", - "email_branding_organization", - "email_branding_request", - "email_branding_something_else", "email_not_received", - "email_template", "error", - "estimate_usage", "features", - "features_email", "features_sms", - "feedback", "find_services_by_name", "find_users_by_email", "forgot_password", @@ -146,7 +132,6 @@ EXCLUDED_ENDPOINTS = tuple( "old_using_notify", "organization_billing", "organization_dashboard", - "organization_preview_email_branding", "organization_settings", "organization_trial_mode_services", "organizations", @@ -165,7 +150,6 @@ EXCLUDED_ENDPOINTS = tuple( "registration_continue", "remove_user_from_organization", "remove_user_from_service", - "request_to_go_live", "resend_email_link", "resend_email_verification", "resume_service", @@ -193,10 +177,8 @@ EXCLUDED_ENDPOINTS = tuple( "service_edit_sms_sender", "service_email_reply_to", "service_name_change", - "service_preview_email_branding", "service_set_auth_type", "service_set_channel", - "service_set_email_branding", "service_set_inbound_number", "service_set_inbound_sms", "service_set_international_sms", @@ -219,16 +201,12 @@ EXCLUDED_ENDPOINTS = tuple( "sign_in", "sign_out", "start_job", - "submit_request_to_go_live", "support", - "support_public", "suspend_service", "template_history", "template_usage", "terms", - "thanks", "tour_step", - "triage", "trial_mode", "trial_mode_new", "trial_services", @@ -236,7 +214,6 @@ EXCLUDED_ENDPOINTS = tuple( "two_factor_email", "two_factor_email_interstitial", "two_factor_email_sent", - "update_email_branding", "uploads", "usage", "user_information", diff --git a/tests/app/utils/test_branding.py b/tests/app/utils/test_branding.py deleted file mode 100644 index 8c1aae4bd..000000000 --- a/tests/app/utils/test_branding.py +++ /dev/null @@ -1,189 +0,0 @@ -from unittest.mock import PropertyMock - -import pytest - -from app.models.service import Service -from app.utils.branding import get_email_choices -from tests import organization_json -from tests.conftest import create_email_branding - - -@pytest.mark.parametrize("function", [get_email_choices]) -@pytest.mark.parametrize( - ("org_type", "expected_options"), - [ - ("federal", []), - ("state", []), - ], -) -def test_get_choices_service_not_assigned_to_org( - service_one, - function, - org_type, - expected_options, -): - service_one["organization_type"] = org_type - service = Service(service_one) - - options = function(service) - assert list(options) == expected_options - - -@pytest.mark.parametrize( - ("org_type", "branding_id", "expected_options"), - [ - ( - "federal", - None, - [ - ("govuk_and_org", "GOV.UK and Test Organization"), - ("organization", "Test Organization"), - ], - ), - ( - "federal", - "some-branding-id", - [ - ("govuk", "GOV.UK"), # central orgs can switch back to gsa.gov - ("govuk_and_org", "GOV.UK and Test Organization"), - ("organization", "Test Organization"), - ], - ), - ("state", None, [("organization", "Test Organization")]), - ("state", "some-branding-id", [("organization", "Test Organization")]), - # ('nhs_central', None, [ - # ('nhs', 'NHS') - # ]), - # ('nhs_central', NHS_EMAIL_BRANDING_ID, [ - # # don't show NHS if it's the current branding - # ]), - ], -) -@pytest.mark.skip(reason="Update for TTS") -def test_get_email_choices_service_assigned_to_org( - mocker, - service_one, - org_type, - branding_id, - expected_options, - mock_get_service_organization, - mock_get_email_branding, -): - service = Service(service_one) - - mocker.patch( - "app.organizations_client.get_organization", - return_value=organization_json(organization_type=org_type), - ) - mocker.patch( - "app.models.service.Service.email_branding_id", - new_callable=PropertyMock, - return_value=branding_id, - ) - - options = get_email_choices(service) - assert list(options) == expected_options - - -@pytest.mark.parametrize( - ("org_type", "branding_id", "expected_options"), - [ - ( - "federal", - "some-branding-id", - [ - # don't show gsa.gov options as org default supersedes it - ("organization", "Test Organization"), - ], - ), - ( - "federal", - "org-branding-id", - [ - # also don't show org option if it's the current branding - ], - ), - ( - "state", - "org-branding-id", - [ - # don't show org option if it's the current branding - ], - ), - ], -) -@pytest.mark.skip(reason="Update for TTS") -def test_get_email_choices_org_has_default_branding( - mocker, - service_one, - org_type, - branding_id, - expected_options, - mock_get_service_organization, - mock_get_email_branding, -): - service = Service(service_one) - - mocker.patch( - "app.organizations_client.get_organization", - return_value=organization_json( - organization_type=org_type, email_branding_id="org-branding-id" - ), - ) - mocker.patch( - "app.models.service.Service.email_branding_id", - new_callable=PropertyMock, - return_value=branding_id, - ) - - options = get_email_choices(service) - assert list(options) == expected_options - - -@pytest.mark.parametrize( - ("branding_name", "expected_options"), - [ - ( - "gsa.gov and something else", - [ - ("govuk", "GOV.UK"), - ("govuk_and_org", "GOV.UK and Test Organization"), - ("organization", "Test Organization"), - ], - ), - ( - "gsa.gov and test OrganisatioN", - [ - ("govuk", "GOV.UK"), - ("organization", "Test Organization"), - ], - ), - ], -) -@pytest.mark.skip(reason="Update for TTS") -def test_get_email_choices_branding_name_in_use( - mocker, - service_one, - branding_name, - expected_options, - mock_get_service_organization, -): - service = Service(service_one) - - mocker.patch( - "app.organizations_client.get_organization", - return_value=organization_json(organization_type="central"), - ) - mocker.patch( - "app.models.service.Service.email_branding_id", - new_callable=PropertyMock, - return_value="some-branding-id", - ) - mocker.patch( - "app.email_branding_client.get_email_branding", - return_value=create_email_branding("_id", {"name": branding_name}), - ) - - options = get_email_choices(service) - # don't show option if its name is similar to current branding - assert list(options) == expected_options diff --git a/tests/conftest.py b/tests/conftest.py index 9d55b796b..9bab089f8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1396,7 +1396,15 @@ def mock_check_verify_code_code_expired(mocker): @pytest.fixture() def mock_create_job(mocker, api_user_active): - def _create(job_id, service_id, scheduled_for=None): + def _create( + job_id, + service_id, + scheduled_for=None, + template_id=None, + original_file_name=None, + notification_count=None, + valid=None, + ): return job_json( service_id, api_user_active, @@ -1892,6 +1900,30 @@ def sample_invite(mocker, service_one): ) +@pytest.fixture() +def expired_invite(service_one): + id_ = USER_ONE_ID + from_user = service_one["users"][0] + email_address = "invited_user@test.gsa.gov" + service_id = service_one["id"] + permissions = "view_activity,send_emails,send_texts,manage_settings,manage_users,manage_api_keys" + created_at = str(datetime.utcnow() - timedelta(days=3)) + auth_type = "sms_auth" + folder_permissions = [] + + return invite_json( + id_, + from_user, + service_id, + email_address, + permissions, + created_at, + "expired", + auth_type, + folder_permissions, + ) + + @pytest.fixture() def mock_create_invite(mocker, sample_invite): def _create_invite( @@ -2182,152 +2214,6 @@ def mock_send_already_registered_email(mocker): return mocker.patch("app.user_api_client.send_already_registered_email") -def create_email_brandings( - number_of_brandings, non_standard_values=None, shuffle=False -): - brandings = [ - { - "id": str(idx), - "name": "org {}".format(idx), - "text": "org {}".format(idx), - "colour": None, - "logo": "logo{}.png".format(idx), - "brand_type": "org", - } - for idx in range(1, number_of_brandings + 1) - ] - - for idx, row in enumerate(non_standard_values or {}): - brandings[row["idx"]].update(non_standard_values[idx]) - - if shuffle: - brandings.insert(3, brandings.pop(4)) - - return brandings - - -@pytest.fixture() -def mock_get_all_email_branding(mocker): - def _get_all_email_branding(sort_key=None): - non_standard_values = [ - {"idx": 1, "colour": "red"}, - {"idx": 2, "colour": "orange"}, - {"idx": 3, "text": None}, - {"idx": 4, "colour": "blue"}, - ] - shuffle = sort_key is None - return create_email_brandings( - 5, non_standard_values=non_standard_values, shuffle=shuffle - ) - - return mocker.patch( - "app.notify_client.email_branding_client.email_branding_client.get_all_email_branding", - side_effect=_get_all_email_branding, - ) - - -@pytest.fixture() -def mock_no_email_branding(mocker): - def _get_email_branding(): - return [] - - return mocker.patch( - "app.email_branding_client.get_all_email_branding", - side_effect=_get_email_branding, - ) - - -def create_email_branding(id, non_standard_values=None): - branding = { - "logo": "example.png", - "name": "Organization name", - "text": "Organization text", - "id": id, - "colour": "#f00", - "brand_type": "org", - } - - if non_standard_values: - branding.update(non_standard_values) - - return {"email_branding": branding} - - -@pytest.fixture() -def mock_get_email_branding(mocker, fake_uuid): - def _get_email_branding(id): - return create_email_branding(fake_uuid) - - return mocker.patch( - "app.email_branding_client.get_email_branding", side_effect=_get_email_branding - ) - - -@pytest.fixture() -def mock_get_email_branding_with_govuk_brand_type(mocker, fake_uuid): - def _get_email_branding(id): - return create_email_branding(fake_uuid, {"brand_type": "govuk"}) - - return mocker.patch( - "app.email_branding_client.get_email_branding", side_effect=_get_email_branding - ) - - -@pytest.fixture() -def mock_get_email_branding_with_both_brand_type(mocker, fake_uuid): - def _get_email_branding(id): - return create_email_branding(fake_uuid, {"brand_type": "both"}) - - return mocker.patch( - "app.email_branding_client.get_email_branding", side_effect=_get_email_branding - ) - - -@pytest.fixture() -def mock_get_email_branding_with_org_banner_brand_type(mocker, fake_uuid): - def _get_email_branding(id): - return create_email_branding(fake_uuid, {"brand_type": "org_banner"}) - - return mocker.patch( - "app.email_branding_client.get_email_branding", side_effect=_get_email_branding - ) - - -@pytest.fixture() -def mock_get_email_branding_without_brand_text(mocker, fake_uuid): - def _get_email_branding_without_brand_text(id): - return create_email_branding( - fake_uuid, {"text": "", "brand_type": "org_banner"} - ) - - return mocker.patch( - "app.email_branding_client.get_email_branding", - side_effect=_get_email_branding_without_brand_text, - ) - - -@pytest.fixture() -def mock_create_email_branding(mocker): - def _create_email_branding(logo, name, text, colour, brand_type): - return - - return mocker.patch( - "app.email_branding_client.create_email_branding", - side_effect=_create_email_branding, - ) - - -@pytest.fixture() -def mock_update_email_branding(mocker): - def _update_email_branding(branding_id, logo, name, text, colour, brand_type): - return - - return mocker.patch( - "app.email_branding_client.update_email_branding", - side_effect=_update_email_branding, - ) - - @pytest.fixture() def mock_get_guest_list(mocker): def _get_guest_list(service_id): diff --git a/tests/javascripts/consent.test.js b/tests/javascripts/consent.test.js deleted file mode 100644 index 9217bd81c..000000000 --- a/tests/javascripts/consent.test.js +++ /dev/null @@ -1,55 +0,0 @@ -const helpers = require('./support/helpers'); - -beforeAll(() => { - - require('../../app/assets/javascripts/govuk/cookie-functions.js'); - require('../../app/assets/javascripts/consent.js'); - -}); - -afterAll(() => { - - require('./support/teardown.js'); - -}); - -describe("Cookie consent", () => { - - describe("hasConsentFor", () => { - - afterEach(() => { - - // remove cookie set by tests - helpers.deleteCookie('cookies_policy'); - - }); - - test("If there is no consent cookie, return false", () => { - - expect(window.GOVUK.hasConsentFor('analytics')).toBe(false); - - }); - - describe("If a consent cookie is set", () => { - - test("If the category is not saved in the cookie, return false", () => { - - window.GOVUK.setConsentCookie({ 'usage': true }); - - expect(window.GOVUK.hasConsentFor('analytics')).toBe(false); - - }); - - test("If the category is saved in the cookie, return its value", () => { - - window.GOVUK.setConsentCookie({ 'analytics': true }); - - expect(window.GOVUK.hasConsentFor('analytics')).toBe(true); - - }); - - }); - - }); - -}); diff --git a/tests/javascripts/liveSearch.test.js b/tests/javascripts/liveSearch.test.js index e0868285e..548e5505f 100644 --- a/tests/javascripts/liveSearch.test.js +++ b/tests/javascripts/liveSearch.test.js @@ -25,451 +25,6 @@ describe('Live search', () => { } }; - describe("With a list of radios", () => { - - searchLabelText = "Search branding styles by name"; - - beforeEach(() => { - - const departmentData = { - name: 'departments', - hideLegend: true, - fields: [ - { - 'label': 'NHS', - 'id': 'nhs', - 'name': 'branding', - 'value': 'nhs' - }, - { - 'label': 'Department for Work and Pensions', - 'id': 'dwp', - 'name': 'branding', - 'value': 'dwp' - }, - { - 'label': 'Department for Education', - 'id': 'dfe', - 'name': 'branding', - 'value': 'dfe' - }, - { - 'label': 'Home Office', - 'id': 'home-office', - 'name': 'branding', - 'value': 'home-office' - } - ] - }; - - // set up DOM - document.body.innerHTML = ` - -
    -
    `; - - searchTextbox = document.getElementById('search'); - liveRegion = document.querySelector('.live-search__status'); - list = document.querySelector('form'); - - // getRadioGroup returns a DOM node so append once DOM is set up - list.appendChild(helpers.getRadioGroup(departmentData)); - - }); - - describe("When the page loads", () => { - - test("If there is no search term, the results should be unchanged", () => { - - // start the module - window.GOVUK.modules.start(); - - const listItems = list.querySelectorAll('.usa-radio'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(listItems.length); - expect(searchTextbox.hasAttribute('aria-label')).toBe(false); - - }); - - test("If there is a single word search term, only the results that match should show", () => { - - searchTextbox.value = 'Department'; - - // start the module - window.GOVUK.modules.start(); - - const listItems = list.querySelectorAll('.usa-radio'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(2); - expect(searchTextbox.hasAttribute('aria-label')).toBe(true); - expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(2)}`); - - }); - - test("If there is a search term made of several words, only the results that match should show", () => { - - searchTextbox.value = 'Department for Work'; - - // start the module - window.GOVUK.modules.start(); - - const listItems = list.querySelectorAll('.usa-radio'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(1); - expect(searchTextbox.hasAttribute('aria-label')).toBe(true); - expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(1)}`); - - }); - - test("If an item doesn't match the search term but is selected, it should still show in the results", () => { - - searchTextbox.value = 'Department for Work'; - - // mark an item as selected - checkedItem = list.querySelector('input[id=nhs]'); - checkedItem.checked = true; - - // start the module - window.GOVUK.modules.start(); - - expect(window.getComputedStyle(checkedItem).display).not.toEqual('none'); - - }); - - }); - - describe("When the search text changes", () => { - - test("If there is no search term, the results should be unchanged", () => { - - searchTextbox.value = 'Department'; - - // start the module - window.GOVUK.modules.start(); - - // simulate the input of new search text - searchTextbox.value = ''; - helpers.triggerEvent(searchTextbox, 'input'); - - const listItems = list.querySelectorAll('.usa-radio'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(listItems.length); - expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(listItemsShowing.length)); - - }); - - test("If there is a single word search term, only the results that match should show", () => { - - searchTextbox.value = 'Department'; - - // start the module - window.GOVUK.modules.start(); - - // simulate the input of new search text - searchTextbox.value = 'Home'; - helpers.triggerEvent(searchTextbox, 'input'); - - const listItems = list.querySelectorAll('.usa-radio'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(1); - expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1)); - - }); - - test("If there is a search term made of several words, only the results that match should show", () => { - - searchTextbox.value = 'Department'; - - // start the module - window.GOVUK.modules.start(); - - // simulate the input of new search text - searchTextbox.value = 'Department for'; - helpers.triggerEvent(searchTextbox, 'input'); - - const listItems = list.querySelectorAll('.usa-radio'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(2); - expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(2)); - - }); - - test("If an item doesn't match the search term but is selected, it should still show in the results", () => { - - searchTextbox.value = 'Department'; - - // mark an item as selected - checkedItem = list.querySelector('input[id=nhs]'); - checkedItem.checked = true; - - // start the module - window.GOVUK.modules.start(); - - // simulate the input of new search text - searchTextbox.value = 'Home Office'; - helpers.triggerEvent(searchTextbox, 'input'); - - expect(window.getComputedStyle(checkedItem).display).not.toEqual('none'); - - }); - - }); - - }); - - describe("With a list of checkboxes", () => { - - searchLabelText = "Search branding styles by name"; - - beforeEach(() => { - - const templatesAndFolders = [ - { - "label": "Appointments", - "type": "folder", - "meta": "2 templates" - }, - { - "label": "New patient", - "type": "template", - "meta": "Email template" - }, - { - "label": "Prescriptions", - "type": "folder", - "meta": "1 template, 1 folder" - }, - { - "label": "New doctor", - "type": "template", - "meta": "Email template" - } - ]; - - // set up DOM - document.body.innerHTML = ` - -
    - -
    `; - - searchTextbox = document.getElementById('search'); - liveRegion = document.querySelector('.live-search__status'); - list = document.querySelector('form'); - - }); - - describe("When the page loads", () => { - - test("If there is no search term, the results should be unchanged", () => { - - // start the module - window.GOVUK.modules.start(); - - const listItems = list.querySelectorAll('.template-list-item'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(listItems.length); - expect(searchTextbox.hasAttribute('aria-label')).toBe(false); - - }); - - test("If there is a single word search term, only the results that match should show", () => { - - searchTextbox.value = 'New'; - - // start the module - window.GOVUK.modules.start(); - - const listItems = list.querySelectorAll('.template-list-item'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - // should match 'New patient' and 'New doctor' - expect(listItemsShowing.length).toEqual(2); - expect(searchTextbox.hasAttribute('aria-label')).toBe(true); - expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(2)}`); - - }); - - test("If there is a search term made of several words, only the results that match should show", () => { - - searchTextbox.value = 'New patient'; - - // start the module - window.GOVUK.modules.start(); - - const listItems = list.querySelectorAll('.template-list-item'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(1); - expect(searchTextbox.hasAttribute('aria-label')).toBe(true); - expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(1)}`); - - }); - - test("If an item doesn't match the search term but is selected, it should still show in the results", () => { - - searchTextbox.value = 'New patient'; - - // mark 'Appointments' item as selected - checkedItem = list.querySelector('input[id=templates-or-folder-0]'); - checkedItem.checked = true; - - // start the module - window.GOVUK.modules.start(); - - // should show despite not matching - expect(window.getComputedStyle(checkedItem).display).not.toEqual('none'); - - }); - - test("If the items have a block of text to match against, only results that match it should show", () => { - - searchTextbox.value = 'Email template'; - - // start the module - window.GOVUK.modules.start(); - - const listItems = list.querySelectorAll('.template-list-item'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - // 2 items contain the "Email template" text - // only the text containing the name of the item is matched against (ie 'New patient') - expect(listItemsShowing.length).toEqual(0); - expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(0)}`); - - }); - - }); - - describe("When the search text changes", () => { - - test("If there is no search term, the results should be unchanged", () => { - - searchTextbox.value = 'Appointments'; - - // start the module - window.GOVUK.modules.start(); - - // simulate input of new search text - searchTextbox.value = ''; - helpers.triggerEvent(searchTextbox, 'input'); - - const listItems = list.querySelectorAll('.template-list-item'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(listItems.length); - expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(listItemsShowing.length)); - - }); - - test("If there is a single word search term, only the results that match should show", () => { - - searchTextbox.value = 'Appointments'; - - // start the module - window.GOVUK.modules.start(); - - // simulate input of new search text - searchTextbox.value = 'Prescriptions'; - helpers.triggerEvent(searchTextbox, 'input'); - - const listItems = list.querySelectorAll('.template-list-item'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(1); - expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1)); - - }); - - test("If there is a search term made of several words, only the results that match should show", () => { - - searchTextbox.value = 'Appointments'; - - // start the module - window.GOVUK.modules.start(); - - // simulate input of new search text - searchTextbox.value = 'New doctor'; - helpers.triggerEvent(searchTextbox, 'input'); - - const listItems = list.querySelectorAll('.template-list-item'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - expect(listItemsShowing.length).toEqual(1); - expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1)); - - }); - - test("If an item doesn't match the search term but is selected, it should still show in the results", () => { - - searchTextbox.value = 'Appointments'; - - // mark 'Appointments' item as selected - checkedItem = list.querySelector('input[id=templates-or-folder-0]'); - checkedItem.checked = true; - - // start the module - window.GOVUK.modules.start(); - - // simulate input of new search text - searchTextbox.value = 'Prescriptions'; - helpers.triggerEvent(searchTextbox, 'input'); - - // should show despite not matching - expect(window.getComputedStyle(checkedItem).display).not.toEqual('none'); - - }); - - test("If the items have a block of text to match against, only results that match it should show", () => { - - searchTextbox.value = 'Appointments'; - - // start the module - window.GOVUK.modules.start(); - - // simulate input of new search text - searchTextbox.value = 'Email template'; - helpers.triggerEvent(searchTextbox, 'input'); - - const listItems = list.querySelectorAll('.template-list-item'); - const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); - - // 2 items contain the "Email template" text - // only the text containing the name of the item is matched against (ie 'New patient') - expect(listItemsShowing.length).toEqual(0); - expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(0)); - - }); - - }); - - }) - describe("With a list of content items", () => { searchLabelText = "Search by name or email address"; diff --git a/tests/javascripts/previewPane.test.js b/tests/javascripts/previewPane.test.js deleted file mode 100644 index beacaa0cb..000000000 --- a/tests/javascripts/previewPane.test.js +++ /dev/null @@ -1,234 +0,0 @@ -const helpers = require('./support/helpers.js'); - -const emailPageURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/set-email-branding'; -const emailPreviewConfirmationURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/preview-email-branding'; -const letterPageURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/set-letter-branding'; -const letterPreviewConfirmationURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/preview-letter-branding'; - -let locationMock; - -beforeAll(() => { - - // mock calls to window.location - // default to the email page, the pathname can be changed inside specific tests - locationMock = new helpers.LocationMock(emailPageURL); - -}); - -afterAll(() => { - - // reset window.location to its original state - locationMock.reset(); - require('./support/teardown.js'); - -}); - -describe('Preview pane', () => { - - let form; - let radios; - - beforeEach(() => { - - const brands = { - "name": "branding_style", - "label": "Branding style", - "cssClasses": [], - "fields": [ - { - "label": "Department for Education", - "value": "dfe", - "checked": true - }, - { - "label": "Home Office", - "value": "ho", - "checked": false - }, - { - "label": "Her Majesty's Revenue and Customs", - "value": "hmrc", - "checked": false - }, - { - "label": "Department for Work and Pensions", - "value": "dwp", - "checked": false - } - ] - }; - - // set up DOM - document.body.innerHTML = - `
    -
    -
    -
    -
    - -
    -
    -
    - -
    `; - - document.querySelector('.govuk-grid-column-full').appendChild(helpers.getRadioGroup(brands)); - form = document.querySelector('form'); - radios = form.querySelector('fieldset'); - - }); - - afterEach(() => { - - document.body.innerHTML = ''; - - // we run the previewPane.js script every test - // the module cache needs resetting each time for the script to execute - jest.resetModules(); - - }); - - describe("If the page type is 'email'", () => { - - describe("When the page loads", () => { - - test("it should add the preview pane", () => { - - // run preview pane script - require('../../app/assets/javascripts/previewPane.js'); - - expect(document.querySelector('iframe')).not.toBeNull(); - - }); - - test("it should change the form to submit the selection instead of posting to a preview page", () => { - - // run preview pane script - require('../../app/assets/javascripts/previewPane.js'); - - expect(form.getAttribute('action')).toEqual(emailPreviewConfirmationURL); - - }); - - test("the preview pane should show the page for the selected brand", () => { - - // run preview pane script - require('../../app/assets/javascripts/previewPane.js'); - - const selectedValue = Array.from(radios.querySelectorAll('input[type=radio]')).filter(radio => radio.checked)[0].value; - - expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_email?branding_style=${selectedValue}`); - - }); - - test("the submit button should change from 'Preview' to 'Save'", () => { - - // run preview pane script - require('../../app/assets/javascripts/previewPane.js'); - - expect(document.querySelector('button[type=submit]').textContent).toEqual('Save'); - - }); - - }); - - describe("If the selection changes", () => { - - test("the page shown should match the selected brand", () => { - - // run preview pane script - require('../../app/assets/javascripts/previewPane.js'); - - const newSelection = radios.querySelectorAll('input[type=radio]')[1]; - - helpers.moveSelectionToRadio(newSelection); - - expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_email?branding_style=${newSelection.value}`); - - }); - - }); - - }); - - describe("If the page type is 'letter'", () => { - - beforeEach(() => { - - // set page URL and page type to 'letter' - window.location.pathname = letterPreviewConfirmationURL; - form.setAttribute('data-preview-type', 'letter'); - - }); - - describe("When the page loads", () => { - - test("it should add the preview pane", () => { - - // run preview pane script - require('../../app/assets/javascripts/previewPane.js'); - - expect(document.querySelector('iframe')).not.toBeNull(); - - }); - - test("it should change the form to submit the selection instead of posting to a preview page", () => { - - // run preview pane script - require('../../app/assets/javascripts/previewPane.js'); - - expect(form.getAttribute('action')).toEqual(letterPreviewConfirmationURL); - - }); - - test("the preview pane should show the page for the selected brand", () => { - - // run preview pane script - require('../../app/assets/javascripts/previewPane.js'); - - const selectedValue = Array.from(radios.querySelectorAll('input[type=radio]')).filter(radio => radio.checked)[0].value; - - expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_letter?branding_style=${selectedValue}`); - - }); - - test("the submit button should change from 'Preview' to 'Save'", () => { - - // run preview pane script - require('../../app/assets/javascripts/previewPane.js'); - - expect(document.querySelector('button[type=submit]').textContent).toEqual('Save'); - - }); - - }); - - describe("If the selection changes", () => { - - test("the page shown should match the selected brand", () => { - - // run preview pane script - require('../../app/assets/javascripts/previewPane.js'); - - const newSelection = radios.querySelectorAll('input[type=radio]')[1]; - - helpers.moveSelectionToRadio(newSelection); - - expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_letter?branding_style=${newSelection.value}`); - - }); - - }); - - }); - -}); diff --git a/tests/javascripts/support/helpers.js b/tests/javascripts/support/helpers.js index b0f6d635b..6f3197e83 100644 --- a/tests/javascripts/support/helpers.js +++ b/tests/javascripts/support/helpers.js @@ -1,7 +1,6 @@ const globals = require('./helpers/globals.js'); const events = require('./helpers/events.js'); const domInterfaces = require('./helpers/dom_interfaces.js'); -const cookies = require('./helpers/cookies.js'); const html = require('./helpers/html.js'); const elements = require('./helpers/elements.js'); const rendering = require('./helpers/rendering.js'); @@ -15,8 +14,6 @@ exports.moveSelectionToRadio = events.moveSelectionToRadio; exports.activateRadioWithSpace = events.activateRadioWithSpace; exports.RangeMock = domInterfaces.RangeMock; exports.SelectionMock = domInterfaces.SelectionMock; -exports.deleteCookie = cookies.deleteCookie; -exports.setCookie = cookies.setCookie; exports.getRadioGroup = html.getRadioGroup; exports.getRadios = html.getRadios; exports.templatesAndFoldersCheckboxes = html.templatesAndFoldersCheckboxes; diff --git a/tests/javascripts/support/helpers/cookies.js b/tests/javascripts/support/helpers/cookies.js deleted file mode 100644 index d4824c9fe..000000000 --- a/tests/javascripts/support/helpers/cookies.js +++ /dev/null @@ -1,25 +0,0 @@ -// Helper for deleting a cookie -function deleteCookie (cookieName, options) { - if (typeof options === 'undefined') { - options = {}; - } - if (!options.domain) { options.domain = window.location.hostname; } - document.cookie = cookieName + '=; path=/; domain=' + options.domain + '; expires=' + (new Date()); -}; - -function setCookie (name, value, options) { - if (typeof options === 'undefined') { - options = {}; - } - if (!options.domain) { options.domain = window.location.hostname; } - var cookieString = name + '=' + value + '; path=/; domain=' + options.domain; - if (options.days) { - var date = new Date(); - date.setTime(date.getTime() + (options.days * 24 * 60 * 60 * 1000)); - cookieString = cookieString + '; expires=' + date.toGMTString(); - } - document.cookie = cookieString; -}; - -exports.deleteCookie = deleteCookie; -exports.setCookie = setCookie;