Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(redesign): introduce new_base.html with waffle flag #4940

Merged
merged 6 commits into from
Jan 26, 2025

Conversation

elisa-a-v
Copy link
Contributor

@elisa-a-v elisa-a-v commented Jan 18, 2025

Closes #4815

This PR adds a middleware that checks a waffle flag (use_new_design) and changes the old template with the new one when a new template exists.

It also includes a copy of base.html without bootstrap, and a new template for the help index that extends from it, both of which need to be updated when tailwind support is ready; things do NOT look good!

To identify the new template we prepend "v2_" to the old template name, which means if the old template_name includes a dir, like "help/index.html", the new template should be in "v2_help/index.html" and NOT in "help/v2_index.html".

image

image

The middleware checks a waffle flag to gradually roll out the
new design when a requested view has a new template available.
- This file will use tailwind instead of bootstrap once
tailwind support has been implemented.
- Templates with new design should extend from new_base.html
instead of the old one.
New template extends from new_base.html and will be
updated with the new design in #4740. It's added now
as an MVP for #4815.
@elisa-a-v elisa-a-v requested a review from mlissner January 18, 2025 01:41
@elisa-a-v elisa-a-v assigned mlissner and unassigned mlissner Jan 18, 2025
{# Default values are to ensure JS parsing even if 500 error thrown #}
var isMember = {{ user.profile.is_member|yesno:"true,false" }},
userAlertCount = {{ user.docket_alerts.subscriptions.count|default:"0" }},
priceRtAlerts = parseFloat({{ MIN_DONATION.rt_alerts|default:0 }}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:

Detected a template variable used in a script tag. Although template variables are HTML escaped, HTML escaping does not always prevent cross-site scripting (XSS) attacks when used directly in JavaScript. If you need this data on the rendered page, consider placing it in the HTML portion (outside of a script tag). Alternatively, use a JavaScript-specific encoder, such as the one available in OWASP ESAPI. For Django, you may also consider using the 'json_script' template tag and retrieving the data in your script by using the element ID (e.g., document.getElementById).

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 165 like so // nosemgrep: generic.html-templates.security.var-in-script-tag.var-in-script-tag

Take care to validate that this is not a true positive finding before ignoring it.
Learn more about ignoring code, files and folders here.

You can view more details about this finding in the Semgrep AppSec Platform.

<script type="text/javascript" nonce="{{ request.csp_nonce }}">
{# Default values are to ensure JS parsing even if 500 error thrown #}
var isMember = {{ user.profile.is_member|yesno:"true,false" }},
userAlertCount = {{ user.docket_alerts.subscriptions.count|default:"0" }},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:

Detected a template variable used in a script tag. Although template variables are HTML escaped, HTML escaping does not always prevent cross-site scripting (XSS) attacks when used directly in JavaScript. If you need this data on the rendered page, consider placing it in the HTML portion (outside of a script tag). Alternatively, use a JavaScript-specific encoder, such as the one available in OWASP ESAPI. For Django, you may also consider using the 'json_script' template tag and retrieving the data in your script by using the element ID (e.g., document.getElementById).

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 164 like so // nosemgrep: generic.html-templates.security.var-in-script-tag.var-in-script-tag

Take care to validate that this is not a true positive finding before ignoring it.
Learn more about ignoring code, files and folders here.

You can view more details about this finding in the Semgrep AppSec Platform.

<script type="text/javascript" src="{% static "js/base.js" %}"></script>
<script type="text/javascript" nonce="{{ request.csp_nonce }}">
{# Default values are to ensure JS parsing even if 500 error thrown #}
var isMember = {{ user.profile.is_member|yesno:"true,false" }},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:

Detected a template variable used in a script tag. Although template variables are HTML escaped, HTML escaping does not always prevent cross-site scripting (XSS) attacks when used directly in JavaScript. If you need this data on the rendered page, consider placing it in the HTML portion (outside of a script tag). Alternatively, use a JavaScript-specific encoder, such as the one available in OWASP ESAPI. For Django, you may also consider using the 'json_script' template tag and retrieving the data in your script by using the element ID (e.g., document.getElementById).

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 163 like so // nosemgrep: generic.html-templates.security.var-in-script-tag.var-in-script-tag

Take care to validate that this is not a true positive finding before ignoring it.
Learn more about ignoring code, files and folders here.

You can view more details about this finding in the Semgrep AppSec Platform.

var isMember = {{ user.profile.is_member|yesno:"true,false" }},
userAlertCount = {{ user.docket_alerts.subscriptions.count|default:"0" }},
priceRtAlerts = parseFloat({{ MIN_DONATION.rt_alerts|default:0 }}),
maxFreeDocketAlerts = {{ MAX_FREE_DOCKET_ALERTS|default:0 }},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:

Detected a template variable used in a script tag. Although template variables are HTML escaped, HTML escaping does not always prevent cross-site scripting (XSS) attacks when used directly in JavaScript. If you need this data on the rendered page, consider placing it in the HTML portion (outside of a script tag). Alternatively, use a JavaScript-specific encoder, such as the one available in OWASP ESAPI. For Django, you may also consider using the 'json_script' template tag and retrieving the data in your script by using the element ID (e.g., document.getElementById).

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 166 like so // nosemgrep: generic.html-templates.security.var-in-script-tag.var-in-script-tag

Take care to validate that this is not a true positive finding before ignoring it.
Learn more about ignoring code, files and folders here.

You can view more details about this finding in the Semgrep AppSec Platform.

userAlertCount = {{ user.docket_alerts.subscriptions.count|default:"0" }},
priceRtAlerts = parseFloat({{ MIN_DONATION.rt_alerts|default:0 }}),
maxFreeDocketAlerts = {{ MAX_FREE_DOCKET_ALERTS|default:0 }},
recapBonusAlerts = {{ DOCKET_ALERT_RECAP_BONUS|default:0 }};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:

Detected a template variable used in a script tag. Although template variables are HTML escaped, HTML escaping does not always prevent cross-site scripting (XSS) attacks when used directly in JavaScript. If you need this data on the rendered page, consider placing it in the HTML portion (outside of a script tag). Alternatively, use a JavaScript-specific encoder, such as the one available in OWASP ESAPI. For Django, you may also consider using the 'json_script' template tag and retrieving the data in your script by using the element ID (e.g., document.getElementById).

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 167 like so // nosemgrep: generic.html-templates.security.var-in-script-tag.var-in-script-tag

Take care to validate that this is not a true positive finding before ignoring it.
Learn more about ignoring code, files and folders here.

You can view more details about this finding in the Semgrep AppSec Platform.

Copy link
Member

@mlissner mlissner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty good! We will need a way of handling tests though, so that they pass. Maybe an easy way to handle this is to make sure that the flag for the new design is off by default, so that the tests continue running on the old design instead of the new. Then, when we're closer to being done, we can flip the default and make sure they work on the new design.

I think the rest looks good. This will be fun and weird.

@mlissner
Copy link
Member

Let's get tests passing before merging, but @ERosendo, it might be good to have your review now anyway, just to see if you think the design is a good direction.

I like it, I think, but this is a weird project with a lot of ways to do things, so more perspectives is probably smart.

@elisa-a-v elisa-a-v marked this pull request as ready for review January 20, 2025 21:13
@elisa-a-v
Copy link
Contributor Author

Maybe an easy way to handle this is to make sure that the flag for the new design is off by default, so that the tests continue running on the old design instead of the new. Then, when we're closer to being done, we can flip the default and make sure they work on the new design.

The tests were indeed creating a flag that is on by default, as per waffle's config (WAFFLE_CREATE_MISSING_FLAGS = True and WAFFLE_FLAG_DEFAULT = env("WAFFLE_FLAG_DEFAULT", default=True)), but that shouldn't have been an issue for the missing templates as Django templates handles that (we're defaulting to the old template if the new one doesn't exist, even when the flag is on).

The issue with the tests was that we already had some templates that started with new_ (new_visualization.html) so the tests were using the wrong templates. I changed the prefix to v2_ instead and also added a check to see if the flag exists before asking waffle if it's active, this way we don't create a new one automatically and default to False.

@elisa-a-v
Copy link
Contributor Author

Oh no I broke the tests again 😮‍💨 I'll see what's going on

@elisa-a-v
Copy link
Contributor Author

The broken tests are those that are counting the queries executed (ApiQueryCountTests), and since we're manually checking the DB for the flag, we now have an additional query every time. I could either patch the test and leave a TODO to clean up after we've completely rolled out the new design, or remove the manual check and just live with the automatic flag creation, what do you think is best here @mlissner ?

@elisa-a-v elisa-a-v requested a review from ERosendo January 22, 2025 15:08
@ERosendo
Copy link
Contributor

@elisa-a-v I really like the middleware you created! It seems like your approach addresses @mlissner's concerns about testing. However, I have a slight concern regarding the _flag_is_active method. the current implementation introduces an additional database query on each page load, which may have a slight impact on overall system performance

While looking through the Django-waffle documentation, I came across some decorators that might make testing easier. There's one called override_flag that I think could be useful. We could potentially use this to decorate our testing classes from the cases.py file (I believe we typically use these classes instead of Django's built-in testing classes directly).

I think exploring this alternative approach could be beneficial because it might also addresses Mike's concerns about the flag during testing, and it would save us an extra database query when loading pages. What do you think?

@ERosendo ERosendo assigned elisa-a-v and unassigned ERosendo Jan 23, 2025
@elisa-a-v
Copy link
Contributor Author

elisa-a-v commented Jan 23, 2025

Thanks @ERosendo !

You raise an important concern with the overhead introduced by the additional DB query, and now I see it makes no sense to keep that there. In practice, the only issue that the check is preventing is the automatic flag creation with a default True value. We shouldn't have an additional DB query in each and every request just for that!!

Let's revert the last two commits and make sure we create the flag before merging. I see I have enough permissions to do it, shall I create it right away just for superusers and staff? @mlissner

@elisa-a-v elisa-a-v assigned mlissner and unassigned elisa-a-v Jan 23, 2025
@mlissner
Copy link
Member

Checking the DB for a query like this is almost instant (the table is tiny and the OS will cache it), so I had accepted that it was going to be something we did. That aside though, I don't see how decorating the tests gets us out of doing the query. Whether or not the flag is true or false, we still have to check it, don't we?

Anyway, I guess my stance is I think whatever we do here is fine to get the tests passing and the waffle in place. If we can avoid the query, great, but I don't see how except to use a setting, and that seems annoying to deal with.

@elisa-a-v
Copy link
Contributor Author

I don't see how decorating the tests gets us out of doing the query.

I don't think it does.

if we can avoid the query, great, but I don't see how except to use a setting

Well, the query is only useful while the flag doesn't exist, because we want to prevent waffle from creating it with a default True value. After that, we don't really need it anymore, and the built-in waffle flag_is_active method is enough.

Note the tests were initially failing not because of a flag that was on, but because of the prefix used that clashed with the preexisting new_visualization.html template that was used as the new version of visualization.html—this caused the tests to fail because these are different templates used in different places, the former is not the replacement for the latter. Changing the prefix from new_ to v2_ fixed this issue (see this commit for which all checks were successful).

The "cleanest" solution code-wise, imo, is reverting the last two commits (the ones that add the DB query and the tests patch), that way we use the plain flag_is_active and the tests are fine. Only thing we need to be careful about is creating the flag before merging.

@mlissner
Copy link
Member

Sounds good to me. Thanks for explaining again.

@elisa-a-v
Copy link
Contributor Author

Cool. Do we have a preferred way to undo commits? I kinda like git reset better than git revert but I don't want to push --force without checking first.

@mlissner
Copy link
Member

Some around here have strong opinions. I'm happy with things that get it done. I'd probably reset and force, personally. :)

@mlissner mlissner assigned elisa-a-v and unassigned mlissner Jan 24, 2025
@elisa-a-v elisa-a-v requested a review from mlissner January 24, 2025 16:39
@elisa-a-v elisa-a-v assigned mlissner and unassigned elisa-a-v Jan 24, 2025
@mlissner mlissner merged commit cdbf894 into main Jan 26, 2025
15 checks passed
@mlissner mlissner deleted the 4815-new-base.html branch January 26, 2025 08:09
@mlissner
Copy link
Member

I'm going for it! Let's see how this goes and if needed, I can revert. Looks like the waffle is in place, so I think we should be good to go.

@mlissner
Copy link
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Create new base.html file and replace base.html for new design
3 participants