snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { Contextualbar } from '@rocket.chat/ui-client';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import PruneMessages from './PruneMessages';
|
||||
|
||||
export default {
|
||||
component: PruneMessages,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
actions: { argTypesRegex: '^on.*' },
|
||||
},
|
||||
decorators: [
|
||||
(fn) => {
|
||||
const methods = useForm({
|
||||
defaultValues: {
|
||||
pinned: true,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<Contextualbar height='100vh'>{fn()}</Contextualbar>
|
||||
</FormProvider>
|
||||
);
|
||||
},
|
||||
],
|
||||
} satisfies Meta<typeof PruneMessages>;
|
||||
|
||||
export const Default = {};
|
||||
|
||||
export const WithCallout = {
|
||||
args: {
|
||||
callOutText: 'This is a callout',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
import { Field, FieldLabel, FieldRow, ButtonGroup, Button, CheckBox, Callout } from '@rocket.chat/fuselage';
|
||||
import {
|
||||
ContextualbarHeader,
|
||||
ContextualbarIcon,
|
||||
ContextualbarTitle,
|
||||
ContextualbarScrollableContent,
|
||||
ContextualbarFooter,
|
||||
ContextualbarClose,
|
||||
ContextualbarDialog,
|
||||
} from '@rocket.chat/ui-client';
|
||||
import { useId } from 'react';
|
||||
import { useFormContext, Controller } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import PruneMessagesDateTimeRow from './PruneMessagesDateTimeRow';
|
||||
import UserAutoCompleteMultiple from '../../../../components/UserAutoCompleteMultiple';
|
||||
|
||||
export type PruneMessagesProps = {
|
||||
callOutText?: string;
|
||||
validateText?: string;
|
||||
users: string[];
|
||||
onClickClose: () => void;
|
||||
onClickPrune: () => void;
|
||||
};
|
||||
|
||||
const PruneMessages = ({ callOutText, validateText, onClickClose, onClickPrune }: PruneMessagesProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { control } = useFormContext();
|
||||
|
||||
const inclusiveCheckboxId = useId();
|
||||
const pinnedCheckboxId = useId();
|
||||
const discussionCheckboxId = useId();
|
||||
const threadsCheckboxId = useId();
|
||||
const attachedCheckboxId = useId();
|
||||
|
||||
return (
|
||||
<ContextualbarDialog>
|
||||
<ContextualbarHeader>
|
||||
<ContextualbarIcon name='eraser' />
|
||||
<ContextualbarTitle>{t('Prune_Messages')}</ContextualbarTitle>
|
||||
{onClickClose && <ContextualbarClose onClick={onClickClose} />}
|
||||
</ContextualbarHeader>
|
||||
<ContextualbarScrollableContent>
|
||||
<PruneMessagesDateTimeRow label={t('Newer_than')} field='newer' />
|
||||
<PruneMessagesDateTimeRow label={t('Older_than')} field='older' />
|
||||
<Field>
|
||||
<FieldLabel flexGrow={0}>{t('Only_from_users')}</FieldLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name='users'
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<UserAutoCompleteMultiple value={value} onChange={onChange} placeholder={t('Please_enter_usernames')} />
|
||||
)}
|
||||
/>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldRow>
|
||||
<FieldLabel htmlFor={inclusiveCheckboxId}>{t('Inclusive')}</FieldLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name='inclusive'
|
||||
render={({ field: { value, ...field } }) => <CheckBox id={inclusiveCheckboxId} {...field} checked={value} />}
|
||||
/>
|
||||
</FieldRow>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldRow>
|
||||
<FieldLabel htmlFor={pinnedCheckboxId}>{t('RetentionPolicy_DoNotPrunePinned')}</FieldLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name='pinned'
|
||||
render={({ field: { value, ...field } }) => <CheckBox id={pinnedCheckboxId} {...field} checked={value} />}
|
||||
/>
|
||||
</FieldRow>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldRow>
|
||||
<FieldLabel htmlFor={discussionCheckboxId}>{t('RetentionPolicy_DoNotPruneDiscussion')}</FieldLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name='discussion'
|
||||
render={({ field: { value, ...field } }) => <CheckBox id={discussionCheckboxId} {...field} checked={value} />}
|
||||
/>
|
||||
</FieldRow>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldRow>
|
||||
<FieldLabel htmlFor={threadsCheckboxId}>{t('RetentionPolicy_DoNotPruneThreads')}</FieldLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name='threads'
|
||||
render={({ field: { value, ...field } }) => <CheckBox id={threadsCheckboxId} {...field} checked={value} />}
|
||||
/>
|
||||
</FieldRow>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldRow>
|
||||
<FieldLabel htmlFor={attachedCheckboxId}>{t('Files_only')}</FieldLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name='attached'
|
||||
render={({ field: { value, ...field } }) => <CheckBox id={attachedCheckboxId} {...field} checked={value} />}
|
||||
/>
|
||||
</FieldRow>
|
||||
</Field>
|
||||
{callOutText && !validateText && <Callout type='warning'>{callOutText}</Callout>}
|
||||
{validateText && <Callout type='warning'>{validateText}</Callout>}
|
||||
</ContextualbarScrollableContent>
|
||||
<ContextualbarFooter>
|
||||
<ButtonGroup stretch>
|
||||
<Button danger disabled={Boolean(validateText)} onClick={onClickPrune}>
|
||||
{t('Prune')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</ContextualbarFooter>
|
||||
</ContextualbarDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default PruneMessages;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Box, Margins, Field, FieldLabel, InputBox } from '@rocket.chat/fuselage';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type PruneMessagesDateTimeRowProps = {
|
||||
label: string;
|
||||
field: 'newer' | 'older';
|
||||
};
|
||||
|
||||
const PruneMessagesDateTimeRow = ({ label, field }: PruneMessagesDateTimeRowProps) => {
|
||||
const { register } = useFormContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Field>
|
||||
<FieldLabel flexGrow={0}>{label}</FieldLabel>
|
||||
<Box display='flex' marginInline='neg-x4'>
|
||||
<Margins inline={4}>
|
||||
<InputBox aria-label={`${label} ${t('Date')}`} type='date' flexGrow={1} height='x20' {...register(`${field}.date`)} />
|
||||
<InputBox aria-label={`${label} ${t('Time')}`} type='time' flexGrow={1} height='x20' {...register(`${field}.time`)} />
|
||||
</Margins>
|
||||
</Box>
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
export default PruneMessagesDateTimeRow;
|
||||
@@ -0,0 +1,176 @@
|
||||
import { isDirectMessageRoom } from '@rocket.chat/core-typings';
|
||||
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
|
||||
import { GenericModal } from '@rocket.chat/ui-client';
|
||||
import { useSetModal, useToastMessageDispatch, useEndpoint, useRoomToolbox } from '@rocket.chat/ui-contexts';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import PruneMessages from './PruneMessages';
|
||||
import { formatDate } from '../../../../lib/utils/dateFormat';
|
||||
import { useRoom } from '../../contexts/RoomContext';
|
||||
|
||||
const getTimeZoneOffset = (): string => {
|
||||
const offset = new Date().getTimezoneOffset();
|
||||
const absOffset = Math.abs(offset);
|
||||
return `${offset < 0 ? '+' : '-'}${`00${Math.floor(absOffset / 60)}`.slice(-2)}:${`00${absOffset % 60}`.slice(-2)}`;
|
||||
};
|
||||
|
||||
export const initialValues = {
|
||||
newer: {
|
||||
date: '',
|
||||
time: '',
|
||||
},
|
||||
older: {
|
||||
date: '',
|
||||
time: '',
|
||||
},
|
||||
users: [],
|
||||
inclusive: false,
|
||||
pinned: false,
|
||||
discussion: false,
|
||||
threads: false,
|
||||
attached: false,
|
||||
};
|
||||
|
||||
const DEFAULT_PRUNE_LIMIT = 2000;
|
||||
|
||||
const PruneMessagesWithData = () => {
|
||||
const { t } = useTranslation();
|
||||
const room = useRoom();
|
||||
const setModal = useSetModal();
|
||||
const { closeTab: close } = useRoomToolbox();
|
||||
const closeModal = useCallback(() => setModal(null), [setModal]);
|
||||
const dispatchToastMessage = useToastMessageDispatch();
|
||||
const pruneMessagesAction = useEndpoint('POST', '/v1/rooms.cleanHistory');
|
||||
|
||||
const [counter, setCounter] = useState(0);
|
||||
|
||||
const methods = useForm({ defaultValues: initialValues });
|
||||
|
||||
const {
|
||||
newer: { date: newerDate, time: newerTime },
|
||||
older: { date: olderDate, time: olderTime },
|
||||
users,
|
||||
inclusive,
|
||||
pinned,
|
||||
discussion,
|
||||
threads,
|
||||
attached,
|
||||
} = methods.watch();
|
||||
|
||||
const fromDate = useMemo(() => {
|
||||
return new Date(`${newerDate || '0001-01-01'}T${newerTime || '00:00'}:00${getTimeZoneOffset()}`);
|
||||
}, [newerDate, newerTime]);
|
||||
|
||||
const toDate = useMemo(() => {
|
||||
return new Date(`${olderDate || '9999-12-31'}T${olderTime || '23:59'}:59${getTimeZoneOffset()}`);
|
||||
}, [olderDate, olderTime]);
|
||||
|
||||
const handlePrune = useStableCallback((): void => {
|
||||
const handlePruneAction = async () => {
|
||||
const limit = DEFAULT_PRUNE_LIMIT;
|
||||
|
||||
try {
|
||||
if (counter === limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { count } = await pruneMessagesAction({
|
||||
roomId: room._id,
|
||||
latest: toDate.toISOString(),
|
||||
oldest: fromDate.toISOString(),
|
||||
inclusive,
|
||||
limit,
|
||||
excludePinned: pinned,
|
||||
filesOnly: attached,
|
||||
ignoreDiscussion: discussion,
|
||||
ignoreThreads: threads,
|
||||
users,
|
||||
});
|
||||
|
||||
setCounter(count);
|
||||
|
||||
if (count < 1) {
|
||||
throw new Error(attached ? t('No_files_found_to_prune') : t('No_messages_found_to_prune'));
|
||||
}
|
||||
|
||||
dispatchToastMessage({
|
||||
type: 'success',
|
||||
message: attached ? t('__count__file_pruned', { count }) : t('__count__message_pruned', { count }),
|
||||
});
|
||||
methods.reset();
|
||||
} catch (error: unknown) {
|
||||
dispatchToastMessage({ type: 'error', message: error });
|
||||
} finally {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
return setModal(
|
||||
<GenericModal
|
||||
variant='danger'
|
||||
onClose={closeModal}
|
||||
onCancel={closeModal}
|
||||
onConfirm={handlePruneAction}
|
||||
confirmText={t('Yes_prune_them')}
|
||||
>
|
||||
{t('Prune_Modal')}
|
||||
</GenericModal>,
|
||||
);
|
||||
});
|
||||
|
||||
const callOutText = useMemo(() => {
|
||||
const name = room && ((isDirectMessageRoom(room) && room.usernames?.join(' x ')) || room.fname || room.name);
|
||||
const exceptPinned = pinned ? ` ${t('except_pinned')}` : '';
|
||||
const ifFrom = users.length ? ` ${t('if_they_are_from', { users: users.map((element) => element).join(', ') })}` : '';
|
||||
const filesOrMessages = attached ? t('files') : t('messages');
|
||||
|
||||
if (newerDate && olderDate) {
|
||||
return (
|
||||
t('Prune_Warning_between', {
|
||||
items: filesOrMessages,
|
||||
roomName: name,
|
||||
fromDate: formatDate(fromDate, 'L LT'),
|
||||
toDate: formatDate(toDate, 'L LT'),
|
||||
}) +
|
||||
exceptPinned +
|
||||
ifFrom
|
||||
);
|
||||
}
|
||||
|
||||
if (newerDate) {
|
||||
return (
|
||||
t('Prune_Warning_after', { items: filesOrMessages, roomName: name, fromDate: formatDate(fromDate, 'L LT') }) + exceptPinned + ifFrom
|
||||
);
|
||||
}
|
||||
|
||||
if (olderDate) {
|
||||
return (
|
||||
t('Prune_Warning_before', { items: filesOrMessages, roomName: name, toDate: formatDate(toDate, 'L LT') }) + exceptPinned + ifFrom
|
||||
);
|
||||
}
|
||||
|
||||
return t('Prune_Warning_all', { items: filesOrMessages, roomName: name }) + exceptPinned + ifFrom;
|
||||
}, [attached, fromDate, newerDate, olderDate, pinned, room, t, toDate, users]);
|
||||
|
||||
const validateText = useMemo(() => {
|
||||
if (fromDate > toDate) {
|
||||
return t('Newer_than_may_not_exceed_Older_than');
|
||||
}
|
||||
|
||||
if (isNaN(fromDate.getTime()) || isNaN(toDate.getTime())) {
|
||||
return t('error-invalid-date');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [fromDate, t, toDate]);
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<PruneMessages callOutText={callOutText} validateText={validateText} users={users} onClickClose={close} onClickPrune={handlePrune} />
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default PruneMessagesWithData;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './PruneMessagesWithData';
|
||||
Reference in New Issue
Block a user