Release Notes Channel: Add support for server body ranges (#9631)
This commit is contained in:
parent
6b2d65c1e7
commit
da7002fc64
6 changed files with 132 additions and 7 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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(),
|
||||
|
|
Loading…
Add table
Reference in a new issue