diff --git a/lib/class-wp-rest-block-directory-controller.php b/lib/class-wp-rest-block-directory-controller.php index 9e911d28c9bd09..df1f2e408506e0 100644 --- a/lib/class-wp-rest-block-directory-controller.php +++ b/lib/class-wp-rest-block-directory-controller.php @@ -97,10 +97,6 @@ public function get_items( $request ) { $result = array(); foreach ( $response->plugins as $plugin ) { - if ( $this->find_plugin_for_slug( $plugin['slug'] ) ) { - continue; - } - $data = $this->prepare_item_for_response( $plugin, $request ); $result[] = $this->prepare_response_for_collection( $data ); } diff --git a/packages/block-directory/src/store/actions.js b/packages/block-directory/src/store/actions.js index 09e7b6b5d52b9a..ec73abe2ffbdce 100644 --- a/packages/block-directory/src/store/actions.js +++ b/packages/block-directory/src/store/actions.js @@ -8,6 +8,7 @@ import { apiFetch, dispatch, select } from '@wordpress/data-controls'; * Internal dependencies */ import { loadAssets } from './controls'; +import getPluginUrl from './utils/get-plugin-url'; /** * Returns an action object used in signalling that the downloadable blocks @@ -54,16 +55,35 @@ export function* installBlockType( block ) { throw new Error( __( 'Block has no assets.' ) ); } yield setIsInstalling( block.id, true ); - const response = yield apiFetch( { - path: 'wp/v2/plugins', - data: { - slug: block.id, - status: 'active', - }, - method: 'POST', + + // If we have a wp:plugin link, the plugin is installed but inactive. + const url = getPluginUrl( block ); + let links = {}; + if ( url ) { + yield apiFetch( { + url, + data: { + status: 'active', + }, + method: 'PUT', + } ); + } else { + const response = yield apiFetch( { + path: 'wp/v2/plugins', + data: { + slug: block.id, + status: 'active', + }, + method: 'POST', + } ); + // Add the `self` link for newly-installed blocks. + links = response._links; + } + + yield addInstalledBlockType( { + ...block, + links: { ...block.links, ...links }, } ); - const endpoint = response?._links?.self[ 0 ]?.href; - yield addInstalledBlockType( { ...block, endpoint } ); yield loadAssets( assets ); const registeredBlocks = yield select( 'core/blocks', 'getBlockTypes' ); @@ -112,14 +132,14 @@ export function* installBlockType( block ) { export function* uninstallBlockType( block ) { try { yield apiFetch( { - url: block.endpoint, + url: getPluginUrl( block ), data: { status: 'inactive', }, method: 'PUT', } ); yield apiFetch( { - url: block.endpoint, + url: getPluginUrl( block ), method: 'DELETE', } ); yield removeInstalledBlockType( block ); diff --git a/packages/block-directory/src/store/test/actions.js b/packages/block-directory/src/store/test/actions.js index 71244383d624d1..147ad2b1e415b3 100644 --- a/packages/block-directory/src/store/test/actions.js +++ b/packages/block-directory/src/store/test/actions.js @@ -4,11 +4,20 @@ import { installBlockType, uninstallBlockType } from '../actions'; describe( 'actions', () => { - const endpoint = '/wp-json/wp/v2/plugins/block/block'; + const pluginEndpoint = + 'https://example.com/wp-json/wp/v2/plugins/block/block'; const item = { id: 'block/block', name: 'Test Block', assets: [ 'script.js' ], + links: { + 'wp:install-plugin': [ + { + href: + 'https://example.com/wp-json/wp/v2/plugins?slug=waves', + }, + ], + }, }; const plugin = { plugin: 'block/block.php', @@ -18,24 +27,25 @@ describe( 'actions', () => { _links: { self: [ { - href: endpoint, + href: pluginEndpoint, }, ], }, }; describe( 'installBlockType', () => { + const block = item; it( 'should install a block successfully', () => { - const generator = installBlockType( item ); + const generator = installBlockType( block ); expect( generator.next().value ).toEqual( { type: 'CLEAR_ERROR_NOTICE', - blockId: item.id, + blockId: block.id, } ); expect( generator.next().value ).toEqual( { type: 'SET_INSTALLING_BLOCK', - blockId: item.id, + blockId: block.id, isInstalling: true, } ); @@ -47,15 +57,86 @@ describe( 'actions', () => { }, } ); - const itemWithEndpoint = { ...item, endpoint }; expect( generator.next( plugin ).value ).toEqual( { type: 'ADD_INSTALLED_BLOCK_TYPE', - item: itemWithEndpoint, + item: { + ...block, + links: { + ...block.links, + self: [ + { + href: pluginEndpoint, + }, + ], + }, + }, + } ); + + expect( generator.next().value ).toEqual( { + type: 'LOAD_ASSETS', + assets: block.assets, + } ); + + expect( generator.next().value ).toEqual( { + args: [], + selectorName: 'getBlockTypes', + storeKey: 'core/blocks', + type: 'SELECT', + } ); + + expect( generator.next( [ block ] ).value ).toEqual( { + type: 'SET_INSTALLING_BLOCK', + blockId: block.id, + isInstalling: false, + } ); + + expect( generator.next() ).toEqual( { + value: true, + done: true, + } ); + } ); + + it( 'should activate an inactive block plugin successfully', () => { + const inactiveBlock = { + ...block, + links: { + ...block.links, + 'wp:plugin': [ + { + href: pluginEndpoint, + }, + ], + }, + }; + const generator = installBlockType( inactiveBlock ); + + expect( generator.next().value ).toEqual( { + type: 'CLEAR_ERROR_NOTICE', + blockId: inactiveBlock.id, + } ); + + expect( generator.next().value ).toEqual( { + type: 'SET_INSTALLING_BLOCK', + blockId: inactiveBlock.id, + isInstalling: true, + } ); + + expect( generator.next().value ).toMatchObject( { + type: 'API_FETCH', + request: { + url: pluginEndpoint, + method: 'PUT', + }, + } ); + + expect( generator.next( plugin ).value ).toEqual( { + type: 'ADD_INSTALLED_BLOCK_TYPE', + item: inactiveBlock, } ); expect( generator.next().value ).toEqual( { type: 'LOAD_ASSETS', - assets: item.assets, + assets: inactiveBlock.assets, } ); expect( generator.next().value ).toEqual( { @@ -65,9 +146,9 @@ describe( 'actions', () => { type: 'SELECT', } ); - expect( generator.next( [ item ] ).value ).toEqual( { + expect( generator.next( [ inactiveBlock ] ).value ).toEqual( { type: 'SET_INSTALLING_BLOCK', - blockId: item.id, + blockId: inactiveBlock.id, isInstalling: false, } ); @@ -78,21 +159,21 @@ describe( 'actions', () => { } ); it( 'should set an error if the plugin has no assets', () => { - const generator = installBlockType( { ...item, assets: [] } ); + const generator = installBlockType( { ...block, assets: [] } ); expect( generator.next().value ).toEqual( { type: 'CLEAR_ERROR_NOTICE', - blockId: item.id, + blockId: block.id, } ); expect( generator.next().value ).toMatchObject( { type: 'SET_ERROR_NOTICE', - blockId: item.id, + blockId: block.id, } ); expect( generator.next().value ).toEqual( { type: 'SET_INSTALLING_BLOCK', - blockId: item.id, + blockId: block.id, isInstalling: false, } ); @@ -103,16 +184,16 @@ describe( 'actions', () => { } ); it( "should set an error if the plugin can't install", () => { - const generator = installBlockType( item ); + const generator = installBlockType( block ); expect( generator.next().value ).toEqual( { type: 'CLEAR_ERROR_NOTICE', - blockId: item.id, + blockId: block.id, } ); expect( generator.next().value ).toEqual( { type: 'SET_INSTALLING_BLOCK', - blockId: item.id, + blockId: block.id, isInstalling: true, } ); @@ -131,12 +212,12 @@ describe( 'actions', () => { }; expect( generator.throw( apiError ).value ).toMatchObject( { type: 'SET_ERROR_NOTICE', - blockId: item.id, + blockId: block.id, } ); expect( generator.next().value ).toEqual( { type: 'SET_INSTALLING_BLOCK', - blockId: item.id, + blockId: block.id, isInstalling: false, } ); @@ -148,16 +229,26 @@ describe( 'actions', () => { } ); describe( 'uninstallBlockType', () => { - const itemWithEndpoint = { ...item, endpoint }; + const block = { + ...item, + links: { + ...item.links, + self: [ + { + href: pluginEndpoint, + }, + ], + }, + }; it( 'should uninstall a block successfully', () => { - const generator = uninstallBlockType( itemWithEndpoint ); + const generator = uninstallBlockType( block ); // First the deactivation step expect( generator.next().value ).toMatchObject( { type: 'API_FETCH', request: { - url: endpoint, + url: pluginEndpoint, method: 'PUT', }, } ); @@ -166,14 +257,14 @@ describe( 'actions', () => { expect( generator.next().value ).toMatchObject( { type: 'API_FETCH', request: { - url: endpoint, + url: pluginEndpoint, method: 'DELETE', }, } ); expect( generator.next().value ).toEqual( { type: 'REMOVE_INSTALLED_BLOCK_TYPE', - item: itemWithEndpoint, + item: block, } ); expect( generator.next() ).toEqual( { @@ -183,12 +274,12 @@ describe( 'actions', () => { } ); it( "should set a global notice if the plugin can't be deleted", () => { - const generator = uninstallBlockType( itemWithEndpoint ); + const generator = uninstallBlockType( block ); expect( generator.next().value ).toMatchObject( { type: 'API_FETCH', request: { - url: endpoint, + url: pluginEndpoint, method: 'PUT', }, } ); @@ -196,7 +287,7 @@ describe( 'actions', () => { expect( generator.next().value ).toMatchObject( { type: 'API_FETCH', request: { - url: endpoint, + url: pluginEndpoint, method: 'DELETE', }, } ); diff --git a/packages/block-directory/src/store/utils/get-plugin-url.js b/packages/block-directory/src/store/utils/get-plugin-url.js new file mode 100644 index 00000000000000..71bd8257d66b43 --- /dev/null +++ b/packages/block-directory/src/store/utils/get-plugin-url.js @@ -0,0 +1,17 @@ +/** + * Get the plugin's direct API link out of a block-directory response. + * + * @param {Object} block The block object + * + * @return {string} The plugin URL, if exists. + */ +export default function getPluginUrl( block ) { + if ( ! block ) { + return false; + } + const link = block.links[ 'wp:plugin' ] || block.links.self; + if ( link && link.length ) { + return link[ 0 ].href; + } + return false; +} diff --git a/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js b/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js index 249e5bba64598b..b77f97f952601a 100644 --- a/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js +++ b/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js @@ -37,6 +37,7 @@ const MOCK_BLOCK1 = { 'https://fake_url.com/block.js', // we will mock this ], humanized_updated: '5 months ago', + links: {}, }; const MOCK_INSTALLED_BLOCK_PLUGIN_DETAILS = { @@ -55,6 +56,13 @@ const MOCK_INSTALLED_BLOCK_PLUGIN_DETAILS = { requires_wp: '', requires_php: '', text_domain: 'block-directory-test-block', + _links: { + self: [ + { + href: '', + }, + ], + }, }; const MOCK_BLOCK2 = {