From 99eef7798a99213c1fc32df0ef7f3b9b96e9c516 Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Fri, 7 Oct 2022 16:26:15 +0200 Subject: [PATCH 01/17] PMM-10764 - timerange selector for PITR backup --- .../BackupInventory.constants.ts | 2 ++ .../BackupInventory.service.ts | 13 ++++++++++++- .../BackupInventory/BackupInventory.types.ts | 9 +++++++++ .../BackupInventory/BackupInventory.utils.ts | 7 +++++++ .../RestoreBackupModal.messages.ts | 1 + .../RestoreBackupModal/RestoreBackupModal.tsx | 19 +++++++++++++++++++ 6 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 public/app/percona/backup/components/BackupInventory/BackupInventory.utils.ts diff --git a/public/app/percona/backup/components/BackupInventory/BackupInventory.constants.ts b/public/app/percona/backup/components/BackupInventory/BackupInventory.constants.ts index d39b64dd0c55d..b8cdbb9d09bd6 100644 --- a/public/app/percona/backup/components/BackupInventory/BackupInventory.constants.ts +++ b/public/app/percona/backup/components/BackupInventory/BackupInventory.constants.ts @@ -3,3 +3,5 @@ export const LIST_ARTIFACTS_CANCEL_TOKEN = 'listArtifacts'; export const BACKUP_CANCEL_TOKEN = 'backup'; export const RESTORE_CANCEL_TOKEN = 'restore'; export const LOGS_LIMIT = 50; +export const DAY_FORMAT = 'YYYY[-]MM[-]DD'; +export const HOUR_FORMAT = 'HH[:]mm[:]ss'; diff --git a/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts b/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts index c91be304c5a9b..c958bf36ec1aa 100644 --- a/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts +++ b/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts @@ -1,11 +1,13 @@ import { CancelToken } from 'axios'; +import { SelectableValue } from '@grafana/data'; import { DBServiceList, ServiceListPayload } from 'app/percona/inventory/Inventory.types'; import { api } from 'app/percona/shared/helpers/api'; import { BackupLogResponse, BackupLogs, DataModel } from '../../Backup.types'; -import { Backup, BackupResponse } from './BackupInventory.types'; +import { Backup, BackupResponse, TimerangesResponse } from './BackupInventory.types'; +import { formatDate } from './BackupInventory.utils'; const BASE_URL = '/v1/management/backup'; @@ -40,6 +42,15 @@ export const BackupInventoryService = { }) ); }, + async listPitrTimeranges(artifactId: string): Promise>> { + const { timeranges } = await api.post(`${BASE_URL}/Artifacts/ListPITRTimeranges`, { + artifact_id: artifactId, + }); + return timeranges.map((value) => ({ + label: `${formatDate(value.start_timestamp)} / ${formatDate(value.end_timestamp)}`, + value: value.end_timestamp, + })); + }, async restore(serviceId: string, artifactId: string, token?: CancelToken) { return api.post( `${BASE_URL}/Backups/Restore`, diff --git a/public/app/percona/backup/components/BackupInventory/BackupInventory.types.ts b/public/app/percona/backup/components/BackupInventory/BackupInventory.types.ts index 67a0333506b20..99ebf139b48b6 100644 --- a/public/app/percona/backup/components/BackupInventory/BackupInventory.types.ts +++ b/public/app/percona/backup/components/BackupInventory/BackupInventory.types.ts @@ -32,3 +32,12 @@ export interface RawBackup { export interface BackupResponse { artifacts: RawBackup[]; } + +export interface RawTimeranges { + start_timestamp: string; + end_timestamp: string; +} + +export interface TimerangesResponse { + timeranges: RawTimeranges[]; +} diff --git a/public/app/percona/backup/components/BackupInventory/BackupInventory.utils.ts b/public/app/percona/backup/components/BackupInventory/BackupInventory.utils.ts new file mode 100644 index 0000000000000..80a44429ecd4e --- /dev/null +++ b/public/app/percona/backup/components/BackupInventory/BackupInventory.utils.ts @@ -0,0 +1,7 @@ +import moment from 'moment/moment'; + +import { DAY_FORMAT, HOUR_FORMAT } from './BackupInventory.constants'; + +export const formatDate = (value: string) => { + return moment(value).format(DAY_FORMAT + '-' + HOUR_FORMAT); +}; diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.messages.ts b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.messages.ts index 98e05159a09f3..6f646728577c2 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.messages.ts +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.messages.ts @@ -1,6 +1,7 @@ export const Messages = { title: 'Restore from backup', serviceSelection: 'Service selection', + timeRange: 'Time range', vendor: 'Vendor', serviceName: 'Service name', dataModel: 'Data model', diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index f9b1bf2dd49af..b1836602df66a 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -4,10 +4,12 @@ import { Field, withTypes } from 'react-final-form'; import { SelectableValue } from '@grafana/data'; import { Button, HorizontalGroup, useStyles } from '@grafana/ui'; +import { BackupMode } from 'app/percona/backup/Backup.types'; import { AsyncSelectField } from 'app/percona/shared/components/Form/AsyncSelectField'; import { Databases, DATABASE_LABELS } from 'app/percona/shared/core'; import { BackupErrorSection } from '../../BackupErrorSection/BackupErrorSection'; +import { BackupInventoryService } from '../BackupInventory.service'; import { Messages } from './RestoreBackupModal.messages'; import { RestoreBackupModalService } from './RestoreBackupModal.service'; @@ -63,6 +65,21 @@ export const RestoreBackupModal: FC = ({ disabled={values.vendor !== DATABASE_LABELS[Databases.mysql]} /> + {backup!.mode === BackupMode.PITR && ( + + {({ input }) => ( +
+ BackupInventoryService.listPitrTimeranges(backup!.id)} + {...input} + defaultOptions + data-testid="time-range-select-input" + /> +
+ )} +
+ )}
@@ -79,9 +96,11 @@ export const RestoreBackupModal: FC = ({
)} + + {!!restoreErrors.length && } Date: Wed, 12 Oct 2022 15:56:30 +0200 Subject: [PATCH 02/17] PMM-10764 Added datetimepicker --- .../DateTimePicker/DateTimePicker.tsx | 40 +++++++++++++-- .../DateTimePickers/TimeOfDayPicker.tsx | 5 +- .../BackupInventory.service.ts | 6 +-- .../BackupInventory/BackupInventory.types.ts | 5 ++ .../RestoreBackupModal/RestoreBackupModal.tsx | 49 +++++++++++++------ .../RestoreBackupModal.types.ts | 3 +- 6 files changed, 84 insertions(+), 24 deletions(-) diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx index b94b60fa7f4d1..b7650587671cb 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx @@ -1,6 +1,7 @@ import { css, cx } from '@emotion/css'; +import { TimePickerProps } from 'rc-time-picker'; import React, { FC, FormEvent, ReactNode, useCallback, useEffect, useState } from 'react'; -import Calendar from 'react-calendar'; +import Calendar, { CalendarProps } from 'react-calendar'; import { useMedia } from 'react-use'; import { dateTimeFormat, DateTime, dateTime, GrafanaTheme2, isDateTime } from '@grafana/data'; @@ -21,11 +22,14 @@ export interface Props { label?: ReactNode; /** Set the latest selectable date */ maxDate?: Date; + // @PERCONA + calendarProps?: CalendarProps; + timepickerProps?: TimePickerProps; } const stopPropagation = (event: React.MouseEvent) => event.stopPropagation(); -export const DateTimePicker: FC = ({ date, maxDate, label, onChange }) => { +export const DateTimePicker: FC = ({ date, maxDate, label, onChange, calendarProps, timepickerProps }) => { const [isOpen, setOpen] = useState(false); const theme = useTheme2(); @@ -61,13 +65,22 @@ export const DateTimePicker: FC = ({ date, maxDate, label, onChange }) => isFullscreen={true} onClose={() => setOpen(false)} maxDate={maxDate} + calendarProps={calendarProps} + timepickerProps={timepickerProps} /> ) : ( setOpen(false)}>
- setOpen(false)} /> + setOpen(false)} + calendarProps={calendarProps} + timepickerProps={timepickerProps} + />
@@ -84,6 +97,9 @@ interface DateTimeCalendarProps { onClose: () => void; isFullscreen: boolean; maxDate?: Date; + // @PERCONA + calendarProps?: CalendarProps; + timepickerProps?: TimePickerProps; } interface InputProps { @@ -161,7 +177,15 @@ const DateTimeInput: FC = ({ date, label, onChange, isFullscreen, on ); }; -const DateTimeCalendar: FC = ({ date, onClose, onChange, isFullscreen, maxDate }) => { +const DateTimeCalendar: FC = ({ + date, + onClose, + onChange, + isFullscreen, + maxDate, + calendarProps, + timepickerProps, +}) => { const calendarStyles = useStyles2(getBodyStyles); const styles = useStyles2(getStyles); const [internalDate, setInternalDate] = useState(() => { @@ -205,9 +229,15 @@ const DateTimeCalendar: FC = ({ date, onClose, onChange, className={calendarStyles.body} tileClassName={calendarStyles.title} maxDate={maxDate} + {...calendarProps} />
- +
diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.types.ts b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.types.ts index 76380d1234222..66071c1c170be 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.types.ts +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.types.ts @@ -1,7 +1,7 @@ import { SelectableValue } from '@grafana/data'; import { ApiVerboseError } from 'app/percona/shared/core'; -import { Backup } from '../BackupInventory.types'; +import { Backup, Timeranges } from '../BackupInventory.types'; export interface RestoreBackupModalProps { isVisible: boolean; @@ -17,6 +17,7 @@ export interface RestoreBackupFormProps { vendor: string; service: SelectableValue; dataModel: string; + timerange?: Timeranges; } export enum ServiceTypeSelect { From d3d59a84ebe4d74ae682c2c00ef0c16842166cc4 Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Tue, 18 Oct 2022 12:40:46 +0200 Subject: [PATCH 03/17] PMM-10764 Improved DateTimePicker --- .../RestoreBackupModal/RestoreBackupModal.tsx | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index 28d1d741b06cb..ed0f560293d08 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -1,8 +1,9 @@ import { Modal, LoaderButton, RadioButtonGroupField, TextInputField, validators } from '@percona/platform-core'; -import React, { FC, useMemo } from 'react'; +import moment from 'moment/moment'; +import React, { FC, useCallback, useMemo, useState } from 'react'; import { Field, withTypes } from 'react-final-form'; -import { SelectableValue } from '@grafana/data'; +import { SelectableValue, toUtc } from '@grafana/data'; import { Button, DateTimePicker, HorizontalGroup, useStyles } from '@grafana/ui'; import { BackupMode } from 'app/percona/backup/Backup.types'; import { AsyncSelectField } from 'app/percona/shared/components/Form/AsyncSelectField'; @@ -10,6 +11,7 @@ import { Databases, DATABASE_LABELS } from 'app/percona/shared/core'; import { BackupErrorSection } from '../../BackupErrorSection/BackupErrorSection'; import { BackupInventoryService } from '../BackupInventory.service'; +import { Timeranges } from '../BackupInventory.types'; import { Messages } from './RestoreBackupModal.messages'; import { RestoreBackupModalService } from './RestoreBackupModal.service'; @@ -50,6 +52,20 @@ export const RestoreBackupModal: FC = ({ } }; + const [selectedTimerange, setSelectedTimerange] = useState(); + const [selectedDay, setSelectedDay] = useState(); + const calculateDisableHours = useCallback(() => { + console.log(new Date()); + console.log(selectedDay); + console.log(selectedTimerange); + + if (!selectedDay) { + const hoursInDay = new Array(24); + + //return moment(selectedTimerange?.endTimestamp) + } + return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 22, 23]; + }, [selectedDay, selectedTimerange]); return (
= ({ {...input} defaultOptions data-testid="time-range-select-input" + onChange={(e) => { + console.log(e); + setSelectedTimerange(e.value); + }} />
)} - { + {selectedTimerange && ( { console.log(e); }} - calendarProps={{ minDate: new Date() }} + calendarProps={{ + minDate: new Date(selectedTimerange.startTimestamp), + maxDate: new Date(selectedTimerange.endTimestamp), + onClickDay: (e) => setSelectedDay(e), + }} timepickerProps={{ - disabledHours: () => { - return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 22, 23]; - }, + disabledHours: calculateDisableHours, hideDisabledOptions: true, }} /> - } + )} )} - - {JSON.stringify(values)}
From 00bbb4589f5cecd64b279740e81005b41e1de1df Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Tue, 18 Oct 2022 14:41:21 +0200 Subject: [PATCH 04/17] PMM-10764 Added algoritmus for time limit --- .../RestoreBackupModal/RestoreBackupModal.tsx | 101 ++++++++++++++++-- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index ed0f560293d08..32df55e9c422e 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -52,20 +52,99 @@ export const RestoreBackupModal: FC = ({ } }; - const [selectedTimerange, setSelectedTimerange] = useState(); + const [selectedTimerange, setSelectedTimerange] = useState({ + startTimestamp: '2022-10-18T11:16:07Z', + endTimestamp: '2022-10-18T11:51:08Z', + }); const [selectedDay, setSelectedDay] = useState(); const calculateDisableHours = useCallback(() => { - console.log(new Date()); - console.log(selectedDay); - console.log(selectedTimerange); + const hoursInDay = []; + if (moment(selectedTimerange?.startTimestamp).dayOfYear() === moment(selectedTimerange?.endTimestamp).dayOfYear()) { + for (let i = 0; i < 24; i++) { + if (moment(selectedTimerange?.startTimestamp).hours() < i) { + hoursInDay.push(i); + } + if (moment(selectedTimerange?.endTimestamp).hours() > i) { + hoursInDay.push(i); + } + } + } else { + // if (!selectedDay) { + // for (let i = 0; i < 24; i++) { + // if (moment(selectedTimerange?.endTimestamp).hours() < i) { + // hoursInDay.push(i); + // } + // } + // console.log(hoursInDay); + // //return moment(selectedTimerange?.endTimestamp); + // } else { + // for (let i = 0; i < 24; i++) { + // if (moment(selectedTimerange?.endTimestamp).day() === moment(selectedDay).day()) { + // } + // if (moment(selectedTimerange?.endTimestamp).hours() < i) { + // hoursInDay.push(i); + // } + // } + //} + } - if (!selectedDay) { - const hoursInDay = new Array(24); + return hoursInDay; + }, [selectedTimerange]); + + const calculateDisableMinutes = useCallback( + (hour) => { + const disabledMinutes = []; + if (hour === moment(selectedTimerange?.startTimestamp).hour()) { + for (let i = 0; i < 60; i++) { + if (i < moment(selectedTimerange?.startTimestamp).minute()) { + console.log(moment(selectedTimerange?.startTimestamp).minute()); + console.log(i); + disabledMinutes.push(i); + } + } + } + if (hour === moment(selectedTimerange?.endTimestamp).hour()) { + for (let i = 0; i < 60; i++) { + if (i > moment(selectedTimerange?.endTimestamp).minute()) { + disabledMinutes.push(i); + } + } + } + return disabledMinutes; + }, + [selectedTimerange] + ); + + const calculateDisableSeconds = useCallback( + (hour, minute) => { + const disabledSeconds = []; + if ( + hour === moment(selectedTimerange?.startTimestamp).hour() && + minute === moment(selectedTimerange?.startTimestamp).minute() + ) { + for (let i = 0; i < 60; i++) { + if (i < moment(selectedTimerange?.startTimestamp).second()) { + console.log(moment(selectedTimerange?.startTimestamp).minute()); + console.log(i); + disabledSeconds.push(i); + } + } + } + if ( + hour === moment(selectedTimerange?.endTimestamp).hour() && + minute === moment(selectedTimerange?.endTimestamp).minute() + ) { + for (let i = 0; i < 60; i++) { + if (i > moment(selectedTimerange?.endTimestamp).second()) { + disabledSeconds.push(i); + } + } + } + return disabledSeconds; + }, + [selectedTimerange] + ); - //return moment(selectedTimerange?.endTimestamp) - } - return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 22, 23]; - }, [selectedDay, selectedTimerange]); return ( = ({ }} timepickerProps={{ disabledHours: calculateDisableHours, + disabledMinutes: calculateDisableMinutes, + disabledSeconds: calculateDisableSeconds, hideDisabledOptions: true, }} /> From 8c6288c3966e1b1502cebb05ca33b34d58201a0c Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Tue, 18 Oct 2022 15:40:36 +0200 Subject: [PATCH 05/17] PMM-10764 working algoritmus for limit DateTime --- .../RestoreBackupModal/RestoreBackupModal.tsx | 81 ++++++++++--------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index 32df55e9c422e..611643aaf41f7 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -52,85 +52,88 @@ export const RestoreBackupModal: FC = ({ } }; - const [selectedTimerange, setSelectedTimerange] = useState({ - startTimestamp: '2022-10-18T11:16:07Z', - endTimestamp: '2022-10-18T11:51:08Z', - }); - const [selectedDay, setSelectedDay] = useState(); + const [selectedTimerange, setSelectedTimerange] = useState(); + // { + // startTimestamp: '2022-10-16T11:16:07Z', + // endTimestamp: '2022-10-18T11:51:08Z', + // } + const [selectedDay, setSelectedDay] = useState( + selectedTimerange ? new Date(selectedTimerange.endTimestamp) : undefined + ); const calculateDisableHours = useCallback(() => { const hoursInDay = []; if (moment(selectedTimerange?.startTimestamp).dayOfYear() === moment(selectedTimerange?.endTimestamp).dayOfYear()) { for (let i = 0; i < 24; i++) { - if (moment(selectedTimerange?.startTimestamp).hours() < i) { + if (i < moment(selectedTimerange?.startTimestamp).hours()) { hoursInDay.push(i); } - if (moment(selectedTimerange?.endTimestamp).hours() > i) { + if (i > moment(selectedTimerange?.endTimestamp).hours()) { hoursInDay.push(i); } } } else { - // if (!selectedDay) { - // for (let i = 0; i < 24; i++) { - // if (moment(selectedTimerange?.endTimestamp).hours() < i) { - // hoursInDay.push(i); - // } - // } - // console.log(hoursInDay); - // //return moment(selectedTimerange?.endTimestamp); - // } else { - // for (let i = 0; i < 24; i++) { - // if (moment(selectedTimerange?.endTimestamp).day() === moment(selectedDay).day()) { - // } - // if (moment(selectedTimerange?.endTimestamp).hours() < i) { - // hoursInDay.push(i); - // } - // } - //} + if (moment(selectedDay).dayOfYear() === moment(selectedTimerange?.startTimestamp).dayOfYear()) { + for (let i = 0; i < 24; i++) { + if (i < moment(selectedTimerange?.startTimestamp).hours()) { + hoursInDay.push(i); + } + } + } + if (moment(selectedDay).dayOfYear() === moment(selectedTimerange?.endTimestamp).dayOfYear()) { + for (let i = 0; i < 24; i++) { + if (i > moment(selectedTimerange?.startTimestamp).hours()) { + hoursInDay.push(i); + } + } + } } return hoursInDay; - }, [selectedTimerange]); + }, [selectedDay, selectedTimerange]); const calculateDisableMinutes = useCallback( (hour) => { const disabledMinutes = []; - if (hour === moment(selectedTimerange?.startTimestamp).hour()) { - for (let i = 0; i < 60; i++) { - if (i < moment(selectedTimerange?.startTimestamp).minute()) { - console.log(moment(selectedTimerange?.startTimestamp).minute()); - console.log(i); - disabledMinutes.push(i); + if (moment(selectedDay).dayOfYear() === moment(selectedTimerange?.startTimestamp).dayOfYear()) { + if (hour === moment(selectedTimerange?.startTimestamp).hour()) { + for (let i = 0; i < 60; i++) { + if (i < moment(selectedTimerange?.startTimestamp).minute()) { + disabledMinutes.push(i); + } } } } - if (hour === moment(selectedTimerange?.endTimestamp).hour()) { - for (let i = 0; i < 60; i++) { - if (i > moment(selectedTimerange?.endTimestamp).minute()) { - disabledMinutes.push(i); + if (moment(selectedDay).dayOfYear() === moment(selectedTimerange?.endTimestamp).dayOfYear()) { + if (hour === moment(selectedTimerange?.endTimestamp).hour()) { + for (let i = 0; i < 60; i++) { + if (i > moment(selectedTimerange?.endTimestamp).minute()) { + disabledMinutes.push(i); + } } } } + return disabledMinutes; }, - [selectedTimerange] + [selectedDay, selectedTimerange?.endTimestamp, selectedTimerange?.startTimestamp] ); const calculateDisableSeconds = useCallback( (hour, minute) => { const disabledSeconds = []; if ( + moment(selectedDay).dayOfYear() === moment(selectedTimerange?.startTimestamp).dayOfYear() && hour === moment(selectedTimerange?.startTimestamp).hour() && minute === moment(selectedTimerange?.startTimestamp).minute() ) { for (let i = 0; i < 60; i++) { if (i < moment(selectedTimerange?.startTimestamp).second()) { - console.log(moment(selectedTimerange?.startTimestamp).minute()); - console.log(i); disabledSeconds.push(i); } } } if ( + moment(selectedDay).dayOfYear() === moment(selectedTimerange?.endTimestamp).dayOfYear() && hour === moment(selectedTimerange?.endTimestamp).hour() && minute === moment(selectedTimerange?.endTimestamp).minute() ) { @@ -142,7 +145,7 @@ export const RestoreBackupModal: FC = ({ } return disabledSeconds; }, - [selectedTimerange] + [selectedDay, selectedTimerange] ); return ( From a901c4f91beba26e41da568e6d27a38667085372 Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Tue, 18 Oct 2022 16:04:37 +0200 Subject: [PATCH 06/17] PMM-10764 improved algoritmus DateTimePicker --- .../RestoreBackupModal/RestoreBackupModal.tsx | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index 611643aaf41f7..8f53fd0942669 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -52,38 +52,27 @@ export const RestoreBackupModal: FC = ({ } }; - const [selectedTimerange, setSelectedTimerange] = useState(); - // { - // startTimestamp: '2022-10-16T11:16:07Z', - // endTimestamp: '2022-10-18T11:51:08Z', - // } + const [selectedTimerange, setSelectedTimerange] = useState({ + startTimestamp: '2022-10-18T11:16:07Z', + endTimestamp: '2022-10-18T11:51:08Z', + }); + const [selectedDay, setSelectedDay] = useState( selectedTimerange ? new Date(selectedTimerange.endTimestamp) : undefined ); const calculateDisableHours = useCallback(() => { const hoursInDay = []; - if (moment(selectedTimerange?.startTimestamp).dayOfYear() === moment(selectedTimerange?.endTimestamp).dayOfYear()) { + if (moment(selectedDay).isSame(selectedTimerange?.startTimestamp, 'date')) { for (let i = 0; i < 24; i++) { if (i < moment(selectedTimerange?.startTimestamp).hours()) { hoursInDay.push(i); } - if (i > moment(selectedTimerange?.endTimestamp).hours()) { - hoursInDay.push(i); - } - } - } else { - if (moment(selectedDay).dayOfYear() === moment(selectedTimerange?.startTimestamp).dayOfYear()) { - for (let i = 0; i < 24; i++) { - if (i < moment(selectedTimerange?.startTimestamp).hours()) { - hoursInDay.push(i); - } - } } - if (moment(selectedDay).dayOfYear() === moment(selectedTimerange?.endTimestamp).dayOfYear()) { - for (let i = 0; i < 24; i++) { - if (i > moment(selectedTimerange?.startTimestamp).hours()) { - hoursInDay.push(i); - } + } + if (moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date')) { + for (let i = 0; i < 24; i++) { + if (i > moment(selectedTimerange?.startTimestamp).hours()) { + hoursInDay.push(i); } } } From bf30639cdd494a33a89fd5b0bb50c7cf335c7566 Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Tue, 18 Oct 2022 16:21:33 +0200 Subject: [PATCH 07/17] PMM-10764 improved algoritm DateTimePicker --- .../RestoreBackupModal/RestoreBackupModal.tsx | 76 +++++++++---------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index 8f53fd0942669..79eceb8153fed 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -52,52 +52,49 @@ export const RestoreBackupModal: FC = ({ } }; - const [selectedTimerange, setSelectedTimerange] = useState({ - startTimestamp: '2022-10-18T11:16:07Z', - endTimestamp: '2022-10-18T11:51:08Z', - }); + const [selectedTimerange, setSelectedTimerange] = useState(); const [selectedDay, setSelectedDay] = useState( selectedTimerange ? new Date(selectedTimerange.endTimestamp) : undefined ); const calculateDisableHours = useCallback(() => { - const hoursInDay = []; - if (moment(selectedDay).isSame(selectedTimerange?.startTimestamp, 'date')) { - for (let i = 0; i < 24; i++) { + const disabledHours = []; + + for (let i = 0; i < 24; i++) { + if (moment(selectedDay).isSame(selectedTimerange?.startTimestamp, 'date')) { if (i < moment(selectedTimerange?.startTimestamp).hours()) { - hoursInDay.push(i); + disabledHours.push(i); } } - } - if (moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date')) { - for (let i = 0; i < 24; i++) { - if (i > moment(selectedTimerange?.startTimestamp).hours()) { - hoursInDay.push(i); + if (moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date')) { + if (i > moment(selectedTimerange?.endTimestamp).hours()) { + disabledHours.push(i); } } } - return hoursInDay; + return disabledHours; }, [selectedDay, selectedTimerange]); const calculateDisableMinutes = useCallback( (hour) => { const disabledMinutes = []; - if (moment(selectedDay).dayOfYear() === moment(selectedTimerange?.startTimestamp).dayOfYear()) { - if (hour === moment(selectedTimerange?.startTimestamp).hour()) { - for (let i = 0; i < 60; i++) { - if (i < moment(selectedTimerange?.startTimestamp).minute()) { - disabledMinutes.push(i); - } + + for (let i = 0; i < 60; i++) { + if ( + moment(selectedDay).isSame(selectedTimerange?.startTimestamp, 'date') && + hour === moment(selectedTimerange?.startTimestamp).hour() + ) { + if (i < moment(selectedTimerange?.startTimestamp).minute()) { + disabledMinutes.push(i); } } - } - if (moment(selectedDay).dayOfYear() === moment(selectedTimerange?.endTimestamp).dayOfYear()) { - if (hour === moment(selectedTimerange?.endTimestamp).hour()) { - for (let i = 0; i < 60; i++) { - if (i > moment(selectedTimerange?.endTimestamp).minute()) { - disabledMinutes.push(i); - } + if ( + moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date') && + hour === moment(selectedTimerange?.endTimestamp).hour() + ) { + if (i > moment(selectedTimerange?.endTimestamp).minute()) { + disabledMinutes.push(i); } } } @@ -110,23 +107,22 @@ export const RestoreBackupModal: FC = ({ const calculateDisableSeconds = useCallback( (hour, minute) => { const disabledSeconds = []; - if ( - moment(selectedDay).dayOfYear() === moment(selectedTimerange?.startTimestamp).dayOfYear() && - hour === moment(selectedTimerange?.startTimestamp).hour() && - minute === moment(selectedTimerange?.startTimestamp).minute() - ) { - for (let i = 0; i < 60; i++) { + + for (let i = 0; i < 60; i++) { + if ( + moment(selectedDay).isSame(selectedTimerange?.startTimestamp, 'date') && + hour === moment(selectedTimerange?.startTimestamp).hour() && + minute === moment(selectedTimerange?.startTimestamp).minute() + ) { if (i < moment(selectedTimerange?.startTimestamp).second()) { disabledSeconds.push(i); } } - } - if ( - moment(selectedDay).dayOfYear() === moment(selectedTimerange?.endTimestamp).dayOfYear() && - hour === moment(selectedTimerange?.endTimestamp).hour() && - minute === moment(selectedTimerange?.endTimestamp).minute() - ) { - for (let i = 0; i < 60; i++) { + if ( + moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date') && + hour === moment(selectedTimerange?.endTimestamp).hour() && + minute === moment(selectedTimerange?.endTimestamp).minute() + ) { if (i > moment(selectedTimerange?.endTimestamp).second()) { disabledSeconds.push(i); } From b5654e8d521e1b6a15e59cd61bee9af4f4e9d426 Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Wed, 19 Oct 2022 16:17:53 +0200 Subject: [PATCH 08/17] PMM-10764 Added pitr restore --- .../TimeRangePicker/CalendarBody.tsx | 6 ++ .../BackupInventory/BackupInventory.tsx | 4 +- .../RestoreBackupModal/RestoreBackupModal.tsx | 72 ++++++++++--------- .../RestoreBackupModal.types.ts | 2 +- 4 files changed, 48 insertions(+), 36 deletions(-) diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx index 86cb0bc80cb3c..aae485a10ddf5 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx @@ -88,6 +88,12 @@ export const getBodyStyles = (theme: GrafanaTheme2) => { display: flex; } + .react-calendar__month-view__days__day:disabled, + .react-calendar__year-view__months__month:disabled, + .react-calendar__decade-view__years__year:disabled { + color: #3f4249; + } + .react-calendar__navigation__label, .react-calendar__navigation__arrow, .react-calendar__navigation { diff --git a/public/app/percona/backup/components/BackupInventory/BackupInventory.tsx b/public/app/percona/backup/components/BackupInventory/BackupInventory.tsx index 15965f1c08837..2458856ef993b 100644 --- a/public/app/percona/backup/components/BackupInventory/BackupInventory.tsx +++ b/public/app/percona/backup/components/BackupInventory/BackupInventory.tsx @@ -139,9 +139,9 @@ export const BackupInventory: FC = () => { setLogsModalVisible(false); }; - const handleRestore = async (serviceId: string, artifactId: string) => { + const handleRestore = async (serviceId: string, artifactId: string, pitrTimestamp: string) => { try { - await BackupInventoryService.restore(serviceId, artifactId, generateToken(RESTORE_CANCEL_TOKEN)); + await BackupInventoryService.restore(serviceId, artifactId, pitrTimestamp, generateToken(RESTORE_CANCEL_TOKEN)); setRestoreErrors([]); setRestoreModalVisible(false); } catch (e) { diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index 79eceb8153fed..383834cd725e5 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -3,7 +3,7 @@ import moment from 'moment/moment'; import React, { FC, useCallback, useMemo, useState } from 'react'; import { Field, withTypes } from 'react-final-form'; -import { SelectableValue, toUtc } from '@grafana/data'; +import { DateTime, SelectableValue, toUtc } from '@grafana/data'; import { Button, DateTimePicker, HorizontalGroup, useStyles } from '@grafana/ui'; import { BackupMode } from 'app/percona/backup/Backup.types'; import { AsyncSelectField } from 'app/percona/shared/components/Form/AsyncSelectField'; @@ -32,9 +32,6 @@ const serviceTypeOptions: Array> = [ }, ]; -console.log(new Date('2022-10-12T11:59:09Z')); -console.log(new Date()); - export const RestoreBackupModal: FC = ({ backup, isVisible, @@ -45,18 +42,18 @@ export const RestoreBackupModal: FC = ({ }) => { const styles = useStyles(getStyles); const initialValues = useMemo(() => (backup ? toFormProps(backup) : undefined), [backup]); - const handleSubmit = ({ serviceType, service }: RestoreBackupFormProps) => { - if (backup) { - const serviceId = serviceType === ServiceTypeSelect.SAME ? backup.serviceId : service.value; - onRestore(serviceId || '', backup.id); - } - }; const [selectedTimerange, setSelectedTimerange] = useState(); - + const [selectedTimerangeFromDatepicker, setSelectedTimerangeFromDatepicker] = useState(); const [selectedDay, setSelectedDay] = useState( selectedTimerange ? new Date(selectedTimerange.endTimestamp) : undefined ); + const handleSubmit = ({ serviceType, service }: RestoreBackupFormProps) => { + if (backup && selectedTimerangeFromDatepicker) { + const serviceId = serviceType === ServiceTypeSelect.SAME ? backup.serviceId : service.value; + onRestore(serviceId || '', backup.id, selectedTimerangeFromDatepicker.toISOString()); + } + }; const calculateDisableHours = useCallback(() => { const disabledHours = []; @@ -101,7 +98,7 @@ export const RestoreBackupModal: FC = ({ return disabledMinutes; }, - [selectedDay, selectedTimerange?.endTimestamp, selectedTimerange?.startTimestamp] + [selectedDay, selectedTimerange] ); const calculateDisableSeconds = useCallback( @@ -153,7 +150,7 @@ export const RestoreBackupModal: FC = ({ {backup!.mode === BackupMode.PITR && ( <> - + {({ input }) => (
= ({ defaultOptions data-testid="time-range-select-input" onChange={(e) => { - console.log(e); + setSelectedTimerangeFromDatepicker(undefined); setSelectedTimerange(e.value); + input.onChange(e); }} />
)}
- {selectedTimerange && ( - { - console.log(e); - }} - calendarProps={{ - minDate: new Date(selectedTimerange.startTimestamp), - maxDate: new Date(selectedTimerange.endTimestamp), - onClickDay: (e) => setSelectedDay(e), - }} - timepickerProps={{ - disabledHours: calculateDisableHours, - disabledMinutes: calculateDisableMinutes, - disabledSeconds: calculateDisableSeconds, - hideDisabledOptions: true, - }} - /> - )} )}
@@ -209,6 +188,33 @@ export const RestoreBackupModal: FC = ({ + {selectedTimerange && ( + { + console.log(e); + setSelectedTimerangeFromDatepicker(e); + }} + calendarProps={{ + minDate: new Date(selectedTimerange.startTimestamp), + maxDate: new Date(selectedTimerange.endTimestamp), + onClickDay: (e) => { + console.log(e); + setSelectedDay(e); + }, + }} + timepickerProps={{ + disabledHours: calculateDisableHours, + disabledMinutes: calculateDisableMinutes, + disabledSeconds: calculateDisableSeconds, + hideDisabledOptions: true, + }} + /> + )} diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.types.ts b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.types.ts index 66071c1c170be..d47206ce6b39d 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.types.ts +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.types.ts @@ -9,7 +9,7 @@ export interface RestoreBackupModalProps { noService?: boolean; restoreErrors?: ApiVerboseError[]; onClose: () => void; - onRestore: (serviceId: string, artifactId: string) => void; + onRestore: (serviceId: string, artifactId: string, pitrTimestamp: string) => void; } export interface RestoreBackupFormProps { From b88d914761d1c509de7f5de1f622a3a51ccd1268 Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Wed, 19 Oct 2022 16:55:08 +0200 Subject: [PATCH 09/17] PMM-10764 fix datetimepicker --- .../DateTimePicker/DateTimePicker.tsx | 6 ++++++ .../BackupInventory.service.ts | 21 ++++++++++++++----- .../RestoreBackupModal/RestoreBackupModal.tsx | 9 +++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx index b7650587671cb..ee1958e255b16 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx @@ -214,6 +214,12 @@ const DateTimeCalendar: FC = ({ setInternalDate(date.toDate()); }, []); + useEffect(() => { + if (date?.isValid()) { + setInternalDate(date.toDate()); + } + }, [date]); + return (
>> { - const { timeranges } = await api.post(`${BASE_URL}/Artifacts/ListPITRTimeranges`, { - artifact_id: artifactId, - }); + // const { timeranges } = await api.post(`${BASE_URL}/Artifacts/ListPITRTimeranges`, { + // artifact_id: artifactId, + // }); + const timeranges = [ + { + start_timestamp: '2022-09-16T11:16:07Z', + end_timestamp: '2022-10-18T11:51:08Z', + }, + { + start_timestamp: '2022-11-16T11:16:07Z', + end_timestamp: '2022-11-18T11:51:08Z', + }, + ]; return timeranges.map((value) => ({ label: `${formatDate(value.start_timestamp)} / ${formatDate(value.end_timestamp)}`, value: { startTimestamp: value.start_timestamp, endTimestamp: value.end_timestamp }, })); }, - async restore(serviceId: string, artifactId: string, token?: CancelToken) { + async restore(serviceId: string, artifactId: string, pitrTimestamp: string, token?: CancelToken) { return api.post( `${BASE_URL}/Backups/Restore`, { service_id: serviceId, artifact_id: artifactId, + pitr_timestamp: pitrTimestamp, }, false, token diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index 383834cd725e5..a67391d4e7a24 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -1,6 +1,6 @@ import { Modal, LoaderButton, RadioButtonGroupField, TextInputField, validators } from '@percona/platform-core'; import moment from 'moment/moment'; -import React, { FC, useCallback, useMemo, useState } from 'react'; +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { Field, withTypes } from 'react-final-form'; import { DateTime, SelectableValue, toUtc } from '@grafana/data'; @@ -130,6 +130,13 @@ export const RestoreBackupModal: FC = ({ [selectedDay, selectedTimerange] ); + useEffect(() => { + if (moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date')) { + setSelectedTimerangeFromDatepicker(toUtc(selectedTimerange?.endTimestamp)); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedDay, selectedTimerange]); + return ( Date: Thu, 20 Oct 2022 15:52:56 +0200 Subject: [PATCH 10/17] PMM-10764 Fixed datetimepicker issue --- .../BackupInventory.service.ts | 28 +++++++++---------- .../RestoreBackupModal/RestoreBackupModal.tsx | 18 ++++++------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts b/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts index c9d26416a35c0..a704c37af7771 100644 --- a/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts +++ b/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts @@ -6,7 +6,7 @@ import { api } from 'app/percona/shared/helpers/api'; import { BackupLogResponse, BackupLogs, DataModel } from '../../Backup.types'; -import { Backup, BackupResponse, Timeranges } from './BackupInventory.types'; +import { Backup, BackupResponse, Timeranges, TimerangesResponse } from './BackupInventory.types'; import { formatDate } from './BackupInventory.utils'; const BASE_URL = '/v1/management/backup'; @@ -43,19 +43,19 @@ export const BackupInventoryService = { ); }, async listPitrTimeranges(artifactId: string): Promise>> { - // const { timeranges } = await api.post(`${BASE_URL}/Artifacts/ListPITRTimeranges`, { - // artifact_id: artifactId, - // }); - const timeranges = [ - { - start_timestamp: '2022-09-16T11:16:07Z', - end_timestamp: '2022-10-18T11:51:08Z', - }, - { - start_timestamp: '2022-11-16T11:16:07Z', - end_timestamp: '2022-11-18T11:51:08Z', - }, - ]; + const { timeranges } = await api.post(`${BASE_URL}/Artifacts/ListPITRTimeranges`, { + artifact_id: artifactId, + }); + // const timeranges = [ + // { + // start_timestamp: '2022-09-16T11:16:07Z', + // end_timestamp: '2022-10-18T11:51:08Z', + // }, + // { + // start_timestamp: '2022-11-16T11:16:07Z', + // end_timestamp: '2022-11-18T11:51:08Z', + // }, + // ]; return timeranges.map((value) => ({ label: `${formatDate(value.start_timestamp)} / ${formatDate(value.end_timestamp)}`, value: { startTimestamp: value.start_timestamp, endTimestamp: value.end_timestamp }, diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index a67391d4e7a24..2a4cd66388cb2 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -45,9 +45,7 @@ export const RestoreBackupModal: FC = ({ const [selectedTimerange, setSelectedTimerange] = useState(); const [selectedTimerangeFromDatepicker, setSelectedTimerangeFromDatepicker] = useState(); - const [selectedDay, setSelectedDay] = useState( - selectedTimerange ? new Date(selectedTimerange.endTimestamp) : undefined - ); + const [selectedDay, setSelectedDay] = useState(); const handleSubmit = ({ serviceType, service }: RestoreBackupFormProps) => { if (backup && selectedTimerangeFromDatepicker) { const serviceId = serviceType === ServiceTypeSelect.SAME ? backup.serviceId : service.value; @@ -134,9 +132,16 @@ export const RestoreBackupModal: FC = ({ if (moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date')) { setSelectedTimerangeFromDatepicker(toUtc(selectedTimerange?.endTimestamp)); } + if (moment(selectedDay).isSame(selectedTimerange?.startTimestamp, 'date')) { + setSelectedTimerangeFromDatepicker(toUtc(selectedTimerange?.startTimestamp)); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedDay, selectedTimerange]); + useEffect(() => { + setSelectedTimerangeFromDatepicker(toUtc(selectedTimerange?.endTimestamp)); + }, [selectedTimerange]); + return ( = ({ defaultOptions data-testid="time-range-select-input" onChange={(e) => { - setSelectedTimerangeFromDatepicker(undefined); setSelectedTimerange(e.value); input.onChange(e); }} @@ -197,11 +201,7 @@ export const RestoreBackupModal: FC = ({ {selectedTimerange && ( { console.log(e); setSelectedTimerangeFromDatepicker(e); From 21b8301a87aa1a3ce59737f4e30a41ee744bcc0e Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Thu, 20 Oct 2022 16:24:53 +0200 Subject: [PATCH 11/17] PMM-10764 Improved DateTimePicker --- .../BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index 2a4cd66388cb2..2b1ae79e886b8 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -67,7 +67,6 @@ export const RestoreBackupModal: FC = ({ } } } - return disabledHours; }, [selectedDay, selectedTimerange]); @@ -139,6 +138,9 @@ export const RestoreBackupModal: FC = ({ }, [selectedDay, selectedTimerange]); useEffect(() => { + if (selectedTimerange) { + setSelectedDay(new Date(selectedTimerange.endTimestamp)); + } setSelectedTimerangeFromDatepicker(toUtc(selectedTimerange?.endTimestamp)); }, [selectedTimerange]); From f6a5cc2a0377e1d87b48cc41290adc4205c1aecd Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Thu, 20 Oct 2022 16:34:43 +0200 Subject: [PATCH 12/17] PMM-10764 - refactore --- .../RestoreBackupModal/RestoreBackupModal.tsx | 100 +++++++++--------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index 2b1ae79e886b8..6012f6ef7fbf7 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -55,15 +55,18 @@ export const RestoreBackupModal: FC = ({ const calculateDisableHours = useCallback(() => { const disabledHours = []; - for (let i = 0; i < 24; i++) { - if (moment(selectedDay).isSame(selectedTimerange?.startTimestamp, 'date')) { - if (i < moment(selectedTimerange?.startTimestamp).hours()) { - disabledHours.push(i); + if (selectedTimerange) { + const { startTimestamp, endTimestamp } = selectedTimerange; + for (let i = 0; i < 24; i++) { + if (moment(selectedDay).isSame(startTimestamp, 'date')) { + if (i < moment(startTimestamp).hours()) { + disabledHours.push(i); + } } - } - if (moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date')) { - if (i > moment(selectedTimerange?.endTimestamp).hours()) { - disabledHours.push(i); + if (moment(selectedDay).isSame(endTimestamp, 'date')) { + if (i > moment(endTimestamp).hours()) { + disabledHours.push(i); + } } } } @@ -73,26 +76,21 @@ export const RestoreBackupModal: FC = ({ const calculateDisableMinutes = useCallback( (hour) => { const disabledMinutes = []; - - for (let i = 0; i < 60; i++) { - if ( - moment(selectedDay).isSame(selectedTimerange?.startTimestamp, 'date') && - hour === moment(selectedTimerange?.startTimestamp).hour() - ) { - if (i < moment(selectedTimerange?.startTimestamp).minute()) { - disabledMinutes.push(i); + if (selectedTimerange) { + const { startTimestamp, endTimestamp } = selectedTimerange; + for (let i = 0; i < 60; i++) { + if (moment(selectedDay).isSame(startTimestamp, 'date') && hour === moment(startTimestamp).hour()) { + if (i < moment(startTimestamp).minute()) { + disabledMinutes.push(i); + } } - } - if ( - moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date') && - hour === moment(selectedTimerange?.endTimestamp).hour() - ) { - if (i > moment(selectedTimerange?.endTimestamp).minute()) { - disabledMinutes.push(i); + if (moment(selectedDay).isSame(endTimestamp, 'date') && hour === moment(endTimestamp).hour()) { + if (i > moment(endTimestamp).minute()) { + disabledMinutes.push(i); + } } } } - return disabledMinutes; }, [selectedDay, selectedTimerange] @@ -101,24 +99,26 @@ export const RestoreBackupModal: FC = ({ const calculateDisableSeconds = useCallback( (hour, minute) => { const disabledSeconds = []; - - for (let i = 0; i < 60; i++) { - if ( - moment(selectedDay).isSame(selectedTimerange?.startTimestamp, 'date') && - hour === moment(selectedTimerange?.startTimestamp).hour() && - minute === moment(selectedTimerange?.startTimestamp).minute() - ) { - if (i < moment(selectedTimerange?.startTimestamp).second()) { - disabledSeconds.push(i); + if (selectedTimerange) { + const { startTimestamp, endTimestamp } = selectedTimerange; + for (let i = 0; i < 60; i++) { + if ( + moment(selectedDay).isSame(startTimestamp, 'date') && + hour === moment(startTimestamp).hour() && + minute === moment(startTimestamp).minute() + ) { + if (i < moment(startTimestamp).second()) { + disabledSeconds.push(i); + } } - } - if ( - moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date') && - hour === moment(selectedTimerange?.endTimestamp).hour() && - minute === moment(selectedTimerange?.endTimestamp).minute() - ) { - if (i > moment(selectedTimerange?.endTimestamp).second()) { - disabledSeconds.push(i); + if ( + moment(selectedDay).isSame(endTimestamp, 'date') && + hour === moment(endTimestamp).hour() && + minute === moment(endTimestamp).minute() + ) { + if (i > moment(endTimestamp).second()) { + disabledSeconds.push(i); + } } } } @@ -128,20 +128,24 @@ export const RestoreBackupModal: FC = ({ ); useEffect(() => { - if (moment(selectedDay).isSame(selectedTimerange?.endTimestamp, 'date')) { - setSelectedTimerangeFromDatepicker(toUtc(selectedTimerange?.endTimestamp)); - } - if (moment(selectedDay).isSame(selectedTimerange?.startTimestamp, 'date')) { - setSelectedTimerangeFromDatepicker(toUtc(selectedTimerange?.startTimestamp)); + if (selectedTimerange) { + const { startTimestamp, endTimestamp } = selectedTimerange; + if (moment(selectedDay).isSame(endTimestamp, 'date')) { + setSelectedTimerangeFromDatepicker(toUtc(endTimestamp)); + } + if (moment(selectedDay).isSame(startTimestamp, 'date')) { + setSelectedTimerangeFromDatepicker(toUtc(startTimestamp)); + } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedDay, selectedTimerange]); useEffect(() => { if (selectedTimerange) { - setSelectedDay(new Date(selectedTimerange.endTimestamp)); + const { endTimestamp } = selectedTimerange; + setSelectedDay(new Date(endTimestamp)); + setSelectedTimerangeFromDatepicker(toUtc(endTimestamp)); } - setSelectedTimerangeFromDatepicker(toUtc(selectedTimerange?.endTimestamp)); }, [selectedTimerange]); return ( From 04c14482034bf89a623c06b6f4364c865453142e Mon Sep 17 00:00:00 2001 From: Filip Mikes Date: Thu, 20 Oct 2022 17:15:51 +0200 Subject: [PATCH 13/17] PMM-10764 Improved design --- .../BackupInventory/BackupInventory.utils.ts | 2 +- .../RestoreBackupModal.styles.ts | 28 ++--- .../RestoreBackupModal/RestoreBackupModal.tsx | 110 +++++++++--------- 3 files changed, 65 insertions(+), 75 deletions(-) diff --git a/public/app/percona/backup/components/BackupInventory/BackupInventory.utils.ts b/public/app/percona/backup/components/BackupInventory/BackupInventory.utils.ts index 80a44429ecd4e..4290c4a9c2ab8 100644 --- a/public/app/percona/backup/components/BackupInventory/BackupInventory.utils.ts +++ b/public/app/percona/backup/components/BackupInventory/BackupInventory.utils.ts @@ -3,5 +3,5 @@ import moment from 'moment/moment'; import { DAY_FORMAT, HOUR_FORMAT } from './BackupInventory.constants'; export const formatDate = (value: string) => { - return moment(value).format(DAY_FORMAT + '-' + HOUR_FORMAT); + return moment(value).format(DAY_FORMAT + ' ' + HOUR_FORMAT); }; diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.styles.ts b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.styles.ts index eccb5f84690f7..d4a89ed129bfa 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.styles.ts +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.styles.ts @@ -1,29 +1,19 @@ import { css } from '@emotion/css'; -import { GrafanaTheme } from '@grafana/data'; - -export const getStyles = ({ palette, typography, spacing }: GrafanaTheme) => ({ - formHalvesContainer: css` - display: flex; - margin-bottom: ${spacing.formInputMargin}; +import { GrafanaTheme2 } from '@grafana/data'; +export const getStyles = ({ v1: { palette, typography, spacing } }: GrafanaTheme2) => ({ + modalWrapper: css` + display: grid; + grid-template-columns: 1fr 1fr; + justify-content: center; + align-items: center; + gap: 0px ${spacing.sm}; & > div { - flex: 0 1 50%; - - &:first-child { - padding-right: ${spacing.md}; - } - - &:last-child { - padding-left: ${spacing.md}; - } + height: 100%; } `, radioGroup: css` - & > div:nth-last-of-type(2) { - flex-wrap: nowrap; - } - & input[type='radio'] + label { height: auto; white-space: nowrap; diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index 6012f6ef7fbf7..e277e6886bab0 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -4,9 +4,10 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { Field, withTypes } from 'react-final-form'; import { DateTime, SelectableValue, toUtc } from '@grafana/data'; -import { Button, DateTimePicker, HorizontalGroup, useStyles } from '@grafana/ui'; +import { Button, DateTimePicker, HorizontalGroup, useStyles2 } from '@grafana/ui'; import { BackupMode } from 'app/percona/backup/Backup.types'; import { AsyncSelectField } from 'app/percona/shared/components/Form/AsyncSelectField'; +import { Label } from 'app/percona/shared/components/Form/Label'; import { Databases, DATABASE_LABELS } from 'app/percona/shared/core'; import { BackupErrorSection } from '../../BackupErrorSection/BackupErrorSection'; @@ -40,7 +41,7 @@ export const RestoreBackupModal: FC = ({ onClose, onRestore, }) => { - const styles = useStyles(getStyles); + const styles = useStyles2(getStyles); const initialValues = useMemo(() => (backup ? toFormProps(backup) : undefined), [backup]); const [selectedTimerange, setSelectedTimerange] = useState(); @@ -155,57 +156,31 @@ export const RestoreBackupModal: FC = ({ onSubmit={handleSubmit} render={({ handleSubmit, valid, submitting, values }) => ( -
-
- - - {backup!.mode === BackupMode.PITR && ( - <> - - {({ input }) => ( -
- BackupInventoryService.listPitrTimeranges(backup!.id)} - {...input} - defaultOptions - data-testid="time-range-select-input" - onChange={(e) => { - setSelectedTimerange(e.value); - input.onChange(e); - }} - /> -
- )} -
- - )} -
-
- - {({ input }) => ( -
- RestoreBackupModalService.loadLocationOptions(backup!.id)} - defaultOptions - {...input} - data-testid="service-select-input" - /> -
- )} -
- - - {selectedTimerange && ( +
+ {backup!.mode === BackupMode.PITR && ( + <> + + {({ input }) => ( +
+ BackupInventoryService.listPitrTimeranges(backup!.id)} + {...input} + defaultOptions + data-testid="time-range-select-input" + onChange={(e) => { + setSelectedTimerange(e.value); + input.onChange(e); + }} + /> +
+ )} +
+ + )} + {selectedTimerange && ( +
+
+ )} + + + + + {({ input }) => ( +
+ RestoreBackupModalService.loadLocationOptions(backup!.id)} + defaultOptions + {...input} + data-testid="service-select-input" + /> +
)} -
-
+ + +
{!!restoreErrors.length && } Date: Fri, 21 Oct 2022 09:03:57 +0200 Subject: [PATCH 14/17] PMM-10764 changes after review --- .../BackupInventory.service.ts | 14 ++----------- .../BackupInventory/BackupInventory.tsx | 2 +- .../RestoreBackupModal/RestoreBackupModal.tsx | 20 +++++++++---------- .../RestoreBackupModal.types.ts | 2 +- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts b/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts index a704c37af7771..9d77d0d54a578 100644 --- a/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts +++ b/public/app/percona/backup/components/BackupInventory/BackupInventory.service.ts @@ -43,25 +43,15 @@ export const BackupInventoryService = { ); }, async listPitrTimeranges(artifactId: string): Promise>> { - const { timeranges } = await api.post(`${BASE_URL}/Artifacts/ListPITRTimeranges`, { + const { timeranges = [] } = await api.post(`${BASE_URL}/Artifacts/ListPITRTimeranges`, { artifact_id: artifactId, }); - // const timeranges = [ - // { - // start_timestamp: '2022-09-16T11:16:07Z', - // end_timestamp: '2022-10-18T11:51:08Z', - // }, - // { - // start_timestamp: '2022-11-16T11:16:07Z', - // end_timestamp: '2022-11-18T11:51:08Z', - // }, - // ]; return timeranges.map((value) => ({ label: `${formatDate(value.start_timestamp)} / ${formatDate(value.end_timestamp)}`, value: { startTimestamp: value.start_timestamp, endTimestamp: value.end_timestamp }, })); }, - async restore(serviceId: string, artifactId: string, pitrTimestamp: string, token?: CancelToken) { + async restore(serviceId: string, artifactId: string, pitrTimestamp?: string, token?: CancelToken) { return api.post( `${BASE_URL}/Backups/Restore`, { diff --git a/public/app/percona/backup/components/BackupInventory/BackupInventory.tsx b/public/app/percona/backup/components/BackupInventory/BackupInventory.tsx index 1fb866bea651d..b857834b188df 100644 --- a/public/app/percona/backup/components/BackupInventory/BackupInventory.tsx +++ b/public/app/percona/backup/components/BackupInventory/BackupInventory.tsx @@ -149,7 +149,7 @@ export const BackupInventory: FC = () => { setLogsModalVisible(false); }; - const handleRestore = async (serviceId: string, artifactId: string, pitrTimestamp: string) => { + const handleRestore = async (serviceId: string, artifactId: string, pitrTimestamp?: string) => { try { await BackupInventoryService.restore(serviceId, artifactId, pitrTimestamp, generateToken(RESTORE_CANCEL_TOKEN)); setRestoreErrors([]); diff --git a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx index e277e6886bab0..0fc8b17b8cd40 100644 --- a/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx +++ b/public/app/percona/backup/components/BackupInventory/RestoreBackupModal/RestoreBackupModal.tsx @@ -48,9 +48,13 @@ export const RestoreBackupModal: FC = ({ const [selectedTimerangeFromDatepicker, setSelectedTimerangeFromDatepicker] = useState(); const [selectedDay, setSelectedDay] = useState(); const handleSubmit = ({ serviceType, service }: RestoreBackupFormProps) => { - if (backup && selectedTimerangeFromDatepicker) { + if (backup) { const serviceId = serviceType === ServiceTypeSelect.SAME ? backup.serviceId : service.value; - onRestore(serviceId || '', backup.id, selectedTimerangeFromDatepicker.toISOString()); + if (backup.mode === BackupMode.PITR && selectedTimerangeFromDatepicker) { + onRestore(serviceId || '', backup.id, selectedTimerangeFromDatepicker.toISOString()); + } else { + onRestore(serviceId || '', backup.id); + } } }; const calculateDisableHours = useCallback(() => { @@ -157,7 +161,7 @@ export const RestoreBackupModal: FC = ({ render={({ handleSubmit, valid, submitting, values }) => (
- {backup!.mode === BackupMode.PITR && ( + {backup?.mode === BackupMode.PITR && ( <> {({ input }) => ( @@ -183,17 +187,11 @@ export const RestoreBackupModal: FC = ({