fix: Overriding Retention Policy not working (#32454)

Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com>
pull/32444/head^2
Douglas Fabris 2 years ago committed by GitHub
parent f83bd56cc5
commit fa55c49a49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/cold-beds-hope.md
  2. 62
      apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts
  3. 2
      apps/meteor/client/components/InfoPanel/InfoPanel.stories.tsx
  4. 23
      apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.tsx
  5. 14
      apps/meteor/client/hooks/useFormatRelativeTime.ts
  6. 23
      apps/meteor/client/views/room/body/RetentionPolicyWarning.tsx
  7. 4
      apps/meteor/client/views/room/body/RoomBody.tsx
  8. 82
      apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx
  9. 36
      apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/useEditRoomInitialValues.ts
  10. 10
      apps/meteor/client/views/room/contextualBar/Info/RoomInfo/RoomInfo.tsx
  11. 10
      apps/meteor/client/views/room/hooks/useRetentionPolicy.ts
  12. 6
      apps/meteor/client/views/teams/contextualBar/info/TeamsInfo.tsx
  13. 4
      apps/meteor/tests/e2e/page-objects/fragments/home-content.ts
  14. 24
      apps/meteor/tests/e2e/page-objects/fragments/home-flextab-room.ts
  15. 138
      apps/meteor/tests/e2e/retention-policy.spec.ts
  16. 2
      packages/i18n/src/locales/en.i18n.json

@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---
Fixes an issue not allowing override retention policy in channels

@ -117,14 +117,10 @@ const validators: RoomSettingsValidators = {
}
},
async retentionEnabled({ userId, value, room, rid }) {
if (!hasRetentionPolicy(room)) {
throw new Meteor.Error('error-action-not-allowed', 'Room does not have retention policy', {
method: 'saveRoomSettings',
action: 'Editing_room',
});
}
if (!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) && value !== room.retention.enabled) {
if (
!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) &&
(!hasRetentionPolicy(room) || value !== room.retention.enabled)
) {
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
method: 'saveRoomSettings',
action: 'Editing_room',
@ -132,14 +128,10 @@ const validators: RoomSettingsValidators = {
}
},
async retentionMaxAge({ userId, value, room, rid }) {
if (!hasRetentionPolicy(room)) {
throw new Meteor.Error('error-action-not-allowed', 'Room does not have retention policy', {
method: 'saveRoomSettings',
action: 'Editing_room',
});
}
if (!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) && value !== room.retention.maxAge) {
if (
!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) &&
(!hasRetentionPolicy(room) || value !== room.retention.maxAge)
) {
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
method: 'saveRoomSettings',
action: 'Editing_room',
@ -147,14 +139,10 @@ const validators: RoomSettingsValidators = {
}
},
async retentionExcludePinned({ userId, value, room, rid }) {
if (!hasRetentionPolicy(room)) {
throw new Meteor.Error('error-action-not-allowed', 'Room does not have retention policy', {
method: 'saveRoomSettings',
action: 'Editing_room',
});
}
if (!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) && value !== room.retention.excludePinned) {
if (
!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) &&
(!hasRetentionPolicy(room) || value !== room.retention.excludePinned)
) {
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
method: 'saveRoomSettings',
action: 'Editing_room',
@ -162,14 +150,10 @@ const validators: RoomSettingsValidators = {
}
},
async retentionFilesOnly({ userId, value, room, rid }) {
if (!hasRetentionPolicy(room)) {
throw new Meteor.Error('error-action-not-allowed', 'Room does not have retention policy', {
method: 'saveRoomSettings',
action: 'Editing_room',
});
}
if (!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) && value !== room.retention.filesOnly) {
if (
!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) &&
(!hasRetentionPolicy(room) || value !== room.retention.filesOnly)
) {
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
method: 'saveRoomSettings',
action: 'Editing_room',
@ -177,14 +161,10 @@ const validators: RoomSettingsValidators = {
}
},
async retentionIgnoreThreads({ userId, value, room, rid }) {
if (!hasRetentionPolicy(room)) {
throw new Meteor.Error('error-action-not-allowed', 'Room does not have retention policy', {
method: 'saveRoomSettings',
action: 'Editing_room',
});
}
if (!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) && value !== room.retention.ignoreThreads) {
if (
!(await hasPermissionAsync(userId, 'edit-room-retention-policy', rid)) &&
(!hasRetentionPolicy(room) || value !== room.retention.ignoreThreads)
) {
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
method: 'saveRoomSettings',
action: 'Editing_room',
@ -469,7 +449,7 @@ export async function saveRoomSettings(
rid,
});
if (setting === 'retentionOverrideGlobal') {
if (setting === 'retentionOverrideGlobal' && settings.retentionOverrideGlobal === false) {
delete settings.retentionMaxAge;
delete settings.retentionExcludePinned;
delete settings.retentionFilesOnly;

@ -52,7 +52,7 @@ export const Default: ComponentStory<typeof InfoPanel> = () => (
</InfoPanel.Section>
<InfoPanel.Section>
<RetentionPolicyCallout maxAgeDefault={30} filesOnlyDefault={false} excludePinnedDefault={true} />
<RetentionPolicyCallout maxAge={30} filesOnly={false} excludePinned={true} />
</InfoPanel.Section>
</InfoPanel>
);

@ -4,23 +4,26 @@ import type { FC } from 'react';
import React from 'react';
import { useFormattedRelativeTime } from '../../hooks/useFormattedRelativeTime';
import { getMaxAgeInMS } from '../../views/room/hooks/useRetentionPolicy';
type RetentionPolicyCalloutProps = {
filesOnlyDefault: boolean;
excludePinnedDefault: boolean;
maxAgeDefault: number;
filesOnly: boolean;
excludePinned: boolean;
maxAge: number;
};
const RetentionPolicyCallout: FC<RetentionPolicyCalloutProps> = ({ filesOnlyDefault, excludePinnedDefault, maxAgeDefault }) => {
const RetentionPolicyCallout: FC<RetentionPolicyCalloutProps> = ({ filesOnly, excludePinned, maxAge }) => {
const t = useTranslation();
const time = useFormattedRelativeTime(maxAgeDefault);
const time = useFormattedRelativeTime(getMaxAgeInMS(maxAge));
return (
<Callout type='warning'>
{filesOnlyDefault && excludePinnedDefault && <p>{t('RetentionPolicy_RoomWarning_FilesOnly', { time })}</p>}
{filesOnlyDefault && !excludePinnedDefault && <p>{t('RetentionPolicy_RoomWarning_UnpinnedFilesOnly', { time })}</p>}
{!filesOnlyDefault && excludePinnedDefault && <p>{t('RetentionPolicy_RoomWarning', { time })}</p>}
{!filesOnlyDefault && !excludePinnedDefault && <p>{t('RetentionPolicy_RoomWarning_Unpinned', { time })}</p>}
<Callout arial-label={t('Retention_policy_warning_callout')} role='alert' aria-live='polite' type='warning'>
<div>
{filesOnly && excludePinned && <p>{t('RetentionPolicy_RoomWarning_FilesOnly', { time })}</p>}
{filesOnly && !excludePinned && <p>{t('RetentionPolicy_RoomWarning_UnpinnedFilesOnly', { time })}</p>}
{!filesOnly && excludePinned && <p>{t('RetentionPolicy_RoomWarning', { time })}</p>}
{!filesOnly && !excludePinned && <p>{t('RetentionPolicy_RoomWarning_Unpinned', { time })}</p>}
</div>
</Callout>
);
};

@ -1,14 +0,0 @@
import moment from 'moment';
import { useCallback } from 'react';
export const useFormatRelativeTime = (): ((timeMs: number) => string) =>
useCallback((timeMs: number) => {
moment.relativeTimeThreshold('s', 60);
moment.relativeTimeThreshold('ss', 0);
moment.relativeTimeThreshold('m', 60);
moment.relativeTimeThreshold('h', 24);
moment.relativeTimeThreshold('d', 31);
moment.relativeTimeThreshold('M', 12);
return moment.duration(timeMs).humanize();
}, []);

@ -1,9 +1,10 @@
import { Icon } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useMemo } from 'react';
import React from 'react';
import { useFormatRelativeTime } from '../../../hooks/useFormatRelativeTime';
import { useFormattedRelativeTime } from '../../../hooks/useFormattedRelativeTime';
import { getMaxAgeInMS } from '../hooks/useRetentionPolicy';
type RetentionPolicyWarningProps = {
filesOnly: boolean;
@ -13,13 +14,16 @@ type RetentionPolicyWarningProps = {
const RetentionPolicyWarning = ({ filesOnly, excludePinned, maxAge }: RetentionPolicyWarningProps): ReactElement => {
const t = useTranslation();
const formatRelativeTime = useFormatRelativeTime();
const time = useMemo(() => formatRelativeTime(maxAge), [formatRelativeTime, maxAge]);
const time = useFormattedRelativeTime(getMaxAgeInMS(maxAge));
if (filesOnly) {
return (
<div className='start__purge-warning error-background error-border error-color'>
<div
aria-label={t('Retention_policy_warning_banner')}
role='alert'
aria-live='polite'
className='start__purge-warning error-background error-border error-color'
>
<Icon name='warning' size='x20' />{' '}
{excludePinned
? t('RetentionPolicy_RoomWarning_UnpinnedFilesOnly', { time })
@ -29,7 +33,12 @@ const RetentionPolicyWarning = ({ filesOnly, excludePinned, maxAge }: RetentionP
}
return (
<div className='start__purge-warning error-background error-border error-color'>
<div
aria-label={t('Retention_policy_warning_banner')}
role='alert'
aria-live='polite'
className='start__purge-warning error-background error-border error-color'
>
<Icon name='warning' size='x20' />{' '}
{excludePinned ? t('RetentionPolicy_RoomWarning_Unpinned', { time }) : t('RetentionPolicy_RoomWarning', { time })}
</div>

@ -22,6 +22,7 @@ import { useRoomToolbox } from '../contexts/RoomToolboxContext';
import { useUserCard } from '../contexts/UserCardContext';
import { useDateScroll } from '../hooks/useDateScroll';
import { useMessageListNavigation } from '../hooks/useMessageListNavigation';
import { useRetentionPolicy } from '../hooks/useRetentionPolicy';
import DropTargetOverlay from './DropTargetOverlay';
import JumpToRecentMessageButton from './JumpToRecentMessageButton';
import LeaderBar from './LeaderBar';
@ -39,7 +40,6 @@ import { useListIsAtBottom } from './hooks/useListIsAtBottom';
import { useQuoteMessageByUrl } from './hooks/useQuoteMessageByUrl';
import { useReadMessageWindowEvents } from './hooks/useReadMessageWindowEvents';
import { useRestoreScrollPosition } from './hooks/useRestoreScrollPosition';
import { useRetentionPolicy } from './hooks/useRetentionPolicy';
import { useHandleUnread } from './hooks/useUnreadMessages';
const RoomBody = (): ReactElement => {
@ -291,7 +291,7 @@ const RoomBody = (): ReactElement => {
<li className='load-more'>{isLoadingMoreMessages ? <LoadingMessagesIndicator /> : null}</li>
) : (
<li className='start color-info-font-color'>
{retentionPolicy ? <RetentionPolicyWarning {...retentionPolicy} /> : null}
{retentionPolicy?.isActive ? <RetentionPolicyWarning {...retentionPolicy} /> : null}
<RoomForeword user={user} room={room} />
</li>
)}

@ -23,6 +23,7 @@ import {
import { useEffectEvent, useUniqueId } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useSetting, useTranslation, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts';
import type { ChangeEvent } from 'react';
import React, { useMemo } from 'react';
import { useForm, Controller } from 'react-hook-form';
@ -39,6 +40,7 @@ import RawText from '../../../../../components/RawText';
import RoomAvatarEditor from '../../../../../components/avatar/RoomAvatarEditor';
import { getDirtyFields } from '../../../../../lib/getDirtyFields';
import { useArchiveRoom } from '../../../../hooks/roomActions/useArchiveRoom';
import { useRetentionPolicy } from '../../../hooks/useRetentionPolicy';
import { useEditRoomInitialValues } from './useEditRoomInitialValues';
import { useEditRoomPermissions } from './useEditRoomPermissions';
@ -54,6 +56,18 @@ const title = {
discussion: 'Edit_discussion' as TranslationKey,
};
const getRetentionSetting = (roomType: IRoomWithRetentionPolicy['t']): string => {
switch (roomType) {
case 'd':
return 'RetentionPolicy_MaxAge_DMs';
case 'p':
return 'RetentionPolicy_MaxAge_Groups';
case 'c':
default:
return 'RetentionPolicy_MaxAge_Channels';
}
};
const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => {
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
@ -61,7 +75,8 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) =>
// eslint-disable-next-line no-nested-ternary
const roomType = 'prid' in room ? 'discussion' : room.teamId ? 'team' : 'channel';
const retentionPolicy = useSetting<boolean>('RetentionPolicy_Enabled');
const retentionPolicy = useRetentionPolicy(room);
const retentionMaxAgeDefault = useSetting<number>(getRetentionSetting(room.t)) ?? 30;
const defaultValues = useEditRoomInitialValues(room);
const namesValidation = useSetting('UTF8_Channel_Names_Validation');
const allowSpecialNames = useSetting('UI_Allow_room_names_with_special_chars');
@ -97,7 +112,6 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) =>
joinCodeRequired,
hideSysMes,
retentionEnabled,
retentionMaxAge,
retentionOverrideGlobal,
roomType: roomTypeP,
reactWhenReadOnly,
@ -128,26 +142,46 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) =>
const handleArchive = useArchiveRoom(room);
const handleUpdateRoomData = useEffectEvent(async ({ hideSysMes, joinCodeRequired, ...formData }) => {
const data = getDirtyFields(formData, dirtyFields);
delete data.archived;
// TODO: add payload validation
const handleUpdateRoomData = useEffectEvent(
async ({
hideSysMes,
joinCodeRequired,
retentionEnabled,
retentionOverrideGlobal,
retentionMaxAge,
retentionExcludePinned,
retentionFilesOnly,
...formData
}) => {
const data = getDirtyFields(formData, dirtyFields);
delete data.archived;
try {
await saveAction({
rid: room._id,
...data,
...((data.joinCode || 'joinCodeRequired' in data) && { joinCode: joinCodeRequired ? data.joinCode : '' }),
...((data.systemMessages || !hideSysMes) && {
systemMessages: hideSysMes && data.systemMessages,
}),
});
try {
await saveAction({
rid: room._id,
...data,
...((data.joinCode || 'joinCodeRequired' in data) && { joinCode: joinCodeRequired ? data.joinCode : '' }),
...((data.systemMessages || !hideSysMes) && {
systemMessages: hideSysMes && data.systemMessages,
}),
retentionEnabled,
retentionOverrideGlobal,
...(retentionEnabled &&
retentionOverrideGlobal && {
retentionMaxAge,
retentionExcludePinned,
retentionFilesOnly,
}),
});
dispatchToastMessage({ type: 'success', message: t('Room_updated_successfully') });
onClickClose();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
}
});
dispatchToastMessage({ type: 'success', message: t('Room_updated_successfully') });
onClickClose();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
}
},
);
const handleSave = useEffectEvent((data) =>
Promise.all([isDirty && handleUpdateRoomData(data), changeArchiving && handleArchive()].filter(Boolean)),
@ -431,7 +465,7 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) =>
</Field>
)}
</FieldGroup>
{retentionPolicy && (
{retentionPolicy?.enabled && (
<Accordion>
<Accordion.Item title={t('Prune')}>
<FieldGroup>
@ -468,7 +502,9 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) =>
<RawText>{t('RetentionPolicyRoom_ReadTheDocs')}</RawText>
</Callout>
<Field>
<FieldLabel htmlFor={retentionMaxAgeField}>{t('RetentionPolicyRoom_MaxAge', { max: retentionMaxAge })}</FieldLabel>
<FieldLabel htmlFor={retentionMaxAgeField}>
{t('RetentionPolicyRoom_MaxAge', { max: retentionMaxAgeDefault })}
</FieldLabel>
<FieldRow>
<Controller
control={control}
@ -477,7 +513,7 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) =>
<NumberInput
id={retentionMaxAgeField}
{...field}
onChange={(currentValue) => onChange(Math.max(1, Number(currentValue)))}
onChange={(e: ChangeEvent<HTMLInputElement>) => onChange(Number(e.currentTarget.value))}
/>
)}
/>

@ -1,29 +1,13 @@
import type { IRoomWithRetentionPolicy } from '@rocket.chat/core-typings';
import { useSetting } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator';
const getPolicyRoomType = (roomType: IRoomWithRetentionPolicy['t']) => {
switch (roomType) {
case 'c':
return 'Channels';
case 'p':
return 'Groups';
case 'd':
return 'DMs';
}
};
import { useRetentionPolicy } from '../../../hooks/useRetentionPolicy';
export const useEditRoomInitialValues = (room: IRoomWithRetentionPolicy) => {
const retentionPolicy = useRetentionPolicy(room);
const { t, ro, archived, topic, description, announcement, joinCodeRequired, sysMes, encrypted, retention, reactWhenReadOnly } = room;
const retentionPolicyEnabled = useSetting('RetentionPolicy_Enabled');
const maxAgeDefault = useSetting<number>(`RetentionPolicy_MaxAge_${getPolicyRoomType(room.t)}`) || 30;
const retentionEnabledDefault = useSetting<boolean>(`RetentionPolicy_AppliesTo${getPolicyRoomType(room.t)}`);
const excludePinnedDefault = useSetting('RetentionPolicy_DoNotPrunePinned');
const filesOnlyDefault = useSetting('RetentionPolicy_FilesOnly');
return useMemo(
() => ({
roomName: t === 'd' && room.usernames ? room.usernames.join(' x ') : roomCoordinator.getRoomName(t, room),
@ -40,25 +24,21 @@ export const useEditRoomInitialValues = (room: IRoomWithRetentionPolicy) => {
systemMessages: Array.isArray(sysMes) ? sysMes : [],
hideSysMes: Array.isArray(sysMes) ? !!sysMes?.length : !!sysMes,
encrypted,
...(retentionPolicyEnabled && {
retentionEnabled: retention?.enabled ?? retentionEnabledDefault,
...(retentionPolicy?.enabled && {
retentionEnabled: retention?.enabled ?? retentionPolicy.isActive,
retentionOverrideGlobal: !!retention?.overrideGlobal,
retentionMaxAge: Math.min(retention?.maxAge, maxAgeDefault) || maxAgeDefault,
retentionExcludePinned: retention?.excludePinned ?? excludePinnedDefault,
retentionFilesOnly: retention?.filesOnly ?? filesOnlyDefault,
retentionMaxAge: retention?.maxAge ?? retentionPolicy.maxAge,
retentionExcludePinned: retention?.excludePinned ?? retentionPolicy.excludePinned,
retentionFilesOnly: retention?.filesOnly ?? retentionPolicy.filesOnly,
}),
}),
[
announcement,
archived,
description,
excludePinnedDefault,
filesOnlyDefault,
joinCodeRequired,
maxAgeDefault,
retention,
retentionEnabledDefault,
retentionPolicyEnabled,
retentionPolicy,
ro,
room,
sysMes,

@ -17,7 +17,7 @@ import RetentionPolicyCallout from '../../../../../components/InfoPanel/Retentio
import MarkdownText from '../../../../../components/MarkdownText';
import type { Action } from '../../../../hooks/useActionSpread';
import { useActionSpread } from '../../../../hooks/useActionSpread';
import { useRetentionPolicy } from '../../../body/hooks/useRetentionPolicy';
import { useRetentionPolicy } from '../../../hooks/useRetentionPolicy';
import { useRoomActions } from '../hooks/useRoomActions';
type RoomInfoProps = {
@ -135,11 +135,11 @@ const RoomInfo = ({ room, icon, onClickBack, onClickClose, onClickEnterRoom, onC
</InfoPanel.Field>
)}
{retentionPolicy && (
{retentionPolicy?.isActive && (
<RetentionPolicyCallout
filesOnlyDefault={retentionPolicy.filesOnly}
excludePinnedDefault={retentionPolicy.excludePinned}
maxAgeDefault={retentionPolicy.maxAge}
filesOnly={retentionPolicy.filesOnly}
excludePinned={retentionPolicy.excludePinned}
maxAge={retentionPolicy.maxAge}
/>
)}
</InfoPanel.Section>

@ -16,6 +16,8 @@ type RetentionPolicySettings = {
maxAgeDMs: number;
};
export const getMaxAgeInMS = (maxAge: number) => maxAge * 24 * 60 * 60 * 1000;
const isActive = (room: IRoom, { enabled, appliesToChannels, appliesToGroups, appliesToDMs }: RetentionPolicySettings): boolean => {
if (!enabled) {
return false;
@ -75,6 +77,8 @@ export const useRetentionPolicy = (
room: IRoom | undefined,
):
| {
enabled: boolean;
isActive: boolean;
filesOnly: boolean;
excludePinned: boolean;
maxAge: number;
@ -92,13 +96,15 @@ export const useRetentionPolicy = (
maxAgeDMs: useSetting('RetentionPolicy_MaxAge_DMs') as number,
} as const;
if (!room || !isActive(room, settings)) {
if (!room) {
return undefined;
}
return {
enabled: settings.enabled,
isActive: isActive(room, settings),
filesOnly: extractFilesOnly(room, settings),
excludePinned: extractExcludePinned(room, settings),
maxAge: getMaxAge(room, settings) * 24 * 60 * 60 * 1000,
maxAge: getMaxAge(room, settings),
};
};

@ -200,11 +200,7 @@ const TeamsInfo = ({
)}
{retentionPolicyEnabled && (
<RetentionPolicyCallout
filesOnlyDefault={filesOnlyDefault}
excludePinnedDefault={excludePinnedDefault}
maxAgeDefault={maxAgeDefault}
/>
<RetentionPolicyCallout filesOnly={filesOnlyDefault} excludePinned={excludePinnedDefault} maxAge={maxAgeDefault} />
)}
</InfoPanel.Section>
</InfoPanel>

@ -13,6 +13,10 @@ export class HomeContent {
return this.page.locator('main header');
}
get channelRetentionPolicyWarning(): Locator {
return this.page.locator('main').getByRole('alert', { name: 'Retention policy warning banner' });
}
get inputMessage(): Locator {
return this.page.locator('[name="msg"]');
}

@ -38,4 +38,28 @@ export class HomeFlextabRoom {
get btnSave(): Locator {
return this.page.locator('role=button[name="Save"]');
}
get calloutRetentionPolicy(): Locator {
return this.page.getByRole('dialog').getByRole('alert', { name: 'Retention policy warning callout' });
}
get pruneAccordion(): Locator {
return this.page.getByRole('dialog').getByRole('button', { name: 'Prune' });
}
getMaxAgeLabel(maxAge = '30') {
return this.page.getByRole('dialog').getByText(`Maximum message age in days (default: ${maxAge})`)
}
get inputRetentionMaxAge(): Locator {
return this.page.getByRole('dialog').locator('input[name="retentionMaxAge"]');
}
get checkboxPruneMessages(): Locator {
return this.page.locator('label', { has: this.page.getByRole('checkbox', { name: 'Automatically prune old messages' }) });
}
get checkboxOverrideGlobalRetention(): Locator {
return this.page.locator('label', { has: this.page.getByRole('checkbox', { name: 'Override global retention policy' }) });
}
}

@ -0,0 +1,138 @@
import { Users } from './fixtures/userStates';
import { HomeChannel } from './page-objects';
import { createTargetChannel, createTargetPrivateChannel, setSettingValueById } from './utils';
import { test, expect } from './utils/test';
test.use({ storageState: Users.admin.state });
test.describe.serial('retention-policy', () => {
let poHomeChannel: HomeChannel;
let targetChannel: string;
let targetGroup: string;
test.beforeAll(async ({ api }) => {
targetChannel = await createTargetChannel(api);
targetGroup = await createTargetPrivateChannel(api);
})
test.beforeEach(async ({ page }) => {
poHomeChannel = new HomeChannel(page);
await page.goto('/home');
});
test.describe('retention policy disabled', () => {
test('should not show prune banner in channel', async () => {
await poHomeChannel.sidenav.openChat(targetChannel);
await expect(poHomeChannel.content.channelRetentionPolicyWarning).not.toBeVisible();
});
test('should not show prune section on edit channel', async () => {
await poHomeChannel.sidenav.openChat(targetChannel);
await poHomeChannel.tabs.btnRoomInfo.click();
await poHomeChannel.tabs.room.btnEdit.click();
await expect(poHomeChannel.tabs.room.pruneAccordion).not.toBeVisible();
});
});
test.describe('retention policy enabled', () => {
test.beforeAll(async ({ api }) => {
await setSettingValueById(api, 'RetentionPolicy_Enabled', true);
})
test.afterAll(async ({ api }) => {
await setSettingValueById(api, 'RetentionPolicy_Enabled', false);
await setSettingValueById(api, 'RetentionPolicy_AppliesToChannels', false);
await setSettingValueById(api, 'RetentionPolicy_AppliesToGroups', false);
await setSettingValueById(api, 'RetentionPolicy_AppliesToDMs', false);
await setSettingValueById(api, 'RetentionPolicy_MaxAge_Channels', 30);
});
test('should not show prune banner even with retention policy setting enabled in any type of room', async () => {
await poHomeChannel.sidenav.openChat(targetChannel);
await expect(poHomeChannel.content.channelRetentionPolicyWarning).not.toBeVisible();
await poHomeChannel.sidenav.openChat(targetGroup);
await expect(poHomeChannel.content.channelRetentionPolicyWarning).not.toBeVisible();
await poHomeChannel.sidenav.openChat('user1');
await expect(poHomeChannel.content.channelRetentionPolicyWarning).not.toBeVisible();
});
test('should show prune section in edit channel', async () => {
await poHomeChannel.sidenav.openChat(targetChannel);
await poHomeChannel.tabs.btnRoomInfo.click();
await poHomeChannel.tabs.room.btnEdit.click();
await expect(poHomeChannel.tabs.room.pruneAccordion).toBeVisible();
});
test.describe('retention policy applies enabled by default', () => {
test.beforeAll(async ({ api }) => {
await setSettingValueById(api, 'RetentionPolicy_AppliesToChannels', true);
await setSettingValueById(api, 'RetentionPolicy_AppliesToGroups', true);
await setSettingValueById(api, 'RetentionPolicy_AppliesToDMs', true);
});
test('should prune old messages checkbox enabled by default in channel and show retention policy banner', async () => {
await poHomeChannel.sidenav.openChat(targetChannel);
await expect(poHomeChannel.content.channelRetentionPolicyWarning).toBeVisible();
await poHomeChannel.tabs.btnRoomInfo.click();
await poHomeChannel.tabs.room.btnEdit.click();
await poHomeChannel.tabs.room.pruneAccordion.click();
await expect(poHomeChannel.tabs.room.checkboxPruneMessages).toBeChecked();
});
test('should prune old messages checkbox enabled by default in group and show retention policy banner', async () => {
await poHomeChannel.sidenav.openChat(targetGroup);
await expect(poHomeChannel.content.channelRetentionPolicyWarning).toBeVisible();
await poHomeChannel.tabs.btnRoomInfo.click();
await poHomeChannel.tabs.room.btnEdit.click();
await poHomeChannel.tabs.room.pruneAccordion.click();
await expect(poHomeChannel.tabs.room.checkboxPruneMessages).toBeChecked();
});
test('should show retention policy banner in DMs', async () => {
await poHomeChannel.sidenav.openChat('user1');
await expect(poHomeChannel.content.channelRetentionPolicyWarning).toBeVisible();
});
});
test.describe('retention policy override', () => {
test.beforeAll(async ({ api }) => {
expect((await setSettingValueById(api, 'RetentionPolicy_MaxAge_Channels', 15)).status()).toBe(200);
});
test('should display the default max age in edit channel', async () => {
await poHomeChannel.sidenav.openChat(targetChannel);
await poHomeChannel.tabs.btnRoomInfo.click();
await poHomeChannel.tabs.room.btnEdit.click();
await poHomeChannel.tabs.room.pruneAccordion.click();
await poHomeChannel.tabs.room.checkboxOverrideGlobalRetention.click();
await expect(poHomeChannel.tabs.room.getMaxAgeLabel('15')).toBeVisible();
});
test('should display overridden retention max age value', async () => {
await poHomeChannel.sidenav.openChat(targetChannel);
await poHomeChannel.tabs.btnRoomInfo.click();
await poHomeChannel.tabs.room.btnEdit.click();
await poHomeChannel.tabs.room.pruneAccordion.click();
await poHomeChannel.tabs.room.checkboxOverrideGlobalRetention.click();
await poHomeChannel.tabs.room.inputRetentionMaxAge.fill('365');
await poHomeChannel.tabs.room.btnSave.click();
await poHomeChannel.dismissToast();
await poHomeChannel.tabs.btnRoomInfo.click();
await poHomeChannel.tabs.room.btnEdit.click();
await poHomeChannel.tabs.room.pruneAccordion.click();
await expect(poHomeChannel.tabs.room.getMaxAgeLabel('15')).toBeVisible();
await expect(poHomeChannel.tabs.room.inputRetentionMaxAge).toHaveValue('365');
});
});
});
});

@ -4500,6 +4500,8 @@
"RetentionPolicyRoom_MaxAge": "Maximum message age in days (default: {{max}})",
"RetentionPolicyRoom_OverrideGlobal": "Override global retention policy",
"RetentionPolicyRoom_ReadTheDocs": "Watch out! Tweaking these settings without utmost care can destroy all message history. Please read the documentation before turning the feature on <a href='https://docs.rocket.chat/use-rocket.chat/workspace-administration/settings/retention-policies'>here</a>.",
"Retention_policy_warning_banner": "Retention policy warning banner",
"Retention_policy_warning_callout": "Retention policy warning callout",
"Retry": "Retry",
"Return_to_home": "Return to home",
"Return_to_previous_page": "Return to previous page",

Loading…
Cancel
Save