Skip to content

Commit 07ad96e

Browse files
authored
Fixed Analytics > Newsletters tab crashing on hover (#24575)
ref https://linear.app/ghost/issue/ENG-2493/analytics-newsletter-visiting-newsletters-tab-will-show-a-loading The Analytics > Newsletters tab was crashing when hovering over the charts for Avg Open Rate and Click Rate. The tooltip component was passing a JS date object to the `formatDisplayDate()` function from shade. For some reason Typescript wasn't catching this (TBD why), but for now this fixes the crash by passing a string to the `formatDisplayDate()` in this instance, and also adding some defensive programming to the `formatDisplayDate()` function itself in case this happens elsewhere. Testing: - [x] Visit Analytics > Newsletters, select the Avg Open Rate graph, then hover over the graph. The app should not crash, and the "Sent on" date in the tooltip should render properly - [x] Same thing for the Avg Click rate graph
1 parent 77433cc commit 07ad96e

File tree

3 files changed

+39
-2
lines changed

3 files changed

+39
-2
lines changed

apps/shade/src/lib/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ export const formatQueryDate = (date: Moment) => {
160160

161161
// Format date for UI, result is in the formate of `12 Jun 2025`
162162
export const formatDisplayDate = (dateString: string): string => {
163+
// If the date is a Date object, convert it to a string
164+
// @ts-expect-error This should error if dateString is not a string, but for some reason Typescript isn't catching this
165+
if (dateString instanceof Date) {
166+
dateString = dateString.toISOString();
167+
}
168+
// Fallback to empty string if dateString is an unexpected type. Better to fallback to empty string than to crash the app
169+
if (!dateString || dateString.length === 0 || typeof dateString !== 'string') {
170+
return '';
171+
}
172+
163173
// Check if this is a datetime string (contains time) or just a date
164174
const hasTime = dateString.includes(':');
165175

apps/shade/test/unit/utils/utils.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,34 @@ describe('utils', function () {
124124
});
125125

126126
describe('formatDisplayDate function', function () {
127+
it('returns an empty string if the date string is an empty string', function () {
128+
const formatted = formatDisplayDate('');
129+
assert.equal(formatted, '');
130+
});
131+
132+
it('returns an empty string if the date string is an invalid type', function () {
133+
// @ts-expect-error This should error if dateString is not a string, but for some reason Typescript isn't catching this
134+
const formatted = formatDisplayDate(123);
135+
assert.equal(formatted, '');
136+
});
137+
138+
it('does not throw an error if the date string is a Date object', function () {
139+
const date = new Date('2023-04-15');
140+
// @ts-expect-error This should error if dateString is not a string, but for some reason Typescript isn't catching this
141+
const formatted = formatDisplayDate(date);
142+
assert.equal(formatted, '15 Apr 2023');
143+
});
144+
145+
it('handles a date string with time but without a timezone', function () {
146+
const formatted = formatDisplayDate('2023-04-15 12:00:00');
147+
assert.equal(formatted, '15 Apr 2023');
148+
});
149+
150+
it('handles an ISO8601 date string', function () {
151+
const formatted = formatDisplayDate('2023-04-15T12:00:00Z');
152+
assert.equal(formatted, '15 Apr 2023');
153+
});
154+
127155
it('formats a date string to display format', function () {
128156
// Using a predefined date for testing, bypassing the current date check
129157
// Test different year formatting without mocking Date

apps/stats/src/views/Stats/Newsletters/components/NewslettersKPIs.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ const BarTooltipContent = ({active, payload}: BarTooltipProps) => {
2222
}
2323

2424
const currentItem = payload[0].payload;
25-
const sendDate = typeof currentItem.send_date === 'string' ?
26-
new Date(currentItem.send_date) : currentItem.send_date;
25+
const sendDate = currentItem.send_date;
2726

2827
return (
2928
<div className="min-w-[220px] max-w-[240px] rounded-lg border bg-background px-3 py-2 shadow-lg">

0 commit comments

Comments
 (0)