diff --git a/client/hosting/server-settings/components/cache-card/index.tsx b/client/hosting/server-settings/components/cache-card/index.tsx index 0ca0f552ece64b..2a0051d3d83f34 100644 --- a/client/hosting/server-settings/components/cache-card/index.tsx +++ b/client/hosting/server-settings/components/cache-card/index.tsx @@ -145,7 +145,7 @@ export default function CacheCard( { disabled }: CacheCardProps ) { : '' } > -
+
@@ -189,7 +240,7 @@ export const SftpCard = ( { __nextHasNoMarginBottom disabled={ isLoading || isSshAccessLoading } checked={ isSshAccessEnabled } - onChange={ () => toggleSshAccess() } + onChange={ handleToggleSshAccess } label={ translate( 'Enable SSH access for this site. {{supportLink}}Learn more{{/supportLink}}.', { @@ -202,7 +253,12 @@ export const SftpCard = ( { ) } /> { isSshAccessEnabled && ( - + ) }
); @@ -241,7 +297,7 @@ export const SftpCard = ( { ) } { displayQuestionsAndButton && ( - +
{ translate( 'SFTP stands for Secure File Transfer Protocol (or SSH File Transfer Protocol). It’s a secure way for you to access your website files on your local computer via a client program such as {{a}}Filezilla{{/a}}. ' + @@ -274,11 +330,11 @@ export const SftpCard = ( { ) } ) } - +
) } { displayQuestionsAndButton && ( <> - +

{ translate( '{{strong}}Ready to access your website files?{{/strong}} Keep in mind, if mistakes happen you can restore your last backup, but will lose changes made after the backup date.', { @@ -287,31 +343,46 @@ export const SftpCard = ( { }, } ) } - - +

+ ) } { username && ( - + { translate( 'URL' ) } - + { translate( 'Port' ) } - + { translate( 'Username' ) } - + { translate( 'Password' ) } { renderPasswordField() } { siteHasSshFeature && ( - { translate( 'SSH access' ) } + + { translate( 'SSH access' ) } + ) } { siteHasSshFeature && renderSshField() } { () => ( <> - { siteHasSshFeature && isSshAccessEnabled && ( - + { siteHasSshFeature && isSshAccessEnabled && siteId && siteSlug && ( + ) } ) } @@ -323,92 +394,3 @@ export const SftpCard = ( { ); }; - -const resetSftpPassword = ( siteId, sshUsername ) => - withAnalytics( - composeAnalytics( - recordGoogleEvent( 'Hosting Configuration', 'Clicked "Reset Password" Button in SFTP Card' ), - recordTracksEvent( 'calypso_hosting_configuration_reset_sftp_password' ), - bumpStat( 'hosting-config', 'reset-sftp-password' ) - ), - resetAtomicSftpPassword( siteId, sshUsername ) - ); - -const createSftpUser = ( siteId, currentUserId ) => - withAnalytics( - composeAnalytics( - recordGoogleEvent( - 'Hosting Configuration', - 'Clicked "Create SFTP Credentials" Button in SFTP Card' - ), - recordTracksEvent( 'calypso_hosting_configuration_create_sftp_user' ), - bumpStat( 'hosting-config', 'create-sftp-user' ) - ), - createAtomicSftpUser( siteId, currentUserId ) - ); - -const enableSshAccess = ( siteId ) => - withAnalytics( - composeAnalytics( - recordTracksEvent( 'calypso_hosting_configuration_enable_ssh_access' ), - bumpStat( 'hosting-config', 'enable-ssh-access' ) - ), - enableAtomicSshAccess( siteId ) - ); - -const disableSshAccess = ( siteId ) => - withAnalytics( - composeAnalytics( - recordTracksEvent( 'calypso_hosting_configuration_disable_ssh_access' ), - bumpStat( 'hosting-config', 'disable-ssh-access' ) - ), - disableAtomicSshAccess( siteId ) - ); - -export default connect( - ( state, { disabled } ) => { - const siteId = getSelectedSiteId( state ); - const siteSlug = getSelectedSiteSlug( state ); - const currentUserId = getCurrentUserId( state ); - let username; - let password; - - if ( ! disabled ) { - const users = getAtomicHostingSftpUsers( state, siteId ); - if ( users !== null ) { - if ( users.length ) { - // Pick first user. Rest of users will be handled in next phases. - username = users[ 0 ].username; - password = users[ 0 ].password; - } else { - // No SFTP user created yet. - username = null; - password = null; - } - } - } - - return { - siteId, - siteSlug, - currentUserId, - username, - password, - siteHasSftpFeature: siteHasFeature( state, siteId, FEATURE_SFTP ), - siteHasSshFeature: siteHasFeature( state, siteId, FEATURE_SSH ), - isSshAccessEnabled: 'ssh' === getAtomicHostingSshAccess( state, siteId ), - isLoadingSftpData: getAtomicHostingIsLoadingSftpData( state, siteId ), - }; - }, - { - requestSftpUsers: requestAtomicSftpUsers, - createSftpUser, - resetSftpPassword, - requestSshAccess: requestAtomicSshAccess, - enableSshAccess, - disableSshAccess, - - removePasswordFromState: ( siteId, username ) => - updateAtomicSftpUser( siteId, [ { username, password: null } ] ), - } -)( localize( SftpCard ) ); diff --git a/client/hosting/server-settings/components/sftp-card/style.scss b/client/hosting/server-settings/components/sftp-card/style.scss new file mode 100644 index 00000000000000..2764e4c94ae6b4 --- /dev/null +++ b/client/hosting/server-settings/components/sftp-card/style.scss @@ -0,0 +1,26 @@ +.sftp-card__questions-container { + margin-bottom: 1.5em; +} + +.sftp-card__password-explainer { + margin-bottom: 8px; +} + +.sftp-card__ssh-label { + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid #e0e0e0; +} + +.sftp-card__enable-warning { + color: var(--color-text-subtle); +} + +.sftp-card__input { + display: block; + margin-bottom: 16px; +} + +.sftp-card__ssh-field .sftp-card__input { + margin-top: 16px; +} diff --git a/client/hosting/server-settings/components/sftp-card/test/index.js b/client/hosting/server-settings/components/sftp-card/test/index.js index e15c9ba5f3edbe..f19a57b1f3ab19 100644 --- a/client/hosting/server-settings/components/sftp-card/test/index.js +++ b/client/hosting/server-settings/components/sftp-card/test/index.js @@ -2,9 +2,10 @@ * @jest-environment jsdom */ +import { FEATURE_SFTP, FEATURE_SSH } from '@automattic/calypso-products'; import { render, screen } from '@testing-library/react'; import { Provider } from 'react-redux'; -import { createStore } from 'redux'; +import configureStore from 'redux-mock-store'; import { SftpCard } from '..'; jest.mock( '@automattic/components/src/spinner', () => ( { @@ -12,22 +13,21 @@ jest.mock( '@automattic/components/src/spinner', () => ( { Spinner: () =>
, } ) ); -const translate = ( x ) => x; -const requestSftpUsers = ( x ) => x; -const removePasswordFromState = ( x ) => x; - -const store = createStore( ( state ) => state, { +const INITIAL_STATE = { ui: { selectedSiteId: 1 }, currentUser: { id: 1 }, - sites: { items: {} }, -} ); - -const props = { - translate, - requestSftpUsers, - removePasswordFromState, + sites: {}, }; +const mockStore = configureStore(); +const defaultStore = mockStore( INITIAL_STATE ); +const storeWithUsername = mockStore( { + ...INITIAL_STATE, + atomicHosting: { + 1: { sftpUsers: [ { username: 'testuser' } ] }, + }, +} ); + describe( 'SftpCard', () => { beforeAll( () => { // Mock the missing `window.matchMedia` function that's not even in JSDOM @@ -46,35 +46,54 @@ describe( 'SftpCard', () => { } ); } ); + function renderWithProvider( props, store = defaultStore ) { + render( + + + + ); + } + describe( 'Sftp Questions', () => { it( 'should display sftp questions if no sftp username', async () => { - render( - - - - ); + const store = mockStore( { + ...INITIAL_STATE, + atomicHosting: { + 1: { sftpUsers: [ { username: null } ] }, + }, + } ); + + renderWithProvider( {}, store ); expect( screen.getByText( 'What is SFTP?' ) ).toBeVisible(); expect( screen.getByText( 'What is SSH?' ) ).toBeVisible(); } ); it( 'should not display sftp questions if sftp username is set', () => { - render( - - - - ); + renderWithProvider( {}, storeWithUsername ); expect( screen.queryByText( 'What is SFTP?' ) ).not.toBeInTheDocument(); expect( screen.queryByText( 'What is SSH?' ) ).not.toBeInTheDocument(); } ); it( 'should not display sftp questions if loading', () => { - render( - - - - ); + const store = mockStore( { + ...INITIAL_STATE, + sites: { + features: { + 1: { + data: { + active: [ FEATURE_SFTP, FEATURE_SSH ], + }, + }, + }, + }, + atomicHosting: { + 1: { isLoadingSftpUsers: true }, + }, + } ); + + renderWithProvider( {}, store ); expect( screen.queryByText( 'What is SFTP?' ) ).not.toBeInTheDocument(); expect( screen.queryByText( 'What is SSH?' ) ).not.toBeInTheDocument(); @@ -83,21 +102,13 @@ describe( 'SftpCard', () => { describe( 'Loading', () => { it( 'should display spinner if username not set and not disabled', () => { - render( - - - - ); + renderWithProvider( {} ); - expect( screen.getByTestId( 'spinner' ) ).toBeVisible(); + expect( screen.queryByTestId( 'spinner' ) ).toBeVisible(); } ); it( 'should not display spinner if disabled', () => { - render( - - - - ); + renderWithProvider( { disabled: true } ); expect( screen.queryByTestId( 'spinner' ) ).not.toBeInTheDocument(); } ); @@ -105,22 +116,15 @@ describe( 'SftpCard', () => { describe( 'Create SFTP credentials', () => { const btnName = 'Create credentials'; + it( 'should display create SFTP credentials if username not set', () => { - render( - - - - ); + renderWithProvider( { disabled: true } ); expect( screen.getByRole( 'button', { name: btnName } ) ).toBeVisible(); } ); it( 'should not display create SFTP credentials if username set', () => { - render( - - - - ); + renderWithProvider( {}, storeWithUsername ); expect( screen.queryByRole( 'button', { name: btnName } ) ).not.toBeInTheDocument(); } ); @@ -128,11 +132,7 @@ describe( 'SftpCard', () => { describe( 'User info fields', () => { it( 'should display user info fields if username set', () => { - render( - - - - ); + renderWithProvider( {}, storeWithUsername ); expect( screen.getByLabelText( 'URL' ) ).toHaveValue( 'sftp.wp.com' ); expect( screen.getByLabelText( 'Port' ) ).toHaveValue( '22' ); @@ -140,11 +140,7 @@ describe( 'SftpCard', () => { } ); it( 'should not display user info fields if username not set', () => { - render( - - - - ); + renderWithProvider(); expect( screen.queryByLabelText( 'URL' ) ).not.toBeInTheDocument(); expect( screen.queryByLabelText( 'Port' ) ).not.toBeInTheDocument(); @@ -154,31 +150,26 @@ describe( 'SftpCard', () => { describe( 'Password', () => { it( 'should display password field if password set', () => { - render( - - - - ); + const store = mockStore( { + ...INITIAL_STATE, + atomicHosting: { + 1: { sftpUsers: [ { username: 'testuser', password: 'secret' } ] }, + }, + } ); + + renderWithProvider( {}, store ); expect( screen.getByLabelText( 'Password' ) ).toBeVisible(); } ); it( 'should not display password field if password not set', () => { - render( - - - - ); + renderWithProvider( {}, storeWithUsername ); expect( screen.queryByLabelText( 'Password' ) ).not.toBeInTheDocument(); } ); it( 'should display password reset button if password not set', () => { - render( - - - - ); + renderWithProvider( {}, storeWithUsername ); expect( screen.getByRole( 'button', { name: 'Reset password' } ) ).toBeVisible(); } ); diff --git a/client/hosting/server-settings/main.tsx b/client/hosting/server-settings/main.tsx index 16157dcb4219be..b6732a7711710a 100644 --- a/client/hosting/server-settings/main.tsx +++ b/client/hosting/server-settings/main.tsx @@ -24,7 +24,7 @@ import DefensiveModeCard from 'calypso/hosting/server-settings/components/defens import { HostingUpsellNudge } from 'calypso/hosting/server-settings/components/hosting-upsell-nudge'; import PhpMyAdminCard from 'calypso/hosting/server-settings/components/phpmyadmin-card'; import RestorePlanSoftwareCard from 'calypso/hosting/server-settings/components/restore-plan-software-card'; -import SFTPCard from 'calypso/hosting/server-settings/components/sftp-card'; +import { SftpCard } from 'calypso/hosting/server-settings/components/sftp-card'; import WebServerSettingsCard from 'calypso/hosting/server-settings/components/web-server-settings-card'; import HostingActivateStatus from 'calypso/hosting/server-settings/hosting-activate-status'; import PageViewTracker from 'calypso/lib/analytics/page-view-tracker'; @@ -111,7 +111,7 @@ const AllCards = ( { const allCards: CardEntry[] = [ { feature: 'sftp', - content: , + content: , type: 'advanced', }, { diff --git a/client/state/selectors/get-atomic-hosting-sftp-users.js b/client/state/selectors/get-atomic-hosting-sftp-users.js index 14ea62106044e2..cd8f260e5bae1b 100644 --- a/client/state/selectors/get-atomic-hosting-sftp-users.js +++ b/client/state/selectors/get-atomic-hosting-sftp-users.js @@ -3,7 +3,7 @@ import 'calypso/state/hosting/init'; /** * Returns the sftp users details for given site. * @param {Object} state Global state tree - * @param {number} siteId The ID of the site we're querying + * @param {number|null} siteId The ID of the site we're querying * @returns {Array} List of SFTP user details */ export function getAtomicHostingSftpUsers( state, siteId ) { diff --git a/client/state/selectors/get-atomic-hosting-ssh-access.js b/client/state/selectors/get-atomic-hosting-ssh-access.js index 3d094cfc1124a9..29bf0f1c7b8b37 100644 --- a/client/state/selectors/get-atomic-hosting-ssh-access.js +++ b/client/state/selectors/get-atomic-hosting-ssh-access.js @@ -3,7 +3,7 @@ import 'calypso/state/hosting/init'; /** * Returns the SSH access status for given site. * @param {Object} state Global state tree - * @param {number} siteId The ID of the site we're querying + * @param {number|null} siteId The ID of the site we're querying * @returns {string} SSH access status */ export function getAtomicHostingSshAccess( state, siteId ) {