added feature flag

This commit is contained in:
Beverly Nguyen
2025-04-10 12:35:04 -07:00
parent d8ca53e08b
commit 6dd44fdc2d
9 changed files with 72 additions and 44 deletions

View File

@@ -151,7 +151,7 @@
"filename": "app/config.py", "filename": "app/config.py",
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc", "hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
"is_verified": false, "is_verified": false,
"line_number": 118, "line_number": 121,
"is_secret": false "is_secret": false
} }
], ],
@@ -674,5 +674,5 @@
} }
] ]
}, },
"generated_at": "2025-03-20T18:22:36Z" "generated_at": "2025-04-10T19:35:01Z"
} }

View File

@@ -167,6 +167,7 @@ jobs:
run: make run-flask & run: make run-flask &
env: env:
NOTIFY_ENVIRONMENT: scanning NOTIFY_ENVIRONMENT: scanning
FEATURE_SOCKET_ENABLED: true
- name: Run OWASP Baseline Scan - name: Run OWASP Baseline Scan
uses: zaproxy/action-baseline@v0.14.0 uses: zaproxy/action-baseline@v0.14.0
with: with:

View File

@@ -172,15 +172,15 @@ def _csp(config):
def create_app(application): def create_app(application):
# @application.context_processor @application.context_processor
# def inject_feature_flags(): def inject_feature_flags():
# this is where feature flags can be easily added as a dictionary within context # this is where feature flags can be easily added as a dictionary within context
# feature_about_page_enabled = application.config.get( feature_socket_enabled = application.config.get(
# "FEATURE_ABOUT_PAGE_ENABLED", False "FEATURE_SOCKET_ENABLED", False
# ) )
# return dict( return dict(
# FEATURE_ABOUT_PAGE_ENABLED=feature_about_page_enabled, FEATURE_SOCKET_ENABLED=feature_socket_enabled,
# ) )
@application.context_processor @application.context_processor
def inject_initial_signin_url(): def inject_initial_signin_url():

View File

