Commit d719ab6e authored by Fran Garcia-Linares's avatar Fran Garcia-Linares Committed by Chris Wells
Browse files

Issue #3310876 by fjgarlin, chrisfromredfin, pontus.andersson: Add information...

Issue #3310876 by fjgarlin, chrisfromredfin, pontus.andersson: Add information on how to create a new source plugin
parent a08421a1
Loading
Loading
Loading
Loading
+13 −0
Changes for modules/project_browser_source_example/README.md: 13 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Project Browser Source Example

If you want to create your own plugin for Project Browser, follow these steps:
* *Step 1*: Copy and paste this whole module into your "custom" modules folder.
* *Step 2*: Search and replace "project_browser_source_example" with your
module's machine name. Give the class an appropriate new name, and make sure
the file is named the same.
* *Step 3*: Read the in-code comments that will guide you through what's
required.

The provided code is the bare minimum code to get a new plugin working. If you
need more advanced functionality, check out the other plugins the module provides,
or reach out on Drupal Slack in the #project-browser channel.
+6 −0
Changes for modules/project_browser_source_example/project_browser_source_example.info.yml: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
name: Project Browser Source Example
type: module
description: Example source plugin for Project Browser containing the bare minimum information to help people build their own source plugin.
core_version_requirement: ^9 || ^10
dependencies:
  - project_browser:project_browser
+35 −0
Changes for modules/project_browser_source_example/project_browser_source_example.install: 35 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Enables this plugin source on install.
 *
 * If you do not want this behavior, you can remove this entire .install file.
 */

/**
 * Implements hook_install().
 */
function project_browser_source_example_install() {
  $configFactory = \Drupal::configFactory();
  $current_source_plugin = $configFactory->getEditable('project_browser.admin_settings')
    ->get('enabled_sources');
  $current_source_plugin[] = 'project_browser_source_example';
  $configFactory->getEditable('project_browser.admin_settings')
    ->set('enabled_sources', $current_source_plugin)
    ->save(TRUE);
}

/**
 * Implements hook_uninstall().
 */
function project_browser_source_example_uninstall() {
  $admin_settings = \Drupal::configFactory()->getEditable('project_browser.admin_settings');
  $enabled_sources = $admin_settings->get('enabled_sources');
  if (($key = array_search('project_browser_source_example', $enabled_sources)) !== FALSE) {
    unset($enabled_sources[$key]);
    $admin_settings
      ->set('enabled_sources', array_values($enabled_sources) ?: ['drupalorg_mockapi'])
      ->save(TRUE);
  }
}
+148 −0
Changes for modules/project_browser_source_example/src/Plugin/ProjectBrowserSource/ProjectBrowserSourceExample.php: 148 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\project_browser_source_example\Plugin\ProjectBrowserSource;

use Drupal\project_browser\Plugin\ProjectBrowserSourceBase;
use Drupal\project_browser\ProjectBrowser\Project;
use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage;

/**
 * Project Browser Source Plugin example code.
 *
 * @ProjectBrowserSource(
 *   id = "project_browser_source_example",
 *   label = @Translation("Example source"),
 *   description = @Translation("Example source plugin for Project Browser."),
 * )
 */
class ProjectBrowserSourceExample extends ProjectBrowserSourceBase {

