Skip to content

Commit

Permalink
Update BnfImporter to also import text paragraphs.
Browse files Browse the repository at this point in the history
This feels a bit dirty, but for now, until we've cleared how to
deal with GraphQL schemas/codegen, it'll do.
Only text_body paragraphs are imported.
  • Loading branch information
rasben committed Dec 17, 2024
1 parent 3f1c7c3 commit c073ca8
Showing 1 changed file with 133 additions and 12 deletions.
145 changes: 133 additions & 12 deletions web/modules/custom/bnf/src/Services/BnfImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
use Drupal\bnf\Exception\AlreadyExistsException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\paragraphs\ParagraphInterface;
use GuzzleHttp\ClientInterface;
use Psr\Log\LoggerInterface;
use function Safe\json_decode;
use function Safe\parse_url;
use function Safe\preg_replace;

/**
* Service related to importing content from an external source.
Expand All @@ -21,6 +23,15 @@ class BnfImporter {

const UUID_FIELD = 'bnf_uuid';

const ALLOWED_PARAGRAPHS = [
'text_body',
];

/**
* The BNF UUID of the content that we import.
*/
protected string $uuid;

/**
* Constructor.
*/
Expand All @@ -31,10 +42,129 @@ public function __construct(
protected LoggerInterface $logger,
) {}

/**
* Building the query we use to get data from source.
*/
protected function getQuery(string $queryName): string {
// Example of GraphQL query: "nodeArticle".
// For now, we only support the title of the nodes.
return <<<GRAPHQL
query {
$queryName(id: "$this->uuid") {
title
paragraphs {
... on ParagraphTextBody {
__typename
body {
format,
value
}
}
}
}
}
GRAPHQL;

}

/**
* Parses paragraphs from GraphQL node data into Drupal-compatible structures.
*
* @param mixed[] $nodeData
* The GraphQL node data containing paragraphs.
*
* @return mixed[]
* Array of paragraph values, that we can use to create paragraph entities.
*/
protected function parseParagraphs(array $nodeData) {
$parsedParagraphs = [];

// Ensure paragraphs exist in the GraphQL response.
if (empty($nodeData['paragraphs'])) {
return $parsedParagraphs;
}

foreach ($nodeData['paragraphs'] as $paragraphData) {
$type = $paragraphData['__typename'] ?? '';

// Convert typename to Drupal paragraph bundle name.
$bundleName = $this->graphqlTypeToBundle($type);

if (!in_array($bundleName, self::ALLOWED_PARAGRAPHS)) {
continue;
}

$paragraph = ['type' => $bundleName];

// Map fields dynamically.
foreach ($paragraphData as $key => $value) {
if ($key === '__typename') {
continue;
}

// Assume Drupal uses field names like "field_{key}".
$drupalFieldName = 'field_' . $key;
$paragraph[$drupalFieldName] = $value;
}

$parsedParagraphs[] = $paragraph;
}

return $parsedParagraphs;
}

/**
* Creating the paragraphs, that we will add to the nodes.
*
* @param mixed[] $nodeData
* The GraphQL node data containing paragraphs.
*
* @return \Drupal\paragraphs\ParagraphInterface[]
* The paragraph entities.
*/
protected function getParagraphs(array $nodeData): array {
$parsedParagraphs = $this->parseParagraphs($nodeData);
$storage = $this->entityTypeManager->getStorage('paragraph');
$paragraphs = [];
foreach ($parsedParagraphs as $paragraphData) {
$paragraph = $storage->create($paragraphData);

if ($paragraph instanceof ParagraphInterface) {
$paragraph->save();
$paragraphs[] = $paragraph;
}
}

return $paragraphs;
}

/**
* Converts a GraphQL typename to a Drupal paragraph bundle name.
*
* @param string $typeName
* The GraphQL typename (e.g., ParagraphTextBody).
*
* @return string
* The Drupal paragraph bundle name (e.g., text_body).
*/
protected function graphqlTypeToBundle(string $typeName): string {
// Removing 'Paragraph' prefix.
$typeName = preg_replace('/^Paragraph/', '', $typeName);

// Converting CamelCase to snake_case.
$pattern = '/(?<=\\w)(?=[A-Z])|(?<=[a-z])(?=[0-9])/';
$typeName = preg_replace($pattern, '_', $typeName);

return strtolower($typeName);
}

/**
* Importing a node from a GraphQL source endpoint.
*/
public function importNode(string $uuid, string $endpointUrl, string $nodeType = 'article'): void {
$this->uuid = $uuid;
$queryName = 'node' . ucfirst($nodeType);

$nodeStorage = $this->entityTypeManager->getStorage('node');

$existingNodes =
Expand All @@ -49,18 +179,6 @@ public function importNode(string $uuid, string $endpointUrl, string $nodeType =
throw new AlreadyExistsException('Cannot import node - already exists.');
}

// Example of GraphQL query: "nodeArticle".
$queryName = 'node' . ucfirst($nodeType);

// For now, we only support the title of the nodes.
$query = <<<GRAPHQL
query {
$queryName(id: "$uuid") {
title
}
}
GRAPHQL;

if (!filter_var($endpointUrl, FILTER_VALIDATE_URL)) {
throw new \InvalidArgumentException('The provided callback URL is not valid.');
}
Expand All @@ -72,6 +190,8 @@ public function importNode(string $uuid, string $endpointUrl, string $nodeType =
throw new \InvalidArgumentException('The provided callback URL must use HTTPS.');
}

$query = $this->getQuery($queryName);

$response = $this->httpClient->request('post', $endpointUrl, [
'headers' => [
'Content-Type' => 'application/json',
Expand All @@ -96,6 +216,7 @@ public function importNode(string $uuid, string $endpointUrl, string $nodeType =
try {
$nodeData['type'] = $nodeType;
$nodeData[self::UUID_FIELD] = $uuid;
$nodeData['field_paragraphs'] = $this->getParagraphs($nodeData);

$node = $nodeStorage->create($nodeData);
$node->save();
Expand Down

0 comments on commit c073ca8

Please sign in to comment.