Skip to content

Commit

Permalink
feat: Improve Retention Policy Warning and Callout messages (#32579)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriellsh authored Jun 21, 2024
1 parent 6274c43 commit d3c493b
Show file tree
Hide file tree
Showing 66 changed files with 389 additions and 280 deletions.
6 changes: 6 additions & 0 deletions .changeset/angry-garlics-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/i18n": patch
---

Improved Retention Policy Warning messages
16 changes: 2 additions & 14 deletions apps/meteor/app/retention-policy/server/cronPruneMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { IRoomWithRetentionPolicy } from '@rocket.chat/core-typings';
import { cronJobs } from '@rocket.chat/cron';
import { Rooms } from '@rocket.chat/models';

import { getCronAdvancedTimerFromPrecisionSetting } from '../../../lib/getCronAdvancedTimerFromPrecisionSetting';
import { cleanRoomHistory } from '../../lib/server/functions/cleanRoomHistory';
import { settings } from '../../settings/server';

Expand Down Expand Up @@ -79,19 +80,6 @@ async function job(): Promise<void> {
}
}

function getSchedule(precision: '0' | '1' | '2' | '3'): string {
switch (precision) {
case '0':
return '*/30 * * * *'; // 30 minutes
case '1':
return '0 * * * *'; // hour
case '2':
return '0 */6 * * *'; // 6 hours
case '3':
return '0 0 * * *'; // day
}
}

const pruneCronName = 'Prune old messages by retention policy';

async function deployCron(precision: string): Promise<void> {
Expand Down Expand Up @@ -138,7 +126,7 @@ settings.watchMultiple(

const precision =
(settings.get<boolean>('RetentionPolicy_Advanced_Precision') && settings.get<string>('RetentionPolicy_Advanced_Precision_Cron')) ||
getSchedule(settings.get('RetentionPolicy_Precision'));
getCronAdvancedTimerFromPrecisionSetting(settings.get('RetentionPolicy_Precision'));

return deployCron(precision);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react';
import React from 'react';

import InfoPanel from '.';
import { createFakeRoom } from '../../../tests/mocks/data';
import RetentionPolicyCallout from './RetentionPolicyCallout';

export default {
Expand All @@ -20,6 +21,8 @@ export default {
},
} as ComponentMeta<typeof InfoPanel>;

const fakeRoom = createFakeRoom();

export const Default: ComponentStory<typeof InfoPanel> = () => (
<InfoPanel>
<InfoPanel.Avatar />
Expand Down Expand Up @@ -52,7 +55,7 @@ export const Default: ComponentStory<typeof InfoPanel> = () => (
</InfoPanel.Section>

<InfoPanel.Section>
<RetentionPolicyCallout maxAge={30} filesOnly={false} excludePinned={true} />
<RetentionPolicyCallout room={fakeRoom} />
</InfoPanel.Section>
</InfoPanel>
);
Expand Down
19 changes: 5 additions & 14 deletions apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { Callout } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React from 'react';

import { useFormattedRelativeTime } from '../../hooks/useFormattedRelativeTime';
import { usePruneWarningMessage } from '../../hooks/usePruneWarningMessage';

type RetentionPolicyCalloutProps = {
filesOnly: boolean;
excludePinned: boolean;
maxAge: number;
};

const RetentionPolicyCallout: FC<RetentionPolicyCalloutProps> = ({ filesOnly, excludePinned, maxAge }) => {
const RetentionPolicyCallout = ({ room }: { room: IRoom }) => {
const message = usePruneWarningMessage(room);
const t = useTranslation();
const time = useFormattedRelativeTime(maxAge);

return (
<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>}
<p>{message}</p>
</div>
</Callout>
);
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/client/definitions/cron.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module 'cron' {
export declare function sendAt(precision: string): Moment;
}
237 changes: 237 additions & 0 deletions apps/meteor/client/hooks/usePruneWarningMessage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import type { IRoomWithRetentionPolicy } from '@rocket.chat/core-typings';
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { renderHook } from '@testing-library/react-hooks';

import { createFakeRoom } from '../../tests/mocks/data';
import { usePruneWarningMessage } from './usePruneWarningMessage';

const createMock = ({
enabled = true,
filesOnly = false,
doNotPrunePinned = false,
appliesToChannels = false,
TTLChannels = 60000,
appliesToGroups = false,
TTLGroups = 60000,
appliesToDMs = false,
TTLDMs = 60000,
precision = '0',
advancedPrecision = false,
advancedPrecisionCron = '*/30 * * * *',
} = {}) => {
return mockAppRoot()
.withTranslations('en', 'core', {
RetentionPolicy_RoomWarning_NextRunDate: '{{maxAge}} {{nextRunDate}}',
RetentionPolicy_RoomWarning_Unpinned_NextRunDate: 'Unpinned {{maxAge}} {{nextRunDate}}',
RetentionPolicy_RoomWarning_FilesOnly_NextRunDate: 'FilesOnly {{maxAge}} {{nextRunDate}}',
RetentionPolicy_RoomWarning_UnpinnedFilesOnly_NextRunDate: 'UnpinnedFilesOnly {{maxAge}} {{nextRunDate}}',
})
.withSetting('RetentionPolicy_Enabled', enabled)
.withSetting('RetentionPolicy_FilesOnly', filesOnly)
.withSetting('RetentionPolicy_DoNotPrunePinned', doNotPrunePinned)
.withSetting('RetentionPolicy_AppliesToChannels', appliesToChannels)
.withSetting('RetentionPolicy_TTL_Channels', TTLChannels)
.withSetting('RetentionPolicy_AppliesToGroups', appliesToGroups)
.withSetting('RetentionPolicy_TTL_Groups', TTLGroups)
.withSetting('RetentionPolicy_AppliesToDMs', appliesToDMs)
.withSetting('RetentionPolicy_TTL_DMs', TTLDMs)
.withSetting('RetentionPolicy_Precision', precision)
.withSetting('RetentionPolicy_Advanced_Precision', advancedPrecision)
.withSetting('RetentionPolicy_Advanced_Precision_Cron', advancedPrecisionCron)
.build();
};

jest.useFakeTimers();

const getRetentionRoomProps = (props: Partial<IRoomWithRetentionPolicy['retention']> = {}) => {
return {
retention: {
enabled: true,
overrideGlobal: true,
maxAge: 30,
filesOnly: false,
excludePinned: false,
ignoreThreads: false,
...props,
},
};
};

const setDate = (minutes = 1, hours = 0, date = 1) => {
// June 12, 2024, 12:00 AM
const fakeDate = new Date();
fakeDate.setFullYear(2024);
fakeDate.setMonth(5);
fakeDate.setDate(date);
fakeDate.setHours(hours);
fakeDate.setMinutes(minutes);
fakeDate.setSeconds(0);
jest.setSystemTime(fakeDate);
};

describe('usePruneWarningMessage hook', () => {
describe('Cron timer and precision', () => {
it('Should update the message after the nextRunDate has passaed', async () => {
setDate();
const fakeRoom = createFakeRoom({ t: 'c' });
const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock({
appliesToChannels: true,
TTLChannels: 60000,
}),
});
expect(result.current).toEqual('a minute June 1, 2024, 12:30 AM');
jest.advanceTimersByTime(31 * 60 * 1000);
expect(result.current).toEqual('a minute June 1, 2024, 1:00 AM');
});

it('Should return the default warning with precision set to every_hour', () => {
const fakeRoom = createFakeRoom({ t: 'c' });
setDate();
const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock({
appliesToChannels: true,
TTLChannels: 60000,
precision: '1',
}),
});
expect(result.current).toEqual('a minute June 1, 2024, 1:00 AM');
});

it('Should return the default warning with precision set to every_six_hours', () => {
const fakeRoom = createFakeRoom({ t: 'c' });
setDate();
const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock({
appliesToChannels: true,
TTLChannels: 60000,
precision: '2',
}),
});
expect(result.current).toEqual('a minute June 1, 2024, 6:00 AM');
});

it('Should return the default warning with precision set to every_day', () => {
const fakeRoom = createFakeRoom({ t: 'c' });
setDate();
const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock({
appliesToChannels: true,
TTLChannels: 60000,
precision: '3',
}),
});
expect(result.current).toEqual('a minute June 2, 2024, 12:00 AM');
});

