fix(events): show event descriptions for keys with special characters - #7856
Merged
Conversation
Event descriptions (and custom names / count-sum-dur labels) added via Data Manager did not render on the Events page for events whose key contains a special character (. $ \ & < > " '). The map lookup escaped the key with an ad-hoc unicode scheme (.->., $->$) before looking it up in events.map. But events.map is keyed by the raw event key (the Data Manager write path stores it verbatim), so the escaped lookup key never matched and the description came back blank. Look the key up raw, exactly as it is stored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… and edit paths common.returnOutput HTML-escapes every string key and value on the way out (escape_html(str, true), which escapes & < > " '). Two paths did not account for it, which made both bugs look intermittent: keys containing only dots or dashes were unaffected. On the read path, the events store decoded res.list but not the keys of res.map, so every map[selectedEventName] lookup compared a raw key against an escaped map and missed - descriptions came back blank on the Events page for keys containing & < > " '. Both are now normalised in the setAllEventsData mutation, the single point both fetch paths commit through, which also fixes fetchRefreshAllEventsData never decoding list at all. On the write path, the event detail page's "Edit event" button passed API-escaped values straight into the edit drawer, so "a & b" was saved back as "a & b" and forked a second drill_meta / events.map entry with no key and no segments - an undeletable ghost row in the Data Manager events table. The events table's own edit action already decoded; handleEdit and handleChangeVisibility now do the same. handleEdit also deep-copies segments, which were previously passed into the drawer by reference, so decoding in place would have mutated the Vuex store. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes incorrect handling of event keys containing special characters by normalizing HTML-escaped keys/values consistently between API responses, Vuex stores, and Data Manager write paths—preventing blank event descriptions and “ghost” duplicate rows.
Changes:
- Normalize
get_eventspayloads by decodinglistentries andmapkeys together in the Events Vuex store. - Ensure Data Manager edits/visibility changes submit decoded (raw) event keys/fields back to the API, and avoid mutating store state by deep-copying
segments. - Document the user-visible fix in the changelog.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| plugins/data-manager/frontend/public/javascripts/countly.views.js | Decode API-escaped event fields before editing / visibility updates; deep-copy segments to avoid store mutation. |
| frontend/express/public/core/events/javascripts/countly.details.models.js | Normalize event list + map key decoding in Vuex; adjust event-map lookup behavior for special-character keys. |
| CHANGELOG.md | Add release notes for the Events page description fix and Data Manager ghost-row fix. |
Event keys are arbitrary user-supplied strings. Assigning a "__proto__" key on a plain object reparents the map rather than adding an own property, which would let unrelated key lookups resolve through the assigned value. Object.create(null) removes that side effect. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kanwarujjaval
approved these changes
Jul 30, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
frontend/express/public/core/events/javascripts/countly.details.models.js:1131
setAllEventsDatanow normalizeslistand decodesmapkeys, which makesselectedEventNameraw. However, other key-indexed fields returned byget_events(notablyomitted_segments) still keep API-escaped keys, so lookups likeres.omitted_segments[selectedEventName]can silently miss for event keys containing& < > " '.
Consider decoding omitted_segments keys here as well so all event-key-indexed structures stay consistent.
if (value && value.map && typeof value.map === "object") {
//null prototype: event keys are arbitrary strings, and assigning a
//"__proto__" key on a plain object reparents the map instead of adding
//an own property, which would let unrelated key lookups resolve through
//the assigned value
var decodedMap = Object.create(null);
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Event descriptions set in Data Manager render blank on the Events page for events whose key contains
.$\&<>"'. Plain keys work, which made the issue look intermittent.Editing such an event also had a second symptom: an event whose key contains
&would be saved back asa & b, forking a seconddrill_meta/events.mapentry that shows up as an undeletable duplicate ("ghost") row in the Data Manager events table.Root cause
Two independent escaping bugs that happen to produce the same blank-description symptom:
.$\— the Events page unicode-escaped the event key (.→.,$→$) before looking it up, butevents.mapis keyed raw, exactly as the key appears inevents.list. The lookup missed.&<>"'—common.returnOutput→escape_html(str, true)HTML-escapes every string key and value in every API response. The events store decodedres.listbut not the keys ofres.map, somap[selectedEventName]compared a raw key against an escaped map.escape_htmlonly touches charCodes 34/38/39/60/62, so dots and dashes passed through untouched — hence the "only some events" appearance.The ghost rows come from the same escaping: the event detail page's Edit event button and the bulk visibility action read values straight out of the store (i.e. still API-escaped) and submitted them back as-is.
Changes
frontend/express/public/core/events/javascripts/countly.details.models.jsgetEventLongName/getLabels: look the key up raw instead of unicode-escaping it.setAllEventsData: normalizelistand themapkeys together. This is the one point both fetch paths commit through, so it also fixesfetchRefreshAllEventsDatanever decodinglistat all. The now-redundant decode infetchAllEventsDatais removed.plugins/data-manager/frontend/public/javascripts/countly.views.jshandleEdit: decodekey/e/name/description/categoryNameand segment names before opening the drawer, and deep-copysegments(previously assigned by reference, so drawer edits mutated the store).handleChangeVisibility: decoderow.keybefore submitting.The decode set in
handleEditdeliberately mirrors the file's existing, already-correctdm-open-edit-event-drawerhandler, which is the path the events table uses to open the very same drawer. The bug was that the detail page's button did not do the same thing.Verification
Two harnesses that lift the real shipped functions out of the files and run them against a payload built by modelling
escape_html, so before/after is measured on actual code rather than a re-implementation.Read path (description resolution, 18 keys incl. dots,
$, backslash,&,&&, quotes, apostrophe, angle brackets,%,#, emoji, accents):The 6 baseline failures were exactly the
&<>"'family.Write path (ghost rows), 10 keys:
handleEdithandleChangeVisibilitynode --checkandeslintclean on both changed files.Notes
EventsGroupsTabViewhas the same raw-row.keypattern but is not affected: event group ids are[CLY]_group_+ an md5 hex digest, which cannot contain the escaped characters.events.mapwrite in the enterprise/i/data-manager/event/edithandler was investigated and dropped — the drawer fires core/i/events/edit_mapfirst and that already persists name/description, so the extra branches were unreachable and added a second whole-map read-modify-write with clobber risk. No enterprise-plugins change is needed for this case.