From 47263206e2c2381c309c9673dae366d96053fa37 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 6 Jul 2020 07:57:38 -0400 Subject: [PATCH] Block Directory: Return inactive plugins in search results (#23688) * Block Directory: Return inactive plugins with block directory results * Active a an inactive block plugin if we see a plugin URL The plugin URL will only exist if the plugin is already installed, so we can use that to activate that plugin. We can also use this link for uninstallation, so we make sure to add it into the block object when installing a plugin. * Add links to mocked plugin response * Fix mocked test data * Fix notice in install test --- ...ass-wp-rest-block-directory-controller.php | 4 - packages/block-directory/src/store/actions.js | 42 +++-- .../block-directory/src/store/test/actions.js | 145 ++++++++++++++---- .../src/store/utils/get-plugin-url.js | 17 ++ .../plugins/block-directory-add.test.js | 8 + 5 files changed, 174 insertions(+), 42 deletions(-) create mode 100644 packages/block-directory/src/store/utils/get-plugin-url.js 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 = {