mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-20 17:15:51 +02:00
Added possibility to create nested structures of elements using Mass Import
This commit is contained in:
parent
22950f2476
commit
07f95bc6ea
7 changed files with 126 additions and 12 deletions
|
@ -364,7 +364,7 @@ abstract class BaseAdminController extends AbstractController
|
|||
|
||||
//Create entries based on input
|
||||
$errors = [];
|
||||
$results = $importer->massCreation($data['lines'], $this->entity_class, $data['parent'], $errors);
|
||||
$results = $importer->massCreation($data['lines'], $this->entity_class, $data['parent'] ?? null, $errors);
|
||||
|
||||
//Show errors to user:
|
||||
foreach ($errors as $error) {
|
||||
|
|
|
@ -148,9 +148,20 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
|||
return false;
|
||||
}
|
||||
|
||||
//If this' parents element, is $another_element, then we are finished
|
||||
return ($this->parent->getID() === $another_element->getID())
|
||||
|| $this->parent->isChildOf($another_element); //Otherwise, check recursively
|
||||
//If the parent element is equal to the element we want to compare, return true
|
||||
if ($this->getParent()->getID() === null || $this->getParent()->getID() === null) {
|
||||
//If the IDs are not yet defined, we have to compare the objects itself
|
||||
if ($this->getParent() === $another_element) {
|
||||
return true;
|
||||
}
|
||||
} else { //If the IDs are defined, we can compare the IDs
|
||||
if ($this->getParent()->getID() === $another_element->getID()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Otherwise, check recursively
|
||||
return $this->parent->isChildOf($another_element);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -83,7 +83,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
* @ORM\Column(type="string", name="config_theme", nullable=true)
|
||||
* @Assert\Choice(choices=User::AVAILABLE_THEMES)
|
||||
*/
|
||||
protected ?string $theme = '';
|
||||
protected ?string $theme = null;
|
||||
|
||||
/**
|
||||
* @var string|null the hash of a token the user must provide when he wants to reset his password
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Services\ImportExportSystem;
|
||||
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use function count;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
@ -61,7 +62,7 @@ class EntityImporter
|
|||
//Expand every line to a single entry:
|
||||
$names = explode("\n", $lines);
|
||||
|
||||
if (!is_a($class_name, AbstractStructuralDBElement::class, true)) {
|
||||
if (!is_a($class_name, AbstractNamedDBElement::class, true)) {
|
||||
throw new InvalidArgumentException('$class_name must be a StructuralDBElement type!');
|
||||
}
|
||||
if (null !== $parent && !is_a($parent, $class_name)) {
|
||||
|
@ -71,7 +72,31 @@ class EntityImporter
|
|||
$errors = [];
|
||||
$valid_entities = [];
|
||||
|
||||
$current_parent = $parent;
|
||||
$last_element = $parent;
|
||||
//We use this array to store all levels of indentation as a stack.
|
||||
$indentations = [0];
|
||||
|
||||
foreach ($names as $name) {
|
||||
//Count intendation level (whitespace characters at the beginning of the line)
|
||||
$identSize = strlen($name)-strlen(ltrim($name));
|
||||
|
||||
//If the line is intendet more than the last line, we have a new parent element
|
||||
if ($identSize > end($indentations)) {
|
||||
$current_parent = $last_element;
|
||||
//Add the new indentation level to the stack
|
||||
$indentations[] = $identSize;
|
||||
}
|
||||
while ($identSize < end($indentations)) {
|
||||
//If the line is intendet less than the last line, we have to go up in the tree
|
||||
if ($current_parent instanceof AbstractStructuralDBElement) {
|
||||
$current_parent = $current_parent->getParent();
|
||||
} else {
|
||||
$current_parent = null;
|
||||
}
|
||||
array_pop($indentations);
|
||||
}
|
||||
|
||||
$name = trim($name);
|
||||
if ('' === $name) {
|
||||
//Skip empty lines (StrucuralDBElements must have a name)
|
||||
|
@ -81,7 +106,10 @@ class EntityImporter
|
|||
//Create new element with given name
|
||||
$entity = new $class_name();
|
||||
$entity->setName($name);
|
||||
$entity->setParent($parent);
|
||||
//Only set the parent if the entity is a StructuralDBElement
|
||||
if ($entity instanceof AbstractStructuralDBElement) {
|
||||
$entity->setParent($current_parent);
|
||||
}
|
||||
|
||||
//Validate entity
|
||||
$tmp = $this->validator->validate($entity);
|
||||
|
@ -94,6 +122,8 @@ class EntityImporter
|
|||
'violations' => $tmp,
|
||||
];
|
||||
}
|
||||
|
||||
$last_element = $entity;
|
||||
}
|
||||
|
||||
return $valid_entities;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
<div class="col-sm-4">
|
||||
<turbo-frame id="admin-tree-frame" target="admin-content-frame" data-turbo-action="advance">
|
||||
{{ tree.treeview(entity) }}
|
||||
{{ tree.treeview(entity) }}
|
||||
</turbo-frame>
|
||||
</div>
|
||||
|
||||
|
@ -176,7 +176,9 @@
|
|||
</div>
|
||||
|
||||
<div id="mass_creation" class="tab-pane fade">
|
||||
<span class="text-muted">{% trans %}mass_creation.help{% endtrans %}</span>
|
||||
<div class="row mb-2">
|
||||
<span class="text-muted offset-sm-3">{% trans %}mass_creation.help{% endtrans %}</span>
|
||||
</div>
|
||||
{{ form(mass_creation_form) }}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||
namespace App\Tests\Services\ImportExportSystem;
|
||||
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Services\Formatters\AmountFormatter;
|
||||
use App\Services\ImportExportSystem\EntityImporter;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
@ -54,7 +55,7 @@ class EntityImporterTest extends WebTestCase
|
|||
$this->assertEmpty($errors);
|
||||
|
||||
$errors = [];
|
||||
$lines = "Test 1 \n Test 2 \n Test 3";
|
||||
$lines = "Test 1\nTest 2 \nTest 3";
|
||||
$results = $this->service->massCreation($lines, AttachmentType::class, null, $errors);
|
||||
$this->assertCount(0, $errors);
|
||||
$this->assertCount(3, $results);
|
||||
|
@ -72,11 +73,78 @@ class EntityImporterTest extends WebTestCase
|
|||
$this->assertSame($parent, $results[0]->getParent());
|
||||
}
|
||||
|
||||
public function testNonStructuralClass(): void
|
||||
{
|
||||
$input = <<<EOT
|
||||
Test1
|
||||
Test1.1
|
||||
Test2
|
||||
EOT;
|
||||
|
||||
$errors = [];
|
||||
$results = $this->service->massCreation($input, User::class, null, $errors);
|
||||
|
||||
//Import must not fail, even with non-structural classes
|
||||
$this->assertCount(3, $results);
|
||||
$this->assertCount(0, $errors);
|
||||
|
||||
$this->assertSame('Test1', $results[0]->getName());
|
||||
$this->assertSame('Test1.1', $results[1]->getName());
|
||||
$this->assertSame('Test2', $results[2]->getName());
|
||||
|
||||
}
|
||||
|
||||
public function testMassCreationNested(): void
|
||||
{
|
||||
$input = <<<EOT
|
||||
Test 1
|
||||
Test 1.1
|
||||
Test 1.1.1
|
||||
Test 1.1.2
|
||||
Test 1.2
|
||||
Test 1.2.1
|
||||
Test 2
|
||||
EOT;
|
||||
|
||||
$errors = [];
|
||||
$parent = new AttachmentType();
|
||||
$results = $this->service->massCreation($input, AttachmentType::class, $parent, $errors);
|
||||
|
||||
//We have 7 elements, an now errros
|
||||
$this->assertCount(0, $errors);
|
||||
$this->assertCount(7, $results);
|
||||
|
||||
$element1 = $results[0];
|
||||
$element11 = $results[1];
|
||||
$element111 = $results[2];
|
||||
$element112 = $results[3];
|
||||
$element12 = $results[4];
|
||||
$element121 = $results[5];
|
||||
$element2 = $results[6];
|
||||
|
||||
$this->assertSame('Test 1', $element1->getName());
|
||||
$this->assertSame('Test 1.1', $element11->getName());
|
||||
$this->assertSame('Test 1.1.1', $element111->getName());
|
||||
$this->assertSame('Test 1.1.2', $element112->getName());
|
||||
$this->assertSame('Test 1.2', $element12->getName());
|
||||
$this->assertSame('Test 1.2.1', $element121->getName());
|
||||
$this->assertSame('Test 2', $element2->getName());
|
||||
|
||||
//Check parents
|
||||
$this->assertSame($parent, $element1->getParent());
|
||||
$this->assertSame($element1, $element11->getParent());
|
||||
$this->assertSame($element11, $element111->getParent());
|
||||
$this->assertSame($element11, $element112->getParent());
|
||||
$this->assertSame($element1, $element12->getParent());
|
||||
$this->assertSame($element12, $element121->getParent());
|
||||
$this->assertSame($parent, $element2->getParent());
|
||||
}
|
||||
|
||||
public function testMassCreationErrors(): void
|
||||
{
|
||||
$errors = [];
|
||||
//Node 1 and Node 2 are created in datafixtures, so their attemp to create them again must fail.
|
||||
$lines = "Test 1 \n Node 1 \n Node 2";
|
||||
$lines = "Test 1\nNode 1\nNode 2";
|
||||
$results = $this->service->massCreation($lines, AttachmentType::class, null, $errors);
|
||||
$this->assertCount(1, $results);
|
||||
$this->assertSame('Test 1', $results[0]->getName());
|
||||
|
|
|
@ -358,7 +358,7 @@
|
|||
</notes>
|
||||
<segment>
|
||||
<source>mass_creation.help</source>
|
||||
<target>Each line will be interpreted as a name of a element, which will be created.</target>
|
||||
<target>Each line will be interpreted as a name of a element, which will be created. You can create nested structures by indentations.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="a5.CFfq" name="edit.caption">
|
||||
|
@ -7255,6 +7255,9 @@ Exampletown</target>
|
|||
<segment>
|
||||
<source>mass_creation.lines.placeholder</source>
|
||||
<target>Element 1
|
||||
Element 1.1
|
||||
Element 1.1.1
|
||||
Element 1.2
|
||||
Element 2
|
||||
Element 3</target>
|
||||
</segment>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue