Skip to content
Snippets Groups Projects
Commit 7b1d0794 authored by Mark Jones's avatar Mark Jones
Browse files

Issue #3487200 by justanothermark: Export/Import evaluations

parent 54cd257d
No related branches found
No related tags found
1 merge request!7Issue #3487200: Added export/import options and allowed assistant field to be NULL in Evaluations.
......@@ -2,10 +2,22 @@ entity.ai_evaluation.settings:
title: 'AI Evaluation'
description: 'Configure an AI Evaluation entity type.'
route_name: entity.ai_evaluation.settings
parent: system.admin_structure
parent: ai.admin_settings
entity.ai_evaluation.collection:
title: 'AI Evaluations'
description: 'List of AI evaluations.'
route_name: entity.ai_evaluation.collection
parent: system.admin_content
parent: entity.ai_evaluation.settings
entity.ai_evaluation.export:
title: 'Export'
description: 'Export AI evaluations.'
route_name: ai_evaluations.ai_assistant.evaluations.export
parent: entity.ai_evaluation.settings
entity.ai_evaluation.import:
title: 'Import'
description: 'Import AI evaluations.'
route_name: ai_evaluations.ai_assistant.evaluations.import
parent: entity.ai_evaluation.settings
......@@ -5,5 +5,15 @@ entity.ai_evaluation.settings:
entity.ai_evaluation.collection:
title: 'AI Evaluations'
route_name: entity.ai_evaluation.collection
base_route: system.admin_content
weight: 10
base_route: entity.ai_evaluation.settings
weight: -15
entity.ai_evaluation.export:
title: 'Export'
route_name: ai_evaluations.ai_assistant.evaluations.export
base_route: entity.ai_evaluation.settings
weight: -10
entity.ai_evaluation.import:
title: 'Import'
route_name: ai_evaluations.ai_assistant.evaluations.import
base_route: entity.ai_evaluation.settings
weight: -5
......@@ -2,6 +2,7 @@
use Drupal\ai_assistant_api\AiAssistantInterface;
use Drupal\ai_evaluations\Entity\AIEvaluation;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Link;
......@@ -138,21 +139,16 @@ function ai_evaluations_entity_view(array &$build, EntityInterface $entity, Enti
return;
}
// @Todo Move to field formatter or similar.
$build['agents'] = [
'#type' => 'container',
'#weight' => 100,
'#cache' => ['max-age' => '-1'],
];
$agent_results = [];
$result_storage = \Drupal::entityTypeManager()->getStorage('ai_agent_result');
$agent_query = $result_storage->getQuery();
$agent_query->accessCheck(FALSE);
$agent_query->condition('ai_evaluation', $entity->id());
$agent_query->sort('id', 'ASC');
if ($result = $agent_query->execute()) {
$agent_results = $result_storage->loadMultiple($result);
if (\Drupal::entityTypeManager()->getDefinition('ai_agent_result', FALSE)) {
$result_storage = \Drupal::entityTypeManager()->getStorage('ai_agent_result');
$agent_query = $result_storage->getQuery();
$agent_query->accessCheck(FALSE);
$agent_query->condition('ai_evaluation', $entity->id());
$agent_query->sort('id', 'ASC');
if ($result = $agent_query->execute()) {
$agent_results = $result_storage->loadMultiple($result);
}
}
$build['messages'] = [
......@@ -199,9 +195,9 @@ function ai_evaluations_entity_view(array &$build, EntityInterface $entity, Enti
'#markup' => '<h3>' . new TranslatableMarkup('Message') . '</h3><pre>' . $message['message'] . '</pre>',
],
'time' => [
'#markup' => '<h3>' . new TranslatableMarkup('Time') . '</h3><pre>' . $message['timestamp'] . '</pre>',
'#markup' => '<h3>' . new TranslatableMarkup('Time') . '</h3><pre>' . (new DrupalDateTime())->setTimestamp($message['timestamp'])->format('d/m/Y H:i:s') . '</pre>',
],
'agent' => $message_agents ?? [],
'agent' => $message_agents,
];
$build['evaluation'] = [
......
entity.ai_evaluation.settings:
path: 'admin/structure/ai-evaluation'
path: 'admin/config/ai/evaluation'
defaults:
_form: '\Drupal\ai_evaluations\Form\AIEvaluationSettingsForm'
_title: 'AI Evaluation'
......@@ -13,6 +13,17 @@ ai_evaluations.vote:
requirements:
_access: 'TRUE'
ai_evaluations.evaluations:
path: 'admin/config/ai/evaluation/evaluations'
defaults:
_controller: '\Drupal\ai_evaluations\Controller\Evaluations::assistantEvaluations'
requirements:
_permission: 'administer ai_evaluation'
options:
parameters:
ai_assistant:
type: 'entity:ai_assistant'
ai_evaluations.ai_assistant.evaluations:
path: 'admin/config/ai/ai-assistant/{ai_assistant}/evaluation'
defaults:
......@@ -23,3 +34,30 @@ ai_evaluations.ai_assistant.evaluations:
parameters:
ai_assistant:
type: 'entity:ai_assistant'
ai_evaluations.ai_assistant.evaluation:
path: '/admin/config/ai/ai-assistant/{ai_assistant}/evaluation/{ai_evaluation}'
defaults:
_controller: '\Drupal\ai_evaluations\Controller\Evaluations::assistantEvaluation'
requirements:
_permission: 'administer ai_evaluation'
options:
parameters:
ai_assistant:
type: 'entity:ai_assistant'
ai_evaluation:
type: 'entity:ai_evaluation'
ai_evaluations.ai_assistant.evaluations.export:
path: 'admin/config/ai/evaluation/export'
defaults:
_controller: '\Drupal\ai_evaluations\Controller\Evaluations::export'
requirements:
_permission: 'administer ai_evaluation'
ai_evaluations.ai_assistant.evaluations.import:
path: 'admin/config/ai/evaluation/import'
defaults:
_form: '\Drupal\ai_evaluations\Form\EvaluationsImportForm'
requirements:
_permission: 'administer ai_evaluation'
......@@ -3,15 +3,26 @@
namespace Drupal\ai_evaluations\Controller;
use Drupal\ai_assistant_api\AiAssistantInterface;
use Drupal\ai_evaluations\AIEvaluationInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
class Evaluations extends ControllerBase {
public function assistantEvaluations(?AiAssistantInterface $ai_assistant = NULL): array {
if (empty($ai_assistant)) {
return [];
}
/**
* Evaluations per assistant route callback.
*
* @param \Drupal\ai_assistant_api\AiAssistantInterface $ai_assistant
* The assistant to show evaluations for.
*
* @return array
* Renderable array of evaluations for an assistant.
*/
public function assistantEvaluations(AiAssistantInterface $ai_assistant): array {
$output = [];
$output['summary'] = [
......@@ -34,24 +45,78 @@ class Evaluations extends ControllerBase {
$output['view'] = [
'#type' => 'view',
'#name' => 'evaluations',
'#arguments' => [$ai_assistant->id()],
'#arguments' => $ai_assistant ? [$ai_assistant->id()] : NULL,
];
return $output;
}
/**
* Specific Evaluation route callback.
*
* @param AIEvaluationInterface $ai_evaluation
* The Evaluation being displayed.
*
* @return array
* Renderable array of evaluations for an assistant.
*/
public function assistantEvaluation(AIEvaluationInterface $ai_evaluation): array {
return $this->entityTypeManager()->getViewBuilder($ai_evaluation->getEntityTypeId())->view($ai_evaluation);
}
/**
* Specific Evaluation route callback.
*
* @param AIEvaluationInterface $ai_evaluation
* The Evaluation being displayed.
*
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
* Renderable array of evaluations for an assistant or a redirect response.
*/
public function evaluation(AIEvaluationInterface $ai_evaluation): array|RedirectResponse {
if (!$ai_evaluation->get('assistant')->isEmpty()) {
return $this->redirect('ai_evaluations.ai_assistant.evaluation', [
'ai_assistant' => $ai_evaluation->get('assistant')->target_id,
'ai_evaluation' => $ai_evaluation->id(),
]);
}
return $this->assistantEvaluation($ai_evaluation);
}
/**
* Export route handler for CSV export of Evaluations.
*
* @return \Symfony\Component\HttpFoundation\Response
* Response that triggers a file download with Evaluations data.
*/
public function export(): Response {
$evaluation_rows = $this->getEvaluationsRows();
$file_contents = implode("\n", $evaluation_rows);
$response = new Response($file_contents);
$date_string = (new DrupalDateTime())->format('ymd');
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, "evaluations-{$date_string}.csv");
$response->headers->set('Content-type', 'text/csv');
$response->headers->set('Content-Disposition', $disposition);
$response->headers->set('Content-Transfer-Encoding', 'binary');
$response->headers->set('Content-length', strlen($file_contents));
return $response;
}
/**
* Get the number of upvotes for an assistant.
*
* @param \Drupal\ai_assistant_api\AiAssistantInterface $aiAssistant
* @param \Drupal\ai_assistant_api\AiAssistantInterface|NULL $ai_assistant
* The assistant to get upvotes for.
*
* @return int
* The number of upvotes.
*/
protected function countUpvotes(AiAssistantInterface $aiAssistant): int {
protected function countUpvotes(?AiAssistantInterface $ai_assistant): int {
$query = $this->entityTypeManager()->getStorage('ai_evaluation')->getQuery();
if ($ai_assistant) {
$query->condition('assistant', $ai_assistant->id());
}
return $query->accessCheck(FALSE)
->condition('assistant', $aiAssistant->id())
->condition('value', 1)
->count()->execute();
}
......@@ -59,17 +124,58 @@ class Evaluations extends ControllerBase {
/**
* Get the number of downvotes for an assistant.
*
* @param \Drupal\ai_assistant_api\AiAssistantInterface $aiAssistant
* @param \Drupal\ai_assistant_api\AiAssistantInterface|NULL $ai_assistant
* The assistant to get downvotes for.
*
* @return int
* The number of downvotes.
*/
protected function countDownvotes(AiAssistantInterface $aiAssistant): int {
protected function countDownvotes(?AiAssistantInterface $ai_assistant): int {
$query = $this->entityTypeManager()->getStorage('ai_evaluation')->getQuery();
if ($ai_assistant) {
$query->condition('assistant', $ai_assistant->id());
}
return $query->accessCheck(FALSE)
->condition('assistant', $aiAssistant->id())
->condition('value', 0)
->count()->execute();
}
/**
* Get the evaluation rows suitable for export.
*
* @return array
* An array of Evaluations prepared for export.
*/
protected function getEvaluationsRows(): array {
$storage = $this->entityTypeManager()->getStorage('ai_evaluation');
$query = $storage->getQuery();
$query->accessCheck(FALSE);
$ids = $query->execute();
$header = [];
$rows = [];
foreach (array_chunk($ids, 10) as $chunk) {
/** @var \Drupal\ai_evaluations\AIEvaluationInterface $evaluation */
foreach ($storage->loadMultiple($chunk) as $evaluation) {
$row = [];
foreach ($evaluation->getFieldDefinitions() as $field) {
// Skip the ID (but not UUID) as it may already be taken.
if ($field->getName() === 'id') {
continue;
}
// Skip entity reference fields as they won't exist when imported.
if ($field->getType() === 'entity_reference') {
continue;
}
$header[$field->getName()] = $field->getName();
$row[$field->getName()] = base64_encode(serialize($evaluation->get($field->getName())->getValue()));
}
$rows[] = implode(',', $row);
}
}
return [implode(',', $header)] + $rows;
}
}
......@@ -48,10 +48,10 @@ use Drupal\user\EntityOwnerTrait;
* "owner" = "uid",
* },
* links = {
* "collection" = "/admin/content/ai-evaluation",
* "collection" = "/admin/config/ai/ai-evaluation/evaluations",
* "add-form" = "/admin/config/ai/ai-evaluations/add",
* "canonical" = "/admin/config/ai/ai-assistant/{ai_assistant}/evaluation/{ai_evaluation}",
* "edit-form" = "/admin/config/ai/ai-assistant/{ai_assistant}/evaluation/{ai_evaluation}/edit",
* "canonical" = "/admin/config/ai/ai-evaluation/{ai_evaluation}",
* "edit-form" = "/admin/config/ai/ai-evaluation/{ai_evaluation}/edit",
* "delete-form" = "/admin/config/ai/ai-evaluations/{ai_evaluation}/delete",
* "delete-multiple-form" = "/admin/content/ai-evaluation/delete-multiple",
* },
......@@ -63,15 +63,6 @@ final class AIEvaluation extends ContentEntityBase implements AIEvaluationInterf
use EntityChangedTrait;
use EntityOwnerTrait;
/**
* {@inheritdoc}
*/
protected function urlRouteParameters($rel) {
return parent::urlRouteParameters($rel) + [
'ai_assistant' => $this->get('assistant')->target_id,
];
}
/**
* {@inheritdoc}
*/
......
<?php
namespace Drupal\ai_evaluations\Form;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class EvaluationsImportForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ai_evaluations.import';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['file'] = [
'#type' => 'file',
'#title' => $this->t('Import file'),
'#extensions' => ['csv'],
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Import'),
];
return $form;
}
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
$all_files = $this->getRequest()->files->get('files', []);
if (!empty($all_files['file'])) {
$file_upload = $all_files['file'];
if ($file_upload->isValid()) {
$form_state->setValue('file', $file_upload->getRealPath());
return;
}
}
$form_state->setErrorByName('file', $this->t('The file could not be uploaded.'));
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($path = $form_state->getValue('file')) {
$file = fopen($path, 'r');
$header = fgetcsv($file, 0, ',');
while(($row = fgetcsv($file, 0, ','))) {
foreach ($row as $delta => $value) {
if (in_array($header[$delta], ['messages', 'model_config'])) {
$values[$header[$delta]] = (array) json_decode(base64_decode($value));
continue;
}
$values[$header[$delta]] = unserialize(base64_decode($value));
}
try {
\Drupal::entityTypeManager()
->getStorage('ai_evaluation')
->create($values)
->save();
}
catch (EntityStorageException $e) {
// This is probably because the entity with this UUID already exists
// so do nothing and continue.
}
}
}
}
}
......@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Drupal\ai_evaluations\Routing;
use Drupal\ai_evaluations\Controller\Evaluations;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
......@@ -17,9 +18,13 @@ final class AIEvaluationHtmlRouteProvider extends AdminHtmlRouteProvider {
*/
protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
$route = parent::getCanonicalRoute($entity_type);
$defaults = $route->getDefaults();
unset($defaults['_entity_view']);
$defaults['_controller'] = Evaluations::class . '::evaluation';
$route->setDefaults($defaults);
$params = $route->getOption('parameters');
$route->setOption('parameters', $params + [
'ai_assistant' => ['type' => 'entity:' . 'ai_assistant'],
'ai_assistant' => ['type' => 'entity:ai_assistant'],
]);
return $route;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment