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,48 +3619,66 @@ function getCallHistoryGroupDataSync(
|
||||||
): unknown {
|
): unknown {
|
||||||
return db.transaction(() => {
|
return db.transaction(() => {
|
||||||
const { limit, offset } = pagination;
|
const { limit, offset } = pagination;
|
||||||
const { status, conversationIds } = filter;
|
const { status, conversationIds, callLinkRoomIds } = filter;
|
||||||
|
|
||||||
// TODO: DESKTOP-6827 Search Calls Tab for adhoc calls
|
|
||||||
if (conversationIds != null) {
|
|
||||||
strictAssert(conversationIds.length > 0, "can't filter by empty array");
|
|
||||||
|
|
||||||
|
const isUsingTempTable = conversationIds != null || callLinkRoomIds != null;
|
||||||
|
if (isUsingTempTable) {
|
||||||
const [createTempTable] = sql`
|
const [createTempTable] = sql`
|
||||||
CREATE TEMP TABLE temp_callHistory_filtered_conversations (
|
CREATE TEMP TABLE temp_callHistory_filtered_peers (
|
||||||
id TEXT,
|
conversationId TEXT,
|
||||||
serviceId TEXT,
|
serviceId TEXT,
|
||||||
groupId TEXT
|
groupId TEXT,
|
||||||
|
callLinkRoomId TEXT
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
db.exec(createTempTable);
|
db.exec(createTempTable);
|
||||||
|
if (conversationIds != null) {
|
||||||
|
strictAssert(conversationIds.length > 0, "can't filter by empty array");
|
||||||
|
|
||||||
batchMultiVarQuery(db, conversationIds, ids => {
|
batchMultiVarQuery(db, conversationIds, ids => {
|
||||||
const idList = sqlJoin(ids.map(id => sqlFragment`${id}`));
|
const idList = sqlJoin(ids.map(id => sqlFragment`${id}`));
|
||||||
|
|
||||||
const [insertQuery, insertParams] = sql`
|
const [insertQuery, insertParams] = sql`
|
||||||
INSERT INTO temp_callHistory_filtered_conversations
|
INSERT INTO temp_callHistory_filtered_peers
|
||||||
(id, serviceId, groupId)
|
(conversationId, serviceId, groupId)
|
||||||
SELECT id, serviceId, groupId
|
SELECT id, serviceId, groupId
|
||||||
FROM conversations
|
FROM conversations
|
||||||
WHERE conversations.id IN (${idList});
|
WHERE conversations.id IN (${idList});
|
||||||
`;
|
`;
|
||||||
|
|
||||||
db.prepare(insertQuery).run(insertParams);
|
db.prepare(insertQuery).run(insertParams);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const innerJoin =
|
// peerId can be a conversation id (legacy), a serviceId, groupId, or call link roomId
|
||||||
conversationIds != null
|
const innerJoin = isUsingTempTable
|
||||||
? // peerId can be a conversation id (legacy), a serviceId, or a groupId
|
? sqlFragment`
|
||||||
sqlFragment`
|
INNER JOIN temp_callHistory_filtered_peers ON (
|
||||||
INNER JOIN temp_callHistory_filtered_conversations ON (
|
temp_callHistory_filtered_peers.conversationId IS c.peerId
|
||||||
temp_callHistory_filtered_conversations.id IS c.peerId
|
OR temp_callHistory_filtered_peers.serviceId IS c.peerId
|
||||||
OR temp_callHistory_filtered_conversations.serviceId IS c.peerId
|
OR temp_callHistory_filtered_peers.groupId IS c.peerId
|
||||||
OR temp_callHistory_filtered_conversations.groupId IS c.peerId
|
OR temp_callHistory_filtered_peers.callLinkRoomId IS c.peerId
|
||||||
)
|
)
|
||||||
`
|
`
|
||||||
: sqlFragment``;
|
: sqlFragment``;
|
||||||
|
|
||||||
const filterClause =
|
const filterClause =
|
||||||
status === CallHistoryFilterStatus.All
|
status === CallHistoryFilterStatus.All
|
||||||
|
@ -3795,9 +3813,9 @@ function getCallHistoryGroupDataSync(
|
||||||
? db.prepare(query).pluck(true).get(params)
|
? db.prepare(query).pluck(true).get(params)
|
||||||
: db.prepare(query).all(params);
|
: db.prepare(query).all(params);
|
||||||
|
|
||||||
if (conversationIds != null) {
|
if (isUsingTempTable) {
|
||||||
const [dropTempTableQuery] = sql`
|
const [dropTempTableQuery] = sql`
|
||||||
DROP TABLE temp_callHistory_filtered_conversations;
|
DROP TABLE temp_callHistory_filtered_peers;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
db.exec(dropTempTableQuery);
|
db.exec(dropTempTableQuery);
|
||||||
|
@ -3819,6 +3837,11 @@ async function getCallHistoryGroupsCount(
|
||||||
limit: 0,
|
limit: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return countSchema.parse(result);
|
return countSchema.parse(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5975,6 +5998,7 @@ async function removeAll(): Promise<void> {
|
||||||
DELETE FROM attachment_downloads;
|
DELETE FROM attachment_downloads;
|
||||||
DELETE FROM badgeImageFiles;
|
DELETE FROM badgeImageFiles;
|
||||||
DELETE FROM badges;
|
DELETE FROM badges;
|
||||||
|
DELETE FROM callLinks;
|
||||||
DELETE FROM callsHistory;
|
DELETE FROM callsHistory;
|
||||||
DELETE FROM conversations;
|
DELETE FROM conversations;
|
||||||
DELETE FROM emojis;
|
DELETE FROM emojis;
|
||||||
|
|
|
@ -31,7 +31,7 @@ import type {
|
||||||
PresentedSource,
|
PresentedSource,
|
||||||
PresentableSource,
|
PresentableSource,
|
||||||
} from '../../types/Calling';
|
} from '../../types/Calling';
|
||||||
import type { CallLinkStateType } from '../../types/CallLink';
|
import type { CallLinkStateType, CallLinkType } from '../../types/CallLink';
|
||||||
import {
|
import {
|
||||||
CALLING_REACTIONS_LIFETIME,
|
CALLING_REACTIONS_LIFETIME,
|
||||||
MAX_CALLING_REACTIONS,
|
MAX_CALLING_REACTIONS,
|
||||||
|
@ -174,15 +174,8 @@ export type AdhocCallsType = {
|
||||||
[roomId: string]: GroupCallStateType;
|
[roomId: string]: GroupCallStateType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CallLinksByRoomIdStateType = ReadonlyDeep<
|
|
||||||
CallLinkStateType & {
|
|
||||||
rootKey: string;
|
|
||||||
adminKey: string | null;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type CallLinksByRoomIdType = ReadonlyDeep<{
|
export type CallLinksByRoomIdType = ReadonlyDeep<{
|
||||||
[roomId: string]: CallLinksByRoomIdStateType;
|
[roomId: string]: CallLinkType;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||||
|
@ -244,8 +237,7 @@ type GroupCallStateChangeActionPayloadType =
|
||||||
};
|
};
|
||||||
|
|
||||||
type HandleCallLinkUpdateActionPayloadType = ReadonlyDeep<{
|
type HandleCallLinkUpdateActionPayloadType = ReadonlyDeep<{
|
||||||
roomId: string;
|
callLink: CallLinkType;
|
||||||
callLinkDetails: CallLinksByRoomIdStateType;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type HangUpActionPayloadType = ReadonlyDeep<{
|
type HangUpActionPayloadType = ReadonlyDeep<{
|
||||||
|
@ -384,6 +376,7 @@ type StartCallLinkLobbyPayloadType = {
|
||||||
peekInfo?: GroupCallPeekInfoType;
|
peekInfo?: GroupCallPeekInfoType;
|
||||||
remoteParticipants: Array<GroupCallParticipantInfoType>;
|
remoteParticipants: Array<GroupCallParticipantInfoType>;
|
||||||
callLinkState: CallLinkStateType;
|
callLinkState: CallLinkStateType;
|
||||||
|
callLinkRoomId: string;
|
||||||
callLinkRootKey: string;
|
callLinkRootKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1333,10 +1326,11 @@ function handleCallLinkUpdate(
|
||||||
'revoked',
|
'revoked',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const callLinkDetails: CallLinksByRoomIdStateType = {
|
const callLink: CallLinkType = {
|
||||||
...CALL_LINK_DEFAULT_STATE,
|
...CALL_LINK_DEFAULT_STATE,
|
||||||
...existingCallLinkState,
|
...existingCallLinkState,
|
||||||
...freshCallLinkState,
|
...freshCallLinkState,
|
||||||
|
roomId,
|
||||||
rootKey,
|
rootKey,
|
||||||
adminKey,
|
adminKey,
|
||||||
};
|
};
|
||||||
|
@ -1352,19 +1346,13 @@ function handleCallLinkUpdate(
|
||||||
log.info(`${logId}: Updated existing call link state`);
|
log.info(`${logId}: Updated existing call link state`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await dataInterface.insertCallLink({
|
await dataInterface.insertCallLink(callLink);
|
||||||
roomId,
|
|
||||||
...callLinkDetails,
|
|
||||||
});
|
|
||||||
log.info(`${logId}: Saved new call link`);
|
log.info(`${logId}: Saved new call link`);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: HANDLE_CALL_LINK_UPDATE,
|
type: HANDLE_CALL_LINK_UPDATE,
|
||||||
payload: {
|
payload: { callLink },
|
||||||
roomId,
|
|
||||||
callLinkDetails,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2006,6 +1994,7 @@ const _startCallLinkLobby = async ({
|
||||||
payload: {
|
payload: {
|
||||||
...callLobbyData,
|
...callLobbyData,
|
||||||
callLinkState,
|
callLinkState,
|
||||||
|
callLinkRoomId: roomId,
|
||||||
callLinkRootKey: rootKey,
|
callLinkRootKey: rootKey,
|
||||||
conversationId: roomId,
|
conversationId: roomId,
|
||||||
isConversationTooBigToRing: false,
|
isConversationTooBigToRing: false,
|
||||||
|
@ -2415,6 +2404,9 @@ export function reducer(
|
||||||
...callLinks,
|
...callLinks,
|
||||||
[conversationId]: {
|
[conversationId]: {
|
||||||
...action.payload.callLinkState,
|
...action.payload.callLinkState,
|
||||||
|
roomId:
|
||||||
|
callLinks[conversationId]?.roomId ??
|
||||||
|
action.payload.callLinkRoomId,
|
||||||
rootKey:
|
rootKey:
|
||||||
callLinks[conversationId]?.rootKey ??
|
callLinks[conversationId]?.rootKey ??
|
||||||
action.payload.callLinkRootKey,
|
action.payload.callLinkRootKey,
|
||||||
|
@ -3355,13 +3347,14 @@ export function reducer(
|
||||||
|
|
||||||
if (action.type === HANDLE_CALL_LINK_UPDATE) {
|
if (action.type === HANDLE_CALL_LINK_UPDATE) {
|
||||||
const { callLinks } = state;
|
const { callLinks } = state;
|
||||||
const { roomId, callLinkDetails } = action.payload;
|
const { callLink } = action.payload;
|
||||||
|
const { roomId } = callLink;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
callLinks: {
|
callLinks: {
|
||||||
...callLinks,
|
...callLinks,
|
||||||
[roomId]: callLinkDetails,
|
[roomId]: callLink,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,17 +79,13 @@ export type CallLinkSelectorType = (roomId: string) => CallLinkType | undefined;
|
||||||
export const getCallLinkSelector = createSelector(
|
export const getCallLinkSelector = createSelector(
|
||||||
getCallLinksByRoomId,
|
getCallLinksByRoomId,
|
||||||
(callLinksByRoomId: CallLinksByRoomIdType): CallLinkSelectorType =>
|
(callLinksByRoomId: CallLinksByRoomIdType): CallLinkSelectorType =>
|
||||||
(roomId: string): CallLinkType | undefined => {
|
(roomId: string): CallLinkType | undefined =>
|
||||||
const callLinkState = getOwn(callLinksByRoomId, roomId);
|
getOwn(callLinksByRoomId, roomId)
|
||||||
if (!callLinkState) {
|
);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
export const getAllCallLinks = createSelector(
|
||||||
roomId,
|
getCallLinksByRoomId,
|
||||||
...callLinkState,
|
(lookup): Array<CallLinkType> => Object.values(lookup)
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export type CallSelectorType = (
|
export type CallSelectorType = (
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { useCallingActions } from '../ducks/calling';
|
||||||
import {
|
import {
|
||||||
getActiveCallState,
|
getActiveCallState,
|
||||||
getAdhocCallSelector,
|
getAdhocCallSelector,
|
||||||
|
getAllCallLinks,
|
||||||
getCallSelector,
|
getCallSelector,
|
||||||
getCallLinkSelector,
|
getCallLinkSelector,
|
||||||
} from '../selectors/calling';
|
} from '../selectors/calling';
|
||||||
|
@ -36,41 +37,67 @@ import { getCallHistoryEdition } from '../selectors/callHistory';
|
||||||
import { getHasPendingUpdate } from '../selectors/updates';
|
import { getHasPendingUpdate } from '../selectors/updates';
|
||||||
import { getHasAnyFailedStorySends } from '../selectors/stories';
|
import { getHasAnyFailedStorySends } from '../selectors/stories';
|
||||||
import { getOtherTabsUnreadStats } from '../selectors/nav';
|
import { getOtherTabsUnreadStats } from '../selectors/nav';
|
||||||
|
import type { CallLinkType } from '../../types/CallLink';
|
||||||
|
import { filterCallLinks } from '../../util/filterCallLinks';
|
||||||
|
|
||||||
function getCallHistoryFilter(
|
function getCallHistoryFilter({
|
||||||
allConversations: Array<ConversationType>,
|
allCallLinks,
|
||||||
regionCode: string | undefined,
|
allConversations,
|
||||||
options: CallHistoryFilterOptions
|
regionCode,
|
||||||
): CallHistoryFilter | null {
|
options,
|
||||||
|
}: {
|
||||||
|
allConversations: Array<ConversationType>;
|
||||||
|
allCallLinks: Array<CallLinkType>;
|
||||||
|
regionCode: string | undefined;
|
||||||
|
options: CallHistoryFilterOptions;
|
||||||
|
}): CallHistoryFilter | null {
|
||||||
|
const { status } = options;
|
||||||
const query = options.query.normalize().trim();
|
const query = options.query.normalize().trim();
|
||||||
|
|
||||||
if (query !== '') {
|
if (query === '') {
|
||||||
const currentConversations = allConversations.filter(conversation => {
|
|
||||||
return conversation.removalStage == null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredConversations = filterAndSortConversations(
|
|
||||||
currentConversations,
|
|
||||||
query,
|
|
||||||
regionCode
|
|
||||||
);
|
|
||||||
|
|
||||||
// If there are no matching conversations, then no calls will match.
|
|
||||||
if (filteredConversations.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: options.status,
|
status,
|
||||||
conversationIds: filteredConversations.map(conversation => {
|
callLinkRoomIds: null,
|
||||||
return conversation.id;
|
conversationIds: null,
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let callLinkRoomIds = null;
|
||||||
|
let conversationIds = null;
|
||||||
|
|
||||||
|
const currentConversations = allConversations.filter(conversation => {
|
||||||
|
return conversation.removalStage == null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredConversations = filterAndSortConversations(
|
||||||
|
currentConversations,
|
||||||
|
query,
|
||||||
|
regionCode
|
||||||
|
);
|
||||||
|
|
||||||
|
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 {
|
return {
|
||||||
status: options.status,
|
status,
|
||||||
conversationIds: null,
|
callLinkRoomIds,
|
||||||
|
conversationIds,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +126,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
|
||||||
const { savePreferredLeftPaneWidth, toggleNavTabsCollapse } =
|
const { savePreferredLeftPaneWidth, toggleNavTabsCollapse } =
|
||||||
useItemsActions();
|
useItemsActions();
|
||||||
|
|
||||||
|
const allCallLinks = useSelector(getAllCallLinks);
|
||||||
const allConversations = useSelector(getAllConversations);
|
const allConversations = useSelector(getAllConversations);
|
||||||
const regionCode = useSelector(getRegionCode);
|
const regionCode = useSelector(getRegionCode);
|
||||||
const getConversation = useSelector(getConversationSelector);
|
const getConversation = useSelector(getConversationSelector);
|
||||||
|
@ -129,11 +157,12 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
|
||||||
|
|
||||||
const getCallHistoryGroupsCount = useCallback(
|
const getCallHistoryGroupsCount = useCallback(
|
||||||
async (options: CallHistoryFilterOptions) => {
|
async (options: CallHistoryFilterOptions) => {
|
||||||
const callHistoryFilter = getCallHistoryFilter(
|
const callHistoryFilter = getCallHistoryFilter({
|
||||||
|
allCallLinks,
|
||||||
allConversations,
|
allConversations,
|
||||||
regionCode,
|
regionCode,
|
||||||
options
|
options,
|
||||||
);
|
});
|
||||||
if (callHistoryFilter == null) {
|
if (callHistoryFilter == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -142,7 +171,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
|
||||||
);
|
);
|
||||||
return count;
|
return count;
|
||||||
},
|
},
|
||||||
[allConversations, regionCode]
|
[allCallLinks, allConversations, regionCode]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getCallHistoryGroups = useCallback(
|
const getCallHistoryGroups = useCallback(
|
||||||
|
@ -150,11 +179,12 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
|
||||||
options: CallHistoryFilterOptions,
|
options: CallHistoryFilterOptions,
|
||||||
pagination: CallHistoryPagination
|
pagination: CallHistoryPagination
|
||||||
) => {
|
) => {
|
||||||
const callHistoryFilter = getCallHistoryFilter(
|
const callHistoryFilter = getCallHistoryFilter({
|
||||||
|
allCallLinks,
|
||||||
allConversations,
|
allConversations,
|
||||||
regionCode,
|
regionCode,
|
||||||
options
|
options,
|
||||||
);
|
});
|
||||||
if (callHistoryFilter == null) {
|
if (callHistoryFilter == null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -164,7 +194,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
|
||||||
);
|
);
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
[allConversations, regionCode]
|
[allCallLinks, allConversations, regionCode]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -11,15 +11,15 @@ export const FAKE_CALL_LINK: CallLinkType = {
|
||||||
name: 'Fun Link',
|
name: 'Fun Link',
|
||||||
restrictions: CallLinkRestrictions.None,
|
restrictions: CallLinkRestrictions.None,
|
||||||
revoked: false,
|
revoked: false,
|
||||||
roomId: 'c097eb04cc278d6bc7ed9fb2ddeac00dc9646ae6ddb38513dad9a8a4fe3c38f4',
|
roomId: 'd517b48dd118bee24068d4938886c8abe192706d84936d52594a9157189d2759',
|
||||||
rootKey: 'bpmc-mrgn-hntf-mffd-mndd-xbxk-zmgq-qszg',
|
rootKey: 'dxbb-xfqz-xkgp-nmrx-bpqn-ptkb-spdt-pdgt',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Please set expiration
|
// Please set expiration
|
||||||
export const FAKE_CALL_LINK_WITH_ADMIN_KEY: CallLinkType = {
|
export const FAKE_CALL_LINK_WITH_ADMIN_KEY: CallLinkType = {
|
||||||
adminKey: 'xXPI77e6MoVHYREW8iKYmQ==',
|
adminKey: 'xXPI77e6MoVHYREW8iKYmQ==',
|
||||||
expiration: Date.now() + MONTH, // set me
|
expiration: Date.now() + MONTH, // set me
|
||||||
name: 'Fun Link',
|
name: 'Admin Link',
|
||||||
restrictions: CallLinkRestrictions.None,
|
restrictions: CallLinkRestrictions.None,
|
||||||
revoked: false,
|
revoked: false,
|
||||||
roomId: 'c097eb04cc278d6bc7ed9fb2ddeac00dc9646ae6ddb38513dad9a8a4fe3c38f4',
|
roomId: 'c097eb04cc278d6bc7ed9fb2ddeac00dc9646ae6ddb38513dad9a8a4fe3c38f4',
|
||||||
|
|
|
@ -23,11 +23,16 @@ import {
|
||||||
} from '../../types/CallDisposition';
|
} from '../../types/CallDisposition';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import type { ConversationAttributesType } from '../../model-types';
|
import type { ConversationAttributesType } from '../../model-types';
|
||||||
|
import {
|
||||||
|
FAKE_CALL_LINK,
|
||||||
|
FAKE_CALL_LINK_WITH_ADMIN_KEY,
|
||||||
|
} from '../../test-both/helpers/fakeCallLink';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
removeAll,
|
removeAll,
|
||||||
getCallHistoryGroups,
|
getCallHistoryGroups,
|
||||||
getCallHistoryGroupsCount,
|
getCallHistoryGroupsCount,
|
||||||
|
insertCallLink,
|
||||||
saveCallHistory,
|
saveCallHistory,
|
||||||
saveConversation,
|
saveConversation,
|
||||||
} = dataInterface;
|
} = dataInterface;
|
||||||
|
@ -90,7 +95,11 @@ describe('sql/getCallHistoryGroups', () => {
|
||||||
await saveCallHistory(call2);
|
await saveCallHistory(call2);
|
||||||
|
|
||||||
const groups = await getCallHistoryGroups(
|
const groups = await getCallHistoryGroups(
|
||||||
{ status: CallHistoryFilterStatus.All, conversationIds: null },
|
{
|
||||||
|
status: CallHistoryFilterStatus.All,
|
||||||
|
callLinkRoomIds: null,
|
||||||
|
conversationIds: null,
|
||||||
|
},
|
||||||
{ offset: 0, limit: 0 }
|
{ offset: 0, limit: 0 }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -121,7 +130,11 @@ describe('sql/getCallHistoryGroups', () => {
|
||||||
await saveCallHistory(call2);
|
await saveCallHistory(call2);
|
||||||
|
|
||||||
const groups = await getCallHistoryGroups(
|
const groups = await getCallHistoryGroups(
|
||||||
{ status: CallHistoryFilterStatus.All, conversationIds: null },
|
{
|
||||||
|
status: CallHistoryFilterStatus.All,
|
||||||
|
callLinkRoomIds: null,
|
||||||
|
conversationIds: null,
|
||||||
|
},
|
||||||
{ offset: 0, limit: 0 }
|
{ offset: 0, limit: 0 }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -156,7 +169,11 @@ describe('sql/getCallHistoryGroups', () => {
|
||||||
await saveCallHistory(call4);
|
await saveCallHistory(call4);
|
||||||
|
|
||||||
const groups = await getCallHistoryGroups(
|
const groups = await getCallHistoryGroups(
|
||||||
{ status: CallHistoryFilterStatus.All, conversationIds: null },
|
{
|
||||||
|
status: CallHistoryFilterStatus.All,
|
||||||
|
callLinkRoomIds: null,
|
||||||
|
conversationIds: null,
|
||||||
|
},
|
||||||
{ offset: 0, limit: 0 }
|
{ offset: 0, limit: 0 }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -218,6 +235,7 @@ describe('sql/getCallHistoryGroups', () => {
|
||||||
const groups = await getCallHistoryGroups(
|
const groups = await getCallHistoryGroups(
|
||||||
{
|
{
|
||||||
status: CallHistoryFilterStatus.All,
|
status: CallHistoryFilterStatus.All,
|
||||||
|
callLinkRoomIds: null,
|
||||||
conversationIds: [conversation1.id],
|
conversationIds: [conversation1.id],
|
||||||
},
|
},
|
||||||
{ offset: 0, limit: 0 }
|
{ offset: 0, limit: 0 }
|
||||||
|
@ -230,6 +248,7 @@ describe('sql/getCallHistoryGroups', () => {
|
||||||
const groups = await getCallHistoryGroups(
|
const groups = await getCallHistoryGroups(
|
||||||
{
|
{
|
||||||
status: CallHistoryFilterStatus.All,
|
status: CallHistoryFilterStatus.All,
|
||||||
|
callLinkRoomIds: null,
|
||||||
conversationIds: [conversation2.id],
|
conversationIds: [conversation2.id],
|
||||||
},
|
},
|
||||||
{ offset: 0, limit: 0 }
|
{ offset: 0, limit: 0 }
|
||||||
|
@ -268,6 +287,7 @@ describe('sql/getCallHistoryGroups', () => {
|
||||||
const groups = await getCallHistoryGroups(
|
const groups = await getCallHistoryGroups(
|
||||||
{
|
{
|
||||||
status: CallHistoryFilterStatus.All,
|
status: CallHistoryFilterStatus.All,
|
||||||
|
callLinkRoomIds: null,
|
||||||
conversationIds: [conversation.id],
|
conversationIds: [conversation.id],
|
||||||
},
|
},
|
||||||
{ offset: 0, limit: 0 }
|
{ offset: 0, limit: 0 }
|
||||||
|
@ -310,7 +330,11 @@ describe('sql/getCallHistoryGroups', () => {
|
||||||
await saveCallHistory(call2);
|
await saveCallHistory(call2);
|
||||||
|
|
||||||
const groups = await getCallHistoryGroups(
|
const groups = await getCallHistoryGroups(
|
||||||
{ status: CallHistoryFilterStatus.Missed, conversationIds: null },
|
{
|
||||||
|
status: CallHistoryFilterStatus.Missed,
|
||||||
|
callLinkRoomIds: null,
|
||||||
|
conversationIds: null,
|
||||||
|
},
|
||||||
{ offset: 0, limit: 0 }
|
{ offset: 0, limit: 0 }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -328,7 +352,7 @@ describe('sql/getCallHistoryGroups', () => {
|
||||||
ringerId: null,
|
ringerId: null,
|
||||||
mode: CallMode.Adhoc,
|
mode: CallMode.Adhoc,
|
||||||
type: CallType.Adhoc,
|
type: CallType.Adhoc,
|
||||||
direction: CallDirection.Outgoing,
|
direction: CallDirection.Incoming,
|
||||||
timestamp,
|
timestamp,
|
||||||
status: AdhocCallStatus.Joined,
|
status: AdhocCallStatus.Joined,
|
||||||
};
|
};
|
||||||
|
@ -341,12 +365,127 @@ describe('sql/getCallHistoryGroups', () => {
|
||||||
await saveCallHistory(call2);
|
await saveCallHistory(call2);
|
||||||
|
|
||||||
const groups = await getCallHistoryGroups(
|
const groups = await getCallHistoryGroups(
|
||||||
{ status: CallHistoryFilterStatus.All, conversationIds: null },
|
{
|
||||||
|
status: CallHistoryFilterStatus.All,
|
||||||
|
callLinkRoomIds: null,
|
||||||
|
conversationIds: null,
|
||||||
|
},
|
||||||
{ offset: 0, limit: 0 }
|
{ offset: 0, limit: 0 }
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.deepEqual(groups, [toAdhocGroup(call2)]);
|
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', () => {
|
describe('sql/getCallHistoryGroupsCount', () => {
|
||||||
|
@ -383,6 +522,7 @@ describe('sql/getCallHistoryGroupsCount', () => {
|
||||||
|
|
||||||
const result = await getCallHistoryGroupsCount({
|
const result = await getCallHistoryGroupsCount({
|
||||||
status: CallHistoryFilterStatus.All,
|
status: CallHistoryFilterStatus.All,
|
||||||
|
callLinkRoomIds: null,
|
||||||
conversationIds: null,
|
conversationIds: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -419,6 +559,7 @@ describe('sql/getCallHistoryGroupsCount', () => {
|
||||||
|
|
||||||
const result = await getCallHistoryGroupsCount({
|
const result = await getCallHistoryGroupsCount({
|
||||||
status: CallHistoryFilterStatus.All,
|
status: CallHistoryFilterStatus.All,
|
||||||
|
callLinkRoomIds: null,
|
||||||
conversationIds: null,
|
conversationIds: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1363,12 +1363,12 @@ describe('calling duck', () => {
|
||||||
sinon.assert.calledWith(dispatch, {
|
sinon.assert.calledWith(dispatch, {
|
||||||
type: 'calling/HANDLE_CALL_LINK_UPDATE',
|
type: 'calling/HANDLE_CALL_LINK_UPDATE',
|
||||||
payload: {
|
payload: {
|
||||||
roomId,
|
callLink: {
|
||||||
callLinkDetails: {
|
|
||||||
name,
|
name,
|
||||||
restrictions,
|
restrictions,
|
||||||
expiration,
|
expiration,
|
||||||
revoked,
|
revoked,
|
||||||
|
roomId,
|
||||||
rootKey,
|
rootKey,
|
||||||
adminKey,
|
adminKey,
|
||||||
},
|
},
|
||||||
|
@ -1383,12 +1383,12 @@ describe('calling duck', () => {
|
||||||
sinon.assert.calledWith(dispatch, {
|
sinon.assert.calledWith(dispatch, {
|
||||||
type: 'calling/HANDLE_CALL_LINK_UPDATE',
|
type: 'calling/HANDLE_CALL_LINK_UPDATE',
|
||||||
payload: {
|
payload: {
|
||||||
roomId,
|
callLink: {
|
||||||
callLinkDetails: {
|
|
||||||
name,
|
name,
|
||||||
restrictions,
|
restrictions,
|
||||||
expiration,
|
expiration,
|
||||||
revoked,
|
revoked,
|
||||||
|
roomId,
|
||||||
rootKey,
|
rootKey,
|
||||||
adminKey: 'banana',
|
adminKey: 'banana',
|
||||||
},
|
},
|
||||||
|
@ -1451,6 +1451,7 @@ describe('calling duck', () => {
|
||||||
payload: {
|
payload: {
|
||||||
...callLobbyData,
|
...callLobbyData,
|
||||||
callLinkState,
|
callLinkState,
|
||||||
|
callLinkRoomId: roomId,
|
||||||
callLinkRootKey: rootKey,
|
callLinkRootKey: rootKey,
|
||||||
conversationId: roomId,
|
conversationId: roomId,
|
||||||
isConversationTooBigToRing: false,
|
isConversationTooBigToRing: false,
|
||||||
|
|
|
@ -133,6 +133,7 @@ export type CallHistoryFilterOptions = Readonly<{
|
||||||
|
|
||||||
export type CallHistoryFilter = Readonly<{
|
export type CallHistoryFilter = Readonly<{
|
||||||
status: CallHistoryFilterStatus;
|
status: CallHistoryFilterStatus;
|
||||||
|
callLinkRoomIds: ReadonlyArray<string> | null;
|
||||||
conversationIds: 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