it('Should return the default warning with advanced precision', () => {
const fakeRoom = createFakeRoom({ t: 'c' });
setDate();
const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock({
appliesToChannels: true,
TTLChannels: 60000,
advancedPrecision: true,
advancedPrecisionCron: '0 0 1 */1 *',
}),
});
expect(result.current).toEqual('a minute July 1, 2024, 12:00 AM');
});
});

describe('No override', () => {
it('Should return the default warning', () => {
const fakeRoom = createFakeRoom({ t: 'c' });
setDate();
const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock({
appliesToChannels: true,
TTLChannels: 60000,
}),
});
expect(result.current).toEqual('a minute June 1, 2024, 12:30 AM');
});

it('Should return the unpinned messages warning', () => {
const fakeRoom = createFakeRoom({ t: 'c' });
setDate();
const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock({
appliesToChannels: true,
TTLChannels: 60000,
doNotPrunePinned: true,
}),
});
expect(result.current).toEqual('Unpinned a minute June 1, 2024, 12:30 AM');
});

it('Should return the files only warning', () => {
const fakeRoom = createFakeRoom({ t: 'c' });
setDate();

const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock({
appliesToChannels: true,
TTLChannels: 60000,
filesOnly: true,
}),
});
expect(result.current).toEqual('FilesOnly a minute June 1, 2024, 12:30 AM');
});

it('Should return the unpinned files only warning', () => {
const fakeRoom = createFakeRoom({ t: 'c' });
setDate();

const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock({
appliesToChannels: true,
TTLChannels: 60000,
filesOnly: true,
doNotPrunePinned: true,
}),
});
expect(result.current).toEqual('UnpinnedFilesOnly a minute June 1, 2024, 12:30 AM');
});
});

describe('Overriden', () => {
it('Should return the default warning', () => {
const fakeRoom = createFakeRoom({ t: 'p', ...getRetentionRoomProps() });
setDate();
const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock(),
});
expect(result.current).toEqual('30 days June 1, 2024, 12:30 AM');
});

it('Should return the unpinned messages warning', () => {
const fakeRoom = createFakeRoom({ t: 'p', ...getRetentionRoomProps({ excludePinned: true }) });
setDate();
const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock(),
});
expect(result.current).toEqual('Unpinned 30 days June 1, 2024, 12:30 AM');
});

it('Should return the files only warning', () => {
const fakeRoom = createFakeRoom({ t: 'p', ...getRetentionRoomProps({ filesOnly: true }) });
setDate();

const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock(),
});
expect(result.current).toEqual('FilesOnly 30 days June 1, 2024, 12:30 AM');
});

it('Should return the unpinned files only warning', () => {
const fakeRoom = createFakeRoom({ t: 'p', ...getRetentionRoomProps({ excludePinned: true, filesOnly: true }) });
setDate();

const { result } = renderHook(() => usePruneWarningMessage(fakeRoom), {
wrapper: createMock(),
});
expect(result.current).toEqual('UnpinnedFilesOnly 30 days June 1, 2024, 12:30 AM');
});
});
});
Loading

0 comments on commit d3c493b

Please sign in to comment.