Improve asset and log CSV exports #783

This commit is contained in:
Michael Stenta 2024-02-02 19:51:14 -05:00
commit 3e891a880d
29 changed files with 884 additions and 283 deletions

View File

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [Recommend running composer update twice #653](https://github.com/farmOS/farmOS/pull/786)
- [Edit form UI improvements #770](https://github.com/farmOS/farmOS/pull/770)
- [Improve asset and log CSV exports #783](https://github.com/farmOS/farmOS/pull/783)
## [3.0.1] 2024-01-18

View File

@ -2,10 +2,24 @@
## CSV
All [Asset](/guide/assets), [Log](/guide/logs), and
[Quantity](/guide/quantities) lists in farmOS include an "Export CSV" link at
the bottom that will generate and download a CSV file. Any sorts or filters
that are applied to the list will be represented in the CSV output.
All [Asset](/guide/assets) and [Log](/guide/logs) lists in farmOS provide an
"Export CSV" action that will generate a CSV of selected records. These include
most of the record's information, including columns that are not visible in the
list pages themselves.
[Quantity](/guide/quantities) lists provide an "Export CSV" link at the bottom of the page
that serve a similar purpose. These exports include all of the columns that are
visible on the Quantity list page, including information about the Quantity
itself, as well as some information about the Log records that the Quantity
is attached to.
Any sorts or filters that are applied to the list will be represented in the
CSV output.
**Warning: CSV exports do not include all data.**
The [farmOS API](/development/api) is the best way to get access to all raw data
in a farmOS instance.
## KML

View File

@ -56,8 +56,9 @@ function farm_modules() {
'farm_lab_test' => t('Lab test logs'),
'farm_birth' => t('Birth logs'),
'farm_medical' => t('Medical logs'),
'farm_export_csv' => t('CSV exporter'),
'farm_import_csv' => t('CSV importer'),
'farm_kml' => t('KML export features'),
'farm_export_kml' => t('KML exporter'),
'farm_import_kml' => t('KML asset importer'),
'farm_map_mapbox' => t('Mapbox map layers: Satellite, Outdoors'),
'farm_api_default_consumer' => t('Default API Consumer'),

View File

@ -6,13 +6,10 @@ dependencies:
- image.style.thumbnail
module:
- asset
- csv_serialization
- farm_group
- farm_location
- image
- options
- rest
- serialization
- state_machine
- user
id: farm_group_members
@ -1052,17 +1049,6 @@ display:
plugin_id: result
empty: false
content: 'Displaying @start - @end of @total'
display_link:
id: display_link
table: views
field: display_link
relationship: none
group_type: group
admin_label: ''
plugin_id: display_link
label: 'Export CSV'
empty: false
display_id: csv
display_extenders: { }
cache_metadata:
max-age: 0
@ -1073,61 +1059,6 @@ display:
- url.query_args
- user.permissions
tags: { }
csv:
id: csv
display_title: 'CSV export (rest)'
display_plugin: rest_export
position: 2
display_options:
pager:
type: none
options:
offset: 0
style:
type: serializer
options:
uses_fields: false
formats:
csv: csv
row:
type: data_field
options:
field_options:
asset_bulk_form:
alias: ''
raw_output: false
image_target_id:
alias: ''
raw_output: false
id:
alias: ''
raw_output: false
name:
alias: ''
raw_output: false
type:
alias: ''
raw_output: false
flag_value:
alias: ''
raw_output: false
status:
alias: ''
raw_output: false
display_description: ''
display_extenders: { }
path: assets.csv
auth:
- cookie
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- request_format
- url
- user.permissions
tags: { }
page:
id: page
display_title: 'Group members (page)'

View File

@ -0,0 +1,5 @@
name: farmOS CSV
description: Provides CSV features for farmOS.
type: module
package: farmOS
core_version_requirement: ^10

View File

@ -0,0 +1,19 @@
services:
farm_csv.normalizer.content_entity_normalizer:
class: Drupal\farm_csv\Normalizer\ContentEntityNormalizer
tags:
- { name: normalizer, priority: 10 }
arguments: ['@entity_type.manager', '@entity_type.repository', '@entity_field.manager']
farm_csv.normalizer.text_long_field_item:
class: Drupal\farm_csv\Normalizer\TextLongFieldItemNormalizer
tags:
- { name: normalizer, priority: 10 }
farm_csv.normalizer.entity_reference_field_item:
class: Drupal\farm_csv\Normalizer\EntityReferenceFieldItemNormalizer
tags:
- { name: normalizer, priority: 10 }
arguments: ['@entity.repository']
farm_csv.normalizer.timestamp_item:
class: Drupal\farm_csv\Normalizer\TimestampItemNormalizer
tags:
- { name: normalizer, priority: 10 }

View File

@ -0,0 +1,43 @@
<?php
namespace Drupal\farm_csv\Normalizer;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\serialization\Normalizer\ContentEntityNormalizer as CoreContentEntityNormalizer;
/**
* Normalizes farmOS content entities for CSV exports.
*/
class ContentEntityNormalizer extends CoreContentEntityNormalizer {
/**
* The supported format.
*/
const FORMAT = 'csv';
/**
* {@inheritdoc}
*/
public function normalize($entity, $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|NULL {
$data = parent::normalize($entity, $format, $context);
// If columns were explicitly included, remove others.
if (!empty($context['include_columns'])) {
foreach (array_keys($data) as $key) {
if (!in_array($key, $context['include_columns'])) {
unset($data[$key]);
}
}
}
return $data;
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, string $format = NULL, array $context = []): bool {
return $data instanceof ContentEntityInterface && $format == static::FORMAT;
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Drupal\farm_csv\Normalizer;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface;
use Drupal\serialization\Normalizer\EntityReferenceFieldItemNormalizer as CoreEntityReferenceFieldItemNormalizer;
/**
* Normalizes entity reference fields for farmOS CSV exports.
*/
class EntityReferenceFieldItemNormalizer extends CoreEntityReferenceFieldItemNormalizer {
/**
* The supported format.
*/
const FORMAT = 'csv';
/**
* {@inheritdoc}
*/
public function normalize($field_item, $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|NULL {
// Attempt to load the referenced entity.
/** @var \Drupal\Core\Entity\EntityInterface $entity */
if ($entity = $field_item->get('entity')->getValue()) {
// Return content entity labels, if desired.
if ($entity instanceof ContentEntityInterface && isset($context['content_entity_labels']) && $context['content_entity_labels'] === TRUE) {
return $entity->label();
}
// Return config entity IDs, if desired.
if ($entity instanceof ConfigEntityInterface && isset($context['config_entity_ids']) && $context['config_entity_ids'] === TRUE) {
return $entity->id();
}
}
// Otherwise, delegate to the parent method.
return parent::normalize($field_item, $format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, string $format = NULL, array $context = []): bool {
return $data instanceof EntityReferenceItemInterface && $format == static::FORMAT;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Drupal\farm_csv\Normalizer;
use Drupal\serialization\Normalizer\FieldItemNormalizer;
use Drupal\text\Plugin\Field\FieldType\TextLongItem;
/**
* Normalizes long text fields for farmOS CSV exports.
*/
class TextLongFieldItemNormalizer extends FieldItemNormalizer {
/**
* The supported format.
*/
const FORMAT = 'csv';
/**
* {@inheritdoc}
*/
public function normalize($field_item, $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|NULL {
/** @var \Drupal\text\Plugin\Field\FieldType\TextLongItem $field_item */
// Return processed text, if desired.
if (isset($context['processed_text']) && $context['processed_text'] === TRUE) {
return $field_item->get('processed')->getValue();
}
// Delegate to the parent method.
return parent::normalize($field_item, $format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, string $format = NULL, array $context = []): bool {
return $data instanceof TextLongItem && $format == static::FORMAT;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Drupal\farm_csv\Normalizer;
use Drupal\serialization\Normalizer\TimestampItemNormalizer as CoreTimestampItemNormalizer;
/**
* Normalizes timestamp fields for farmOS CSV exports.
*/
class TimestampItemNormalizer extends CoreTimestampItemNormalizer {
/**
* The supported format.
*/
const FORMAT = 'csv';
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|NULL {
$data = parent::normalize($object, $format, $context);
// Return the RFC3339 formatted date, if desired.
if (isset($context['rfc3339_dates']) && $context['rfc3339_dates'] === TRUE) {
return $data['value'];
}
return $data;
}
}

View File

@ -0,0 +1,5 @@
name: farmOS Export
description: Features for exporting records.
type: module
package: farmOS
core_version_requirement: ^10

View File

@ -0,0 +1,11 @@
langcode: en
status: true
dependencies:
module:
- asset
- farm_export_csv
id: asset_csv_action
label: 'Export CSV'
type: asset
plugin: entity:csv_action:asset
configuration: { }

View File

@ -0,0 +1,11 @@
langcode: en
status: true
dependencies:
module:
- farm_export_csv
- log
id: log_csv_action
label: 'Export CSV'
type: log
plugin: entity:csv_action:log
configuration: { }

View File

@ -0,0 +1,8 @@
name: farmOS Export CSV
description: Provides a CSV export action for farmOS.
type: module
package: farmOS
core_version_requirement: ^10
dependencies:
- farm:farm_export
- farm:farm_csv

View File

@ -0,0 +1,27 @@
<?php
/**
* @file
* The farmOS Export CSV module.
*/
use Drupal\farm_export_csv\Form\EntityCsvActionForm;
use Drupal\farm_export_csv\Routing\EntityCsvActionRouteProvider;
/**
* Implements hook_entity_type_build().
*/
function farm_export_csv_entity_type_build(array &$entity_types) {
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
// Enable the entity CSV export action on assets and logs.
foreach (['asset', 'log'] as $entity_type) {
if (!empty($entity_types[$entity_type])) {
$route_providers = $entity_types[$entity_type]->getRouteProviderClasses();
$route_providers['csv'] = EntityCsvActionRouteProvider::class;
$entity_types[$entity_type]->setHandlerClass('route_provider', $route_providers);
$entity_types[$entity_type]->setLinkTemplate('csv-action-form', '/' . $entity_type . '/csv');
$entity_types[$entity_type]->setFormClass('csv-action-form', EntityCsvActionForm::class);
}
}
}

View File

@ -0,0 +1,381 @@
<?php
namespace Drupal\farm_export_csv\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Form\BaseFormIdInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\Url;
use Drupal\file\FileRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Provides an entity CSV action form.
*
* @see \Drupal\farm_export_csv\Plugin\Action\EntityCsv
* @see \Drupal\Core\Entity\Form\DeleteMultipleForm
*/
class EntityCsvActionForm extends ConfirmFormBase implements BaseFormIdInterface {
/**
* The tempstore factory.
*
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $tempStore;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The serializer service.
*
* @var \Symfony\Component\Serializer\SerializerInterface
*/
protected $serializer;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The default file scheme.
*
* @var string
*/
protected $defaultFileScheme;
/**
* The file repository service.
*
* @var \Drupal\file\FileRepositoryInterface
*/
protected $fileRepository;
/**
* The file URL generator.
*
* @var \Drupal\Core\File\FileUrlGeneratorInterface
*/
protected $fileUrlGenerator;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* The entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The entities to export.
*
* @var \Drupal\Core\Entity\EntityInterface[]
*/
protected $entities;
/**
* Constructs an EntityCsvActionForm form object.
*
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Symfony\Component\Serializer\SerializerInterface $serializer
* The serializer service.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file system service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\file\FileRepositoryInterface $file_repository
* The file repository service.
* @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
* The file URL generator.
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, SerializerInterface $serializer, FileSystemInterface $file_system, ConfigFactoryInterface $config_factory, FileRepositoryInterface $file_repository, FileUrlGeneratorInterface $file_url_generator, AccountInterface $user) {
$this->tempStore = $temp_store_factory->get('entity_csv_confirm');
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->serializer = $serializer;
$this->fileSystem = $file_system;
$this->defaultFileScheme = $config_factory->get('system.file')->get('default_scheme') ?? 'public';
$this->fileRepository = $file_repository;
$this->fileUrlGenerator = $file_url_generator;
$this->user = $user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('tempstore.private'),
$container->get('entity_type.manager'),
$container->get('entity_field.manager'),
$container->get('serializer'),
$container->get('file_system'),
$container->get('config.factory'),
$container->get('file.repository'),
$container->get('file_url_generator'),
$container->get('current_user'),
);
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return 'entity_export_csv_action_form';
}
/**
* {@inheritdoc}
*/
public function getFormId() {
// Get entity type ID from the route because ::buildForm has not yet been
// called.
$entity_type_id = $this->getRouteMatch()->getParameter('entity_type_id');
return $entity_type_id . '_export_csv_action_form';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->formatPlural(count($this->entities), 'Export a CSV of @count @item?', 'Export a CSV of @count @items?', [
'@item' => $this->entityType->getSingularLabel(),
'@items' => $this->entityType->getPluralLabel(),
]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
if ($this->entityType->hasLinkTemplate('collection')) {
return new Url('entity.' . $this->entityType->id() . '.collection');
}
else {
return new Url('<front>');
}
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return '';
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Export');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) {
// If we don't have an entity type or list of entities, redirect.
$this->entityType = $this->entityTypeManager->getDefinition($entity_type_id);
$this->entities = $this->tempStore->get($this->user->id() . ':' . $entity_type_id);
if (empty($entity_type_id) || empty($this->entities)) {
return new RedirectResponse($this->getCancelUrl()
->setAbsolute()
->toString());
}
// Make it clear that CSV exports are limited.
$message = $this->t('Note: CSV exports do not include all @item data.', ['@item' => $this->entityType->getSingularLabel()]);
$form['warning'] = [
'#type' => 'html_tag',
'#tag' => 'strong',
'#value' => $message,
];
// Delegate to the parent method.
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Filter out entities the user doesn't have access to.
$inaccessible_entities = [];
$accessible_entities = [];
foreach ($this->entities as $entity) {
if (!$entity->access('view', $this->currentUser())) {
$inaccessible_entities[] = $entity;
continue;
}
$accessible_entities[] = $entity;
}
// Serialize the entities with the csv format.
$context = [
// Define the columns to include.
'include_columns' => $this->getIncludeColumns(),
// Return processed text from long text fields.
'processed_text' => TRUE,
// Return content entity labels and config entity IDs.
'content_entity_labels' => TRUE,
'config_entity_ids' => TRUE,
// Return RFC3339 dates.
'rfc3339_dates' => TRUE,
];
$output = $this->serializer->serialize($accessible_entities, 'csv', $context);
// Prepare the file directory.
$directory = $this->defaultFileScheme . '://csv';
$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
// Create the file.
$filename = 'csv_export-' . date('c') . '.csv';
$destination = "$directory/$filename";
try {
$file = $this->fileRepository->writeData($output, $destination);
}
// If file creation failed, bail with a warning.
catch (\Exception $e) {
$this->messenger()->addWarning($this->t('Could not create file.'));
return;
}
// Make the file temporary.
$file->status = 0;
$file->save();
// Add warning message for inaccessible entities.
if (!empty($inaccessible_entities)) {
$inaccessible_count = count($inaccessible_entities);
$this->messenger()->addWarning($this->formatPlural($inaccessible_count, 'Could not export @count @item because you do not have the necessary permissions.', 'Could not export @count @items because you do not have the necessary permissions.', [
'@item' => $this->entityType->getSingularLabel(),
'@items' => $this->entityType->getPluralLabel(),
]));
}
// Add confirmation message.
if (count($accessible_entities)) {
$this->messenger()->addStatus($this->formatPlural(count($accessible_entities), 'Exported @count @item.', 'Exported @count @items', [
'@item' => $this->entityType->getSingularLabel(),
'@items' => $this->entityType->getPluralLabel(),
]));
}
// Show a link to the file.
$url = $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri());
$this->messenger()->addMessage($this->t('CSV file created: <a href=":url">%filename</a>', [
':url' => $url,
'%filename' => $file->label(),
]));
$this->tempStore->delete($this->currentUser()->id() . ':' . $this->entityType->id());
$form_state->setRedirectUrl($this->getCancelUrl());
}
/**
* Get a list of columns to include in CSV exports.
*
* @return string[]
* An array of column names.
*/
protected function getIncludeColumns() {
// Start with ID and UUID.
$columns = [
'id',
'uuid',
];
// Define which field types are supported.
$supported_field_types = [
'boolean',
'created',
'changed',
'entity_reference',
'list_string',
'state',
'string',
'text_long',
'timestamp',
];
// Add base field for supported field types.
$base_field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($this->entityType->id());
foreach ($base_field_definitions as $field_name => $field_definition) {
if (!in_array($field_name, $columns) && in_array($field_definition->getType(), $supported_field_types)) {
$columns[] = $field_name;
}
}
// Add bundle fields for supported field types.
$bundles = $this->entityTypeManager->getStorage($this->entityType->getBundleEntityType())->loadMultiple();
foreach ($bundles as $bundle) {
if ($this->entityTypeManager->hasHandler($this->entityType->id(), 'bundle_plugin')) {
$bundle_fields = $this->entityTypeManager->getHandler($this->entityType->id(), 'bundle_plugin')->getFieldDefinitions($bundle->id());
foreach ($bundle_fields as $field_name => $field_definition) {
if (!in_array($field_name, $columns) && in_array($field_definition->getType(), $supported_field_types)) {
$columns[] = $field_name;
}
}
}
}
// Remove revision and language columns.
$remove_columns = [
'default_langcode',
'revision_translation_affected',
'revision_created',
'revision_user',
];
$columns = array_filter($columns, function ($name) use ($remove_columns) {
return !in_array($name, $remove_columns);
});
return $columns;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Drupal\farm_export_csv\Plugin\Action\Derivative;
use Drupal\Core\Action\Plugin\Action\Derivative\EntityActionDeriverBase;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Provides an action deriver for the CSV action.
*
* @see \Drupal\farm_export_csv\Plugin\Action\EntityCsv
*/
class EntityCsvDeriver extends EntityActionDeriverBase {
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
if (empty($this->derivatives)) {
$definitions = [];
foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) {
$definition = $base_plugin_definition;
$definition['type'] = $entity_type_id;
$definition['label'] = $this->t('Export @entity_type CSV', ['@entity_type' => $entity_type->getSingularLabel()]);
$definition['confirm_form_route_name'] = 'entity.' . $entity_type->id() . '.csv_form';
$definitions[$entity_type_id] = $definition;
}
$this->derivatives = $definitions;
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
/**
* {@inheritdoc}
*/
protected function isApplicable(EntityTypeInterface $entity_type) {
return in_array($entity_type->id(), ['log', 'asset']);
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace Drupal\farm_export_csv\Plugin\Action;
use Drupal\Core\Action\Plugin\Action\EntityActionBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Action that exports a CSV file of entities.
*
* @Action(
* id = "entity:csv_action",
* action_label = @Translation("Export entity as CSV"),
* deriver = "Drupal\farm_export_csv\Plugin\Action\Derivative\EntityCsvDeriver",
* )
*/
class EntityCsv extends EntityActionBase {
/**
* The tempstore object.
*
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $tempStore;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new EntityCsv object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Session\AccountInterface $current_user
* Current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
$this->tempStore = $temp_store_factory->get('entity_csv_confirm');
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('tempstore.private'),
$container->get('current_user'),
);
}
/**
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
/** @var \Drupal\Core\Entity\EntityInterface[] $entities */
$this->tempStore->set($this->currentUser->id() . ':' . $this->getPluginDefinition()['type'], $entities);
}
/**
* {@inheritdoc}
*/
public function execute($object = NULL) {
$this->executeMultiple([$object]);
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
return $object->access('view', $account, $return_as_object);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Drupal\farm_export_csv\Routing;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides routes for the entity CSV export action.
*/
class EntityCsvActionRouteProvider implements EntityRouteProviderInterface {
/**
* {@inheritdoc}
*/
public function getRoutes(EntityTypeInterface $entity_type) {
$collection = new RouteCollection();
$entity_type_id = $entity_type->id();
if ($route = $this->getEntityCsvFormRoute($entity_type)) {
$collection->add("entity.$entity_type_id.csv_form", $route);
}
return $collection;
}
/**
* Gets the entity CSV export form route.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return \Symfony\Component\Routing\Route|null
* The generated route, if available.
*/
protected function getEntityCsvFormRoute(EntityTypeInterface $entity_type) {
if ($entity_type->hasLinkTemplate('csv-action-form')) {
$route = new Route($entity_type->getLinkTemplate('csv-action-form'));
$route->setDefault('_form', $entity_type->getFormClass('csv-action-form'));
$route->setDefault('entity_type_id', $entity_type->id());
$route->setRequirement('_user_is_logged_in', 'TRUE');
return $route;
}
}
}

View File

@ -3,7 +3,7 @@ status: true
dependencies:
module:
- asset
- farm_kml
- farm_export_kml
id: asset_kml_action
label: 'Export KML'
type: asset

View File

@ -2,7 +2,7 @@ langcode: en
status: true
dependencies:
module:
- farm_kml
- farm_export_kml
- log
id: log_kml_action
label: 'Export KML'

View File

@ -0,0 +1,8 @@
name: farmOS Export KML
description: Provides a KML export action for farmOS.
type: module
package: farmOS
core_version_requirement: ^10
dependencies:
- farm:farm_export
- farm:farm_kml

View File

@ -1,6 +1,6 @@
<?php
namespace Drupal\farm_kml\Plugin\Action\Derivative;
namespace Drupal\farm_export_kml\Plugin\Action\Derivative;
use Drupal\Core\Action\Plugin\Action\Derivative\EntityActionDeriverBase;
use Drupal\Core\Entity\EntityTypeInterface;
@ -8,7 +8,7 @@ use Drupal\Core\Entity\EntityTypeInterface;
/**
* Provides an action deriver for the KML action.
*
* @see \Drupal\farm_kml\Plugin\Action\EntityKml
* @see \Drupal\farm_export_kml\Plugin\Action\EntityKml
*/
class EntityKmlDeriver extends EntityActionDeriverBase {

View File

@ -1,6 +1,6 @@
<?php
namespace Drupal\farm_kml\Plugin\Action;
namespace Drupal\farm_export_kml\Plugin\Action;
use Drupal\Core\Action\Plugin\Action\EntityActionBase;
use Drupal\Core\Config\ConfigFactoryInterface;
@ -18,7 +18,7 @@ use Symfony\Component\Serializer\SerializerInterface;
* @Action(
* id = "entity:kml_action",
* action_label = @Translation("Export entity geometry as KML"),
* deriver = "Drupal\farm_kml\Plugin\Action\Derivative\EntityKmlDeriver",
* deriver = "Drupal\farm_export_kml\Plugin\Action\Derivative\EntityKmlDeriver",
* )
*/
class EntityKml extends EntityActionBase {

View File

@ -0,0 +1,25 @@
<?php
/**
* @file
* Post update functions for farm_kml module.
*/
use Drupal\system\Entity\Action;
/**
* Move KML export actions to new farm_export_kml module.
*/
function farm_kml_post_update_move_kml_export_actions(&$sandbox = NULL) {
// Delete the existing KML export action config entities.
$configs = Action::loadMultiple(['asset_kml_action', 'log_kml_action']);
foreach ($configs as $config) {
$config->delete();
}
// Install the farm_export_kml module. This will recreate the actions.
if (!\Drupal::service('module_handler')->moduleExists('farm_export_kml')) {
\Drupal::service('module_installer')->install(['farm_export_kml']);
}
}

View File

@ -6,14 +6,11 @@ dependencies:
- system.menu.admin
module:
- asset
- csv_serialization
- entity_browser
- farm_location
- farm_ui_views
- image
- options
- rest
- serialization
- state_machine
- user
enforced:
@ -1155,61 +1152,6 @@ display:
- url.query_args
- user.permissions
tags: { }
csv:
id: csv
display_title: 'CSV export (rest)'
display_plugin: rest_export
position: 7
display_options:
pager:
type: none
options:
offset: 0
style:
type: serializer
options:
uses_fields: false
formats:
csv: csv
row:
type: data_field
options:
field_options:
asset_bulk_form:
alias: ''
raw_output: false
image_target_id:
alias: ''
raw_output: false
id:
alias: ''
raw_output: false
name:
alias: ''
raw_output: false
type:
alias: ''
raw_output: false
flag_value:
alias: ''
raw_output: false
status:
alias: ''
raw_output: false
display_description: ''
display_extenders: { }
path: assets.csv
auth:
- cookie
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- request_format
- url
- user.permissions
tags: { }
entity_browser:
id: entity_browser
display_title: 'Entity browser'
@ -1693,17 +1635,6 @@ display:
plugin_id: result
empty: false
content: 'Displaying @start - @end of @total'
display_link:
id: display_link
table: views
field: display_link
relationship: none
group_type: group
admin_label: ''
plugin_id: display_link
label: 'Export CSV'
empty: false
display_id: csv
display_extenders:
collapsible_filter:
collapsible: true
@ -2118,17 +2049,6 @@ display:
plugin_id: result
empty: false
content: 'Displaying @start - @end of @total'
display_link:
id: display_link
table: views
field: display_link
relationship: none
group_type: group
admin_label: ''
plugin_id: display_link
label: 'Export CSV'
empty: false
display_id: csv
display_extenders:
collapsible_filter:
collapsible: true

View File

@ -5,13 +5,10 @@ dependencies:
- system.menu.admin
- taxonomy.vocabulary.log_category
module:
- csv_serialization
- entity_reference_revisions
- farm_ui_views
- log
- options
- rest
- serialization
- state_machine
- taxonomy
- user
@ -2299,71 +2296,6 @@ display:
- url.query_args
- user.permissions
tags: { }
csv:
id: csv
display_title: 'CSV export (rest)'
display_plugin: rest_export
position: 7
display_options:
pager:
type: none
options:
offset: 0
style:
type: serializer
options:
uses_fields: false
formats:
csv: csv
row:
type: data_field
options:
field_options:
log_bulk_form:
alias: ''
raw_output: false
status:
alias: ''
raw_output: false
id:
alias: ''
raw_output: false
timestamp:
alias: ''
raw_output: false
name:
alias: ''
raw_output: false
type:
alias: ''
raw_output: false
asset_target_id:
alias: ''
raw_output: false
flag_value:
alias: ''
raw_output: false
category_target_id:
alias: ''
raw_output: false
owner_target_id:
alias: ''
raw_output: false
display_description: ''
display_extenders: { }
path: logs.csv
auth:
- cookie
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- request_format
- url
- user
- user.permissions
tags: { }
page:
id: page
display_title: 'All logs (page)'
@ -2384,17 +2316,6 @@ display:
plugin_id: result
empty: false
content: 'Displaying @start - @end of @total'
display_link:
id: display_link
table: views
field: display_link
relationship: none
group_type: group
admin_label: ''
plugin_id: display_link
label: 'Export CSV'
empty: false
display_id: csv
display_extenders:
collapsible_filter:
collapsible: true
@ -2702,17 +2623,6 @@ display:
plugin_id: result
empty: false
content: 'Displaying @start - @end of @total'
display_link:
id: display_link
table: views
field: display_link
relationship: none
group_type: group
admin_label: ''
plugin_id: display_link
label: 'Export CSV'
empty: false
display_id: csv
display_extenders:
collapsible_filter:
collapsible: true

View File

@ -21,3 +21,12 @@ function farm_ui_views_post_update_enable_collapsible_filter(&$sandbox = NULL) {
$views_settings->set('display_extenders', $display_extenders)->save();
}
/**
* Install the farmOS CSV Export module.
*/
function farm_ui_views_post_update_install_farm_export_csv(&$sandbox) {
if (!\Drupal::service('module_handler')->moduleExists('farm_export_csv')) {
\Drupal::service('module_installer')->install(['farm_export_csv']);
}
}

View File

@ -50,51 +50,18 @@ class FarmUiViewsTest extends FarmBrowserTestBase {
$this->assertSession()->pageTextContains($equipment->label());
$this->assertSession()->pageTextContains($water->label());
// Check that an "Export CSV" link appears on /assets.
$this->assertSession()->pageTextContains('Export CSV');
// Check that the "Export CSV" link includes exposed filters.
$this->drupalGet('/assets', ['query' => ['status' => 'active']]);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkByHrefExists('/assets.csv?status=active');
// Check that both assets are visible in /assets.csv.
$this->drupalGet('/assets.csv');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($equipment->label());
$this->assertSession()->pageTextContains($water->label());
// Check that only water assets are visible in /assets/water.
$this->drupalGet('/assets/water');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextNotContains($equipment->label());
$this->assertSession()->pageTextContains($water->label());
// Check that an "Export CSV" link appears on /assets/water, and it
// automatically filters by asset type.
$this->assertSession()->pageTextContains('Export CSV');
$this->assertSession()->linkByHrefExists('/assets.csv?type%5B0%5D=water');
// Check that only water assets are visible in /assets.csv?type[]=water.
$this->drupalGet('/assets.csv', ['query' => ['type' => ['water']]]);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextNotContains($equipment->label());
$this->assertSession()->pageTextContains($water->label());
// Check that /assets/equipment includes equipment-specific columns.
$this->drupalGet('/assets/equipment');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('Manufacturer');
$this->assertSession()->pageTextContains('Model');
$this->assertSession()->pageTextContains('Serial number');
// Check that /assets.csv?type[]=equipment includes equipment-specific
// columns.
$this->drupalGet('/assets.csv', ['query' => ['type' => ['equipment']]]);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('Manufacturer');
$this->assertSession()->pageTextContains('Model');
$this->assertSession()->pageTextContains('Serial number');
}
}