Skip to content

Commit 167efec

Browse files
authored
Add support for ternaries in marker labels (#5857)
Add support for ternaries in marker labels, eg `"{marker.data.canceled ? '❌' : ''} {marker.data.delay}"`. What to know: 1. The conditional clause can refer to payload fields only, ie `marker.data.*`. 2. The condition is checked for truthiness only, no other conditional operator can be used. Truthiness = classical JS truthiness, ie not null/undefined/empty. 3. The true/false branches should be single-quoted string literals, `"marker.data.* ? 'yes' : 'no'"`.
1 parent b79d3b0 commit 167efec

File tree

3 files changed

+108
-6
lines changed

3 files changed

+108
-6
lines changed

src/profile-logic/marker-schema.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ export function getSchemaFromMarker(
9898
return schemaName ? (markerSchemaByName[schemaName] ?? null) : null;
9999
}
100100

101+
// Matches ternary expressions inside marker labels, ie {marker.data.field ? 'truthy' : 'falsy'}
102+
const TERNARY_RE = /^\s*([\w.]+)\s*\?\s*'([^']*)'\s*:\s*'([^']*)'\s*$/;
103+
101104
/**
102105
* Marker schema can create a dynamic tooltip label. For instance a schema with
103106
* a `tooltipLabel` field of "Event at {marker.data.url}" would create a label based
@@ -142,8 +145,9 @@ export function parseLabel(
142145
console.error(oneLine`
143146
Error processing the label "${label}" because of the ${part}.
144147
Currently the labels in the marker schema take the form of
145-
"marker.data.keyName" or "marker.startTime". No other type
146-
of access is currently supported.
148+
"marker.data.keyName", "marker.startTime", or a ternary like
149+
"marker.data.keyName ? 'yes' : 'no'". No other type of access
150+
is currently supported.
147151
`);
148152
return () => '';
149153
}
@@ -161,6 +165,29 @@ export function parseLabel(
161165
// Given: "Marker information: {marker.name} – {marker.data.info}"
162166
// Handle: ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
163167

168+
// Check for a ternary expression:
169+
// Given: "{marker.data.canceled ? '❌' : ''} {marker.data.delay}"
170+
// Handle: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
171+
//
172+
// Only marker.data.* fields are supported as conditions. The true/false
173+
// branches must be single-quoted string literals.
174+
const ternaryMatch = part.match(TERNARY_RE);
175+
if (ternaryMatch) {
176+
const [, condRef, truthyStr, falsyStr] = ternaryMatch;
177+
const condKeys = condRef.split('.');
178+
if (
179+
condKeys.length === 3 &&
180+
condKeys[0] === 'marker' &&
181+
condKeys[1] === 'data' &&
182+
condKeys[2]
183+
) {
184+
const condPayloadKey = condKeys[2];
185+
return (marker) =>
186+
(marker.data as any)?.[condPayloadKey] ? truthyStr : falsyStr;
187+
}
188+
return parseError(label, part);
189+
}
190+
164191
const keys = part.trim().split('.');
165192

166193
if (keys.length !== 2 && keys.length !== 3) {

src/test/unit/__snapshots__/marker-schema.test.ts.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,31 +259,31 @@ Array [
259259
exports[`marker schema labels parseErrors errors if looking up into a part of the marker that does not exist 1`] = `
260260
Array [
261261
Array [
262-
"Error processing the label \\"Parse error: \\"{marker.nothing}\\"\\" because of the marker.nothing. Currently the labels in the marker schema take the form of \\"marker.data.keyName\\" or \\"marker.startTime\\". No other type of access is currently supported.",
262+
"Error processing the label \\"Parse error: \\"{marker.nothing}\\"\\" because of the marker.nothing. Currently the labels in the marker schema take the form of \\"marker.data.keyName\\", \\"marker.startTime\\", or a ternary like \\"marker.data.keyName ? 'yes' : 'no'\\". No other type of access is currently supported.",
263263
],
264264
]
265265
`;
266266

267267
exports[`marker schema labels parseErrors errors if not looking up into a marker 1`] = `
268268
Array [
269269
Array [
270-
"Error processing the label \\"Parse error: \\"{duration}\\"\\" because of the duration. Currently the labels in the marker schema take the form of \\"marker.data.keyName\\" or \\"marker.startTime\\". No other type of access is currently supported.",
270+
"Error processing the label \\"Parse error: \\"{duration}\\"\\" because of the duration. Currently the labels in the marker schema take the form of \\"marker.data.keyName\\", \\"marker.startTime\\", or a ternary like \\"marker.data.keyName ? 'yes' : 'no'\\". No other type of access is currently supported.",
271271
],
272272
]
273273
`;
274274

275275
exports[`marker schema labels parseErrors errors when accessing random properties 1`] = `
276276
Array [
277277
Array [
278-
"Error processing the label \\"Parse error: \\"{property.value}\\"\\" because of the property.value. Currently the labels in the marker schema take the form of \\"marker.data.keyName\\" or \\"marker.startTime\\". No other type of access is currently supported.",
278+
"Error processing the label \\"Parse error: \\"{property.value}\\"\\" because of the property.value. Currently the labels in the marker schema take the form of \\"marker.data.keyName\\", \\"marker.startTime\\", or a ternary like \\"marker.data.keyName ? 'yes' : 'no'\\". No other type of access is currently supported.",
279279
],
280280
]
281281
`;
282282

283283
exports[`marker schema labels parseErrors errors when accessing twice into a payload 1`] = `
284284
Array [
285285
Array [
286-
"Error processing the label \\"Parse error: \\"{marker.data.duration.extra}\\"\\" because of the marker.data.duration.extra. Currently the labels in the marker schema take the form of \\"marker.data.keyName\\" or \\"marker.startTime\\". No other type of access is currently supported.",
286+
"Error processing the label \\"Parse error: \\"{marker.data.duration.extra}\\"\\" because of the marker.data.duration.extra. Currently the labels in the marker schema take the form of \\"marker.data.keyName\\", \\"marker.startTime\\", or a ternary like \\"marker.data.keyName ? 'yes' : 'no'\\". No other type of access is currently supported.",
287287
],
288288
]
289289
`;

src/test/unit/marker-schema.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,81 @@ describe('marker schema labels', function () {
172172
expect(console.error).toHaveBeenCalledTimes(0);
173173
});
174174

175+
describe('ternary expressions', function () {
176+
it('returns the truthy string when the field is truthy', function () {
177+
expect(
178+
applyLabel({
179+
label: "{marker.data.canceled ? '❌' : ''}",
180+
schemaFields: [
181+
{ key: 'canceled', label: 'Canceled', format: 'string' },
182+
],
183+
payload: { canceled: true },
184+
})
185+
).toEqual('❌');
186+
expect(console.error).toHaveBeenCalledTimes(0);
187+
});
188+
189+
it('returns the falsy string when the field is falsy', function () {
190+
expect(
191+
applyLabel({
192+
label: "{marker.data.canceled ? '❌' : ''}",
193+
schemaFields: [
194+
{ key: 'canceled', label: 'Canceled', format: 'string' },
195+
],
196+
payload: { canceled: false },
197+
})
198+
).toEqual('');
199+
expect(console.error).toHaveBeenCalledTimes(0);
200+
});
201+
202+
it('returns the falsy string when the field is absent from the payload', function () {
203+
expect(
204+
applyLabel({
205+
label: "{marker.data.canceled ? '❌' : ''}",
206+
schemaFields: [],
207+
payload: {},
208+
})
209+
).toEqual('');
210+
expect(console.error).toHaveBeenCalledTimes(0);
211+
});
212+
213+
it('returns the falsy string when there is no payload', function () {
214+
expect(
215+
applyLabel({
216+
label: "{marker.data.canceled ? 'yes' : 'no'}",
217+
schemaFields: [],
218+
payload: null,
219+
})
220+
).toEqual('no');
221+
expect(console.error).toHaveBeenCalledTimes(0);
222+
});
223+
224+
it('can mix a ternary with regular field lookups', function () {
225+
expect(
226+
applyLabel({
227+
label: "{marker.data.canceled ? '❌' : ''} {marker.data.delay}",
228+
schemaFields: [
229+
{ key: 'canceled', label: 'Canceled', format: 'string' },
230+
{ key: 'delay', label: 'Delay', format: 'milliseconds' },
231+
],
232+
payload: { canceled: true, delay: 250 },
233+
})
234+
).toEqual('❌ 250ms');
235+
expect(console.error).toHaveBeenCalledTimes(0);
236+
});
237+
238+
it('errors when the condition is not a marker.data.* reference', function () {
239+
expect(
240+
applyLabel({
241+
label: "{marker.name ? 'yes' : 'no'}",
242+
schemaFields: [],
243+
payload: {},
244+
})
245+
).toEqual('');
246+
expect(console.error).toHaveBeenCalledTimes(1);
247+
});
248+
});
249+
175250
describe('parseErrors', function () {
176251
function testParseError(label: string) {
177252
expect(

0 commit comments

Comments
 (0)