@@ -10,29 +10,35 @@ document.addEventListener("DOMContentLoaded", function () {
const isJobPage = window.location.pathname.includes("/jobs/"); const isJobPage = window.location.pathname.includes("/jobs/");
if (!isJobPage) return; if (!isJobPage) return;
const jobId = document.querySelector("[data-job-id]")?.dataset?.jobId; const jobEl = document.querySelector("[data-job-id]");
const jobId = jobEl?.dataset?.jobId;
const featureEnabled = jobEl?.dataset?.feature === "true";
if (!jobId) return; if (!jobId) return;
const socket = io("http://localhost:6011");
socket.on("connect", () => { if (featureEnabled) {
console.log("Connected to Socket.IO server"); const socket = io("http://localhost:6011");
socket.emit("join", { room: `job-${jobId}` });
console.log("join", { room: `job-${jobId}` });
});
window.addEventListener("beforeunload", () => { socket.on("connect", () => {
socket.emit("leave", { room: `job-${jobId}` }); console.log("Connected to Socket.IO server");
}); socket.emit("join", { room: `job-${jobId}` });
console.log("join", { room: `job-${jobId}` });
});
const debouncedUpdate = debounce((data) => { window.addEventListener("beforeunload", () => {
updateAllJobSections(); socket.emit("leave", { room: `job-${jobId}` });
}, 1000); });
socket.on("job_updated", (data) => { const debouncedUpdate = debounce((data) => {
if (data.job_id !== jobId) return; updateAllJobSections();
debouncedUpdate(data); }, 1000);
});
socket.on("job_updated", (data) => {
if (data.job_id !== jobId) return;
debouncedUpdate(data);
});
}
function updateAllJobSections() { function updateAllJobSections() {
const resourceEl = document.querySelector('[data-socket-update="status"]'); const resourceEl = document.querySelector('[data-socket-update="status"]');

View File

@@ -89,6 +89,9 @@ class Config(object):
} }
FEATURE_SOCKET_ENABLED = getenv("FEATURE_SOCKET_ENABLED", "false") == "true"
def _s3_credentials_from_env(bucket_prefix): def _s3_credentials_from_env(bucket_prefix):
return { return {
"bucket": getenv( "bucket": getenv(

View File

@@ -25,15 +25,17 @@ from app.utils.user import user_is_logged_in
def check_feature_flags(): def check_feature_flags():
# Placeholder for future feature flag checks # Placeholder for future feature flag checks
# Example: # Example:
# if request.path.startswith("/some-feature") and not current_app.config.get("FEATURE_SOME_FEATURE_ENABLED", False): if request.path.startswith("/jobs") and not current_app.config.get(
# abort(404) "FEATURE_SOCKET_ENABLED", False
):
abort(404)
pass pass
@main.route("/test/feature-flags") @main.route("/test/feature-flags")
def test_feature_flags(): def test_feature_flags():
return jsonify( return jsonify(
{"FEATURE_ABOUT_PAGE_ENABLED": current_app.config["FEATURE_ABOUT_PAGE_ENABLED"]} {"FEATURE_SOCKET_ENABLED": current_app.config["FEATURE_SOCKET_ENABLED"]}
) )

View File

@@ -4,6 +4,7 @@ from functools import partial
from flask import ( from flask import (
Response, Response,
current_app,
abort, abort,
jsonify, jsonify,
redirect, redirect,
@@ -59,6 +60,7 @@ def view_job(service_id, job_id):
filter_args["status"] = set_status_filters(filter_args) filter_args["status"] = set_status_filters(filter_args)
return render_template( return render_template(
"views/jobs/job.html", "views/jobs/job.html",
FEATURE_SOCKET_ENABLED=current_app.config["FEATURE_SOCKET_ENABLED"],
job=job, job=job,
status=request.args.get("status", ""), status=request.args.get("status", ""),
updates_url=url_for( updates_url=url_for(

View File

@@ -3,7 +3,7 @@
{% from "components/page-footer.html" import page_footer %} {% from "components/page-footer.html" import page_footer %}
{% from "components/page-header.html" import page_header %} {% from "components/page-header.html" import page_header %}
{% from "components/components/back-link/macro.njk" import usaBackLink %} {% from "components/components/back-link/macro.njk" import usaBackLink %}
{% from 'components/ajax-block.html' import socket_block %}
{% block service_page_title %} {% block service_page_title %}
{{ "Message status" }} {{ "Message status" }}
{% endblock %} {% endblock %}
@@ -11,20 +11,33 @@
{% block maincolumn_content %} {% block maincolumn_content %}
{{ page_header("Message status") }} {{ page_header("Message status") }}
<div <div data-job-id="{{ job.id }}" data-feature="{{FEATURE_SOCKET_ENABLED}}">
data-socket-update="status" {% if not job.finished_processing %}
data-resource="{{ updates_url }}" <div
data-key="status" data-resource="{{ updates_url }}"
data-job-id="{{ job.id }}" {% if FEATURE_SOCKET_ENABLED %}
> data-socket-update="status"
{% else %}
data-module="update-content"
{% endif %}
data-key="status"
data-form=""
>
{% endif %}
{{ partials['status']|safe }} {{ partials['status']|safe }}
{% if not job.finished_processing %}
</div> </div>
{% endif %}
{% if not finished %} {% if not finished %}
<div <div
data-socket-update="counts"
data-job-id="{{ job.id }}"
data-resource="{{ updates_url }}" data-resource="{{ updates_url }}"
{% if FEATURE_SOCKET_ENABLED %}
data-socket-update="counts"
{% else %}
data-module="update-content"
{% endif %}
data-key="counts" data-key="counts"
data-form=""
> >
{% endif %} {% endif %}
{{ partials['counts']|safe }} {{ partials['counts']|safe }}
@@ -37,9 +50,12 @@
</p> </p>
{% if not job.processing_finished %} {% if not job.processing_finished %}
<div <div
data-socket-update="notifications"
data-job-id="{{ job.id }}"
data-resource="{{ updates_url }}" data-resource="{{ updates_url }}"
{% if FEATURE_SOCKET_ENABLED %}
data-socket-update="notifications"
{% else %}
data-module="update-content"
{% endif %}
data-key="notifications" data-key="notifications"
data-form="" data-form=""
> >
@@ -48,7 +64,6 @@
{% if not job.processing_finished %} {% if not job.processing_finished %}
</div> </div>
{% endif %} {% endif %}
</div>
<div> </div>
{% endblock %} {% endblock %}

View File

@@ -85,7 +85,6 @@ const javascripts = () => {
paths.src + 'javascripts/sidenav.js', paths.src + 'javascripts/sidenav.js',
paths.src + 'javascripts/validation.js', paths.src + 'javascripts/validation.js',
paths.src + 'javascripts/socketio.js', paths.src + 'javascripts/socketio.js',
]) ])
.pipe(plugins.prettyerror()) .pipe(plugins.prettyerror())
.pipe( .pipe(