Skip to content

Embed configurable Freshdesk feedback widget - #342

Merged
cooper667 merged 4 commits into
mainfrom
freshdesk-feedback-widget
Jul 15, 2026
Merged

cooper667 merged 4 commits into
mainfrom
freshdesk-feedback-widget

Conversation

@A-Souhei

@A-Souhei A-Souhei commented Jul 11, 2026 •

Copy link
Copy Markdown
Contributor
image

What

Adds the in-page Freshworks/Freshdesk feedback widget (floating "Contact us" launcher → in-page ticket form), matching the embed Avenir already uses on its other Spectrum apps (FamPlan, DemProj). It is config-gated: nothing is rendered unless a widget id is set.

  • helpers.get_freshdesk_widget_id() — reads CKAN_UNAIDS_FRESHDESK_WIDGET_ID (env) → ckanext.unaids.freshdesk_widget_id (config); returns a positive int within JS safe-integer range, else None.
  • helpers.get_freshdesk_app_name() — reads CKAN_UNAIDS_FRESHDESK_APP_NAME (env) → ckanext.unaids.freshdesk_app_name (config). When set, the widget bootstrap calls FreshworksWidget('prefill', 'ticketForm', …) to auto-select the "Select application" custom field (cf_select_model), so tickets raised from ADR are tagged/routed correctly — matching the client's own integration. Optional: leave it unset and the widget still works, just without the prefill.
  • base.html head_extras — when the widget id is set, injects window.fwSettings={'widget_id': <id>} + the Freshworks bootstrap (and the prefill call when an app name is set), then loads widget.freshworks.com/widgets/<id>.js.
  • All widget cosmetics (button colour, text, position, form fields) are configured server-side in Freshdesk for that widget id — not in this code.

⚠️ Deployment requirements — READ BEFORE ENABLING

This embeds a third-party script on every page. Before turning it on in any environment, the deploy/proxy layer must:

  1. Content-Security-Policy — allow the Freshworks origins, or the widget is silently blocked:
    • script-src https://widget.freshworks.com
    • frame-src https://*.freshworks.com
    • connect-src https://*.freshworks.com
  2. Inline bootstrap — the fwSettings bootstrap is an inline <script>. Under a strict CSP it needs a nonce or hash (or 'unsafe-inline').
  3. Privacy / vendor sign-off — third-party JS runs with full page-origin access, and visitor metadata reaches Freshworks. Requires a conscious privacy decision (consent and/or page-scoping if needed).

Until these are in place, leave the widget id unset (default) → widget is not rendered.

Configuration

Widget id (required to show the widget; unset = widget off):

  • env: CKAN_UNAIDS_FRESHDESK_WIDGET_ID=<numeric widget id from Freshdesk>
  • ini: ckanext.unaids.freshdesk_widget_id = <numeric widget id>

Application name (optional; enables the "Select application" prefill):

  • env: CKAN_UNAIDS_FRESHDESK_APP_NAME=AIDS Data Repository (ADR)
  • ini: ckanext.unaids.freshdesk_app_name = AIDS Data Repository (ADR)

Both follow env-first, then config; blank/whitespace is treated as unset.

Security / validation

Both values are operator-controlled (env/ini), never user input.

  • get_freshdesk_widget_id() coerces to a positive integer ≤ 2⁵³−1; any non-numeric/negative/zero/oversized value → None (widget hidden). As a guaranteed int emitted via | tojson, it cannot break out of the inline <script> or the script src.
  • get_freshdesk_app_name() is free text but is emitted via | tojson, which HTML-safe-escapes < > & ' → \uXXXX, so it cannot break out of the inline <script> either.

Reviewed by Claude + Codex; both converged on MERGE (no injection; prefill correctly buffered before the widget script loads; gating correct).

Testing

  • TestGetFreshdeskWidgetId (14 cases): valid ids, whitespace trim, 0/negative/non-numeric/injection → None, the 2⁵³−1 / 2⁵³ safe-integer boundary, blank-env→config fallback, env-over-config precedence, unset→None.
  • TestGetFreshdeskAppName (7 cases): value pass-through + trim, blank/whitespace → None, config fallback, env-over-config precedence, unset→None.
  • TestFreshdeskWidgetTemplate (3 cases): renders base.html and asserts the app name is emitted escaped (no </script> breakout), the prefill appears only when the app name is set, and no widget/fwSettings is emitted when the id is unset.

Verified live against the real ADR Freshworks widget (launcher renders, form opens, "Select application" pre-set to "AIDS Data Repository (ADR)").

@cooper667

Copy link
Copy Markdown
Contributor

@A-Souhei conflicts 😢

A-Souhei added 3 commits July 15, 2026 11:05
Add the in-page Freshworks/Freshdesk feedback widget, matching the embed
Avenir uses on its other Spectrum apps. It is gated on a widget id read
from CKAN_UNAIDS_FRESHDESK_WIDGET_ID (env) or the
ckanext.unaids.freshdesk_widget_id config key; when unset the widget is
not rendered.

get_freshdesk_widget_id() coerces the value to a positive integer within
JavaScript's safe-integer range, so a malformed value can neither inject
into the inline script nor lose precision in the emitted fwSettings. All
widget cosmetics (button colour, text, position, form fields) are
controlled server-side in Freshdesk.
Add get_freshdesk_app_name() (env CKAN_UNAIDS_FRESHDESK_APP_NAME or
config ckanext.unaids.freshdesk_app_name). When set, the widget bootstrap
calls FreshworksWidget('prefill', 'ticketForm', ...) to auto-select the
"Select application" custom field (cf_select_model) so tickets raised from
ADR are tagged/routed correctly, matching the client's own integration.

The prefill call is placed right after the queue stub (before the widget
script loads) so it is buffered and applied on init - the client's own
snippet gates it inside the script's load handler on
"FreshworksWidget is not a function", which is false by then, so their
prefill likely never fires.
Render base.html and assert the widget behaviour the helper unit tests
can't cover: the app name is emitted escaped (no </script> breakout via
tojson), the prefill call only appears when the app name is set, and no
widget/fwSettings is emitted when the widget id is absent.
@A-Souhei
A-Souhei force-pushed the freshdesk-feedback-widget branch from c2ce7fd to 0a68b5f Compare July 15, 2026 08:36
@cooper667

Copy link
Copy Markdown
Contributor

14 tests for an invalid id is very defensive but it's fine 😅

The template regression test renders the app via the `app` fixture; every
other app-loading test class in the suite loads scheming_datasets. Without
it, this test built a Flask URL map lacking the scheming dataset-type
routes, which leaked into test_plugin.py::TestValidatePackage and broke its
url_for('validate-package_resource.download'). Aligning the plugin set
keeps the widget assertions intact and stops the cross-test pollution.
@cooper667
cooper667 merged commit 9d47c21 into main Jul 15, 2026
3 checks passed
@cooper667
cooper667 deleted the freshdesk-feedback-widget branch July 15, 2026 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants