Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: poll edge cases #2768

Merged
merged 10 commits into from
Nov 8, 2024
8 changes: 1 addition & 7 deletions package/src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -852,13 +852,7 @@ const ChannelWithContext = <
setThreadMessages(updatedThreadMessages);
}

if (
channel &&
thread?.id &&
event.message?.id === thread.id &&
!threadInstance &&
!thread.poll_id
) {
if (channel && thread?.id && event.message?.id === thread.id && !threadInstance) {
const updatedThread = channel.state.formatMessage(event.message);
setThread(updatedThread);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ export type LatestMessagePreview<
export type LatestMessagePreviewSelectorReturnType = {
createdBy?: UserResponse | null;
latestVotesByOption?: Record<string, PollVote[]>;
name?: string;
};

const selector = <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(
nextValue: PollState<StreamChatGenerics>,
): LatestMessagePreviewSelectorReturnType => ({
createdBy: nextValue.created_by,
latestVotesByOption: nextValue.latest_votes_by_option,
name: nextValue.name,
});

const getMessageSenderName = <
Expand Down Expand Up @@ -145,8 +147,8 @@ const getLatestMessageDisplayText = <
{ bold: false, text: t('🏙 Attachment...') },
];
}
if (message.poll && pollState) {
const { createdBy, latestVotesByOption } = pollState;
if (message.poll_id && pollState) {
const { createdBy, latestVotesByOption, name } = pollState;
let latestVotes;
if (latestVotesByOption) {
latestVotes = Object.values(latestVotesByOption)
Expand All @@ -161,7 +163,7 @@ const getLatestMessageDisplayText = <
}
const previewMessage = `${
client.userID === previewUser?.id ? 'You' : previewUser?.name
} ${previewAction}: ${message.poll.name}`;
} ${previewAction}: ${name}`;
return [
{ bold: false, text: '📊 ' },
{ bold: false, text: previewMessage },
Expand Down Expand Up @@ -310,7 +312,7 @@ export const useLatestMessagePreview = <
const poll = client.polls.fromState(pollId);
const pollState: LatestMessagePreviewSelectorReturnType =
useStateStore(poll?.state, selector) ?? {};
const { createdBy, latestVotesByOption } = pollState;
const { createdBy, latestVotesByOption, name } = pollState;

useEffect(
() =>
Expand All @@ -325,7 +327,15 @@ export const useLatestMessagePreview = <
}),
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[channelLastMessageString, forceUpdate, readEvents, readStatus, latestVotesByOption, createdBy],
[
channelLastMessageString,
forceUpdate,
readEvents,
readStatus,
latestVotesByOption,
createdBy,
name,
],
);

return latestMessagePreview;
Expand Down
5 changes: 4 additions & 1 deletion package/src/components/Chat/hooks/handleEventToSyncDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,14 @@ export const handleEventToSyncDB = <
'poll.vote_removed',
].includes(type)
) {
const poll = event.poll;
const { poll, poll_vote, type } = event;
if (poll) {
return updatePollMessage({
eventType: type,
flush,
poll,
poll_vote,
userID: client?.userID || '',
});
}
}
Expand Down
7 changes: 6 additions & 1 deletion package/src/components/Poll/CreatePollContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ export const CreatePollContent = () => {
{t<string>('Multiple answers')}
</Text>
<Switch
onValueChange={() => setMultipleAnswersAllowed(!multipleAnswersAllowed)}
onValueChange={() => {
if (multipleAnswersAllowed) {
setMaxVotesPerPersonEnabled(false);
}
setMultipleAnswersAllowed(!multipleAnswersAllowed);
}}
value={multipleAnswersAllowed}
/>
</View>
Expand Down
23 changes: 20 additions & 3 deletions package/src/components/Reply/Reply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import dayjs from 'dayjs';

import merge from 'lodash/merge';

import type { Attachment } from 'stream-chat';
import type { Attachment, PollState } from 'stream-chat';

import { useChatContext } from '../../contexts';
import { useMessageContext } from '../../contexts/messageContext/MessageContext';
import {
MessageInputContext,
Expand All @@ -22,6 +23,7 @@ import {
TranslationContextValue,
useTranslationContext,
} from '../../contexts/translationContext/TranslationContext';
import { useStateStore } from '../../hooks';
import { DefaultStreamChatGenerics, FileTypes } from '../../types/types';
import { getResizedImageUrl } from '../../utils/getResizedImageUrl';
import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle';
Expand All @@ -31,6 +33,7 @@ import { FileIcon as FileIconDefault } from '../Attachment/FileIcon';
import { VideoThumbnail } from '../Attachment/VideoThumbnail';
import { MessageAvatar as MessageAvatarDefault } from '../Message/MessageSimple/MessageAvatar';
import { MessageTextContainer } from '../Message/MessageSimple/MessageTextContainer';
import { MessageType } from '../MessageList/hooks/useMessageList';

const styles = StyleSheet.create({
container: {
Expand Down Expand Up @@ -72,6 +75,16 @@ const styles = StyleSheet.create({
},
});

export type ReplySelectorReturnType = {
name?: string;
};

const selector = <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(
nextValue: PollState<StreamChatGenerics>,
): ReplySelectorReturnType => ({
name: nextValue.name,
});

type ReplyPropsWithContext<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
> = Pick<MessageInputContextValue<StreamChatGenerics>, 'quotedMessage'> &
Expand Down Expand Up @@ -134,6 +147,7 @@ const ReplyWithContext = <
>(
props: ReplyPropsWithContext<StreamChatGenerics>,
) => {
const { client } = useChatContext();
const {
attachmentSize = 40,
FileAttachmentIcon,
Expand Down Expand Up @@ -167,6 +181,9 @@ const ReplyWithContext = <
},
} = useTheme();

const poll = client.polls.fromState((quotedMessage as MessageType)?.poll_id ?? '');
const { name: pollName }: ReplySelectorReturnType = useStateStore(poll?.state, selector) ?? {};

const messageText = typeof quotedMessage === 'boolean' ? '' : quotedMessage.text || '';

const emojiOnlyText = useMemo(() => {
Expand Down Expand Up @@ -262,8 +279,8 @@ const ReplyWithContext = <
text:
quotedMessage.type === 'deleted'
? `_${t('Message deleted')}_`
: quotedMessage.poll
? `📊 ${quotedMessage.poll.name}`
: pollName
? `📊 ${pollName}`
: quotedMessage.text
? quotedMessage.text.length > 170
? `${quotedMessage.text.slice(0, 170)}...`
Expand Down
2 changes: 1 addition & 1 deletion package/src/store/QuickSqliteClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import type { PreparedQueries, Table } from './types';
*
*/
export class QuickSqliteClient {
static dbVersion = 6;
static dbVersion = 7;

static dbName = DB_NAME;
static dbLocation = DB_LOCATION;
Expand Down
19 changes: 18 additions & 1 deletion package/src/store/apis/getChannelMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import type { DefaultStreamChatGenerics } from '../../types/types';
import { isBlockedMessage } from '../../utils/utils';
import { mapStorableToMessage } from '../mappers/mapStorableToMessage';
import { QuickSqliteClient } from '../QuickSqliteClient';
import type { TableRowJoinedUser } from '../types';
import { createSelectQuery } from '../sqlite-utils/createSelectQuery';
import type { TableRow, TableRowJoinedUser } from '../types';

export const getChannelMessages = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
Expand All @@ -35,6 +36,21 @@ export const getChannelMessages = <
}
messageIdVsReactions[reaction.messageId].push(reaction);
});
const messageIdsVsPolls: Record<string, TableRow<'poll'>> = {};
const pollsById: Record<string, TableRow<'poll'>> = {};
const messagesWithPolls = messageRows.filter((message) => !!message.poll_id);
const polls = QuickSqliteClient.executeSql.apply(
null,
createSelectQuery('poll', ['*'], {
id: messagesWithPolls.map((message) => message.poll_id),
}),
);
polls.forEach((poll) => {
pollsById[poll.id] = poll;
});
messagesWithPolls.forEach((message) => {
messageIdsVsPolls[message.poll_id] = pollsById[message.poll_id];
});

// Populate the messages.
const cidVsMessages: Record<string, MessageResponse<StreamChatGenerics>[]> = {};
Expand All @@ -48,6 +64,7 @@ export const getChannelMessages = <
mapStorableToMessage<StreamChatGenerics>({
currentUserId,
messageRow: m,
pollRow: messageIdsVsPolls[m.poll_id],
reactionRows: messageIdVsReactions[m.id],
}),
);
Expand Down
4 changes: 3 additions & 1 deletion package/src/store/apis/updateMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export const updateMessage = ({
return queries;
}

const storableMessage = mapMessageToStorable(message);
const storableMessage = mapMessageToStorable({
...message,
});

queries.push(
createUpdateQuery('messages', storableMessage, {
Expand Down
54 changes: 40 additions & 14 deletions package/src/store/apis/updatePollMessage.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,67 @@
import type { PollResponse } from 'stream-chat';
import { isVoteAnswer, PollAnswer, PollResponse, PollVote } from 'stream-chat';

import { DefaultStreamChatGenerics } from '../../types/types';
import { mapPollToStorable } from '../mappers/mapPollToStorable';
import { mapStorableToPoll } from '../mappers/mapStorableToPoll';
import { QuickSqliteClient } from '../QuickSqliteClient';
import { createSelectQuery } from '../sqlite-utils/createSelectQuery';
import { createUpdateQuery } from '../sqlite-utils/createUpdateQuery';
import type { PreparedQueries } from '../types';

export const updatePollMessage = ({
export const updatePollMessage = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
>({
eventType,
flush = true,
poll,
poll_vote,
userID,
}: {
poll: PollResponse;
eventType: string;
poll: PollResponse<StreamChatGenerics>;
userID: string;
flush?: boolean;
poll_vote?: PollVote<StreamChatGenerics> | PollAnswer<StreamChatGenerics>;
}) => {
const queries: PreparedQueries[] = [];

const messagesWithPoll = QuickSqliteClient.executeSql.apply(
const pollsFromDB = QuickSqliteClient.executeSql.apply(
null,
createSelectQuery('messages', ['*'], {
poll_id: poll.id,
createSelectQuery('poll', ['*'], {
id: poll.id,
}),
);

for (const message of messagesWithPoll) {
const storablePoll = JSON.stringify({
for (const pollFromDB of pollsFromDB) {
const serializedPoll = mapStorableToPoll(pollFromDB);
const { latest_answers = [], own_votes = [] } = serializedPoll;
let newOwnVotes = own_votes;
if (poll_vote && poll_vote.user?.id === userID) {
newOwnVotes =
eventType === 'poll.vote_removed'
? newOwnVotes.filter((vote) => vote.id !== poll_vote.id)
: [poll_vote, ...newOwnVotes.filter((vote) => vote.id !== poll_vote.id)];
}
let newLatestAnswers = latest_answers;
if (poll_vote && isVoteAnswer(poll_vote)) {
newLatestAnswers =
eventType === 'poll.vote_removed'
? newLatestAnswers.filter((answer) => answer.id !== poll_vote?.id)
: [poll_vote, ...newLatestAnswers.filter((answer) => answer.id !== poll_vote?.id)];
}

const storablePoll = mapPollToStorable({
...poll,
latest_votes: message.poll.latest_votes,
own_votes: message.poll.own_votes,
latest_answers: newLatestAnswers,
own_votes: newOwnVotes,
});
const storableMessage = { ...message, poll: storablePoll };

queries.push(
createUpdateQuery('messages', storableMessage, {
id: message.id,
createUpdateQuery('poll', storablePoll, {
id: poll.id,
}),
);
QuickSqliteClient.logger?.('info', 'updatePoll', {
message: storableMessage,
poll: storablePoll,
});
}
Expand Down
7 changes: 7 additions & 0 deletions package/src/store/apis/upsertMessages.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { MessageResponse } from 'stream-chat';

import { mapMessageToStorable } from '../mappers/mapMessageToStorable';
import { mapPollToStorable } from '../mappers/mapPollToStorable';
import { mapReactionToStorable } from '../mappers/mapReactionToStorable';
import { mapUserToStorable } from '../mappers/mapUserToStorable';
import { QuickSqliteClient } from '../QuickSqliteClient';
Expand All @@ -16,6 +17,7 @@ export const upsertMessages = ({
const storableMessages: Array<ReturnType<typeof mapMessageToStorable>> = [];
const storableUsers: Array<ReturnType<typeof mapUserToStorable>> = [];
const storableReactions: Array<ReturnType<typeof mapReactionToStorable>> = [];
const storablePolls: Array<ReturnType<typeof mapPollToStorable>> = [];

messages?.forEach((message: MessageResponse) => {
storableMessages.push(mapMessageToStorable(message));
Expand All @@ -28,6 +30,9 @@ export const upsertMessages = ({
}
storableReactions.push(mapReactionToStorable(r));
});
if (message.poll) {
storablePolls.push(mapPollToStorable(message.poll));
}
});

const finalQueries = [
Expand All @@ -36,11 +41,13 @@ export const upsertMessages = ({
...storableReactions.map((storableReaction) =>
createUpsertQuery('reactions', storableReaction),
),
...storablePolls.map((storablePoll) => createUpsertQuery('poll', storablePoll)),
];

QuickSqliteClient.logger?.('info', 'upsertMessages', {
flush,
messages: storableMessages,
polls: storablePolls,
reactions: storableReactions,
users: storableUsers,
});
Expand Down
2 changes: 1 addition & 1 deletion package/src/store/mappers/mapMessageToStorable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const mapMessageToStorable = (
message_text_updated_at,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
own_reactions,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
poll,
poll_id,
reaction_groups,
Expand All @@ -36,7 +37,6 @@ export const mapMessageToStorable = (
extraData: JSON.stringify(extraData),
id,
messageTextUpdatedAt: mapDateTimeToStorable(message_text_updated_at),
poll: JSON.stringify(poll),
poll_id: poll_id || '',
reactionGroups: JSON.stringify(reaction_groups),
text,
Expand Down
Loading
Loading