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

feat: Improve Retention Policy Warning and Callout messages #32579

Merged
merged 11 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading