Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
## Version 25.03.xx
Fixes:
- [events] Fix sum chart tooltip displaying the raw floating-point value instead of a rounded number
- [events] Fixed event descriptions (and custom names / count-sum-dur labels) not showing on the Events page for events whose key contains special characters (`.`, `$`, `\`, `&`, `<`, `>`, `"`, `'`)

Enterprise Fixes:
- [data-manager] Fixed editing an event whose key contains `&` creating undeletable duplicate rows in the events table

## Version 25.03.50
Fixes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/<=/g, "&le;").replace(/>=/g, "&ge;");
},
getEventLongName: function(eventKey, eventMap) {
var mapKey = eventKey.replace(/\\/g, "\\\\").replace(/\$/g, "\\u0024").replace(/\./g, "\\u002e");
//events.map is keyed by the raw event key, exactly as it appears in events.list, so do not escape it here
var mapKey = eventKey;
if (eventMap && eventMap[mapKey] && eventMap[mapKey].name) {
return eventMap[mapKey].name;
}
Expand All @@ -244,7 +245,8 @@
var allEvents = context.state.allEventsData;
var groupData = context.state.groupData.displayMap;
var eventMap = allEvents.map;
var mapKey = context.state.selectedEventName.replace(/\\/g, "\\\\").replace(/\$/g, "\\u0024").replace(/\./g, '\\u002e');
//events.map is keyed by the raw event key, exactly as it appears in events.list, so do not escape it here
var mapKey = context.state.selectedEventName;
var countString = (mapKey.startsWith('[CLY]_group') && groupData.c) ? groupData.c : (eventMap && eventMap[mapKey] && eventMap[mapKey].count) ? eventMap[mapKey].count : jQuery.i18n.map["events.table.count"];
var sumString = (mapKey.startsWith('[CLY]_group') && groupData.s) ? groupData.s : (eventMap && eventMap[mapKey] && eventMap[mapKey].sum) ? eventMap[mapKey].sum : jQuery.i18n.map["events.table.sum"];
var durString = (mapKey.startsWith('[CLY]_group') && groupData.d) ? groupData.d : (eventMap && eventMap[mapKey] && eventMap[mapKey].dur) ? eventMap[mapKey].dur : jQuery.i18n.map["events.table.dur"];
Expand Down Expand Up @@ -893,9 +895,7 @@
return countlyAllEvents.service.fetchAllEventsData(context, period)
.then(function(res) {
if (res) {
if (Array.isArray(res.list)) {
res.list = res.list.map(eventName => countlyCommon.unescapeHtml(eventName));
}
// setAllEventsData decodes res.list and res.map keys in place
context.commit("setAllEventsData", res);
var is_group = false;
if (context.state.selectedEventName && context.state.selectedEventName.startsWith('[CLY]_group')) {
Expand Down Expand Up @@ -1106,6 +1106,25 @@

var allEventsMutations = {
setAllEventsData: function(state, value) {
// The API HTML-escapes every key and value on the way out (common.returnOutput),
// but the rest of this store works with raw event keys - selectedEventName is
// taken from `list`. Normalise `list` and the `map` keys together here, the one
// point both fetch paths commit through, so lookups like map[selectedEventName]
// don't silently miss for keys containing & < > " '. Keys without those chars
// (dots, dashes) were unaffected, which is why only some events lost their
// description on the Events page.
if (value && Array.isArray(value.list)) {
value.list = value.list.map(function(eventName) {
return countlyCommon.unescapeHtml(eventName);
});
}
if (value && value.map && typeof value.map === "object") {
var decodedMap = {};
Comment thread
LaNinja06 marked this conversation as resolved.
Outdated
Object.keys(value.map).forEach(function(eventKey) {
decodedMap[countlyCommon.unescapeHtml(eventKey)] = value.map[eventKey];
});
value.map = decodedMap;
}
state.allEventsData = value;
},
setAllEventsList: function(state, value) {
Expand Down
20 changes: 18 additions & 2 deletions plugins/data-manager/frontend/public/javascripts/countly.views.js
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,9 @@
var isVisible = command === 'visible';
var events = [];
rows.forEach(function(row) {
events.push(row.key);
// row.key is still API-escaped; change_visibility writes events.map[key]
// directly, so submitting it raw would create a ghost map entry
events.push(countlyCommon.unescapeHtml(row.key));
});
this.$store.dispatch('countlyDataManager/changeVisibility', { events: events, isVisible: isVisible }).then(function() {
countlyEvent.refreshEvents();
Expand Down Expand Up @@ -1338,8 +1340,22 @@
},
handleEdit: function() {
var event = JSON.parse(JSON.stringify(this.event));
event.segments = this.segments;
event.segments = JSON.parse(JSON.stringify(this.segments));
event.isEditMode = true;
// The store holds values exactly as the API returned them, and common.returnOutput
// HTML-escapes every string on the way out. The events table decodes these before
// opening the same drawer (see the 'dm-open-edit-event-drawer' handler) - do the
// same here, otherwise a key like "a & b" is submitted back as "a &amp; b" and
// forks a second drill_meta doc / events.map entry that shows up as a ghost row.
event.key = countlyCommon.unescapeHtml(event.key);
event.e = countlyCommon.unescapeHtml(event.e);
event.name = countlyCommon.unescapeHtml(event.name);
event.description = countlyCommon.unescapeHtml(event.description);
event.categoryName = countlyCommon.unescapeHtml(event.categoryName);
event.segments = event.segments.map(function(seg) {
seg.name = countlyCommon.unescapeHtml(seg.name);
return seg;
});
this.openDrawer("events", event);
},
handleEditSegment: function(seg) {
Expand Down
Loading