Skip to content

Commit

Permalink
Add "Blueprint" option when configuring entry/term imports (#64)
Browse files Browse the repository at this point in the history
* Add "Blueprint" dropdown when configuring imports

* Hide mapping table after changing the blueprint.

* The mapping table should use the configured blueprint.

* Created entries/terms should use the configured blueprint.

* Make it a little more user friendly.

* Make everything work for existing imports.

* The blueprint should be required.

* Instructions.

* wip

* Add `blueprint` key in tests

* Add tests.

* Clear the Blink cache.

* Fix styling

* wip

* Make it searchable. It makes the UI a little nicer.

---------

Co-authored-by: duncanmcclean <[email protected]>
  • Loading branch information
duncanmcclean and duncanmcclean authored Dec 19, 2024
1 parent fdea314 commit 97da8e8
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 24 deletions.
1 change: 1 addition & 0 deletions lang/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
'utility_description' => 'Import entries, taxonomies, and users from XML and CSV files.',

'configuration_instructions' => 'You can add or modify your Blueprint fields to customize what data is imported and what fieldtype it will be stored in. You can save, refresh, and come back to this import config later until it\'s ready to run.',
'destination_blueprint_instructions' => 'Select which blueprint should be used for imported content.',
'destination_collection_instructions' => 'Select the collection to import entries into.',
'destination_site_instructions' => 'Which site should the entries be imported into?',
'destination_taxonomy_instructions' => 'Select the taxonomy to import terms into.',
Expand Down
63 changes: 63 additions & 0 deletions resources/js/components/Fieldtypes/BlueprintFieldtype.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<template>
<v-select
searchable
:options="options"
:get-option-label="(option) => option.title"
:get-option-key="(option) => option.handle"
:value="value"
:reduce="opt => opt.handle"
@input="update($event)"
/>
</template>

<script>
export default {
mixins: [Fieldtype],
inject: ['storeName'],
mounted() {
if (! this.value && this.type && (this.collection || this.taxonomy)) {
this.$emit('input', this.options[0].handle);
}
},
computed: {
type() {
return this.$store.state.publish[this.storeName].values.destination.type;
},
collection() {
return this.$store.state.publish[this.storeName].values.destination.collection[0];
},
taxonomy() {
return this.$store.state.publish[this.storeName].values.destination.taxonomy[0];
},
options() {
if (this.type === 'entries') {
return this.meta.collectionBlueprints[this.collection];
}
if (this.type === 'terms') {
return this.meta.taxonomyBlueprints[this.taxonomy];
}
},
},
watch: {
type() {
this.$emit('input', null);
},
collection() {
this.$emit('input', this.options[0].handle);
},
taxonomy() {
this.$emit('input', this.options[0].handle);
},
}
}
</script>
2 changes: 2 additions & 0 deletions resources/js/cp.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import CreateImportForm from "./components/CreateImportForm.vue";
import EditImportForm from "./components/EditImportForm.vue";
import ImportsListing from "./components/ImportsListing.vue";
import BlueprintFieldtype from "./components/Fieldtypes/BlueprintFieldtype.vue";
import ImportMappingsFieldtype from "./components/Fieldtypes/ImportMappingsFieldtype.vue";

Statamic.$components.register('create-import-form', CreateImportForm);
Statamic.$components.register('edit-import-form', EditImportForm);
Statamic.$components.register('imports-listing', ImportsListing);
Statamic.$components.register('blueprint-fieldtype', BlueprintFieldtype);
Statamic.$components.register('import_mappings-fieldtype', ImportMappingsFieldtype);
22 changes: 22 additions & 0 deletions src/Fieldtypes/BlueprintFieldtype.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Statamic\Importer\Fieldtypes;

use Statamic\Facades\Collection;
use Statamic\Facades\Taxonomy;
use Statamic\Fields\Fieldtype;

class BlueprintFieldtype extends Fieldtype
{
public function preload()
{
return [
'collectionBlueprints' => Collection::all()->mapWithKeys(function ($collection) {
return [$collection->handle() => $collection->entryBlueprints()->values()];
})->all(),
'taxonomyBlueprints' => Taxonomy::all()->mapWithKeys(function ($taxonomy) {
return [$taxonomy->handle() => $taxonomy->termBlueprints()->values()];
})->all(),
];
}
}
15 changes: 15 additions & 0 deletions src/Imports/Blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,17 @@ function (string $attribute, mixed $value, Closure $fail) use ($import) {
'validate' => 'required_if:destination.type,terms',
],
],
[
'handle' => 'blueprint',
'field' => [
'type' => 'blueprint',
'display' => __('Blueprint'),
'instructions' => __('importer::messages.destination_blueprint_instructions'),
'width' => 50,
'unless' => ['destination.type' => 'users'],
'validate' => 'required_unless:destination.type,users',
],
],
Site::hasMultiple() ? [
'handle' => 'site',
'field' => [
Expand Down Expand Up @@ -255,6 +266,10 @@ private static function buildFieldConditions(Import $import): array
$conditions['destination.taxonomy'] = 'contains '.$import->get('destination.taxonomy');
}

if ($import->get('destination.blueprint')) {
$conditions['destination.blueprint'] = 'equals '.$import->get('destination.blueprint');
}

if ($import->get('destination.site')) {
$conditions['destination.site'] = 'contains '.$import->get('destination.site');
}
Expand Down
16 changes: 14 additions & 2 deletions src/Imports/Import.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,20 @@ public function blueprint(): StatamicBlueprint
public function destinationBlueprint(): StatamicBlueprint
{
return match ($this->get('destination.type')) {
'entries' => Collection::find($this->get('destination.collection'))->entryBlueprint(),
'terms' => Taxonomy::find($this->get('destination.taxonomy'))->termBlueprint(),
'entries' => Collection::find($this->get('destination.collection'))
->entryBlueprints()
->when(
$this->get('destination.blueprint'),
fn ($collection) => $collection->filter(fn ($blueprint) => $blueprint->handle() === $this->get('destination.blueprint'))
)
->first(),
'terms' => Taxonomy::find($this->get('destination.taxonomy'))
->termBlueprints()
->when(
$this->get('destination.blueprint'),
fn ($taxonomy) => $taxonomy->filter(fn ($blueprint) => $blueprint->handle() === $this->get('destination.blueprint'))
)
->first(),
'users' => User::blueprint(),
};
}
Expand Down
9 changes: 7 additions & 2 deletions src/Jobs/ImportItemJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ protected function findOrCreateEntry(array $data): void
return;
}

