Skip to content

Commit

Permalink
Social: Unified connections management (#40679)
Browse files Browse the repository at this point in the history
* Social: Use connections REST endpoint for initial state (#40677)

* Create connections class for caching

* Update script data to use connections from the REST endpoint

* changelog

* Restore deprecated connection fields for time being

* Disable caching for now

* Fix display_name for Mastodon

* Remove the unused caching logic

* Social: Connections API schema front end changes (#40539)

* Social: Use connections REST endpoint for initial state (#40677)

* Create connections class for caching

* Update script data to use connections from the REST endpoint

* changelog

* Restore deprecated connection fields for time being

* Disable caching for now

* Fix display_name for Mastodon

* Remove the unused caching logic

* Update connect-form.tsx

* Create connections class for caching

* Update types for connection object

* Update connection actions to reflect new types

* Mark 'id' as deprecated

* Add changelog

* Deprecated fields should be optional

* Fix TS error

* Update unit tests

* Replace/remove deprecated props usage

* Remove unused code

* Fix connection selectors

* Fix unit tests

* Reduce the number of changes

* Fix type

* Oops! It should be negation

* Restore class-connections.php

* Social | Add site context for publicize endpoints (#40704)

* Allow requests as blog in base controller

* Add filters for connections controller

* Add changelog

* Update baseline.php

* Rename the 'include' param to 'scope' for clarity

* Return shared connections by default

* Remove scope parameter in favour of request context

* Only pass test_connections to WPCOM

* Update baseline.php

* Social: Implement connections caching with the updated endpoint (#40892)

* Update connections class to implement caching

* Add get_all_for_user method

* Pass cached connections to initial state

* Invalidate cache on XMLRPC request

* Add changelog

* Remove eager loading of connections following cache invalidation

* Improve clear caching logic to handle race condition

* Social: Replace can_disconnect with a store data selector (#40888)

* Pass the connected users WPCOM data to the UI

* Augment wpcom for user object

* Create canUserManageConnection selector

* Replace can_disconnect with the new selector

* Fix unit tests

* Add changelog

* Fix user data for WPCOM sites

* Fix unit tests

* Add changelog

* Social | Restore must_reauth as connection status (#40946)

* Add "must_reauth" to status in REST schema

* Restore the UI changes to consider must_reauth status

* Add changelog

* Don't disable connections with must_reauth status

must_reauth means that the connection will break soon, but it still
works. We'll display an appropriate notice.

---------

Co-authored-by: Paul Bunkham <[email protected]>

* Social | Clean up connections controller to use connections class (#40982)

* Create Proxy_Requests class for re-usability

* Move is_wpcom utility to Publicize_Utils class

* Move connections specific logic from REST controller to connections class

* Add changelog

* Update baseline.php

* Move publicize permissions check to its own method

* Social | Fix connect button for broken connections (#40995)

* Social | Fix connected accounts not marked as such on confirmation screen (#40997)

* Social | Fix connected accounts not marked as such on confirmation screen

* Add a comment

* Social | Update connections schema to change `user_id` to `wpcom_user_id` (#41025)

* Social | Update connections schema to change user_id to wpcom_user_id

* Fix wpcom_user_id for post connections field

* Add changelog

* Social | Implement the other CRUD operations for connections (#40928)

* WIP

* Pass the connection ID in the URL

* Pass connection_id for delete proxy request

* Allow overriding $request_options

* Clean up debugging

* Add changelog

* Add changelog for js changes

* Fix static analysis issues

* Remove unused code

* Update baseline.php

---------

Co-authored-by: Paul Bunkham <[email protected]>

* Unify changelogs

---------

Co-authored-by: Paul Bunkham <[email protected]>
  • Loading branch information
2 people authored and coder-karen committed Jan 24, 2025
1 parent ff36261 commit 2f1b247
Show file tree
Hide file tree
Showing 50 changed files with 1,049 additions and 333 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Social | Unify connections management API schema
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function ConnectionInfo( { connection, service }: ConnectionInfoProps ) {
<div className={ styles[ 'connection-item' ] }>
<ConnectionIcon
serviceName={ connection.service_name }
label={ connection.display_name || connection.external_display }
label={ connection.display_name }
profilePicture={ connection.profile_picture }
/>
<div className={ styles[ 'connection-name-wrapper' ] }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@ export function ConnectionName( { connection }: ConnectionNameProps ) {
return (
<div className={ styles[ 'connection-name' ] }>
{ ! connection.profile_link ? (
<span className={ styles[ 'profile-link' ] }>
{ connection.display_name || connection.external_name }
</span>
<span className={ styles[ 'profile-link' ] }>{ connection.display_name }</span>
) : (
<ExternalLink className={ styles[ 'profile-link' ] } href={ connection.profile_link }>
{ connection.display_name || connection.external_display || connection.external_name }
{ connection.display_name }
</ExternalLink>
) }
{ isUpdating ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@ export function Disconnect( {

const { deleteConnectionById } = useDispatch( socialStore );

const { isDisconnecting } = useSelect(
const { isDisconnecting, canManageConnection } = useSelect(
select => {
const { getDeletingConnections } = select( socialStore );
const { getDeletingConnections, canUserManageConnection } = select( socialStore );

return {
isDisconnecting: getDeletingConnections().includes( connection.connection_id ),
canManageConnection: canUserManageConnection( connection ),
};
},
[ connection.connection_id ]
[ connection ]
);

const onClickDisconnect = useCallback( async () => {
Expand All @@ -49,7 +50,7 @@ export function Disconnect( {
} );
}, [ connection.connection_id, deleteConnectionById ] );

if ( ! connection.can_disconnect ) {
if ( ! canManageConnection ) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ export function Reconnect( { connection, service, variant = 'link' }: ReconnectP
const { deleteConnectionById, setKeyringResult, openConnectionsModal, setReconnectingAccount } =
useDispatch( socialStore );

const { isDisconnecting } = useSelect(
const { isDisconnecting, canManageConnection } = useSelect(
select => {
const { getDeletingConnections } = select( socialStore );
const { getDeletingConnections, canUserManageConnection } = select( socialStore );

return {
isDisconnecting: getDeletingConnections().includes( connection.connection_id ),
canManageConnection: canUserManageConnection( connection ),
};
},
[ connection.connection_id ]
[ connection ]
);

const onConfirm = useCallback(
Expand Down Expand Up @@ -63,7 +64,7 @@ export function Reconnect( { connection, service, variant = 'link' }: ReconnectP
const formData = new FormData();

if ( service.ID === 'mastodon' ) {
formData.set( 'instance', connection.external_display );
formData.set( 'instance', connection.external_handle );
}

if ( service.ID === 'bluesky' ) {
Expand All @@ -80,7 +81,7 @@ export function Reconnect( { connection, service, variant = 'link' }: ReconnectP
setReconnectingAccount,
] );

if ( ! connection.can_disconnect ) {
if ( ! canManageConnection ) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ describe( 'Disconnecting a connection', () => {
service_name: 'facebook',
connection_id: '2',
display_name: 'Facebook',
can_disconnect: true,
} }
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ describe( 'Marking a connection as shared', () => {
service_name: 'facebook',
connection_id: '2',
display_name: 'Facebook',
can_disconnect: true,
} }
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ describe( 'Reconnect', () => {

const mockConnection = {
connection_id: '123',
can_disconnect: true,
external_display: 'mockDisplay',
display_name: 'mockDisplay',
};

beforeEach( () => {
Expand Down Expand Up @@ -58,12 +57,8 @@ describe( 'Reconnect', () => {
} );

test( 'does not render the button if connection cannot be disconnected', () => {
const nonDisconnectableConnection = {
...mockConnection,
can_disconnect: false,
};

render( <Reconnect connection={ nonDisconnectableConnection } service={ mockService } /> );
setup( { canUserManageConnection: false } );
render( <Reconnect connection={ mockConnection } service={ mockService } /> );

expect( screen.queryByRole( 'button' ) ).not.toBeInTheDocument();
} );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class PublicizeConnection extends Component {
}

isDisabled() {
return this.props.disabled || this.connectionIsFailing() || this.connectionNeedsReauth();
return this.props.disabled || this.connectionIsFailing();
}

render() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
import { Button } from '@automattic/jetpack-components';
import { ExternalLink } from '@wordpress/components';
import { useDispatch } from '@wordpress/data';
import { useDispatch, useSelect } from '@wordpress/data';
import { createInterpolateElement, Fragment } from '@wordpress/element';
import { __, _x } from '@wordpress/i18n';
import usePublicizeConfig from '../../hooks/use-publicize-config';
import useSocialMediaConnections from '../../hooks/use-social-media-connections';
import { store } from '../../social-store';
import { Connection } from '../../social-store/types';
import { checkConnectionCode } from '../../utils/connections';
import { getSocialScriptData } from '../../utils/script-data';
import Notice from '../notice';
import { useServiceLabel } from '../services/use-service-label';
import styles from './styles.module.scss';

export const BrokenConnectionsNotice: React.FC = () => {
const { connections } = useSocialMediaConnections();

const brokenConnections = connections.filter( connection => {
return (
connection.status === 'broken' ||
// This is a legacy check for connections that are not healthy.
// TODO remove this check when we are sure that all connections have
// the status property (same schema for connections endpoints), e.g. on Simple/Atomic sites
checkConnectionCode( connection, 'broken' )
);
} );
const brokenConnections = useSelect( select => select( store ).getBrokenConnections(), [] );

const { connectionsPageUrl } = usePublicizeConfig();

Expand Down Expand Up @@ -87,11 +75,9 @@ export const BrokenConnectionsNotice: React.FC = () => {
{
// Since Intl.ListFormat is not allowed in Jetpack yet,
// we join the connections with a comma and space
connectionsList.map( ( { display_name, external_display, id }, i ) => (
<Fragment key={ id }>
<span className={ styles[ 'broken-connection' ] }>
{ display_name || external_display }
</span>
connectionsList.map( ( { display_name, connection_id }, i ) => (
<Fragment key={ connection_id }>
<span className={ styles[ 'broken-connection' ] }>{ display_name }</span>
{ i < connectionsList.length - 1 &&
_x(
',',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,17 @@ export const ConnectionsList: React.FC = () => {
<div>
<ul className={ styles[ 'connections-list' ] }>
{ connections.map( conn => {
const { display_name, id, service_name, profile_picture, connection_id } = conn;
const currentId = connection_id ? connection_id : id;
const { display_name, service_name, profile_picture, connection_id } = conn;

return (
<PublicizeConnection
disabled={ shouldBeDisabled( conn ) }
enabled={ canBeTurnedOn( conn ) && conn.enabled }
key={ currentId }
id={ currentId }
key={ connection_id }
id={ connection_id }
label={ display_name }
name={ service_name }
toggleConnection={ toggleConnection( currentId, conn ) }
toggleConnection={ toggleConnection( connection_id, conn ) }
profilePicture={ profile_picture }
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import { createInterpolateElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { usePublicizeConfig } from '../../..';
import useSocialMediaConnections from '../../hooks/use-social-media-connections';
import { checkConnectionCode } from '../../utils/connections';
import Notice from '../notice';
import { useService } from '../services/use-service';

export const UnsupportedConnectionsNotice: React.FC = () => {
const { connections } = useSocialMediaConnections();

const { connectionsPageUrl } = usePublicizeConfig();

const unsupportedConnections = connections.filter( connection =>
checkConnectionCode( connection, 'unsupported' )
const getServices = useService();

const unsupportedConnections = connections.filter(
connection =>
// If getServices returns falsy, it means the service is unsupported.
! getServices( connection.service_name )
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,16 @@ export const useConnectionState = () => {
*/
const isInGoodShape = useCallback(
( connection: Connection ) => {
const { id, is_healthy, connection_id, status } = connection;
const currentId = connection_id ? connection_id : id;
const { connection_id: id, status } = connection;

// 1. Be healthy
const isHealthy = false !== is_healthy && status !== 'broken';
const isHealthy = status !== 'broken';

// 2. Have no validation errors
const hasValidationErrors = validationErrors[ currentId ] !== undefined && ! isConvertible;
const hasValidationErrors = validationErrors[ id ] !== undefined && ! isConvertible;

// 3. Not have a NO_MEDIA_ERROR when media is required
const hasNoMediaError = validationErrors[ currentId ] === NO_MEDIA_ERROR;
const hasNoMediaError = validationErrors[ id ] === NO_MEDIA_ERROR;

return isHealthy && ! hasValidationErrors && ! hasNoMediaError;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export function ConfirmationForm( { keyringResult, onComplete, isAdmin }: Confir
display_name: accountInfo?.label,
profile_picture: accountInfo?.profile_picture,
service_name: service.ID,
external_id: external_user_ID,
external_id: external_user_ID.toString(),
} );

onComplete();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type ConnectFormProps = {
*
* @param {ConnectFormProps} props - Component props
*
* @return {import('react').ReactNode} Connect form component
* @return Connect form component
*/
export function ConnectForm( {
service,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function CustomInputs( { service }: CustomInputsProps ) {
name="handle"
defaultValue={
reconnectingAccount?.service_name === 'bluesky'
? reconnectingAccount?.external_name
? reconnectingAccount?.external_handle
: undefined
}
autoComplete="off"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { IconTooltip, Text } from '@automattic/jetpack-components';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { store as socialStore } from '../../social-store';
import { Connection } from '../../social-store/types';
import { ConnectionName } from '../connection-management/connection-name';
import { ConnectionStatus } from '../connection-management/connection-status';
Expand All @@ -19,6 +21,11 @@ export const ServiceConnectionInfo = ( {
service,
isAdmin,
}: ServiceConnectionInfoProps ) => {
const canManageConnection = useSelect(
select => select( socialStore ).canUserManageConnection( connection ),
[ connection ]
);

return (
<div className={ styles[ 'service-connection' ] }>
<div>
Expand All @@ -40,7 +47,7 @@ export const ServiceConnectionInfo = ( {
* if the user can disconnect the connection.
* Otherwise, non-admin authors will see only the status without any further context.
*/
if ( conn.status === 'broken' && conn.can_disconnect ) {
if ( conn.status === 'broken' && canManageConnection ) {
return <ConnectionStatus connection={ conn } service={ service } />;
}

Expand All @@ -63,7 +70,7 @@ export const ServiceConnectionInfo = ( {
* Now if the user is not an admin, we tell them that the connection
* was added by an admin and show the connection status if it's broken.
*/
return ! conn.can_disconnect ? (
return ! canManageConnection ? (
<>
<Text className={ styles.description }>
{ __(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Button, useBreakpointMatch } from '@automattic/jetpack-components';
import { Panel, PanelBody } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { useEffect, useReducer, useRef } from '@wordpress/element';
import { __, _x } from '@wordpress/i18n';
import { Icon, chevronDown, chevronUp } from '@wordpress/icons';
import { store as socialStore } from '../../social-store';
import { ConnectForm } from './connect-form';
import { ServiceItemDetails, ServicesItemDetailsProps } from './service-item-details';
import { ServiceStatus } from './service-status';
Expand Down Expand Up @@ -40,8 +42,13 @@ export function ServiceItem( {

const brokenConnections = serviceConnections.filter( ( { status } ) => status === 'broken' );

const hasOwnBrokenConnections = brokenConnections.some(
( { can_disconnect } ) => can_disconnect
const hasOwnBrokenConnections = useSelect(
select => {
const { canUserManageConnection } = select( socialStore );

return brokenConnections.some( canUserManageConnection );
},
[ brokenConnections ]
);

const hideInitialConnectForm =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Alert } from '@automattic/jetpack-components';
import { useSelect } from '@wordpress/data';
import { __, _n, sprintf } from '@wordpress/i18n';
import { store as socialStore } from '../../social-store';
import { Connection } from '../../social-store/types';
import styles from './style.module.scss';

Expand All @@ -16,13 +18,16 @@ export type ServiceStatusProps = {
* @return {import('react').ReactNode} Service status component
*/
export function ServiceStatus( { serviceConnections, brokenConnections }: ServiceStatusProps ) {
const canFix = useSelect(
select => brokenConnections.some( select( socialStore ).canUserManageConnection ),
[ brokenConnections ]
);

if ( ! serviceConnections.length ) {
return null;
}

if ( brokenConnections.length > 0 ) {
const canFix = brokenConnections.some( ( { can_disconnect } ) => can_disconnect );

return (
<Alert
level={ canFix ? 'error' : 'warning' }
Expand Down
Loading

0 comments on commit 2f1b247

Please sign in to comment.