NEW: getByLink plugin allows arrays of links (#2666)

* NEW: getByLink plugin allows arrays of links

* Allow configurable resolver

* Remove unused exception tag

* Refactor to preserve API backward compat

* Unit test

* Add plugin to readOne

* Fix test

* add new test
This commit is contained in:
Aaron Carlino 2021-09-07 09:34:52 +12:00 committed by GitHub
parent 1794eee0ea
commit f3a76ccf2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 158 additions and 32 deletions

View File

@ -7,3 +7,7 @@ modelConfig:
plugins: plugins:
getByLink: getByLink:
after: filter after: filter
readOne:
plugins:
getByLink:
after: filter

View File

@ -12,6 +12,7 @@ use SilverStripe\GraphQL\Schema\Exception\SchemaBuilderException;
use SilverStripe\GraphQL\Schema\Field\ModelQuery; use SilverStripe\GraphQL\Schema\Field\ModelQuery;
use SilverStripe\GraphQL\Schema\Interfaces\ModelQueryPlugin; use SilverStripe\GraphQL\Schema\Interfaces\ModelQueryPlugin;
use SilverStripe\GraphQL\Schema\Schema; use SilverStripe\GraphQL\Schema\Schema;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataList;
if (!interface_exists(ModelQueryPlugin::class)) { if (!interface_exists(ModelQueryPlugin::class)) {
@ -29,7 +30,18 @@ class LinkablePlugin implements ModelQueryPlugin
* @var string * @var string
* @config * @config
*/ */
private static $field_name = 'link'; private static $single_field_name = 'link';
/**
* @var string
* @config
*/
private static $list_field_name = 'links';
/**
* @var array
*/
private static $resolver = [__CLASS__, 'applyLinkFilter'];
/** /**
* @return string * @return string
@ -43,7 +55,6 @@ class LinkablePlugin implements ModelQueryPlugin
* @param ModelQuery $query * @param ModelQuery $query
* @param Schema $schema * @param Schema $schema
* @param array $config * @param array $config
* @throws SchemaBuilderException
*/ */
public function apply(ModelQuery $query, Schema $schema, array $config = []): void public function apply(ModelQuery $query, Schema $schema, array $config = []): void
{ {
@ -52,43 +63,42 @@ class LinkablePlugin implements ModelQueryPlugin
if ($class !== SiteTree::class && !is_subclass_of($class, SiteTree::class)) { if ($class !== SiteTree::class && !is_subclass_of($class, SiteTree::class)) {
return; return;
} }
Schema::invariant( $singleFieldName = $this->config()->get('single_field_name');
!$query->isList(), $listFieldName = $this->config()->get('list_field_name');
'Plugin %s cannot be applied to queries that return lists. Query "%s" is a list', $fieldName = $query->isList() ? $listFieldName : $singleFieldName;
static::getIdentifier(), $type = $query->isList() ? '[String]' : 'String';
$query->getName() $query->addArg($fieldName, $type);
$query->addResolverAfterware(
$config['resolver'] ?? static::config()->get('resolver')
); );
$fieldName = $this->config()->get('field_name');
$query->addArg($fieldName, 'String');
$query->addResolverAfterware([static::class, 'applyLinkFilter']);
} }
/** /**
* @param $obj
* @param array $args
* @param array $context * @param array $context
* @param ResolveInfo $info * @return callable
* @param callable $done
* @return SiteTree|DataList|null
*/ */
public static function applyLinkFilter( public static function applyLinkFilter($obj, array $args, array $context, ResolveInfo $info)
$obj, {
array $args, $singleFieldName = static::config()->get('single_field_name');
array $context, $listFieldName = static::config()->get('list_field_name');
ResolveInfo $info, $filterLink = $args['filter'][$singleFieldName] ?? ($args['filter'][$listFieldName] ?? null);
callable $done $argLink = $args[$singleFieldName] ?? ($args[$listFieldName] ?? null);
) { $linkData = $filterLink ?: $argLink;
$fieldName = static::config()->get('field_name'); if (!$linkData) {
$filterLink = $args['filter'][$fieldName] ?? null;
$argLink = $args[$fieldName] ?? null;
$filterLink = $filterLink ?: $argLink;
if ($filterLink) {
$done();
return SiteTree::get_by_link($filterLink);
}
return $obj; return $obj;
} }
// Normalise to an array for both cases. The readOne operation will get
// ->first() run on it by the firstResult plugin.
$links = is_array($linkData) ? $linkData : [$linkData];
$result = ArrayList::create();
foreach ($links as $link) {
$page = SiteTree::get_by_link($link);
if ($page) {
$result->push($page);
}
}
return $result;
}
} }

View File

@ -0,0 +1,112 @@
<?php
namespace SilverStripe\CMS\Tests\GraphQL;
use SilverStripe\AssetAdmin\Tests\GraphQL\FakeResolveInfo;
use SilverStripe\CMS\GraphQL\LinkablePlugin;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\GraphQL\Schema\DataObject\DataObjectModel;
use SilverStripe\GraphQL\Schema\Field\ModelQuery;
use SilverStripe\GraphQL\Schema\Schema;
use SilverStripe\GraphQL\Schema\SchemaConfig;
class LinkablePluginTest extends SapphireTest
{
protected function setUp()
{
parent::setUp();
if (!class_exists(Schema::class)) {
$this->markTestSkipped('GraphQL 4 test ' . __CLASS__ . ' skipped');
}
}
/**
* @param bool $list
* @dataProvider provideApply
*/
public function testApply(bool $list)
{
$query = new ModelQuery(
new DataObjectModel(SiteTree::class, new SchemaConfig()),
'testQuery'
);
$query->setType($list ? '[SiteTree]' : 'SiteTree');
$plugin = new LinkablePlugin();
$plugin->apply($query, new Schema('test'));
$args = $query->getArgs();
$field = $list ? 'links' : 'link';
$this->assertArrayHasKey($field, $args);
$this->assertEquals($list ? '[String]' : 'String', $args[$field]->getType());
}
public function testResolver()
{
$page = SiteTree::create([
'Title' => 'Test page',
'URLSegment' => 'test-page',
'ParentID' => 0,
]);
$page->write();
$page->publishRecursive();
$page = SiteTree::create([
'Title' => 'Other test page',
'URLSegment' => 'other-test-page',
'ParentID' => 0,
]);
$page->write();
$page->publishRecursive();
$result = LinkablePlugin::applyLinkFilter('test', ['link' => 'test-page'], [], new FakeResolveInfo());
$this->assertTrue($result->exists());
$this->assertEquals('Test page', $result->first()->Title);
$result = LinkablePlugin::applyLinkFilter('test', ['links' => ['test-page']], [], new FakeResolveInfo());
$this->assertTrue($result->exists());
$this->assertEquals('Test page', $result->first()->Title);
$result = LinkablePlugin::applyLinkFilter(
'test',
['links' => ['test-page', 'other-test-page']],
[],
new FakeResolveInfo()
);
$this->assertTrue($result->exists());
$this->assertCount(2, $result);
$titles = $result->column('Title');
$this->assertTrue(in_array('Test page', $titles));
$this->assertTrue(in_array('Other test page', $titles));
$result = LinkablePlugin::applyLinkFilter(
'test',
['links' => ['test-page', 'fail-page']],
[],
new FakeResolveInfo()
);
$this->assertTrue($result->exists());
$this->assertCount(1, $result);
$this->assertEquals('Test page', $result->first()->Title);
$result = LinkablePlugin::applyLinkFilter('test', ['link' => 'fail-page'], [], new FakeResolveInfo());
$this->assertFalse($result->exists());
$result = LinkablePlugin::applyLinkFilter('test', ['links' => ['fail-page']], [], new FakeResolveInfo());
$this->assertFalse($result->exists());
$result = LinkablePlugin::applyLinkFilter('test', ['notAnArg' => 'fail'], [], new FakeResolveInfo());
$this->assertEquals('test', $result);
}
public function provideApply()
{
return [
[true],
[false],
];
}
}