  /**
   * {@inheritdoc}
   */
  public function getProjects(array $query = []): ProjectsResultsPage {
    // Each of the following steps could be done in a separate function. We are
    // including them here for simplicity.
    // --
    // Step 1: Interpret the $query made and adapt it to your needs so you can
    // filter and sort the data accordingly. Refer to ProjectBrowserSourceInterface
    // for information on which properties are available.
    $categories_filter = explode(',', $query['categories'] ?? '');
    $machine_name_filter = $query['machine_name'] ?? '';

    // Step 2: Get the projects from wherever your source is and create Project
    // Browser "Project" objects for the final result-set. This could be your
    // own REST endpoint, GraphQL, a Google Spreadsheet, anything! Refer to
    // other plugins if you want more examples. The $query should be taken into
    // account when filtering, but adapted to the way that you obtain the data.
    $projects_from_source = [
      // The source data can use any keys, we will adapt it later.
      [
        'identifier' => 'p1',
        'unique_name' => 'project_1',
        'label' => 'Project 1',
        'short_description' => 'Quick summary to show in the cards.',
        'long_description' => 'Extended project information to show in the detail page',
        'author' => 'Jane Doe',
        'logo' => \Drupal::request()->getSchemeAndHttpHost() . '/core/misc/logo/drupal-logo.svg',
        'created_at' => strtotime('1 year ago'),
        'updated_at' => strtotime('1 month ago'),
        'categories' => ['cat_1:Category 1'],
        'composer_namespace' => 'my-awesome-namespace/project_1',
      ],
    ];

    // Very basic filtering for this example based on the query made.
    // The filtering itself is done at the source, it can happen before or
    // after, it's up to your source and how it works.
    if (in_array('cat_2', $categories_filter) && !in_array('cat_1', $categories_filter)) {
      array_pop($projects_from_source);
    }
    if (!empty($machine_name_filter) && $machine_name_filter !== 'project_1') {
      array_pop($projects_from_source);
    }

    // Step 3: You MUST set each of the following properties for every
    // project in your result-set. Here is an example of a Project object
    // fully populated.
    $projects = [];
    foreach ($projects_from_source as $project_from_source) {
      // Empty array if no author information is provided.
      $author = [
        'name' => $project_from_source['author'],
      ];

      // Empty array if there is no logo.
      $logo = [
        'file' => [
          // Url of the logo in "https" format.
          'uri' => $project_from_source['logo'],
          'resource' => 'image',
        ],
        'alt' => 'Project 1 logo',
      ];

      // Empty array if there are no categories.
      $categories = [];
      foreach ($project_from_source['categories'] as $category) {
        [$id, $name] = explode(':', $category);
        $categories[] = [
          'id' => $id,
          'name' => $name,
        ];
      }

      $projects[] = (new Project())
        ->setAuthor($author)
        ->setCreatedTimestamp($project_from_source['created_at'])
        ->setChangedTimestamp($project_from_source['updated_at'])
        ->setProjectTitle($project_from_source['label'])
        ->setId($project_from_source['identifier'])
        ->setSummary([
          'summary' => $project_from_source['short_description'],
          'value' => $project_from_source['long_description'],
        ])
        ->setLogo($logo)
        ->setModuleCategories($categories)
        ->setMachineName($project_from_source['unique_name'])
        ->setComposerNamespace($project_from_source['composer_namespace'])
        // Maybe the source won't have all fields, but we still need to
        // populate the values of all the properties.
        ->setIsCompatible(TRUE)
        ->setProjectUsageTotal(0)
        ->setProjectStarUserCount(0)
        // Images: Array of images using the same structure as $logo, above.
        ->setImages([])
        // Status: 1 enabled / 0 disabled.
        ->setProjectStatus(1)
        ->setIsCovered(TRUE)
        ->setIsActive(TRUE)
        ->setIsMaintained(TRUE);
    }

    // Return one page of results. The first parameter is the total number of
    // results for the set, as filtered by $query.
    return new ProjectsResultsPage(count($projects), $projects, (string) $this->getPluginDefinition()['label'], $this->getPluginId());
  }

  /**
   * {@inheritdoc}
   */
  public function getCategories(): array {
    // Step 1: The list of categories that the modules can have. The array
    // must have the "id" and "name" properties defined. If your source data
    // does not use categories then return an empty array. These can be
    // hardcoded, come from REST or GraphQL endpoints, etc.
    return [
      [
        'id' => 'cat_1',
        'name' => 'Category 1',
      ],
      [
        'id' => 'cat_2',
        'name' => 'Category 2',
      ],
    ];
  }

}
+59 −0
Changes for tests/src/FunctionalJavascript/ProjectBrowserExamplePluginTest.php: 59 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\project_browser\FunctionalJavascript;

use Drupal\FunctionalJavascriptTests\WebDriverTestBase;

/**
 * Provides tests for the Project Browser Example plugins.
 *
 * @group project_browser
 */
class ProjectBrowserExamplePluginTest extends WebDriverTestBase {

  use ProjectBrowserUiTestTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'project_browser',
    'project_browser_source_example',
  ];

  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'stark';

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->drupalLogin($this->drupalCreateUser([
      'administer modules',
      'administer site configuration',
    ]));
    // Update configuration, enable only example source.
    $this->config('project_browser.admin_settings')->set('enabled_sources', ['project_browser_source_example'])->save(TRUE);
  }

  /**
   * Tests the Example plugin.
   */
  public function testExamplePlugin(): void {
    $assert_session = $this->assertSession();

    $this->getSession()->resizeWindow(1200, 1200);
    $this->drupalGet('admin/modules/browse');
    $this->svelteInitHelper('css', '#project-browser .grid');
    $this->assertEquals('Grid', $this->getElementText('#project-browser .toggle-buttons .grid-button'));
    $assert_session->waitForElementVisible('css', '#project-browser .project');
    $this->assertTrue($assert_session->waitForText('Project 1'));
    $assert_session->pageTextNotContains('No records available');
    $this->svelteInitHelper('css', '.pb-categories input[type="checkbox"]');
    $assert_session->elementsCount('css', '.pb-categories input[type="checkbox"]', 2);
  }

}