Skip to content

Commit

Permalink
[PLAT-15247][Platform][Backup]Create Backup scheduled policy List
Browse files Browse the repository at this point in the history
Summary:
This diff implements Backup scheduled policy list.
To support ifinite scroll, we've added 'react-infinite-scroll-component'.
The feature is currently under the feature flag called 'enableBackupPITR' and it is disabled by default.

The following operations are also supported.
1. editing Policy
2. Deleting Policy
3. Show Intervals
4. Show tables that are backed up

[[ https://www.figma.com/design/DdKb47p9sREh4L1CvBHGYq/PITR?node-id=4450-96923&node-type=frame&m=dev | FIGMA ]]

Test Plan:
Tested manually

{F283955}
{F283956}
{F283957}
{F283958}
{F283959}

Reviewers: lsangappa

Reviewed By: lsangappa

Subscribers: yugaware

Differential Revision: https://phorge.dev.yugabyte.com/D37939
  • Loading branch information
haikarthikssk committed Sep 11, 2024
1 parent 5fa6dc9 commit be0d1d1
Show file tree
Hide file tree
Showing 12 changed files with 1,023 additions and 23 deletions.
35 changes: 35 additions & 0 deletions managed/ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions managed/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
"react-fa": "5.0.0",
"react-hook-form": "7.40.0",
"react-i18next": "11.18.6",
"react-infinite-scroll-component": "6.1.0",
"react-intl": "2.9.0",
"react-leaflet": "^1.9.1",
"react-measure": "1.4.7",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface ScheduleTaskParams {
keyspaceList: IBackup['commonBackupInfo']['responseList'];
isTableByTableBackup: IBackup['isTableByTableBackup']
expiryTimeUnit: string;
pointInTimeRestoreEnabled?: boolean;
}

export enum IBackupScheduleStatus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,138 @@
* http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt
*/

import { FC } from 'react';
import { FC, useMemo } from 'react';
import { useInfiniteQuery } from 'react-query';
import { keyBy } from 'lodash';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { useToggle } from 'react-use';
import { Grid, makeStyles } from '@material-ui/core';
import { YBButton } from '../../../components';
import { YBLoadingCircleIcon } from '../../../../components/common/indicators';
import InfiniteScroll from 'react-infinite-scroll-component';
import CreateScheduledBackupModal from './create/CreateScheduledBackupModal';
import { BACKUP_REFETCH_INTERVAL } from '../../../../components/backupv2/common/BackupUtils';
import { ScheduledCard } from './list/ScheduledCard';
import { ScheduledBackupEmpty } from './ScheduledBackupEmpty';
import { AllowedTasks } from '../../../helpers/dtos';
import { getScheduledBackupList } from '../../../../components/backupv2/common/BackupScheduleAPI';

interface ScheduledBackupListProps {
universeUUID: string;
allowedTasks: AllowedTasks;
}

const ScheduledBackupList: FC<ScheduledBackupListProps> = () => {
const useStyles = makeStyles((theme) => ({
noMoreBackup: {
display: 'flex',
justifyContent: 'center',
marginTop: '32px',
opacity: 0.5
},
cardList: {
display: 'flex',
flexDirection: 'column',
gap: '32px'
}
}));

const ScheduledBackupList: FC<ScheduledBackupListProps> = ({ universeUUID }) => {
const [createScheduledBackupModalVisible, toggleCreateScheduledBackupModalVisible] = useToggle(
false
);

const { data: scheduledBackupList, isLoading, fetchNextPage, hasNextPage } = useInfiniteQuery(
['scheduled_backup_list'],
({ pageParam = 0 }) => getScheduledBackupList(pageParam, universeUUID),
{
getNextPageParam: (lastPage) => lastPage.data.hasNext,
refetchInterval: BACKUP_REFETCH_INTERVAL
}
);

const storageConfigs = useSelector((reduxState: any) => reduxState.customer.configs);

const storageConfigsMap = useMemo(() => keyBy(storageConfigs?.data ?? [], 'configUUID'), [
storageConfigs
]);

const { t } = useTranslation('translation', {
keyPrefix: 'backup.scheduled.list'
});

const classes = useStyles();

if (isLoading) {
return <YBLoadingCircleIcon />;
}

const schedules = scheduledBackupList?.pages
.flatMap((page) => {
return page.data.entities;
})
.filter(
(schedule) =>
schedule.backupInfo !== undefined && schedule.backupInfo.universeUUID === universeUUID
);

if (!schedules || schedules?.length === 0) {
return (
<div>
<ScheduledBackupEmpty
hasPerm={true}
onActionButtonClick={() => {
toggleCreateScheduledBackupModalVisible(true);
}}
/>
<CreateScheduledBackupModal
visible={createScheduledBackupModalVisible}
onHide={() => {
toggleCreateScheduledBackupModalVisible(false);
}}
/>
</div>
);
}

return (
<div>
<ScheduledBackupEmpty
hasPerm={true}
onActionButtonClick={() => {
toggleCreateScheduledBackupModalVisible(true);
}}
/>
<CreateScheduledBackupModal
visible={createScheduledBackupModalVisible}
onHide={() => {
toggleCreateScheduledBackupModalVisible(false);
}}
/>
</div>
<InfiniteScroll
dataLength={2}
hasMore={!!hasNextPage}
next={fetchNextPage}
loader={<YBLoadingCircleIcon />}
endMessage={<div className={classes.noMoreBackup}>{t('noMoreSchedules')}</div>}
height={'550px'}
>
<div className={classes.cardList}>
<Grid container spacing={2} justifyContent="flex-end">
<Grid item>
<YBButton
variant="primary"
onClick={() => {
toggleCreateScheduledBackupModalVisible(true);
}}
>
{t('createPolicy')}
</YBButton>
</Grid>
</Grid>
{schedules.map((schedule) => (
<ScheduledCard
key={schedule.scheduleUUID}
schedule={schedule}
universeUUID={universeUUID}
storageConfig={storageConfigsMap[schedule.backupInfo.storageConfigUUID]}
/>
))}
<CreateScheduledBackupModal
visible={createScheduledBackupModalVisible}
onHide={() => {
toggleCreateScheduledBackupModalVisible(false);
}}
/>
</div>
</InfiniteScroll>
);
};

Expand Down
40 changes: 39 additions & 1 deletion managed/ui/src/redesign/features/backup/scheduled/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,48 @@ import { ROOT_URL } from '../../../../../config';
* @param payload - The backup information.
* @returns A promise that resolves to the taskUUID of the backup schedule creation.
*/
export const createScheduledBackup = (payload: IBackupSchedule['backupInfo']) => {
export const createScheduledBackupPolicy = (payload: IBackupSchedule['backupInfo']) => {
const customerUUID = localStorage.getItem('customerId');
return axios.post(`${ROOT_URL}/customers/${customerUUID}/create_backup_schedule_async`, {
...payload,
customerUUID
});
};

// toggle on/off scheduled backup policy
export const toggleScheduledBackupPolicy = (
universeUUID: string,
values: Partial<IBackupSchedule> & Pick<IBackupSchedule, 'scheduleUUID'>
) => {
const customerUUID = localStorage.getItem('customerId');

if (values['cronExpression']) {
delete values['frequency'];
}

return axios.put<IBackupSchedule>(
`${ROOT_URL}/customers/${customerUUID}/universes/${universeUUID}/schedules/${values['scheduleUUID']}/pause_resume`,
values
);
};

// delete scheduled backup policy
export const deleteSchedulePolicy = (universeUUID: string, scheduledPolicyUUID: string) => {
const customerUUID = localStorage.getItem('customerId');

return axios.delete<IBackupSchedule>(
`${ROOT_URL}/customers/${customerUUID}/universes/${universeUUID}/schedules/${scheduledPolicyUUID}/delete_backup_schedule_async`
);
};

// edit scheduled backup policy
export const editScheduledBackupPolicy = (
universeUUID: string,
values: Partial<IBackupSchedule> & Pick<IBackupSchedule, 'scheduleUUID'>
) => {
const customerUUID = localStorage.getItem('customerId');
return axios.put<IBackupSchedule>(
`${ROOT_URL}/customers/${customerUUID}/universes/${universeUUID}/schedules/${values['scheduleUUID']}/edit_backup_schedule_async`,
values
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { BackupFrequencyModel, TimeUnit } from '../../models/IBackupFrequency';

type BackupFrequencyFieldProps = {
control: Control<BackupFrequencyModel>;
isEditMode?: boolean;
};

const useStyles = makeStyles((theme) => ({
Expand Down Expand Up @@ -74,7 +75,7 @@ const useStyles = makeStyles((theme) => ({
}
}));

const BackupFrequencyField: FC<BackupFrequencyFieldProps> = ({ control }) => {
const BackupFrequencyField: FC<BackupFrequencyFieldProps> = ({ control, isEditMode = false }) => {
const classes = useStyles();

const { t } = useTranslation('translation', {
Expand Down Expand Up @@ -139,6 +140,7 @@ const BackupFrequencyField: FC<BackupFrequencyFieldProps> = ({ control }) => {
control={control}
name="useIncrementalBackup"
data-testid="useIncrementalBackup"
disabled={isEditMode}
/>
{useIncrementalBackupVal && (
<div className={classes.incBackupFields}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import { forwardRef, Fragment, useContext, useImperativeHandle } from 'react';
import cronstrue from 'cronstrue';
import { useMutation } from 'react-query';
import { useMutation, useQueryClient } from 'react-query';
import { toast } from 'react-toastify';
import { values } from 'lodash';
import { Trans, useTranslation } from 'react-i18next';
Expand All @@ -28,7 +28,7 @@ import { BACKUP_API_TYPES } from '../../../../../../../components/backupv2';

import { GetUniverseUUID, prepareScheduledBackupPayload } from '../../../ScheduledBackupUtils';

import { createScheduledBackup } from '../../../api/api';
import { createScheduledBackupPolicy } from '../../../api/api';

import { ReactComponent as CheckIcon } from '../../../../../../assets/check-white.svg';
import BulbIcon from '../../../../../../assets/bulb.svg';
Expand Down Expand Up @@ -100,9 +100,10 @@ const BackupSummary = forwardRef<PageRef>((_, forwardRef) => {

const classes = useStyles();
const universeUUID = GetUniverseUUID();
const queryClient = useQueryClient();

const doCreateScheduledBackup = useMutation(
(payload: ExtendedBackupScheduleProps) => createScheduledBackup(payload),
(payload: ExtendedBackupScheduleProps) => createScheduledBackupPolicy(payload),
{
onSuccess: () => {
toast.success(
Expand All @@ -112,6 +113,7 @@ const BackupSummary = forwardRef<PageRef>((_, forwardRef) => {
</span>
);
hideModal();
queryClient.invalidateQueries('scheduled_backup_list');
},
onError: (error: any) => {
toast.error(error?.response?.data?.error || t('errorMsg'));
Expand Down Expand Up @@ -192,7 +194,7 @@ const BackupSummary = forwardRef<PageRef>((_, forwardRef) => {
{
name: t(dbType),
value: backupObjects.keyspace?.isDefaultOption
? t(apiType === 'Ysql' ? 'allDatabases' : 'allKeyspaces', { keyPrefix: 'backup' })
? t(apiType === 'Ysql' ? 'allDatabase' : 'allKeyspaces', { keyPrefix: 'backup' })
: backupObjects.keyspace?.label
},
{
Expand Down
Loading

0 comments on commit be0d1d1

Please sign in to comment.