Added possibility to export Parts from part tables

This commit is contained in:
Jan Böhmer 2023-03-12 00:27:04 +01:00
parent 3b36b2a4dc
commit 49944cda87
9 changed files with 161 additions and 8 deletions

View file

@ -107,6 +107,13 @@ export default class extends DatatablesController {
//Hide the select element (the tomselect button is the sibling of the select element)
select_target.nextElementSibling.classList.add('d-none');
}
//If the selected option has a data-turbo attribute, set it to the form
if (selected_option.dataset.turbo) {
this.element.dataset.turbo = selected_option.dataset.turbo;
} else {
this.element.dataset.turbo = true;
}
}
confirmDeletionAtSubmit(event) {

View file

@ -0,0 +1,63 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Controller;
use App\Services\ImportExportSystem\EntityExporter;
use App\Services\Parts\PartsTableActionHandler;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class PartImportExportController extends AbstractController
{
private EntityManagerInterface $entityManager;
private PartsTableActionHandler $partsTableActionHandler;
public function __construct(EntityManagerInterface $entityManager, PartsTableActionHandler $partsTableActionHandler)
{
$this->entityManager = $entityManager;
$this->partsTableActionHandler = $partsTableActionHandler;
}
/**
* @Route("/parts/export", name="parts_export", methods={"GET"})
* @return Response
*/
public function exportParts(Request $request, EntityExporter $entityExporter): Response
{
$ids = $request->query->get('ids', '');
$parts = $this->partsTableActionHandler->idStringToArray($ids);
if (empty($parts)) {
throw new \RuntimeException('No parts found!');
}
//Ensure that we have access to the parts
foreach ($parts as $part) {
$this->denyAccessUnlessGranted('read', $part);
}
return $entityExporter->exportEntityFromRequest($parts, $request);
}
}

View file

@ -95,6 +95,25 @@ class SelectAPIController extends AbstractController
return $this->getResponseForClass(Project::class, false);
}
/**
* @Route("/export_level", name="select_export_level")
*/
public function exportLevel(): Response
{
$entries = [
1 => $this->translator->trans('export.level.simple'),
2 => $this->translator->trans('export.level.extended'),
3 => $this->translator->trans('export.level.full'),
];
return $this->json(array_map(function ($key, $value) {
return [
'text' => $value,
'value' => $key,
];
}, array_keys($entries), $entries));
}
/**
* @Route("/label_profiles", name="select_label_profiles")
* @return Response

View file

@ -52,7 +52,7 @@ class EntityExporter
$resolver->setDefault('format', 'csv');
$resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml']);
$resolver->setDefault('csv_delimiter', ',');
$resolver->setDefault('csv_delimiter', ';');
$resolver->setAllowedTypes('csv_delimiter', 'string');
$resolver->setDefault('level', 'extended');

View file

@ -102,6 +102,34 @@ final class PartsTableActionHandler
);
}
//When action starts with "export_" we have to redirect to the export controller
$matches = [];
if (preg_match('/^export_(json|yaml|xml|csv)$/', $action, $matches)) {
$ids = implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts));
switch ($target_id) {
case 1:
default:
$level = 'simple';
break;
case 2:
$level = 'extended';
break;
case 3:
$level = 'full';
break;
}
return new RedirectResponse(
$this->urlGenerator->generate('parts_export', [
'format' => $matches[1],
'level' => $level,
'ids' => $ids,
'_redirect' => $redirect_url
])
);
}
//Iterate over the parts and apply the action to it:
foreach ($selected_parts as $part) {

View file

@ -63,6 +63,12 @@
<optgroup label="{% trans %}part_list.action.action.delete{% endtrans %}">
<option {% if not is_granted('@parts.delete') %}disabled{% endif %} value="delete">{% trans %}part_list.action.action.delete{% endtrans %}</option>
</optgroup>
<optgroup label="{% trans %}part_list.action.action.export{% endtrans %}">
<option {% if not is_granted('@parts.read') %}disabled{% endif %} value="export_json" data-url="{{ path('select_export_level')}}" data-turbo="false">{% trans %}part_list.action.export_json{% endtrans %}</option>
<option {% if not is_granted('@parts.read') %}disabled{% endif %} value="export_csv" data-url="{{ path('select_export_level')}}" data-turbo="false">{% trans %}part_list.action.export_csv{% endtrans %}</option>
<option {% if not is_granted('@parts.read') %}disabled{% endif %} value="export_yaml" data-url="{{ path('select_export_level')}}" data-turbo="false">{% trans %}part_list.action.export_yaml{% endtrans %}</option>
<option {% if not is_granted('@parts.read') %}disabled{% endif %} value="export_xml" data-url="{{ path('select_export_level')}}" data-turbo="false">{% trans %}part_list.action.export_xml{% endtrans %}</option>
</optgroup>
</select>
<select class="form-select d-none" data-controller="elements--structural-entity-select" name="target" {{ stimulus_target('elements/datatables/parts', 'selectTargetPicker') }}>

View file

@ -10970,34 +10970,64 @@ Element 3</target>
</segment>
</unit>
<unit id="Qt585vm" name="attachment.max_file_size">
<segment state="translated">
<segment>
<source>attachment.max_file_size</source>
<target>Maximum file size</target>
</segment>
</unit>
<unit id="tkkbiag" name="user.saml_user">
<segment state="translated">
<segment>
<source>user.saml_user</source>
<target>SSO / SAML user</target>
</segment>
</unit>
<unit id="fhepjKr" name="user.saml_user.pw_change_hint">
<segment state="translated">
<segment>
<source>user.saml_user.pw_change_hint</source>
<target>Your user uses single sign-on (SSO). You can not change the password and 2FA settings here. Configure them on your central SSO provider instead!</target>
</segment>
</unit>
<unit id="32beTBH" name="login.sso_saml_login">
<segment state="translated">
<segment>
<source>login.sso_saml_login</source>
<target>Single Sign-On Login (SSO)</target>
</segment>
</unit>
<unit id="wnMLanX" name="login.local_login_hint">
<segment state="translated">
<segment>
<source>login.local_login_hint</source>
<target>The form below is only for log in with a local user. If you want to log in via single sign-on, press the button above.</target>
</segment>
</unit>
<unit id="fa76Qc9" name="part_list.action.action.export">
<segment>
<source>part_list.action.action.export</source>
<target>Export parts</target>
</segment>
</unit>
<unit id="OfOI7tn" name="part_list.action.export_json">
<segment>
<source>part_list.action.export_json</source>
<target>Export to JSON</target>
</segment>
</unit>
<unit id="8Y5uz7l" name="part_list.action.export_csv">
<segment>
<source>part_list.action.export_csv</source>
<target>Export to CSV</target>
</segment>
</unit>
<unit id="gtllBTO" name="part_list.action.export_yaml">
<segment>
<source>part_list.action.export_yaml</source>
<target>Export to YAML</target>
</segment>
</unit>
<unit id="IW9wGBS" name="part_list.action.export_xml">
<segment>
<source>part_list.action.export_xml</source>
<target>Export to XML</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -8,7 +8,7 @@
</segment>
</unit>
<unit id="Dpb9AmY" name="saml.error.cannot_login_local_user_per_saml">
<segment state="translated">
<segment>
<source>saml.error.cannot_login_local_user_per_saml</source>
<target>You can not login as local user via SSO! Use your local user password instead.</target>
</segment>

View file

@ -300,7 +300,7 @@
</segment>
</unit>
<unit id="m8kMFhf" name="validator.attachment.name_not_blank">
<segment state="translated">
<segment>
<source>validator.attachment.name_not_blank</source>
<target>Set a value here, or upload a file to automatically use its filename as name for the attachment.</target>
</segment>