Skip to content

Commit

Permalink
BackupScheduleSetting: add next scheduled backup time on Latest Backu…
Browse files Browse the repository at this point in the history
…p section (#95602)

* Decouple convertHourToRange into an utils file

convertHourToRange now receives a moment instance

* Add useNextBackupSchedule hook

* Add NextScheduledBackup component

* Add NextScheduledBackup on backup successful view

Also we are moving the actions toolbar to the bottom

* Fix daily backup <p> font size

* Refactor actions toolbar to allow SplitButton

Migrating actions to its own components and refactor RestoreButton to allow be grouped as a SplitButton

* Add useSplitButton prop to Toolbar component

Refactored classNames using clsx to consider custom class names depending on the type of content

* Refactor to include Toolbar using useSplitButton prop

* Adjust split button styles

* Fix button margin

* Render NextScheduledBackup under a feature flag

* Remove unnecessary optional chaining operator

* Ensure next backup is not moved to the next day

Ensure next backup is not moved to the next day within the backup window
  • Loading branch information
Initsogar authored Oct 23, 2024
1 parent 1ba623e commit 2c17e5a
Show file tree
Hide file tree
Showing 14 changed files with 408 additions and 107 deletions.
94 changes: 28 additions & 66 deletions client/components/activity-card/toolbar/actions-button.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
import { Gridicon } from '@automattic/components';
import { Icon, download as downloadIcon } from '@wordpress/icons';
import { useTranslate } from 'i18n-calypso';
import { useRef, useState } from 'react';
import * as React from 'react';
import ExternalLink from 'calypso/components/external-link';
import CredentialsPrompt from 'calypso/components/activity-card/toolbar/actions/credentials-prompt';
import DownloadButton from 'calypso/components/activity-card/toolbar/actions/download-button';
import RestoreButton from 'calypso/components/activity-card/toolbar/actions/restore-button';
import ViewFilesButton from 'calypso/components/activity-card/toolbar/actions/view-files-button';
import Button from 'calypso/components/forms/form-button';
import missingCredentialsIcon from 'calypso/components/jetpack/daily-backup-status/missing-credentials.svg';
import PopoverMenu from 'calypso/components/popover-menu';
import { getActionableRewindId } from 'calypso/lib/jetpack/actionable-rewind-id';
import { SUCCESSFUL_BACKUP_ACTIVITIES } from 'calypso/lib/jetpack/backup-utils';
import { settingsPath } from 'calypso/lib/jetpack/paths';
import { backupDownloadPath, backupRestorePath } from 'calypso/my-sites/backup/paths';
import { backupDownloadPath } from 'calypso/my-sites/backup/paths';
import { useDispatch, useSelector } from 'calypso/state';
import { rewindRequestBackup } from 'calypso/state/activity-log/actions';
import { recordTracksEvent } from 'calypso/state/analytics/actions/record';
import { areJetpackCredentialsInvalid } from 'calypso/state/jetpack/credentials/selectors';
import getDoesRewindNeedCredentials from 'calypso/state/selectors/get-does-rewind-need-credentials';
import getIsRestoreInProgress from 'calypso/state/selectors/get-is-restore-in-progress';
import isSiteAutomatedTransfer from 'calypso/state/selectors/is-site-automated-transfer';
import { getSiteSlug, isJetpackSiteMultiSite } from 'calypso/state/sites/selectors';
import { Activity } from '../types';
import ViewFilesButton from './buttons/view-files-button';

type SingleSiteOwnProps = {
siteId: number;
siteSlug: string;
rewindId: string;
isSuccessfulBackup: boolean;
useSplitButton?: boolean;
};

const SingleSiteActionsButton: React.FC< SingleSiteOwnProps > = ( {
siteId,
siteSlug,
rewindId,
isSuccessfulBackup,
useSplitButton = false,
} ) => {
const translate = useTranslate();
const dispatch = useDispatch();
Expand All @@ -48,33 +45,26 @@ const SingleSiteActionsButton: React.FC< SingleSiteOwnProps > = ( {
setPopoverVisible( false );
};

const doesRewindNeedCredentials = useSelector( ( state ) =>
const needsCredentials = useSelector( ( state ) =>
getDoesRewindNeedCredentials( state, siteId )
);

const areCredentialsInvalid = useSelector( ( state ) =>
areJetpackCredentialsInvalid( state, siteId, 'main' )
);

const isRestoreInProgress = useSelector( ( state ) => getIsRestoreInProgress( state, siteId ) );

const isAtomic = useSelector( ( state ) => isSiteAutomatedTransfer( state, siteId ) );
if ( useSplitButton ) {
const secondaryActions = [
needsCredentials && <CredentialsPrompt siteSlug={ siteSlug } />,
isSuccessfulBackup && <ViewFilesButton siteSlug={ siteSlug } rewindId={ rewindId } />,
<DownloadButton siteId={ siteId } siteSlug={ siteSlug } rewindId={ rewindId } />,
];

const isRestoreDisabled =
doesRewindNeedCredentials || isRestoreInProgress || ( ! isAtomic && areCredentialsInvalid );

const onRestoreClick = () => {
dispatch( recordTracksEvent( 'calypso_jetpack_backup_actions_restore_click' ) );
};

const onDownloadClick = () => {
dispatch(
recordTracksEvent( 'calypso_jetpack_backup_actions_download_click', {
rewind_id: rewindId,
} )
return (
<RestoreButton
siteId={ siteId }
siteSlug={ siteSlug }
rewindId={ rewindId }
secondaryActions={ secondaryActions }
/>
);
dispatch( rewindRequestBackup( siteId, rewindId ) );
};
}

return (
<>
Expand All @@ -94,41 +84,10 @@ const SingleSiteActionsButton: React.FC< SingleSiteOwnProps > = ( {
onClose={ closePopoverMenu }
className="toolbar__actions-popover"
>
<Button
href={ ! isRestoreDisabled && backupRestorePath( siteSlug, rewindId ) }
className="toolbar__restore-button"
disabled={ isRestoreDisabled }
onClick={ onRestoreClick }
>
{ translate( 'Restore to this point' ) }
</Button>
{ ! isAtomic && ( doesRewindNeedCredentials || areCredentialsInvalid ) && (
<div className="toolbar__credentials-warning">
<img src={ missingCredentialsIcon } alt="" role="presentation" />
<div className="toolbar__credentials-warning-text">
{ translate(
'{{a}}Enter your server credentials{{/a}} to enable one-click restores from your backups.',
{
components: {
a: <ExternalLink href={ settingsPath( siteSlug ) } />,
},
}
) }
</div>
</div>
) }
<RestoreButton siteId={ siteId } siteSlug={ siteSlug } rewindId={ rewindId } />
{ needsCredentials && <CredentialsPrompt siteSlug={ siteSlug } /> }
{ isSuccessfulBackup && <ViewFilesButton siteSlug={ siteSlug } rewindId={ rewindId } /> }
<Button
borderless
compact
isPrimary={ false }
onClick={ onDownloadClick }
href={ backupDownloadPath( siteSlug, rewindId ) }
className="toolbar__download-button"
>
<Icon icon={ downloadIcon } className="toolbar__download-button-icon" size={ 18 } />
{ translate( 'Download backup' ) }
</Button>
<DownloadButton siteId={ siteId } siteSlug={ siteSlug } rewindId={ rewindId } />
</PopoverMenu>
</>
);
Expand Down Expand Up @@ -179,13 +138,15 @@ type OwnProps = {
activity: Activity;
availableActions?: Array< string >;
onClickClone?: ( period: string ) => void;
useSplitButton?: boolean;
};

const ActionsButton: React.FC< OwnProps > = ( {
siteId,
activity,
availableActions,
onClickClone,
useSplitButton = false,
} ) => {
const siteSlug = useSelector( ( state ) => getSiteSlug( state, siteId ) );

Expand Down Expand Up @@ -220,6 +181,7 @@ const ActionsButton: React.FC< OwnProps > = ( {
siteSlug={ siteSlug ?? '' }
rewindId={ actionableRewindId ?? '' }
isSuccessfulBackup={ isSuccessfulBackup }
useSplitButton={ useSplitButton }
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useTranslate } from 'i18n-calypso';
import { FunctionComponent } from 'react';
import missingCredentialsIcon from 'calypso/components/jetpack/daily-backup-status/missing-credentials.svg';
import { settingsPath } from 'calypso/lib/jetpack/paths';
import { useDispatch } from 'calypso/state';
import { recordTracksEvent } from 'calypso/state/analytics/actions/record';

type CredentialsPromptProps = {
siteSlug: string;
};

const CredentialsPrompt: FunctionComponent< CredentialsPromptProps > = ( { siteSlug } ) => {
const dispatch = useDispatch();
const translate = useTranslate();

const onClick = () => {
dispatch( recordTracksEvent( 'calypso_jetpack_backup_actions_credentials_click' ) );
};

return (
<div className="toolbar__credentials-warning">
<img src={ missingCredentialsIcon } alt="" role="presentation" />
<div className="toolbar__credentials-warning-text">
{ translate(
'{{a}}Enter your server credentials{{/a}} to enable one-click restores from your backups.',
{
components: {
a: <a href={ settingsPath( siteSlug ) } onClick={ onClick } />,
},
}
) }
</div>
</div>
);
};

export default CredentialsPrompt;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { download as downloadIcon, Icon } from '@wordpress/icons';
import { useTranslate } from 'i18n-calypso';
import { FunctionComponent } from 'react';
import Button from 'calypso/components/forms/form-button';
import { backupDownloadPath } from 'calypso/my-sites/backup/paths';
import { useDispatch } from 'calypso/state';
import { rewindRequestBackup } from 'calypso/state/activity-log/actions';
import { recordTracksEvent } from 'calypso/state/analytics/actions/record';

type DownloadButtonProps = {
siteId: number;
siteSlug: string;
rewindId: string;
};

const DownloadButton: FunctionComponent< DownloadButtonProps > = ( {
siteId,
siteSlug,
rewindId,
} ) => {
const translate = useTranslate();
const dispatch = useDispatch();
const onDownloadClick = () => {
dispatch(
recordTracksEvent( 'calypso_jetpack_backup_actions_download_click', {
rewind_id: rewindId,
} )
);
dispatch( rewindRequestBackup( siteId, rewindId ) );
};
return (
<Button
borderless
compact
isPrimary={ false }
onClick={ onDownloadClick }
href={ backupDownloadPath( siteSlug, rewindId ) }
className="toolbar__download-button"
>
<Icon icon={ downloadIcon } className="toolbar__download-button-icon" size={ 18 } />
{ translate( 'Download backup' ) }
</Button>
);
};

export default DownloadButton;
61 changes: 61 additions & 0 deletions client/components/activity-card/toolbar/actions/restore-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useTranslate } from 'i18n-calypso';
import { FunctionComponent, ReactNode } from 'react';
import Button from 'calypso/components/forms/form-button';
import SplitButton from 'calypso/components/split-button';
import { backupRestorePath } from 'calypso/my-sites/backup/paths';
import { useDispatch, useSelector } from 'calypso/state';
import { recordTracksEvent } from 'calypso/state/analytics/actions/record';
import canRestoreSite from 'calypso/state/rewind/selectors/can-restore-site';

type RestoreButtonProps = {
siteId: number;
siteSlug: string;
rewindId: string;
secondaryActions?: ReactNode[];
};

const RestoreButton: FunctionComponent< RestoreButtonProps > = ( {
siteId,
siteSlug,
rewindId,
secondaryActions = [],
} ) => {
const translate = useTranslate();
const dispatch = useDispatch();
const isRestoreDisabled = useSelector( ( state ) => ! canRestoreSite( state, siteId ) );
const onRestoreClick = () => {
dispatch( recordTracksEvent( 'calypso_jetpack_backup_actions_restore_click' ) );
};

const buttonLabel = translate( 'Restore to this point' );

// Conditionally render as a SplitButton if there are secondary actions
if ( secondaryActions.length > 0 ) {
return (
<SplitButton
className="toolbar__restore-button"
disableMain={ isRestoreDisabled }
href={ ! isRestoreDisabled && backupRestorePath( siteSlug, rewindId ) }
label={ buttonLabel }
onClick={ onRestoreClick }
primary
whiteSeparator
>
{ secondaryActions }
</SplitButton>
);
}

return (
<Button
href={ ! isRestoreDisabled && backupRestorePath( siteSlug, rewindId ) }
className="toolbar__restore-button"
disabled={ isRestoreDisabled }
onClick={ onRestoreClick }
>
{ buttonLabel }
</Button>
);
};

export default RestoreButton;
24 changes: 19 additions & 5 deletions client/components/activity-card/toolbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import clsx from 'clsx';
import * as React from 'react';
import { isSuccessfulRealtimeBackup } from 'calypso/lib/jetpack/backup-utils';
import { Activity } from '../types';
Expand All @@ -13,6 +14,8 @@ type OwnProps = {
onToggleContent?: () => void;
availableActions?: Array< string >;
onClickClone?: ( period: string ) => void;
hideExpandedContent?: boolean;
useSplitButton?: boolean;
};

const Toolbar: React.FC< OwnProps > = ( {
Expand All @@ -22,6 +25,8 @@ const Toolbar: React.FC< OwnProps > = ( {
onToggleContent,
availableActions,
onClickClone,
hideExpandedContent = false,
useSplitButton = false,
} ) => {
const isRewindable = isSuccessfulRealtimeBackup( activity );
const { streams } = activity;
Expand All @@ -30,18 +35,27 @@ const Toolbar: React.FC< OwnProps > = ( {
return null;
}

const showStreams = streams && ! hideExpandedContent;

const classNames = clsx( {
// force the actions to stay in the left if we aren't showing the content link
'activity-card__toolbar': showStreams || useSplitButton,
'activity-card__toolbar--reverse': ! showStreams && ! useSplitButton,
'activity-card__split-button': useSplitButton,
} );

return (
<div
// force the actions to stay in the left if we aren't showing the content link
className={ streams ? 'activity-card__toolbar' : 'activity-card__toolbar--reverse' }
>
{ streams && <ExpandContent isExpanded={ isContentExpanded } onToggle={ onToggleContent } /> }
<div className={ classNames }>
{ showStreams && (
<ExpandContent isExpanded={ isContentExpanded } onToggle={ onToggleContent } />
) }
{ isRewindable && (
<ActionsButton
siteId={ siteId }
activity={ activity }
availableActions={ availableActions }
onClickClone={ onClickClone }
useSplitButton={ useSplitButton }
/>
) }
</div>
Expand Down
18 changes: 18 additions & 0 deletions client/components/activity-card/toolbar/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@
}
}

.activity-card__split-button {
border-top: 0;
padding-top: 0;
padding-bottom: 0;
top: 0;

.split-button {
a, button {
display: inline-block;
font-size: 0.875rem;
}

&.toolbar__restore-button {
margin: 0;
}
}
}

.activity-card__toolbar--reverse {
flex-direction: row-reverse;
}
Expand Down
Loading

0 comments on commit 2c17e5a

Please sign in to comment.