Release Notes Channel: Add support for server body ranges (#9631)

This commit is contained in:
yash-signal 2025-01-31 14:52:48 -06:00 committed by GitHub
parent 6b2d65c1e7
commit da7002fc64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 132 additions and 7 deletions

View file

@ -18,6 +18,7 @@ import { incrementMessageCounter } from '../util/incrementMessageCounter';
import { SeenStatus } from '../MessageSeenStatus';
import { saveNewMessageBatcher } from '../util/messageBatcher';
import { generateMessageId } from '../util/generateMessageId';
import type { RawBodyRange } from '../types/BodyRange';
import { BodyRange } from '../types/BodyRange';
import * as RemoteConfig from '../RemoteConfig';
import { isBeta, isProduction } from '../util/version';
@ -50,6 +51,13 @@ export type ReleaseNoteType = ReleaseNoteResponseType &
let initComplete = false;
const STYLE_MAPPING: Record<string, BodyRange.Style> = {
bold: BodyRange.Style.BOLD,
italic: BodyRange.Style.ITALIC,
strikethrough: BodyRange.Style.STRIKETHROUGH,
spoiler: BodyRange.Style.SPOILER,
mono: BodyRange.Style.MONOSPACE,
};
export class ReleaseNotesFetcher {
#timeout: NodeJS.Timeout | undefined;
#isRunning = false;
@ -245,10 +253,37 @@ export class ReleaseNotesFetcher {
return;
}
const { title, body } = note;
const messageBody = `${title}\n\n${body}`;
const bodyRanges = [
const { title, body, bodyRanges: noteBodyRanges } = note;
const titleBodySeparator = '\n\n';
const filteredNoteBodyRanges: Array<RawBodyRange> = (
noteBodyRanges ?? []
)
.map(range => {
if (
range.length == null ||
range.start == null ||
range.style == null ||
!STYLE_MAPPING[range.style] ||
range.start + range.length - 1 >= body.length
) {
return null;
}
const relativeStart =
range.start + title.length + titleBodySeparator.length;
return {
start: relativeStart,
length: range.length,
style: STYLE_MAPPING[range.style],
};
})
.filter(isNotNil);
const messageBody = `${title}${titleBodySeparator}${body}`;
const bodyRanges: Array<RawBodyRange> = [
{ start: 0, length: title.length, style: BodyRange.Style.BOLD },
...filteredNoteBodyRanges,
];
const timestamp = Date.now() + index;

View file

@ -4,6 +4,7 @@
import createDebug from 'debug';
import { expect } from 'playwright/test';
import { assert } from 'chai';
import type { App } from '../playwright';
import { Bootstrap } from '../bootstrap';
import { MINUTE } from '../../util/durations';
@ -41,7 +42,7 @@ describe('release notes', function (this: Mocha.Suite) {
await bootstrap.teardown();
});
it('shows release notes with an image', async () => {
it('shows release notes with an image and body ranges', async () => {
const firstWindow = await app.getWindow();
await firstWindow.evaluate('window.SignalCI.resetReleaseNotesFetcher()');
@ -68,5 +69,68 @@ describe('release notes', function (this: Mocha.Suite) {
await expect(
timelineMessage.locator('img.module-image__image')
).toBeVisible();
const boldCallBodyRange = timelineMessage
.locator('span > strong')
.getByText('Call', { exact: true });
assert.isTrue(
await boldCallBodyRange.isVisible(),
'expected message to have bold text'
);
const italicBodyRange = timelineMessage
.locator('span > em')
.getByText('links', { exact: true });
assert.isTrue(
await italicBodyRange.isVisible(),
'expected message to have italicized text'
);
const strikethroughBodyRange = timelineMessage
.locator('span > s')
.getByText('are', { exact: true });
assert.isTrue(
await strikethroughBodyRange.isVisible(),
'expected message to have strikethrough text'
);
const spoilerBodyRange = timelineMessage
.locator('.MessageTextRenderer__formatting--spoiler')
.getByText('the', { exact: true });
assert.isTrue(
(await spoilerBodyRange.count()) > 0,
'expected message to have spoiler text'
);
const monospaceBodyRange = timelineMessage
.locator('span.MessageTextRenderer__formatting--monospace')
.getByText('missing', { exact: true });
assert.isTrue(
await monospaceBodyRange.isVisible(),
'expected message to have monospace text'
);
const secondTimelineMessage = await getTimelineMessageWithText(
secondWindow,
'Bold text has invalid ranges, italic has valid'
);
await expect(secondTimelineMessage).toBeVisible();
const boldCallBodyRanges = secondTimelineMessage.locator('span > strong');
// 1 for the title
assert.isTrue((await boldCallBodyRanges.count()) === 1);
const italicBodyRanges = secondTimelineMessage.locator('span > em');
assert.isTrue(
(await italicBodyRanges.count()) === 1,
'expected message to have italic text'
);
});
});

View file

@ -10,6 +10,12 @@
"desktopMinVersion": "7.30.0",
"androidMinVersion": "1482",
"uuid": "1813845b-cc98-4210-a07c-164f9b328ca8"
},
{
"ctaId": "sample_note",
"desktopMinVersion": "7.30.0",
"androidMinVersion": "1482",
"uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
],
"megaphones": [

View file

@ -1,4 +1,11 @@
{
"bodyRanges": [
{ "style": "bold", "start": 0, "length": 4 },
{ "style": "italic", "start": 5, "length": 5 },
{ "style": "strikethrough", "start": 11, "length": 3 },
{ "style": "spoiler", "start": 15, "length": 3 },
{ "style": "mono", "start": 19, "length": 7 }
],
"mediaHeight": "864",
"mediaWidth": "1536",
"media": "/static/release-notes/call_links.png",

View file

@ -0,0 +1,13 @@
{
"bodyRanges": [
{ "style": "bold", "start": 109, "length": 1 },
{ "style": "bold", "start": 0, "length": 110 },
{ "style": "bold", "start": 10, "length": 0 },
{ "style": "bold", "start": 10, "length": 100 },
{ "style": "italic", "start": 100, "length": 9 }
],
"uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"title": "Formatting Test",
"body": "Sample body text that tests formatting boundaries. Bold text has invalid ranges, italic has valid. length 109",
"callToActionText": "Create Call Link"
}

View file

@ -1231,9 +1231,9 @@ export const releaseNoteSchema = z.object({
bodyRanges: z
.array(
z.object({
style: z.string(),
start: z.number(),
length: z.number(),
style: z.string().optional(),
start: z.number().optional(),
length: z.number().optional(),
})
)
.optional(),