Skip to content

Commit

Permalink
Initial TemplateCategory client (#1884)
Browse files Browse the repository at this point in the history
* Initial TemplateCategory client

* Hook the api client up to the admin front end

- Implemented add and update a category
- Tweaks to the api client
- Added tests for the API client
- Renamed desc_en/desc_fr to description_en/fr for consistency

* Address PR comments

- Fixed desc_en/desc_fr -> description_en/description_fr typos
- Delete the template_categories key when updating a category
- Updated tests to check we are properly deleting Redis cache keys when updating a template

* Delete template_categories cache when creating a new cat

* Map old process_types to new ones for the UI

- Fix tests

* Update desc_col naming

* Fix letter tests

* Fix a number of failing tests

- Added mock to get all template categories
- Added template_category_json()
- Updated template_json() with a template_category param
- Sorted config keys under config.py

* Fix a good chunk of tests

* chore: fix failing tests

* chore: remove duplicate param; remove unused imports

* chore: fix import

---------

Co-authored-by: Andrew Leith <[email protected]>
  • Loading branch information
whabanks and andrewleith authored Jul 9, 2024
1 parent 9bdf63c commit c878f83
Show file tree
Hide file tree
Showing 13 changed files with 386 additions and 190 deletions.
86 changes: 44 additions & 42 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,38 @@


class Config(object):
# for waffles: pull out the routes into a flat list of the form ['/home', '/accueil', '/why-gc-notify', ...]
EXTRA_ROUTES = [item for sublist in map(lambda x: x.values(), GC_ARTICLES_ROUTES.values()) for item in sublist]

ACTIVITY_STATS_LIMIT_DAYS = 7
if os.environ.get("HEROKU_APP_NAME", "") != "":
ADMIN_BASE_URL = "https://" + os.environ.get("HEROKU_APP_NAME", "") + ".herokuapp.com"
else:
ADMIN_BASE_URL = os.environ.get("ADMIN_BASE_URL", "http://localhost:6012")
ADMIN_CLIENT_USER_NAME = "notify-admin"
ADMIN_CLIENT_SECRET = os.environ.get("ADMIN_CLIENT_SECRET")
ANTIVIRUS_API_HOST = os.environ.get("ANTIVIRUS_API_HOST")
ANTIVIRUS_API_KEY = os.environ.get("ANTIVIRUS_API_KEY")
ALLOW_DEBUG_ROUTE = env.bool("ALLOW_DEBUG_ROUTE", False)

# List of allowed service IDs that are allowed to send HTML through their templates.
ALLOW_HTML_SERVICE_IDS: List[str] = [id.strip() for id in os.getenv("ALLOW_HTML_SERVICE_IDS", "").split(",")]

ADMIN_BASE_URL = (
"https://" + os.environ.get("HEROKU_APP_NAME", "") + ".herokuapp.com"
if os.environ.get("HEROKU_APP_NAME", "") != ""
else os.environ.get("ADMIN_BASE_URL", "http://localhost:6012")
)
ADMIN_CLIENT_SECRET = os.environ.get("ADMIN_CLIENT_SECRET")
ADMIN_CLIENT_USER_NAME = "notify-admin"
ANTIVIRUS_API_HOST = os.environ.get("ANTIVIRUS_API_HOST")
ANTIVIRUS_API_KEY = os.environ.get("ANTIVIRUS_API_KEY")
API_HOST_NAME = os.environ.get("API_HOST_NAME")
ASSET_DOMAIN = os.getenv("ASSET_DOMAIN", "assets.notification.canada.ca")
ASSET_PATH = "/static/"
ASSETS_DEBUG = False
AWS_REGION = os.environ.get("AWS_REGION", "us-east-1")

# Bounce Rate parameters
BR_DISPLAY_VOLUME_MINIMUM = 1000

BULK_SEND_AWS_BUCKET = os.getenv("BULK_SEND_AWS_BUCKET")
BULK_SEND_TEST_SERVICE_ID = os.getenv("BULK_SEND_TEST_SERVICE_ID")

CHECK_PROXY_HEADER = False
CONTACT_EMAIL = os.environ.get("CONTACT_EMAIL", "[email protected]")
CRM_GITHUB_PERSONAL_ACCESS_TOKEN = os.getenv("CRM_GITHUB_PERSONAL_ACCESS_TOKEN")
CRM_ORG_LIST_URL = os.getenv("CRM_ORG_LIST_URL")
CSV_MAX_ROWS = env.int("CSV_MAX_ROWS", 50_000)
CSV_MAX_ROWS_BULK_SEND = env.int("CSV_MAX_ROWS_BULK_SEND", 100_000)
CSV_UPLOAD_BUCKET_NAME = os.getenv("CSV_UPLOAD_BUCKET_NAME", "notification-alpha-canada-ca-csv-upload")
CRM_GITHUB_PERSONAL_ACCESS_TOKEN = os.getenv("CRM_GITHUB_PERSONAL_ACCESS_TOKEN")
CRM_ORG_LIST_URL = os.getenv("CRM_ORG_LIST_URL")
DANGEROUS_SALT = os.environ.get("DANGEROUS_SALT")
DEBUG = False
DEBUG_KEY = os.environ.get("DEBUG_KEY", "")
Expand All @@ -74,8 +73,21 @@ class Config(object):
DOCUMENTATION_DOMAIN = os.getenv("DOCUMENTATION_DOMAIN", "documentation.notification.canada.ca")
EMAIL_2FA_EXPIRY_SECONDS = 1_800 # 30 Minutes
EMAIL_EXPIRY_SECONDS = 3600 # 1 hour
FREE_YEARLY_SMS_LIMIT = env.int("FREE_YEARLY_SMS_LIMIT", 25_000)

# for waffles: pull out the routes into a flat list of the form ['/home', '/accueil', '/why-gc-notify', ...]
EXTRA_ROUTES = [item for sublist in map(lambda x: x.values(), GC_ARTICLES_ROUTES.values()) for item in sublist]

# FEATURE FLAGS
FF_NEW_BRANDING = env.bool("FF_NEW_BRANDING", False)
FF_SALESFORCE_CONTACT = env.bool("FF_SALESFORCE_CONTACT", True)
FF_TEMPLATE_CATEGORY = env.bool("FF_TEMPLATE_CATEGORY", False)
FF_TOU = env.bool("FF_TOU", False)

FREE_YEARLY_EMAIL_LIMIT = env.int("FREE_YEARLY_EMAIL_LIMIT", 10_000_000)
FREE_YEARLY_SMS_LIMIT = env.int("FREE_YEARLY_SMS_LIMIT", 25_000)
GC_ARTICLES_API = os.environ.get("GC_ARTICLES_API", "articles.alpha.canada.ca/notification-gc-notify")
GC_ARTICLES_API_AUTH_PASSWORD = os.environ.get("GC_ARTICLES_API_AUTH_PASSWORD")
GC_ARTICLES_API_AUTH_USERNAME = os.environ.get("GC_ARTICLES_API_AUTH_USERNAME")
GOOGLE_ANALYTICS_ID = os.getenv("GOOGLE_ANALYTICS_ID", "UA-102484926-14")
GOOGLE_TAG_MANAGER_ID = os.getenv("GOOGLE_TAG_MANAGER_ID", "GTM-KRKRZQV")
HC_EN_SERVICE_ID = os.getenv("HC_EN_SERVICE_ID")
Expand All @@ -84,40 +96,38 @@ class Config(object):
HTTP_PROTOCOL = "http"
INVITATION_EXPIRY_SECONDS = 3_600 * 24 * 2 # 2 days - also set on api
IP_GEOLOCATE_SERVICE = os.environ.get("IP_GEOLOCATE_SERVICE", "").rstrip("/")
GC_ARTICLES_API = os.environ.get("GC_ARTICLES_API", "articles.alpha.canada.ca/notification-gc-notify")
GC_ARTICLES_API_AUTH_USERNAME = os.environ.get("GC_ARTICLES_API_AUTH_USERNAME")
GC_ARTICLES_API_AUTH_PASSWORD = os.environ.get("GC_ARTICLES_API_AUTH_PASSWORD")

LANGUAGES = ["en", "fr"]
LOGO_UPLOAD_BUCKET_NAME = os.getenv("ASSET_UPLOAD_BUCKET_NAME", "notification-alpha-canada-ca-asset-upload")
MAX_FAILED_LOGIN_COUNT = 10
MOU_BUCKET_NAME = os.getenv("MOU_BUCKET_NAME", "")

NOTIFY_APP_NAME = "admin"
NOTIFY_BAD_FILLER_UUID = "00000000-0000-0000-0000-000000000000"
NOTIFY_ENVIRONMENT = "development"
NOTIFY_LOG_LEVEL = "DEBUG"
NOTIFY_LOG_PATH = os.getenv("NOTIFY_LOG_PATH", "")
NOTIFY_SERVICE_ID = "d6aa2c68-a2d9-4437-ab19-3ae8eb202553"

NOTIFY_TEMPLATE_PREFILL_SERVICE_ID = "93305b36-b0a0-4a34-9ab2-c1b7bb5ca489"
NOTIFY_USER_ID = "6af522d0-2915-4e52-83a3-3690455a5fe6"
PERMANENT_SESSION_LIFETIME = 8 * 60 * 60 # 8 hours

REDIS_URL = os.environ.get("REDIS_URL")
PERMANENT_SESSION_LIFETIME = 8 * 60 * 60 # 8 hours
REDIS_ENABLED = env.bool("REDIS_ENABLED", False)

REDIS_URL = os.environ.get("REDIS_URL")
ROUTE_SECRET_KEY_1 = os.environ.get("ROUTE_SECRET_KEY_1", "")
ROUTE_SECRET_KEY_2 = os.environ.get("ROUTE_SECRET_KEY_2", "")
WAF_SECRET = os.environ.get("WAF_SECRET", "waf-secret")

# Scan files integration
SCANFILES_AUTH_TOKEN = os.environ.get("SCANFILES_AUTH_TOKEN", "")
SCANFILES_URL = os.environ.get("SCANFILES_URL", "")

SECRET_KEY = env.list("SECRET_KEY", [])
SECURITY_EMAIL = os.environ.get("SECURITY_EMAIL", "[email protected]")
SEND_FILE_MAX_AGE_DEFAULT = 365 * 24 * 60 * 60 # 1 year
SENDING_DOMAIN = os.environ.get("SENDING_DOMAIN", "notification.alpha.canada.ca")
SENSITIVE_SERVICES = os.environ.get("SENSITIVE_SERVICES", "")
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_NAME = "notify_admin_session"
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_SECURE = True
SESSION_REFRESH_EACH_REQUEST = True
SESSION_COOKIE_SAMESITE = "Lax"
SENSITIVE_SERVICES = os.environ.get("SENSITIVE_SERVICES", "")
SHOW_STYLEGUIDE = env.bool("SHOW_STYLEGUIDE", True)

# Hosted graphite statsd prefix
Expand All @@ -128,24 +138,16 @@ class Config(object):

TEMPLATE_PREVIEW_API_HOST = os.environ.get("TEMPLATE_PREVIEW_API_HOST", "http://localhost:6013")
TEMPLATE_PREVIEW_API_KEY = os.environ.get("TEMPLATE_PREVIEW_API_KEY", "my-secret-key")

WAF_SECRET = os.environ.get("WAF_SECRET", "waf-secret")
WTF_CSRF_ENABLED = True
WTF_CSRF_TIME_LIMIT = None

ZENDESK_API_KEY = os.environ.get("ZENDESK_API_KEY")

# Bounce Rate parameters
BR_DISPLAY_VOLUME_MINIMUM = 1000

# Scan files integration
SCANFILES_URL = os.environ.get("SCANFILES_URL", "")
SCANFILES_AUTH_TOKEN = os.environ.get("SCANFILES_AUTH_TOKEN", "")
# Various IDs
BULK_SEND_TEST_SERVICE_ID = os.getenv("BULK_SEND_TEST_SERVICE_ID")

# FEATURE FLAGS
FF_SALESFORCE_CONTACT = env.bool("FF_SALESFORCE_CONTACT", True)
FF_NEW_BRANDING = env.bool("FF_NEW_BRANDING", False)
FF_TEMPLATE_CATEGORY = env.bool("FF_TEMPLATE_CATEGORY", False)
FF_TOU = env.bool("FF_TOU", False)
NOTIFY_USER_ID = "6af522d0-2915-4e52-83a3-3690455a5fe6"
NOTIFY_SERVICE_ID = "d6aa2c68-a2d9-4437-ab19-3ae8eb202553"
NO_BRANDING_ID = os.environ.get("NO_BRANDING_ID", "0af93cf1-2c49-485f-878f-f3e662e651ef")

@classmethod
Expand Down
20 changes: 10 additions & 10 deletions app/main/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1911,24 +1911,24 @@ class BrandingRequestForm(StripWhitespaceForm):
class TemplateCategoryForm(StripWhitespaceForm):
name_en = StringField("Name EN", validators=[DataRequired(message=_l("This cannot be empty"))])
name_fr = StringField("Name FR", validators=[DataRequired(message=_l("This cannot be empty"))])
desc_en = StringField("Desc EN", validators=[DataRequired(message=_l("This cannot be empty"))])
desc_fr = StringField("Desc FR", validators=[DataRequired(message=_l("This cannot be empty"))])
description_en = StringField("Desc EN", validators=[DataRequired(message=_l("This cannot be empty"))])
description_fr = StringField("Desc FR", validators=[DataRequired(message=_l("This cannot be empty"))])
hidden = RadioField(_l("Hide category"), choices=[("True", _l("Hide")), ("False", _l("Show"))])
email_priority = RadioField(
email_process_type = RadioField(
_l("Email Priority"),
choices=[
("high", _l("High")),
("medium", _l("Medium")),
("low", _l("Low")),
("priority", _l("High")),
("normal", _l("Medium")),
("bulk", _l("Low")),
],
validators=[DataRequired(message=_l("This cannot be empty"))],
)
sms_priority = RadioField(
sms_process_type = RadioField(
_l("Text message priority"),
choices=[
("high", _l("High")),
("medium", _l("Medium")),
("low", _l("Low")),
("priority", _l("High")),
("normal", _l("Medium")),
("bulk", _l("Low")),
],
validators=[DataRequired(message=_l("This cannot be empty"))],
)
64 changes: 54 additions & 10 deletions app/main/views/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@
"letter": LetterTemplateForm,
}

# Todo: Remove this once the process_types in the backend are updated to use low/med/high
category_mapping = {
"bulk": "low",
"normal": "medium",
"priority": "high",
}

form_objects_with_category = {
"email": EmailTemplateFormWithCategory,
"sms": SMSTemplateFormWithCategory,
Expand Down Expand Up @@ -840,15 +847,11 @@ def abort_403_if_not_admin_user():
def _get_categories_and_prepare_form(template, template_type):
categories = template_category_api_client.get_all_template_categories()

# TODO: Remove this, this will come from the DB
if "/edit" in request.path:
template["template_category"] = "1"

form = form_objects_with_category[template_type](**template)

# alphabetize choices
name_col = "name_en" if get_current_locale(current_app) == "en" else "name_fr"
desc_col = "desc_en" if get_current_locale(current_app) == "en" else "desc_fr"
desc_col = "description_en" if get_current_locale(current_app) == "en" else "description_fr"
categories = sorted(categories, key=lambda x: x[name_col])
form.template_category.choices = [(cat["id"], cat[name_col]) for cat in categories]

Expand Down Expand Up @@ -1239,26 +1242,67 @@ def add_recipients(service_id, template_id):
def template_categories():
template_category_list = template_category_api_client.get_all_template_categories()

# Todo: Remove this once the process_types in the backend are updated to use low/med/high
# Maps bulk/normal/priority to low/med/high for display in the front end.
for cat in template_category_list:
if cat["sms_process_type"] in category_mapping:
cat["sms_process_type"] = category_mapping[cat["sms_process_type"]]

if cat["email_process_type"] in category_mapping:
cat["email_process_type"] = category_mapping[cat["email_process_type"]]

return render_template(
"views/templates/template_categories.html", search_form=SearchByNameForm(), template_categories=template_category_list
)


@main.route("/template-categories/add", methods=["GET", "POST"])
def add_template_category():
form = TemplateCategoryForm()

if form.validate_on_submit():
template_category_api_client.create_template_category(
name_en=form.data["name_en"],
name_fr=form.data["name_fr"],
description_en=form.data["description_en"],
description_fr=form.data["description_fr"],
hidden=form.data["hidden"],
email_process_type=form.data["email_process_type"],
sms_process_type=form.data["sms_process_type"],
)

return redirect(url_for(".template_categories"))

return render_template("views/templates/template_category.html", search_form=SearchByNameForm(), form=form)


@main.route("/template-categories/<template_category_id>", methods=["GET", "POST"])
@user_is_platform_admin
def template_category(template_category_id):
template_category = template_category_api_client.get_template_category(template_category_id)
form = TemplateCategoryForm(
name_en=template_category["name_en"],
name_fr=template_category["name_fr"],
desc_en=template_category["desc_en"],
desc_fr=template_category["desc_fr"],
description_en=template_category["description_en"],
description_fr=template_category["description_fr"],
hidden=template_category["hidden"],
email_priority=template_category["email_priority"],
sms_priority=template_category["sms_priority"],
email_process_type=template_category["email_process_type"],
sms_process_type=template_category["sms_process_type"],
)

form.validate_on_submit()
if form.validate_on_submit():
template_category_api_client.update_template_category(
template_category_id,
name_en=form.data["name_en"],
name_fr=form.data["name_fr"],
description_en=form.data["description_en"],
description_fr=form.data["description_fr"],
hidden=form.data["hidden"],
email_process_type=form.data["email_process_type"],
sms_process_type=form.data["sms_process_type"],
)

return redirect(url_for(".template_categories"))

return render_template(
"views/templates/template_category.html", search_form=SearchByNameForm(), template_category=template_category, form=form
Expand Down
Loading

0 comments on commit c878f83

Please sign in to comment.