$entry = Entry::make()->collection($collection)->locale($site);
$entry = Entry::make()
->collection($collection)
->blueprint($this->import->get('destination.blueprint'))
->locale($site);
}

if ($entry->id() && ! in_array('update', $this->import->get('strategy'))) {
Expand Down Expand Up @@ -126,7 +129,9 @@ protected function findOrCreateTerm(array $data): void
return;
}

$term = Term::make()->taxonomy($this->import->get('destination.taxonomy'));
$term = Term::make()
->taxonomy($this->import->get('destination.taxonomy'))
->blueprint($this->import->get('destination.blueprint'));
}

if (Term::find($term->id()) && ! in_array('update', $this->import->get('strategy'))) {
Expand Down
5 changes: 5 additions & 0 deletions tests/Imports/StoreImportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function it_stores_a_collection_import()
'destination' => [
'type' => 'entries',
'collection' => ['posts'],
'blueprint' => 'post',
],
'strategy' => ['create', 'update'],
])
Expand Down Expand Up @@ -75,6 +76,7 @@ public function it_stores_a_collection_import_with_a_site()
'destination' => [
'type' => 'entries',
'collection' => ['posts'],
'blueprint' => 'post',
'site' => ['en'],
],
'strategy' => ['create', 'update'],
Expand Down Expand Up @@ -113,6 +115,7 @@ public function it_cant_store_a_collection_import_when_the_collection_is_not_ava
'destination' => [
'type' => 'entries',
'collection' => ['posts'],
'blueprint' => 'post',
'site' => ['fr'],
],
'strategy' => ['create', 'update'],
Expand Down Expand Up @@ -143,6 +146,7 @@ public function it_cant_store_a_collection_import_without_a_site_when_multisite_
'destination' => [
'type' => 'entries',
'collection' => ['posts'],
'blueprint' => 'post',
],
'strategy' => ['create', 'update'],
])
Expand All @@ -167,6 +171,7 @@ public function it_stores_a_taxonomy_import()
'destination' => [
'type' => 'terms',
'taxonomy' => ['categories'],
'blueprint' => 'post',
],
'strategy' => ['create', 'update'],
])
Expand Down
18 changes: 9 additions & 9 deletions tests/Imports/UpdateImportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function can_update_an_import()
->patch("/cp/utilities/importer/{$this->import->id()}", [
'name' => 'Old Posts',
'file' => ['posts.csv'],
'destination' => ['type' => 'entries', 'collection' => ['posts']],
'destination' => ['type' => 'entries', 'collection' => ['posts'], 'blueprint' => 'post'],
'strategy' => ['create', 'update'],
'source' => ['csv_delimiter' => ','],
'mappings' => [
Expand Down Expand Up @@ -95,7 +95,7 @@ public function can_replace_the_file()
->patch("/cp/utilities/importer/{$this->import->id()}", [
'name' => 'Posts',
'file' => ['123456789/latest-posts.csv'],
'destination' => ['type' => 'entries', 'collection' => ['posts']],
'destination' => ['type' => 'entries', 'collection' => ['posts'], 'blueprint' => 'post'],
'strategy' => ['create', 'update'],
'source' => ['csv_delimiter' => ','],
'mappings' => [
Expand Down Expand Up @@ -124,7 +124,7 @@ public function validation_error_is_thrown_when_file_does_not_exist()
->patch("/cp/utilities/importer/{$this->import->id()}", [
'name' => 'Posts',
'file' => ['123456789/latest-posts.pdf'],
'destination' => ['type' => 'entries', 'collection' => ['posts']],
'destination' => ['type' => 'entries', 'collection' => ['posts'], 'blueprint' => 'post'],
'strategy' => ['create', 'update'],
'mappings' => [
'title' => ['key' => 'Title'],
Expand Down Expand Up @@ -155,7 +155,7 @@ public function validation_error_is_thrown_when_file_mime_type_is_not_allowed()
->patch("/cp/utilities/importer/{$this->import->id()}", [
'name' => 'Posts',
'file' => ['123456789/latest-posts.pdf'],
'destination' => ['type' => 'entries', 'collection' => ['posts']],
'destination' => ['type' => 'entries', 'collection' => ['posts'], 'blueprint' => 'post'],
'strategy' => ['create', 'update'],
'mappings' => [
'title' => ['key' => 'Title'],
Expand Down Expand Up @@ -183,7 +183,7 @@ public function validation_error_is_thrown_without_an_import_strategy()
->patch("/cp/utilities/importer/{$this->import->id()}", [
'name' => 'Posts',
'file' => ['posts.csv'],
'destination' => ['type' => 'entries', 'collection' => ['posts']],
'destination' => ['type' => 'entries', 'collection' => ['posts'], 'blueprint' => 'post'],
'strategy' => [],
'mappings' => [
'title' => ['key' => 'Title'],
Expand All @@ -209,7 +209,7 @@ public function validation_error_is_thrown_without_any_mappings()
->patch("/cp/utilities/importer/{$this->import->id()}", [
'name' => 'Posts',
'file' => ['posts.csv'],
'destination' => ['type' => 'entries', 'collection' => ['posts']],
'destination' => ['type' => 'entries', 'collection' => ['posts'], 'blueprint' => 'post'],
'strategy' => ['create', 'update'],
'mappings' => [
'title' => ['key' => null],
Expand All @@ -231,7 +231,7 @@ public function throws_validation_errors_for_mapping_fields()
->patch("/cp/utilities/importer/{$this->import->id()}", [
'name' => 'Posts',
'file' => ['posts.csv'],
'destination' => ['type' => 'entries', 'collection' => ['posts']],
'destination' => ['type' => 'entries', 'collection' => ['posts'], 'blueprint' => 'post'],
'strategy' => ['create', 'update'],
'mappings' => [
'author' => ['key' => 'Author Email', 'related_field' => null],
Expand All @@ -249,7 +249,7 @@ public function validation_error_is_thrown_without_unique_field()
->patch("/cp/utilities/importer/{$this->import->id()}", [
'name' => 'Posts',
'file' => ['posts.csv'],
'destination' => ['type' => 'entries', 'collection' => ['posts']],
'destination' => ['type' => 'entries', 'collection' => ['posts'], 'blueprint' => 'post'],
'strategy' => ['create', 'update'],
'mappings' => [
'title' => ['key' => 'Title'],
Expand All @@ -271,7 +271,7 @@ public function validation_error_is_thrown_when_no_mapping_is_configured_for_uni
->patch("/cp/utilities/importer/{$this->import->id()}", [
'name' => 'Posts',
'file' => ['posts.csv'],
'destination' => ['type' => 'entries', 'collection' => ['posts']],
'destination' => ['type' => 'entries', 'collection' => ['posts'], 'blueprint' => 'post'],
'strategy' => ['create', 'update'],
'mappings' => [
'title' => ['key' => 'Title'],
Expand Down
Loading

0 comments on commit 97da8e8

Please sign in to comment.