feat: Disable OTR messages selection when exporting messages (#34220)

Co-authored-by: gabriellsh <40830821+gabriellsh@users.noreply.github.com>
pull/34165/head^2
Douglas Fabris 1 year ago committed by GitHub
parent 24170d938c
commit ff04c19f2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/pretty-islands-wink.md
  2. 8
      apps/meteor/client/components/message/variants/RoomMessage.tsx
  3. 10
      apps/meteor/client/components/message/variants/ThreadMessagePreview.tsx
  4. 6
      apps/meteor/client/views/room/MessageList/contexts/SelectedMessagesContext.tsx
  5. 7
      apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx
  6. 46
      apps/meteor/tests/e2e/otr.spec.ts
  7. 4
      apps/meteor/tests/e2e/page-objects/fragments/home-content.ts
  8. 21
      apps/meteor/tests/e2e/page-objects/fragments/home-flextab-otr.ts
  9. 4
      apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts
  10. 1
      packages/i18n/src/locales/en.i18n.json

@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': minor
---
Disables OTR messages selection when exporting messages

@ -55,8 +55,10 @@ const RoomMessage = ({
const { openUserCard, triggerProps } = useUserCard();
const selecting = useIsSelecting();
const isOTRMessage = message.t === 'otr' || message.t === 'otr-ack';
const toggleSelected = useToggleSelect(message._id);
const selected = useIsSelectedMessage(message._id);
const selected = useIsSelectedMessage(message._id, isOTRMessage);
useCountSelected();
@ -70,7 +72,7 @@ const RoomMessage = ({
aria-roledescription={t('message')}
tabIndex={0}
aria-labelledby={`${message._id}-displayName ${message._id}-time ${message._id}-content ${message._id}-read-status`}
onClick={selecting ? toggleSelected : undefined}
onClick={selecting && !isOTRMessage ? toggleSelected : undefined}
isSelected={selected}
isEditing={editing}
isPending={message.temp}
@ -99,7 +101,7 @@ const RoomMessage = ({
{...triggerProps}
/>
)}
{selecting && <CheckBox checked={selected} onChange={toggleSelected} />}
{selecting && <CheckBox disabled={isOTRMessage} checked={selected} onChange={toggleSelected} />}
{sequential && <StatusIndicators message={message} />}
</MessageLeftContainer>
<MessageContainer>

@ -45,8 +45,10 @@ const ThreadMessagePreview = ({ message, showUserAvatar, sequential, ...props }:
const { t } = useTranslation();
const isSelecting = useIsSelecting();
const isOTRMessage = message.t === 'otr' || message.t === 'otr-ack';
const toggleSelected = useToggleSelect(message._id);
const isSelected = useIsSelectedMessage(message._id);
const isSelected = useIsSelectedMessage(message._id, isOTRMessage);
useCountSelected();
const messageType = parentMessage.isSuccess ? MessageTypes.getType(parentMessage.data) : null;
@ -65,6 +67,10 @@ const ThreadMessagePreview = ({ message, showUserAvatar, sequential, ...props }:
return goToThread({ rid: message.rid, tmid: message.tmid, msg: message._id });
}
if (isOTRMessage) {
return;
}
return toggleSelected();
};
@ -117,7 +123,7 @@ const ThreadMessagePreview = ({ message, showUserAvatar, sequential, ...props }:
size='x18'
/>
)}
{isSelecting && <CheckBox checked={isSelected} onChange={toggleSelected} />}
{isSelecting && <CheckBox disabled={isOTRMessage} checked={isSelected} onChange={toggleSelected} />}
</ThreadMessageLeftContainer>
<ThreadMessageContainer>
<ThreadMessageBody>

@ -11,7 +11,7 @@ export const SelectedMessageContext = createContext({
selectedMessageStore,
} as SelectMessageContextValue);
export const useIsSelectedMessage = (mid: string): boolean => {
export const useIsSelectedMessage = (mid: string, omit?: boolean): boolean => {
const { selectedMessageStore } = useContext(SelectedMessageContext);
const subscribe = useCallback(
@ -24,14 +24,14 @@ export const useIsSelectedMessage = (mid: string): boolean => {
const isSelected = useSyncExternalStore(subscribe, getSnapshot);
useEffect(() => {
if (isSelected) {
if (isSelected || omit) {
return;
}
selectedMessageStore.addAvailableMessage(mid);
return () => selectedMessageStore.removeAvailableMessage(mid);
}, [mid, selectedMessageStore, isSelected]);
}, [mid, selectedMessageStore, isSelected, omit]);
return isSelected;
};

@ -176,6 +176,13 @@ const ExportMessages = () => {
<ContextualbarScrollableContent>
<form ref={formFocus} tabIndex={-1} aria-labelledby={`${formId}-title`} id={formId} onSubmit={handleSubmit(handleExport)}>
<FieldGroup>
{room.createdOTR && (
<Field>
<Callout role='alert' type='warning'>
{t('OTR_messages_cannot_be_exported')}
</Callout>
</Field>
)}
<Field>
<FieldLabel htmlFor={methodField}>{t('Method')}</FieldLabel>
<FieldRow>

@ -0,0 +1,46 @@
import { Users } from './fixtures/userStates';
import { HomeChannel } from './page-objects';
import { createDirectMessage } from './utils';
import { test, expect } from './utils/test';
test.use({ storageState: Users.admin.state });
test.describe.serial('OTR', () => {
let poHomeChannel: HomeChannel;
test.beforeEach(async ({ page, api }) => {
await createDirectMessage(api);
poHomeChannel = new HomeChannel(page);
await page.goto('/home');
});
test('should not allow export OTR messages', async ({ browser }) => {
const user1Page = await browser.newPage({ storageState: Users.user1.state });
const user1Channel = new HomeChannel(user1Page);
await test.step('log in user1', async () => {
await user1Page.goto(`/direct/${Users.admin.data.username}`);
await user1Channel.content.waitForChannel();
});
await test.step('invite OTR with user1', async () => {
await poHomeChannel.sidenav.openChat(Users.user1.data.username);
await poHomeChannel.tabs.kebab.click({ force: true });
await poHomeChannel.tabs.btnEnableOTR.click({ force: true });
await poHomeChannel.tabs.otr.btnStartOTR.click();
});
await test.step('accept handshake with user1', async () => {
await user1Channel.tabs.otr.btnAcceptOTR.click();
});
await poHomeChannel.content.sendMessage('hello OTR');
await poHomeChannel.tabs.kebab.click({ force: true });
await poHomeChannel.tabs.btnExportMessages.click();
await poHomeChannel.content.getMessageByText('hello OTR').click();
await expect(poHomeChannel.content.btnClearSelection).toBeDisabled();
await user1Page.close();
});
});

@ -417,4 +417,8 @@ export class HomeContent {
await this.page.getByRole('dialog').getByRole('textbox', { name: 'Message' }).fill(text);
await this.page.getByRole('dialog').getByRole('button', { name: 'Send', exact: true }).click();
}
get btnClearSelection() {
return this.page.getByRole('button', { name: 'Clear selection' });
}
}

@ -0,0 +1,21 @@
import type { Locator, Page } from '@playwright/test';
export class HomeFlextabOtr {
private readonly page: Page;
constructor(page: Page) {
this.page = page;
}
get otrDialog(): Locator {
return this.page.getByRole('dialog', { name: 'OTR' });
}
get btnStartOTR(): Locator {
return this.otrDialog.getByRole('button', { name: 'Start OTR' });
}
get btnAcceptOTR(): Locator {
return this.page.getByRole('dialog').getByRole('button', { name: 'Yes' });
}
}

@ -4,6 +4,7 @@ import { HomeFlextabChannels } from './home-flextab-channels';
import { HomeFlextabExportMessages } from './home-flextab-exportMessages';
import { HomeFlextabMembers } from './home-flextab-members';
import { HomeFlextabNotificationPreferences } from './home-flextab-notificationPreferences';
import { HomeFlextabOtr } from './home-flextab-otr';
import { HomeFlextabRoom } from './home-flextab-room';
export class HomeFlextab {
@ -17,6 +18,8 @@ export class HomeFlextab {
readonly notificationPreferences: HomeFlextabNotificationPreferences;
readonly otr: HomeFlextabOtr;
readonly exportMessages: HomeFlextabExportMessages;
constructor(page: Page) {
@ -25,6 +28,7 @@ export class HomeFlextab {
this.room = new HomeFlextabRoom(page);
this.channels = new HomeFlextabChannels(page);
this.notificationPreferences = new HomeFlextabNotificationPreferences(page);
this.otr = new HomeFlextabOtr(page);
this.exportMessages = new HomeFlextabExportMessages(page);
}

@ -4231,6 +4231,7 @@
"others": "others",
"Others": "Others",
"OTR": "OTR",
"OTR_messages_cannot_be_exported": "OTR messages cannot be exported",
"OTR_unavailable_for_federation": "OTR is unavailable for federated rooms",
"OTR_Description": "Off-the-record chats are secure, private and disappear once ended.",
"OTR_Chat_Declined_Title": "OTR Chat invite Declined",

Loading…
Cancel
Save