Search call links in calls tab
This commit is contained in:
parent
fc9c5488c5
commit
dea641bae4
9 changed files with 384 additions and 112 deletions
|
@ -3619,28 +3619,29 @@ function getCallHistoryGroupDataSync(
|
|||
): unknown {
|
||||
return db.transaction(() => {
|
||||
const { limit, offset } = pagination;
|
||||
const { status, conversationIds } = filter;
|
||||
|
||||
// TODO: DESKTOP-6827 Search Calls Tab for adhoc calls
|
||||
if (conversationIds != null) {
|
||||
strictAssert(conversationIds.length > 0, "can't filter by empty array");
|
||||
const { status, conversationIds, callLinkRoomIds } = filter;
|
||||
|
||||
const isUsingTempTable = conversationIds != null || callLinkRoomIds != null;
|
||||
if (isUsingTempTable) {
|
||||
const [createTempTable] = sql`
|
||||
CREATE TEMP TABLE temp_callHistory_filtered_conversations (
|
||||
id TEXT,
|
||||
CREATE TEMP TABLE temp_callHistory_filtered_peers (
|
||||
conversationId TEXT,
|
||||
serviceId TEXT,
|
||||
groupId TEXT
|
||||
groupId TEXT,
|
||||
callLinkRoomId TEXT
|
||||
);
|
||||
`;
|
||||
|
||||
db.exec(createTempTable);
|
||||
if (conversationIds != null) {
|
||||
strictAssert(conversationIds.length > 0, "can't filter by empty array");
|
||||
|
||||
batchMultiVarQuery(db, conversationIds, ids => {
|
||||
const idList = sqlJoin(ids.map(id => sqlFragment`${id}`));
|
||||
|
||||
const [insertQuery, insertParams] = sql`
|
||||
INSERT INTO temp_callHistory_filtered_conversations
|
||||
(id, serviceId, groupId)
|
||||
INSERT INTO temp_callHistory_filtered_peers
|
||||
(conversationId, serviceId, groupId)
|
||||
SELECT id, serviceId, groupId
|
||||
FROM conversations
|
||||
WHERE conversations.id IN (${idList});
|
||||
|
@ -3650,14 +3651,31 @@ function getCallHistoryGroupDataSync(
|
|||
});
|
||||
}
|
||||
|
||||
const innerJoin =
|
||||
conversationIds != null
|
||||
? // peerId can be a conversation id (legacy), a serviceId, or a groupId
|
||||
sqlFragment`
|
||||
INNER JOIN temp_callHistory_filtered_conversations ON (
|
||||
temp_callHistory_filtered_conversations.id IS c.peerId
|
||||
OR temp_callHistory_filtered_conversations.serviceId IS c.peerId
|
||||
OR temp_callHistory_filtered_conversations.groupId IS c.peerId
|
||||
if (callLinkRoomIds != null) {
|
||||
strictAssert(callLinkRoomIds.length > 0, "can't filter by empty array");
|
||||
|
||||
batchMultiVarQuery(db, callLinkRoomIds, ids => {
|
||||
const idList = sqlJoin(ids.map(id => sqlFragment`(${id})`));
|
||||
|
||||
const [insertQuery, insertParams] = sql`
|
||||
INSERT INTO temp_callHistory_filtered_peers
|
||||
(callLinkRoomId)
|
||||
VALUES ${idList};
|
||||
`;
|
||||
|
||||
db.prepare(insertQuery).run(insertParams);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// peerId can be a conversation id (legacy), a serviceId, groupId, or call link roomId
|
||||
const innerJoin = isUsingTempTable
|
||||
? sqlFragment`
|
||||
INNER JOIN temp_callHistory_filtered_peers ON (
|
||||
temp_callHistory_filtered_peers.conversationId IS c.peerId
|
||||
OR temp_callHistory_filtered_peers.serviceId IS c.peerId
|
||||
OR temp_callHistory_filtered_peers.groupId IS c.peerId
|
||||
OR temp_callHistory_filtered_peers.callLinkRoomId IS c.peerId
|
||||
)
|
||||
`
|
||||
: sqlFragment``;
|
||||
|
@ -3795,9 +3813,9 @@ function getCallHistoryGroupDataSync(
|
|||
? db.prepare(query).pluck(true).get(params)
|
||||
: db.prepare(query).all(params);
|
||||
|
||||
if (conversationIds != null) {
|
||||
if (isUsingTempTable) {
|
||||
const [dropTempTableQuery] = sql`
|
||||
DROP TABLE temp_callHistory_filtered_conversations;
|
||||
DROP TABLE temp_callHistory_filtered_peers;
|
||||
`;
|
||||
|
||||
db.exec(dropTempTableQuery);
|
||||
|
@ -3819,6 +3837,11 @@ async function getCallHistoryGroupsCount(
|
|||
limit: 0,
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
if (result == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return countSchema.parse(result);
|
||||
}
|
||||
|
||||
|
@ -5975,6 +5998,7 @@ async function removeAll(): Promise<void> {
|
|||
DELETE FROM attachment_downloads;
|
||||
DELETE FROM badgeImageFiles;
|
||||
DELETE FROM badges;
|
||||
DELETE FROM callLinks;
|
||||
DELETE FROM callsHistory;
|
||||
DELETE FROM conversations;
|
||||
DELETE FROM emojis;
|
||||
|
|
|
@ -31,7 +31,7 @@ import type {
|
|||
PresentedSource,
|
||||
PresentableSource,
|
||||
} from '../../types/Calling';
|
||||
import type { CallLinkStateType } from '../../types/CallLink';
|
||||
import type { CallLinkStateType, CallLinkType } from '../../types/CallLink';
|
||||
import {
|
||||
CALLING_REACTIONS_LIFETIME,
|
||||
MAX_CALLING_REACTIONS,
|
||||
|
@ -174,15 +174,8 @@ export type AdhocCallsType = {
|
|||
[roomId: string]: GroupCallStateType;
|
||||
};
|
||||
|
||||
export type CallLinksByRoomIdStateType = ReadonlyDeep<
|
||||
CallLinkStateType & {
|
||||
rootKey: string;
|
||||
adminKey: string | null;
|
||||
}
|
||||
>;
|
||||
|
||||
export type CallLinksByRoomIdType = ReadonlyDeep<{
|
||||
[roomId: string]: CallLinksByRoomIdStateType;
|
||||
[roomId: string]: CallLinkType;
|
||||
}>;
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
|
@ -244,8 +237,7 @@ type GroupCallStateChangeActionPayloadType =
|
|||
};
|
||||
|
||||
type HandleCallLinkUpdateActionPayloadType = ReadonlyDeep<{
|
||||
roomId: string;
|
||||
callLinkDetails: CallLinksByRoomIdStateType;
|
||||
callLink: CallLinkType;
|
||||
}>;
|
||||
|
||||
type HangUpActionPayloadType = ReadonlyDeep<{
|
||||
|
@ -384,6 +376,7 @@ type StartCallLinkLobbyPayloadType = {
|
|||
peekInfo?: GroupCallPeekInfoType;
|
||||
remoteParticipants: Array<GroupCallParticipantInfoType>;
|
||||
callLinkState: CallLinkStateType;
|
||||
callLinkRoomId: string;
|
||||
callLinkRootKey: string;
|
||||
};
|
||||
|
||||
|
@ -1333,10 +1326,11 @@ function handleCallLinkUpdate(
|
|||
'revoked',
|
||||
]);
|
||||
|
||||
const callLinkDetails: CallLinksByRoomIdStateType = {
|
||||
const callLink: CallLinkType = {
|
||||
...CALL_LINK_DEFAULT_STATE,
|
||||
...existingCallLinkState,
|
||||
...freshCallLinkState,
|
||||
roomId,
|
||||
rootKey,
|
||||
adminKey,
|
||||
};
|
||||
|
@ -1352,19 +1346,13 @@ function handleCallLinkUpdate(
|
|||
log.info(`${logId}: Updated existing call link state`);
|
||||
}
|
||||
} else {
|
||||
await dataInterface.insertCallLink({
|
||||
roomId,
|
||||
...callLinkDetails,
|
||||
});
|
||||
await dataInterface.insertCallLink(callLink);
|
||||
log.info(`${logId}: Saved new call link`);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: HANDLE_CALL_LINK_UPDATE,
|
||||
payload: {
|
||||
roomId,
|
||||
callLinkDetails,
|
||||
},
|
||||
payload: { callLink },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -2006,6 +1994,7 @@ const _startCallLinkLobby = async ({
|
|||
payload: {
|
||||
...callLobbyData,
|
||||
callLinkState,
|
||||
callLinkRoomId: roomId,
|
||||
callLinkRootKey: rootKey,
|
||||
conversationId: roomId,
|
||||
isConversationTooBigToRing: false,
|
||||
|
@ -2415,6 +2404,9 @@ export function reducer(
|
|||
...callLinks,
|
||||
[conversationId]: {
|
||||
...action.payload.callLinkState,
|
||||
roomId:
|
||||
callLinks[conversationId]?.roomId ??
|
||||
action.payload.callLinkRoomId,
|
||||
rootKey:
|
||||
callLinks[conversationId]?.rootKey ??
|
||||
action.payload.callLinkRootKey,
|
||||
|
@ -3355,13 +3347,14 @@ export function reducer(
|
|||
|
||||
if (action.type === HANDLE_CALL_LINK_UPDATE) {
|
||||
const { callLinks } = state;
|
||||
const { roomId, callLinkDetails } = action.payload;
|
||||
const { callLink } = action.payload;
|
||||
const { roomId } = callLink;
|
||||
|
||||
return {
|
||||
...state,
|
||||
callLinks: {
|
||||
...callLinks,
|
||||
[roomId]: callLinkDetails,
|
||||
[roomId]: callLink,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -79,17 +79,13 @@ export type CallLinkSelectorType = (roomId: string) => CallLinkType | undefined;
|
|||
export const getCallLinkSelector = createSelector(
|
||||
getCallLinksByRoomId,
|
||||
(callLinksByRoomId: CallLinksByRoomIdType): CallLinkSelectorType =>
|
||||
(roomId: string): CallLinkType | undefined => {
|
||||
const callLinkState = getOwn(callLinksByRoomId, roomId);
|
||||
if (!callLinkState) {
|
||||
return;
|
||||
}
|
||||
(roomId: string): CallLinkType | undefined =>
|
||||
getOwn(callLinksByRoomId, roomId)
|
||||
);
|
||||
|
||||
return {
|
||||
roomId,
|
||||
...callLinkState,
|
||||
};
|
||||
}
|
||||
export const getAllCallLinks = createSelector(
|
||||
getCallLinksByRoomId,
|
||||
(lookup): Array<CallLinkType> => Object.values(lookup)
|
||||
);
|
||||
|
||||
export type CallSelectorType = (
|
||||
|
|
|
@ -28,6 +28,7 @@ import { useCallingActions } from '../ducks/calling';
|
|||
import {
|
||||
getActiveCallState,
|
||||
getAdhocCallSelector,
|
||||
getAllCallLinks,
|
||||
getCallSelector,
|
||||
getCallLinkSelector,
|
||||
} from '../selectors/calling';
|
||||
|
@ -36,15 +37,34 @@ import { getCallHistoryEdition } from '../selectors/callHistory';
|
|||
import { getHasPendingUpdate } from '../selectors/updates';
|
||||
import { getHasAnyFailedStorySends } from '../selectors/stories';
|
||||
import { getOtherTabsUnreadStats } from '../selectors/nav';
|
||||
import type { CallLinkType } from '../../types/CallLink';
|
||||
import { filterCallLinks } from '../../util/filterCallLinks';
|
||||
|
||||
function getCallHistoryFilter(
|
||||
allConversations: Array<ConversationType>,
|
||||
regionCode: string | undefined,
|
||||
options: CallHistoryFilterOptions
|
||||
): CallHistoryFilter | null {
|
||||
function getCallHistoryFilter({
|
||||
allCallLinks,
|
||||
allConversations,
|
||||
regionCode,
|
||||
options,
|
||||
}: {
|
||||
allConversations: Array<ConversationType>;
|
||||
allCallLinks: Array<CallLinkType>;
|
||||
regionCode: string | undefined;
|
||||
options: CallHistoryFilterOptions;
|
||||
}): CallHistoryFilter | null {
|
||||
const { status } = options;
|
||||
const query = options.query.normalize().trim();
|
||||
|
||||
if (query !== '') {
|
||||
if (query === '') {
|
||||
return {
|
||||
status,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: null,
|
||||
};
|
||||
}
|
||||
|
||||
let callLinkRoomIds = null;
|
||||
let conversationIds = null;
|
||||
|
||||
const currentConversations = allConversations.filter(conversation => {
|
||||
return conversation.removalStage == null;
|
||||
});
|
||||
|
@ -55,22 +75,29 @@ function getCallHistoryFilter(
|
|||
regionCode
|
||||
);
|
||||
|
||||
// If there are no matching conversations, then no calls will match.
|
||||
if (filteredConversations.length === 0) {
|
||||
if (filteredConversations.length > 0) {
|
||||
conversationIds = filteredConversations.map(conversation => {
|
||||
return conversation.id;
|
||||
});
|
||||
}
|
||||
|
||||
const filteredCallLinks = filterCallLinks(allCallLinks, query);
|
||||
if (filteredCallLinks.length > 0) {
|
||||
callLinkRoomIds = filteredCallLinks.map(callLink => {
|
||||
return callLink.roomId;
|
||||
});
|
||||
}
|
||||
|
||||
// If the search query resulted in no matching call links or conversations, then
|
||||
// no calls will match.
|
||||
if (callLinkRoomIds == null && conversationIds == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
status: options.status,
|
||||
conversationIds: filteredConversations.map(conversation => {
|
||||
return conversation.id;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: options.status,
|
||||
conversationIds: null,
|
||||
status,
|
||||
callLinkRoomIds,
|
||||
conversationIds,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -99,6 +126,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
|
|||
const { savePreferredLeftPaneWidth, toggleNavTabsCollapse } =
|
||||
useItemsActions();
|
||||
|
||||
const allCallLinks = useSelector(getAllCallLinks);
|
||||
const allConversations = useSelector(getAllConversations);
|
||||
const regionCode = useSelector(getRegionCode);
|
||||
const getConversation = useSelector(getConversationSelector);
|
||||
|
@ -129,11 +157,12 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
|
|||
|
||||
const getCallHistoryGroupsCount = useCallback(
|
||||
async (options: CallHistoryFilterOptions) => {
|
||||
const callHistoryFilter = getCallHistoryFilter(
|
||||
const callHistoryFilter = getCallHistoryFilter({
|
||||
allCallLinks,
|
||||
allConversations,
|
||||
regionCode,
|
||||
options
|
||||
);
|
||||
options,
|
||||
});
|
||||
if (callHistoryFilter == null) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -142,7 +171,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
|
|||
);
|
||||
return count;
|
||||
},
|
||||
[allConversations, regionCode]
|
||||
[allCallLinks, allConversations, regionCode]
|
||||
);
|
||||
|
||||
const getCallHistoryGroups = useCallback(
|
||||
|
@ -150,11 +179,12 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
|
|||
options: CallHistoryFilterOptions,
|
||||
pagination: CallHistoryPagination
|
||||
) => {
|
||||
const callHistoryFilter = getCallHistoryFilter(
|
||||
const callHistoryFilter = getCallHistoryFilter({
|
||||
allCallLinks,
|
||||
allConversations,
|
||||
regionCode,
|
||||
options
|
||||
);
|
||||
options,
|
||||
});
|
||||
if (callHistoryFilter == null) {
|
||||
return [];
|
||||
}
|
||||
|
@ -164,7 +194,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
|
|||
);
|
||||
return results;
|
||||
},
|
||||
[allConversations, regionCode]
|
||||
[allCallLinks, allConversations, regionCode]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -11,15 +11,15 @@ export const FAKE_CALL_LINK: CallLinkType = {
|
|||
name: 'Fun Link',
|
||||
restrictions: CallLinkRestrictions.None,
|
||||
revoked: false,
|
||||
roomId: 'c097eb04cc278d6bc7ed9fb2ddeac00dc9646ae6ddb38513dad9a8a4fe3c38f4',
|
||||
rootKey: 'bpmc-mrgn-hntf-mffd-mndd-xbxk-zmgq-qszg',
|
||||
roomId: 'd517b48dd118bee24068d4938886c8abe192706d84936d52594a9157189d2759',
|
||||
rootKey: 'dxbb-xfqz-xkgp-nmrx-bpqn-ptkb-spdt-pdgt',
|
||||
};
|
||||
|
||||
// Please set expiration
|
||||
export const FAKE_CALL_LINK_WITH_ADMIN_KEY: CallLinkType = {
|
||||
adminKey: 'xXPI77e6MoVHYREW8iKYmQ==',
|
||||
expiration: Date.now() + MONTH, // set me
|
||||
name: 'Fun Link',
|
||||
name: 'Admin Link',
|
||||
restrictions: CallLinkRestrictions.None,
|
||||
revoked: false,
|
||||
roomId: 'c097eb04cc278d6bc7ed9fb2ddeac00dc9646ae6ddb38513dad9a8a4fe3c38f4',
|
||||
|
|
|
@ -23,11 +23,16 @@ import {
|
|||
} from '../../types/CallDisposition';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import type { ConversationAttributesType } from '../../model-types';
|
||||
import {
|
||||
FAKE_CALL_LINK,
|
||||
FAKE_CALL_LINK_WITH_ADMIN_KEY,
|
||||
} from '../../test-both/helpers/fakeCallLink';
|
||||
|
||||
const {
|
||||
removeAll,
|
||||
getCallHistoryGroups,
|
||||
getCallHistoryGroupsCount,
|
||||
insertCallLink,
|
||||
saveCallHistory,
|
||||
saveConversation,
|
||||
} = dataInterface;
|
||||
|
@ -90,7 +95,11 @@ describe('sql/getCallHistoryGroups', () => {
|
|||
await saveCallHistory(call2);
|
||||
|
||||
const groups = await getCallHistoryGroups(
|
||||
{ status: CallHistoryFilterStatus.All, conversationIds: null },
|
||||
{
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: null,
|
||||
},
|
||||
{ offset: 0, limit: 0 }
|
||||
);
|
||||
|
||||
|
@ -121,7 +130,11 @@ describe('sql/getCallHistoryGroups', () => {
|
|||
await saveCallHistory(call2);
|
||||
|
||||
const groups = await getCallHistoryGroups(
|
||||
{ status: CallHistoryFilterStatus.All, conversationIds: null },
|
||||
{
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: null,
|
||||
},
|
||||
{ offset: 0, limit: 0 }
|
||||
);
|
||||
|
||||
|
@ -156,7 +169,11 @@ describe('sql/getCallHistoryGroups', () => {
|
|||
await saveCallHistory(call4);
|
||||
|
||||
const groups = await getCallHistoryGroups(
|
||||
{ status: CallHistoryFilterStatus.All, conversationIds: null },
|
||||
{
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: null,
|
||||
},
|
||||
{ offset: 0, limit: 0 }
|
||||
);
|
||||
|
||||
|
@ -218,6 +235,7 @@ describe('sql/getCallHistoryGroups', () => {
|
|||
const groups = await getCallHistoryGroups(
|
||||
{
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: [conversation1.id],
|
||||
},
|
||||
{ offset: 0, limit: 0 }
|
||||
|
@ -230,6 +248,7 @@ describe('sql/getCallHistoryGroups', () => {
|
|||
const groups = await getCallHistoryGroups(
|
||||
{
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: [conversation2.id],
|
||||
},
|
||||
{ offset: 0, limit: 0 }
|
||||
|
@ -268,6 +287,7 @@ describe('sql/getCallHistoryGroups', () => {
|
|||
const groups = await getCallHistoryGroups(
|
||||
{
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: [conversation.id],
|
||||
},
|
||||
{ offset: 0, limit: 0 }
|
||||
|
@ -310,7 +330,11 @@ describe('sql/getCallHistoryGroups', () => {
|
|||
await saveCallHistory(call2);
|
||||
|
||||
const groups = await getCallHistoryGroups(
|
||||
{ status: CallHistoryFilterStatus.Missed, conversationIds: null },
|
||||
{
|
||||
status: CallHistoryFilterStatus.Missed,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: null,
|
||||
},
|
||||
{ offset: 0, limit: 0 }
|
||||
);
|
||||
|
||||
|
@ -328,7 +352,7 @@ describe('sql/getCallHistoryGroups', () => {
|
|||
ringerId: null,
|
||||
mode: CallMode.Adhoc,
|
||||
type: CallType.Adhoc,
|
||||
direction: CallDirection.Outgoing,
|
||||
direction: CallDirection.Incoming,
|
||||
timestamp,
|
||||
status: AdhocCallStatus.Joined,
|
||||
};
|
||||
|
@ -341,12 +365,127 @@ describe('sql/getCallHistoryGroups', () => {
|
|||
await saveCallHistory(call2);
|
||||
|
||||
const groups = await getCallHistoryGroups(
|
||||
{ status: CallHistoryFilterStatus.All, conversationIds: null },
|
||||
{
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: null,
|
||||
},
|
||||
{ offset: 0, limit: 0 }
|
||||
);
|
||||
|
||||
assert.deepEqual(groups, [toAdhocGroup(call2)]);
|
||||
});
|
||||
|
||||
it('should search call links', async () => {
|
||||
const now = Date.now();
|
||||
|
||||
const { roomId: roomId1 } = FAKE_CALL_LINK;
|
||||
const { roomId: roomId2 } = FAKE_CALL_LINK_WITH_ADMIN_KEY;
|
||||
|
||||
await insertCallLink(FAKE_CALL_LINK);
|
||||
await insertCallLink(FAKE_CALL_LINK_WITH_ADMIN_KEY);
|
||||
|
||||
const conversation1Uuid = generateAci();
|
||||
const conversation2GroupId = 'groupId:2';
|
||||
|
||||
const conversation1: ConversationAttributesType = {
|
||||
type: 'private',
|
||||
version: 0,
|
||||
id: 'id:1',
|
||||
serviceId: conversation1Uuid,
|
||||
};
|
||||
|
||||
const conversation2: ConversationAttributesType = {
|
||||
type: 'group',
|
||||
version: 2,
|
||||
id: 'id:2',
|
||||
groupId: conversation2GroupId,
|
||||
};
|
||||
|
||||
await saveConversation(conversation1);
|
||||
await saveConversation(conversation2);
|
||||
|
||||
function toAdhocCall(callId: string, roomId: string, timestamp: number) {
|
||||
return {
|
||||
callId,
|
||||
peerId: roomId,
|
||||
ringerId: null,
|
||||
mode: CallMode.Adhoc,
|
||||
type: CallType.Adhoc,
|
||||
direction: CallDirection.Outgoing,
|
||||
timestamp,
|
||||
status: AdhocCallStatus.Joined,
|
||||
};
|
||||
}
|
||||
|
||||
function toConversationCall(
|
||||
callId: string,
|
||||
timestamp: number,
|
||||
mode: CallMode,
|
||||
peerId: string | ServiceIdString
|
||||
) {
|
||||
return {
|
||||
callId,
|
||||
peerId,
|
||||
ringerId: null,
|
||||
mode,
|
||||
type: CallType.Video,
|
||||
direction: CallDirection.Incoming,
|
||||
timestamp,
|
||||
status: DirectCallStatus.Accepted,
|
||||
};
|
||||
}
|
||||
|
||||
const call1 = toAdhocCall('1', roomId1, now - 30);
|
||||
const call2 = toAdhocCall('2', roomId2, now - 20);
|
||||
const call3 = toConversationCall(
|
||||
'3',
|
||||
now - 10,
|
||||
CallMode.Direct,
|
||||
conversation1Uuid
|
||||
);
|
||||
const call4 = toConversationCall(
|
||||
'4',
|
||||
now,
|
||||
CallMode.Group,
|
||||
conversation2GroupId
|
||||
);
|
||||
|
||||
await saveCallHistory(call1);
|
||||
await saveCallHistory(call2);
|
||||
await saveCallHistory(call3);
|
||||
await saveCallHistory(call4);
|
||||
|
||||
{
|
||||
const groups = await getCallHistoryGroups(
|
||||
{
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: [roomId1],
|
||||
conversationIds: null,
|
||||
},
|
||||
{ offset: 0, limit: 0 }
|
||||
);
|
||||
|
||||
assert.deepEqual(groups, [toAdhocGroup(call1)], 'just call link');
|
||||
}
|
||||
|
||||
{
|
||||
const groups = await getCallHistoryGroups(
|
||||
{
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: [roomId2],
|
||||
conversationIds: [conversation2.id],
|
||||
},
|
||||
{ offset: 0, limit: 0 }
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
groups,
|
||||
[toGroup([call4]), toAdhocGroup(call2)],
|
||||
'call link and conversation'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('sql/getCallHistoryGroupsCount', () => {
|
||||
|
@ -383,6 +522,7 @@ describe('sql/getCallHistoryGroupsCount', () => {
|
|||
|
||||
const result = await getCallHistoryGroupsCount({
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: null,
|
||||
});
|
||||
|
||||
|
@ -419,6 +559,7 @@ describe('sql/getCallHistoryGroupsCount', () => {
|
|||
|
||||
const result = await getCallHistoryGroupsCount({
|
||||
status: CallHistoryFilterStatus.All,
|
||||
callLinkRoomIds: null,
|
||||
conversationIds: null,
|
||||
});
|
||||
|
||||
|
|
|
@ -1363,12 +1363,12 @@ describe('calling duck', () => {
|
|||
sinon.assert.calledWith(dispatch, {
|
||||
type: 'calling/HANDLE_CALL_LINK_UPDATE',
|
||||
payload: {
|
||||
roomId,
|
||||
callLinkDetails: {
|
||||
callLink: {
|
||||
name,
|
||||
restrictions,
|
||||
expiration,
|
||||
revoked,
|
||||
roomId,
|
||||
rootKey,
|
||||
adminKey,
|
||||
},
|
||||
|
@ -1383,12 +1383,12 @@ describe('calling duck', () => {
|
|||
sinon.assert.calledWith(dispatch, {
|
||||
type: 'calling/HANDLE_CALL_LINK_UPDATE',
|
||||
payload: {
|
||||
roomId,
|
||||
callLinkDetails: {
|
||||
callLink: {
|
||||
name,
|
||||
restrictions,
|
||||
expiration,
|
||||
revoked,
|
||||
roomId,
|
||||
rootKey,
|
||||
adminKey: 'banana',
|
||||
},
|
||||
|
@ -1451,6 +1451,7 @@ describe('calling duck', () => {
|
|||
payload: {
|
||||
...callLobbyData,
|
||||
callLinkState,
|
||||
callLinkRoomId: roomId,
|
||||
callLinkRootKey: rootKey,
|
||||
conversationId: roomId,
|
||||
isConversationTooBigToRing: false,
|
||||
|
|
|
@ -133,6 +133,7 @@ export type CallHistoryFilterOptions = Readonly<{
|
|||
|
||||
export type CallHistoryFilter = Readonly<{
|
||||
status: CallHistoryFilterStatus;
|
||||
callLinkRoomIds: ReadonlyArray<string> | null;
|
||||
conversationIds: ReadonlyArray<string> | null;
|
||||
}>;
|
||||
|
||||
|
|
86
ts/util/filterCallLinks.ts
Normal file
86
ts/util/filterCallLinks.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type Fuse from 'fuse.js';
|
||||
import { fuseGetFnRemoveDiacritics, getCachedFuseIndex } from './fuse';
|
||||
import { removeDiacritics } from './removeDiacritics';
|
||||
import type { CallLinkType } from '../types/CallLink';
|
||||
|
||||
// Based on parameters in filterAndSortConversations
|
||||
const FUSE_OPTIONS: Fuse.IFuseOptions<CallLinkType> = {
|
||||
threshold: 0.2,
|
||||
includeScore: true,
|
||||
useExtendedSearch: true,
|
||||
shouldSort: true,
|
||||
distance: 200,
|
||||
keys: [
|
||||
{
|
||||
name: 'name',
|
||||
weight: 1,
|
||||
},
|
||||
],
|
||||
getFn: (item, path) => {
|
||||
if (
|
||||
(path === 'name' || (path.length === 1 && path[0] === 'name')) &&
|
||||
item.name === ''
|
||||
) {
|
||||
return removeDiacritics(
|
||||
window.i18n('icu:calling__call-link-default-title')
|
||||
);
|
||||
}
|
||||
|
||||
return fuseGetFnRemoveDiacritics(item, path);
|
||||
},
|
||||
};
|
||||
|
||||
function searchCallLinks(
|
||||
callLinks: ReadonlyArray<CallLinkType>,
|
||||
searchTerm: string
|
||||
): ReadonlyArray<Pick<Fuse.FuseResult<CallLinkType>, 'item' | 'score'>> {
|
||||
// TODO: DESKTOP-6974
|
||||
|
||||
// Escape the search term
|
||||
const extendedSearchTerm = removeDiacritics(searchTerm);
|
||||
|
||||
const index = getCachedFuseIndex(callLinks, FUSE_OPTIONS);
|
||||
|
||||
return index.search(extendedSearchTerm);
|
||||
}
|
||||
|
||||
function startsWithLetter(title: string) {
|
||||
return /^\p{Letter}/u.test(title);
|
||||
}
|
||||
|
||||
function sortAlphabetically(a: CallLinkType, b: CallLinkType) {
|
||||
const aStartsWithLetter = startsWithLetter(a.name);
|
||||
const bStartsWithLetter = startsWithLetter(b.name);
|
||||
if (aStartsWithLetter && !bStartsWithLetter) {
|
||||
return -1;
|
||||
}
|
||||
if (!aStartsWithLetter && bStartsWithLetter) {
|
||||
return 1;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
export function filterCallLinks(
|
||||
callLinks: ReadonlyArray<CallLinkType>,
|
||||
searchTerm: string
|
||||
): Array<CallLinkType> {
|
||||
if (searchTerm.length) {
|
||||
return searchCallLinks(callLinks, searchTerm)
|
||||
.slice()
|
||||
.sort((a, b) => {
|
||||
const score = (a.score ?? 0) - (b.score ?? 0);
|
||||
if (score !== 0) {
|
||||
return score;
|
||||
}
|
||||
return sortAlphabetically(a.item, b.item);
|
||||
})
|
||||
.map(result => result.item);
|
||||
}
|
||||
|
||||
return callLinks.concat().sort((a, b) => {
|
||||
return sortAlphabetically(a, b);
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue