New 'unseenStatus' field for certain secondary message types

This commit is contained in:
Scott Nonnenberg 2022-04-22 11:35:14 -07:00 committed by GitHub
parent ed9f54d7d6
commit 3a1df01c9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 610 additions and 143 deletions

View file

@ -50,8 +50,8 @@ export type MessageMetricsType = {
export type ConversationMetricsType = {
oldest?: MessageMetricsType;
newest?: MessageMetricsType;
oldestUnread?: MessageMetricsType;
totalUnread: number;
oldestUnseen?: MessageMetricsType;
totalUnseen: number;
};
export type ConversationType = ConversationAttributesType;
export type EmojiType = {

View file

@ -110,6 +110,7 @@ import type {
UnprocessedType,
UnprocessedUpdateType,
} from './Interface';
import { SeenStatus } from '../MessageSeenStatus';
type ConversationRow = Readonly<{
json: string;
@ -1737,6 +1738,20 @@ function saveMessageSync(
expireTimer,
expirationStartTimestamp,
} = data;
let { seenStatus } = data;
if (readStatus === ReadStatus.Unread && seenStatus !== SeenStatus.Unseen) {
log.warn(
`saveMessage: Message ${id}/${type} is unread but had seenStatus=${seenStatus}. Forcing to UnseenStatus.Unseen.`
);
// eslint-disable-next-line no-param-reassign
data = {
...data,
seenStatus: SeenStatus.Unseen,
};
seenStatus = SeenStatus.Unseen;
}
const payload = {
id,
@ -1762,6 +1777,7 @@ function saveMessageSync(
storyId: storyId || null,
type: type || null,
readStatus: readStatus ?? null,
seenStatus: seenStatus ?? SeenStatus.NotApplicable,
};
if (id && !forceSave) {
@ -1791,7 +1807,8 @@ function saveMessageSync(
sourceDevice = $sourceDevice,
storyId = $storyId,
type = $type,
readStatus = $readStatus
readStatus = $readStatus,
seenStatus = $seenStatus
WHERE id = $id;
`
).run(payload);
@ -1834,7 +1851,8 @@ function saveMessageSync(
sourceDevice,
storyId,
type,
readStatus
readStatus,
seenStatus
) values (
$id,
$json,
@ -1858,7 +1876,8 @@ function saveMessageSync(
$sourceDevice,
$storyId,
$type,
$readStatus
$readStatus,
$seenStatus
);
`
).run({
@ -2110,16 +2129,21 @@ async function getUnreadByConversationAndMarkRead({
UPDATE messages
SET
readStatus = ${ReadStatus.Read},
seenStatus = ${SeenStatus.Seen},
json = json_patch(json, $jsonPatch)
WHERE
readStatus = ${ReadStatus.Unread} AND
conversationId = $conversationId AND
seenStatus = ${SeenStatus.Unseen} AND
isStory = 0 AND
(${_storyIdPredicate(storyId, isGroup)}) AND
received_at <= $newestUnreadAt;
`
).run({
conversationId,
jsonPatch: JSON.stringify({ readStatus: ReadStatus.Read }),
jsonPatch: JSON.stringify({
readStatus: ReadStatus.Read,
seenStatus: SeenStatus.Seen,
}),
newestUnreadAt,
storyId: storyId || null,
});
@ -2644,7 +2668,7 @@ async function getLastConversationMessage({
return jsonToObject(row.json);
}
function getOldestUnreadMessageForConversation(
function getOldestUnseenMessageForConversation(
conversationId: string,
storyId?: UUIDStringType,
isGroup?: boolean
@ -2655,7 +2679,7 @@ function getOldestUnreadMessageForConversation(
`
SELECT * FROM messages WHERE
conversationId = $conversationId AND
readStatus = ${ReadStatus.Unread} AND
seenStatus = ${SeenStatus.Unseen} AND
isStory IS 0 AND
(${_storyIdPredicate(storyId, isGroup)})
ORDER BY received_at ASC, sent_at ASC
@ -2709,6 +2733,35 @@ function getTotalUnreadForConversationSync(
return row['count(id)'];
}
function getTotalUnseenForConversationSync(
conversationId: string,
storyId?: UUIDStringType,
isGroup?: boolean
): number {
const db = getInstance();
const row = db
.prepare<Query>(
`
SELECT count(id)
FROM messages
WHERE
conversationId = $conversationId AND
seenStatus = ${SeenStatus.Unseen} AND
isStory IS 0 AND
(${_storyIdPredicate(storyId, isGroup)})
`
)
.get({
conversationId,
storyId: storyId || null,
});
if (!row) {
throw new Error('getTotalUnseenForConversationSync: Unable to get count');
}
return row['count(id)'];
}
async function getMessageMetricsForConversation(
conversationId: string,
@ -2732,12 +2785,12 @@ function getMessageMetricsForConversationSync(
storyId,
isGroup
);
const oldestUnread = getOldestUnreadMessageForConversation(
const oldestUnseen = getOldestUnseenMessageForConversation(
conversationId,
storyId,
isGroup
);
const totalUnread = getTotalUnreadForConversationSync(
const totalUnseen = getTotalUnseenForConversationSync(
conversationId,
storyId,
isGroup
@ -2746,10 +2799,10 @@ function getMessageMetricsForConversationSync(
return {
oldest: oldest ? pick(oldest, ['received_at', 'sent_at', 'id']) : undefined,
newest: newest ? pick(newest, ['received_at', 'sent_at', 'id']) : undefined,
oldestUnread: oldestUnread
? pick(oldestUnread, ['received_at', 'sent_at', 'id'])
oldestUnseen: oldestUnseen
? pick(oldestUnseen, ['received_at', 'sent_at', 'id'])
: undefined,
totalUnread,
totalUnseen,
};
}

View file

@ -0,0 +1,88 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { Database } from 'better-sqlite3';
import { ReadStatus } from '../../messages/MessageReadStatus';
import { SeenStatus } from '../../MessageSeenStatus';
import type { LoggerType } from '../../types/Logging';
export default function updateToSchemaVersion56(
currentVersion: number,
db: Database,
logger: LoggerType
): void {
if (currentVersion >= 56) {
return;
}
db.transaction(() => {
db.exec(
`
--- Add column to messages table
ALTER TABLE messages ADD COLUMN seenStatus NUMBER default 0;
--- Add index to make searching on this field easy
CREATE INDEX messages_unseen_no_story ON messages
(conversationId, seenStatus, isStory, received_at, sent_at)
WHERE
seenStatus IS NOT NULL;
CREATE INDEX messages_unseen_with_story ON messages
(conversationId, seenStatus, isStory, storyId, received_at, sent_at)
WHERE
seenStatus IS NOT NULL;
--- Update seenStatus to UnseenStatus.Unseen for certain messages
--- (NULL included because 'timer-notification' in 1:1 convos had type = NULL)
UPDATE messages
SET
seenStatus = ${SeenStatus.Unseen}
WHERE
readStatus = ${ReadStatus.Unread} AND
(
type IS NULL
OR
type IN (
'call-history',
'change-number-notification',
'chat-session-refreshed',
'delivery-issue',
'group',
'incoming',
'keychange',
'timer-notification',
'verified-change'
)
);
--- Set readStatus to ReadStatus.Read for all other message types
UPDATE messages
SET
readStatus = ${ReadStatus.Read}
WHERE
readStatus = ${ReadStatus.Unread} AND
type IS NOT NULL AND
type NOT IN (
'call-history',
'change-number-notification',
'chat-session-refreshed',
'delivery-issue',
'group',
'incoming',
'keychange',
'timer-notification',
'verified-change'
);
`
);
db.pragma('user_version = 56');
})();
logger.info('updateToSchemaVersion56: success!');
}

View file

@ -31,6 +31,7 @@ import updateToSchemaVersion52 from './52-optimize-stories';
import updateToSchemaVersion53 from './53-gv2-banned-members';
import updateToSchemaVersion54 from './54-unprocessed-received-at-counter';
import updateToSchemaVersion55 from './55-report-message-aci';
import updateToSchemaVersion56 from './56-add-unseen-to-message';
function updateToSchemaVersion1(
currentVersion: number,
@ -1925,6 +1926,7 @@ export const SCHEMA_VERSIONS = [
updateToSchemaVersion53,
updateToSchemaVersion54,
updateToSchemaVersion55,
updateToSchemaVersion56,
];
export function updateSchema(db: Database, logger: LoggerType): void {