Problem/Motivation

We need a method to upgrade/migrate from D7 field collections to D8 paragraphs.

Proposed resolution

Remaining tasks

a field collections field plugin, look at entity reference

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#39 interdiff.2911243.37-39.txt4.16 KBmikelutz
#39 2911243-39.paragraphs.Create-field-collections-field-plugin.patch56.32 KBmikelutz
#37 interdiff.2911243.34-37.txt1.71 KBmikelutz
#37 2911243-37.paragraphs.Create-field-collections-field-plugin.patch56.64 KBmikelutz
#34 interdiff.2911243.30-34.txt20.39 KBmikelutz
#34 2911243-34.paragraphs.Create-field-collections-field-plugin.patch56.59 KBmikelutz
#30 2911243-30.paragraphs.Create-field-collections-field-plugin.patch52.61 KBmikelutz
#30 interdiff.2911243.29-30.txt537 bytesmikelutz
#29 interdiff.2911243.26-29.txt17.59 KBmikelutz
#29 2911243-29.paragraphs.Create-field-collections-field-plugin.patch52.61 KBmikelutz
#26 2911243-26.paragraphs.Create-field-collections-field-plugin.patch49.86 KBmikelutz
#24 interdiff.2911243.23-24.txt566 bytesmikelutz
#24 2911243-24.paragraphs.Create-field-collections-field-plugin.patch49.94 KBmikelutz
#23 interdiff.2911243.21-23.txt455 bytesmikelutz
#23 2911243-23.paragraphs.Create-field-collections-field-plugin.patch49.96 KBmikelutz
#21 interdiff.2911243.20-21.txt30.24 KBmikelutz
#21 2911243-21.paragraphs.Create-field-collections-field-plugin.patch49.93 KBmikelutz
#20 2911243-20.paragraphs.Create-field-collections-field-plugin.patch37.1 KBmikelutz
#18 interdiff.2911243.17-18.txt10.7 KBmikelutz
#18 2911243-18.paragraphs.Create-field-collections-field-plugin.patch2.61 MBmikelutz
#17 interdiff.2911243.13-17.txt6.96 KBmikelutz
#17 2911243-17.paragraphs.Create-field-collections-field-plugin(no fixture).txt36.73 KBmikelutz
#17 2911243-17.paragraphs.Create-field-collections-field-plugin.patch2.61 MBmikelutz
#2 2911243-2.paragraphs.Create-field-collection-field-plugin.patch3.8 KBmikelutz
#2 2911243-2.paragraphs.Create-field-collection-field-plugin.tests_.txt3.35 KBmikelutz
#3 2911243-3.paragraphs.Create-field-collections-field-plugin.patch4.6 KBmikelutz
#3 interdiff.2911243.2-3.txt5.35 KBmikelutz
#4 2911243-4.paragraphs.Create-field-collections-field-plugin.patch14.92 KBmikelutz
#4 interdiff.2911243.3-4.txt12.75 KBmikelutz
#10 2911243-10.paragraphs.Create-field-collections-field-plugin.patch2.64 MBmikelutz
#10 2911243-10.paragraphs.Create-field-collections-field-plugin-no-fixture.txt26.57 KBmikelutz
#10 interdiff.2911243.4-10.txt2.64 MBmikelutz
#10 interdiff.2911243.4-10-no-fixture.txt32.24 KBmikelutz
#12 2911243-12.paragraphs.Create-field-collections-field-plugin.patch2.64 MBmikelutz
#12 interdiff.2911243.10-12.txt1.04 KBmikelutz
#13 interdiff.2911243.12-13(no-tests).txt7 KBmikelutz
#13 2911243-13.paragraphs.Create-field-collections-field-plugin(notests).diff23.44 KBmikelutz
#13 interdiff.2911243.12-13.txt1.69 MBmikelutz
#13 2911243-13.paragraphs.Create-field-collections-field-plugin.patch2.6 MBmikelutz

Comments

heddn created an issue. See original summary.

mikelutz’s picture

Here is the beginning of the field and process plugins. The patch correctly creates the entity_reference_revisions field storage for paragraph and field collection fields, as well as creating the proper field storage for fields attached to the field collection or paragraph entity.

The test file attached needs to be applied on top of the fixture and test class from https://www.drupal.org/project/paragraphs/issues/2911241#comment-12463990 (patchfile)

Next up: Field Instance Migrations. :-)

mikelutz’s picture

Alright, I'm learning some of this as I go. I didn't need custom processors, I was able to do what I needed with static maps. This patch has the start of the field instance work in it, but I need to add some more work to fine tune the field settings. I'll wrap that up tomorrow, make sure the form and view modes come in okay, and then work on the deriver and content migrations.

mikelutz’s picture

StatusFileSize
new14.92 KB
new12.75 KB

This update finishes the field instance migration. This patch is dependant on the latest patch from 2911241 (as field instances require the field_collection and paragraphs type source plugins ) I haven't finished the tests for this section yet, I'm getting to the point where it's getting tough to separate out the various child threads of this meta, as all the patches are interdependent. Tests require the fixture from the other thread to run anyway, so I can't even queue them up here. I'm tempted to post complete combined patches in the main issue queue, but I don't want to keep including the 2.5 mg fixture file each patch. Speaking of the fixture, this patch also handles things that aren't in it, namely some edge cases with paragraphs and field collections combined, and allowed paragraph bundle settings in d7 (the fixture only has one paragraph type and field)

This puts me back to wanting to redo the fixture in another issue queue, but unless it's merged into core, I can't queue d.o. automated tests against it anyway.

Anyway, next I need to take care of widget and formatter settings, and then set up the driver and content migrations, and then we should be done.

heddn’s picture

Status: Active » Needs review

Let's submit this for tests. And a review is up next.

heddn’s picture

Status: Needs review » Needs work
  1. +++ b/paragraphs.module
    @@ -436,3 +436,11 @@ function paragraphs_library_info_alter(&$libraries, $extension) {
    +  $migrations['d7_field_instance']['migration_dependancies']['optional'][] = 'd7_field_collection_type';
    +  $migrations['d7_field_instance']['migration_dependancies']['optional'][] = 'd7_paragraph_type';
    

    This makes sense. It would make even more sense after #2904765: Alter derived migration definition early in life lands. So let's add an TODO to revisit after that is committed.

  2. +++ b/src/Plugin/migrate/field/Paragraphs.php
    @@ -0,0 +1,104 @@
    +          'prefix' => 'field_',
    ...
    +        'fc_prefix' => 'field_',
    
    +++ b/src/Plugin/migrate/process/ParagraphsFieldInstanceSettings.php
    @@ -0,0 +1,186 @@
    +    $prefix = $this->configuration['fc_prefix'];
    

    We called this 'fc_prefix' and 'prefix' Let's make it consistent. It is already 'prefix' in #2911241: Migrate field collections types as paragraph types, so unless we want to rename it everywhere and add in a BC shim, let's stick with 'prefix'.

  3. +++ b/src/Plugin/migrate/process/ParagraphsFieldInstanceSettings.php
    @@ -0,0 +1,186 @@
    +class ParagraphsFieldInstanceSettings extends ProcessPluginBase implements ContainerFactoryPluginInterface {
    

    Can we create a plugin dedicated for paragraphs and another for field collections? Breaking these out makes more sense to me.

  4. +++ b/src/Plugin/migrate/process/ParagraphsFieldInstanceSettings.php
    @@ -0,0 +1,186 @@
    +        // included..
    

    Nit: double periods.

  5. +++ b/src/Plugin/migrate/process/ProcessOnValue.php
    @@ -0,0 +1,82 @@
    + *   id = "process_on_value"
    

    The namespace for process plugins is flat. Perhaps we should prefix the name here with the name of the module so as to avoid any possible conflicts? And let's keep this as super simple as needed. So remove any of the not_equals logic, etc.

  6. +++ b/src/Plugin/migrate/process/StripPrefix.php
    @@ -0,0 +1,34 @@
    +      id = "strip_prefix"
    

    same applies here. prefix with paragraph_.

heddn’s picture

And let's make this even easier on ourselves and break the pargraph field stuff into another issue. So this is all about field collections and the other about paragraphs.

heddn’s picture

  1. +++ b/src/Plugin/migrate/process/ParagraphsFieldInstanceSettings.php
    @@ -0,0 +1,186 @@
    +        // None selected therefore allow all in d7, but maybe we have
    +        // additional types from a field collection migration that shouldn't be
    +        // included..
    

    We can only assume so much. And only migrate what is in the source. If the source says allow all bundles, let's copy over that configuration. Folks can always change this around, post migration.

  2. +++ b/src/Plugin/migrate/process/ParagraphsFieldInstanceSettings.php
    @@ -0,0 +1,186 @@
    +          // Yes, new bundles.  Don't allow the new ones to be selected
    +          // in this instance.
    

    Let's keep it simple. It might make sense to put some comments that we are keeping things simple. And give some suggestions on how that could easily be changed by someone if they didn't like the default. If there isn't an easy way to change the default, folks can still make config changes post migration. They'll already have to do that for views, etc. So this is handled with good docs. Not some technical mind reading exercise.

heddn’s picture

+++ b/src/Plugin/migrate/field/Paragraphs.php
@@ -0,0 +1,104 @@
+    // I don't believe we can register a plugin in the process system based on
+    // the entity type of the field instance, which would be a more appropiate
+    // place to put the process below.  By putting it here, there could be an
+    // edge case issue where there is a paragraph bundle on a d7 installation
+    // but no field referencing it, and therefore this plugin wouldn't register
+    // and the field storage for the fields on the bundle would not map to the
+    // paragraph entity properly.  I don't know what the point of that would
+    // be, but there may be a better place to put this.

I think, if I understand the problem correctly that we need to pend this part of the changes until we write the migration deriver for the paragraph or field collection. See D6NodeDeriver as an example. It calls plugin.manager.migrate.field and builds all the fields for the specific migration.

We do however, need to alter the FieldInstance and Field source plugins for field collections to now point to paragraphs. See d7_commerce_field in commerce_migrate as an example. And commerce_migrate_commerce_migration_plugins_alter for how we register the replacement source field plugins. That example would get 10x easier once #2904765: Alter derived migration definition early in life. If we can bump that along to RTBC... that would make things easier here. There's a reason it is marked contrib project blocker.

mikelutz’s picture

I've addressed everything in #9 I separated out the field and process plugins for field collections and paragraphs. I'm opposed to splitting the development work into separate issues, however as they do share common tests, and alters in the module file.

As far as #8 goes, while I understand what you are saying, I tend to disagree. The logic implemented takes only a few lines of code, and exists to maintain the configuration and functionality of the D7 site. If the D7 site used only paragraphs and not field collections, then configuration is brought over exactly. In the case where we are moving both field collections and paragraphs (god knows why, but let's assume a long running site with some legacy field collections that switched to paragraphs for some new content types) we have to do some work to merge them together sanely. Namely, configuring the field instance that came from field collections to only allow that bundle (to replicate the functionality from field collections), and making sure that paragraphs fields can't use the new paragraph types from field collections (because they couldn't before either)

Yes, we make an assumption either way that some people will want to override, but for a few lines of code, we preserve the fields the way they worked in D7 as closely as possible, and if someone wants to deviate from that, they are welcome to.

And, just to be a pain, I've changed the fixture file. I had a big issue with the comments module not liking a node type with a long machine name. (comment_node_paragraphs_migration_test being > 32 characters). This caused my tests to fail so I made one with a modified machine name, and an extra paragraph bundle to test more of the field instance settings migration logic. The actual patched fixture file is smaller (I did some cleanup, clearing cache, and dumping accesslog table) but of course the patch is huge.. :-( I included diffs without the fixture if people would prefer to scan them for the code changes.

mikelutz’s picture

Status: Needs work » Needs review
mikelutz’s picture

It seems D8.5 requires new annotations on the Field Plugins, so this adds them for tests.

mikelutz’s picture

This adds support to migrate field widgets and formatters. After this patch, the configuration for content types/paragraph types and all associated fields and displays should be migrated. There's another batch of changes to the fixture to have some variations in widget options and formatter options for testing purposes, so I've again included code-only diffs for ease of review. I'm moving on to the deriver and base migrations for the content now, and then hopefully this will be done!

heddn’s picture

Status: Needs review » Needs work

The review patch in #13 didn't seem to have any tests? But things are looking good here.

  1. +++ b/paragraphs.module
    @@ -436,3 +441,101 @@ function paragraphs_library_info_alter(&$libraries, $extension) {
    +  /* @todo refactor/rethink this when https://www.drupal.org/project/drupal/issues/2904765 is resolved */
    

    Nit: Comment style should be two slashes and less than 80 characters per line.

  2. +++ b/paragraphs.module
    @@ -436,3 +441,101 @@ function paragraphs_library_info_alter(&$libraries, $extension) {
    +  $bundle_process = $migration['process']['bundle'];
    ...
    +  $entity_type_process = $migration['process'][$destination];
    

    Do we need to do an isset/empty check here?

  3. +++ b/src/Plugin/migrate/process/ParagraphsProcessOnValue.php
    @@ -0,0 +1,65 @@
    +      throw new MigrateException("source_value not set for ProcessOnValue plugin");
    ...
    +      throw new MigrateException("expected_value not set for ProcessOnValue plugin");
    ...
    +      throw new MigrateException("process not set or invalid for ProcessOnValue plugin");
    

    Let's throw an \InvalidArgumentException instead. The migration is fine, just the arguments passed to it are a little broken.

  4. +++ b/src/Plugin/migrate/process/ParagraphsProcessOnValue.php
    @@ -0,0 +1,65 @@
    +      throw new MigrateSkipRowException('source_value is not valid for ProcessOnValue plugin');
    

    Instead of calling this ProcessOnValue, we typically use the plugin id. "paragraphs_process_on_value"

  5. +++ b/src/Plugin/migrate/process/ParagraphsStripPrefix.php
    @@ -0,0 +1,35 @@
    + *   id = "paragraphs_strip_prefix"
    

    I think I suggested namespacing this with the module. However, what if we called it by the name of the previous module? field_collection_strip_prefix? That would make more semantic sense to me.

mikelutz’s picture

@heddn, IIRC the tests for #13 are bundled in the big patch. The new tests are dependent on the fixture patch included with that one, and would fail otherwise, so I didn't include them.

mikelutz’s picture

This addresses the feedback, with the exception of item #5. I don't think the effort to refactor that plugin is worth it at this point, It is in use in all the patches I'm posting in all the issues surrounding this meta issue, and nothing in the plugin is field_collection specific, it's a generic use plugin, that I only happen to use in processing the field collections.

Additionally, fixes some typos in the migration alter function, and adds the values processor to the field plugins to be used in conjunction with the other patches I'm about to post.

mikelutz’s picture

This is a small update to fix a type in the field formatter processor for field collections, and remove one of the custom processors that can be replaced with a simple extract. It also updates and cleans the tests to run more efficiently.

Status: Needs review » Needs work

The last submitted patch, 18: 2911243-18.paragraphs.Create-field-collections-field-plugin.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mikelutz’s picture

I'm leaving this as needs work for now, I have a bit more feedback to incorporate, and I would like to go back over it, knowing what I know now. This is the patch from before rebased against the new fixture that was committed, and it includes a little bit of refactoring. No interdiff patch because the old one was huge, this is a fresh start on this issue.

mikelutz’s picture

Status: Needs work » Needs review
StatusFileSize
new49.93 KB
new30.24 KB

Okay, I'm passing this off for review. This patch pulls in the fields, field instances, widgets, formatters, and view modes needed to create the new paragraphs. I've added unit tests for each of the process plugins created, and tweaked some of the logic slightly. I also removed the strip prefix plugin in favor of a simple substr plugin, implemented ConfigurablePluginInterface where appropriate and added more documentation.

Status: Needs review » Needs work

The last submitted patch, 21: 2911243-21.paragraphs.Create-field-collections-field-plugin.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mikelutz’s picture

StatusFileSize
new49.96 KB
new455 bytes

grr

mikelutz’s picture

Status: Needs work » Needs review
StatusFileSize
new49.94 KB
new566 bytes

grr again.. I knew I didn't need the @group annotation in my base test class..

Status: Needs review » Needs work
mikelutz’s picture

StatusFileSize
new49.86 KB

Rebase against latest -dev

mikelutz’s picture

Status: Needs work » Needs review
heddn’s picture

Status: Needs review » Needs work

Wow, a real pleasure to review. Really only some very small nits. This is really close.

  1. +++ b/paragraphs.module
    @@ -440,3 +446,107 @@ function paragraphs_library_info_alter(&$libraries, $extension) {
    +      if ((is_a($migration['class'], FieldMigration::class, TRUE))) {
    

    Nit: extra parenthesis isn't needed.

  2. +++ b/paragraphs.module
    @@ -440,3 +446,107 @@ function paragraphs_library_info_alter(&$libraries, $extension) {
    +      'start' => 6,
    

    Should we use the class constant so as to explain more clearly what this magic number means? FieldCollectionFieldInstanceSettings::FIELD_COLLECTION_PREFIX_LENGTH

  3. +++ b/src/Plugin/migrate/field/FieldCollection.php
    @@ -0,0 +1,81 @@
    +        'prefix' => 'field_',
    

    I don't think this is needed any more.

  4. +++ b/src/Plugin/migrate/process/ParagraphsFieldInstanceSettings.php
    @@ -0,0 +1,86 @@
    +          // No bundles are selected and therefore this field was set to allow
    +          // all bundles in d7, but maybe we now have additional bundles
    +          // from a field collection migration or pre-existing that shouldn't be
    +          // included.
    +          if (count($value['allowed_bundles']) == count($bundles)) {
    

    I think I suggested we make this simply map exactly what is in the source. If the source says all, then all it should be. There's a reason for incremental migrations. For folks to modify the resulting site to meet their needs. I don't like guessing. We end up adding complexity and are usually wrong. Unless you feel super strong, let's remove all this logic.

  5. +++ b/src/Plugin/migrate/process/ParagraphsProcessOnValue.php
    @@ -0,0 +1,83 @@
    + * Runs a processor on a value if a source value is equal to an expected value.
    

    Can we say "Runs a migration process on a value..." since we then use the name/term it is called in core. Otherwise it leaves me guessing if we are referring to the same thing. Which I think we are referring to a process plugin.

  6. +++ b/src/Plugin/migrate/process/ParagraphsProcessOnValue.php
    @@ -0,0 +1,83 @@
    + * source_value (required) string. The source property to check against.
    + * expected_value (required) string. The value to check against.  If the
    ...
    + * process (required) array.  The process array to execute if the source proerty
    

    can we put a colon: after the key to distinguish it more clearly?

  7. +++ b/src/Plugin/migrate/process/ProcessPluginBase.php
    @@ -0,0 +1,72 @@
    +    return NestedArray::mergeDeep($this->defaultConfiguration(), $configuration);
    

    I think we want the other way around? The configuration set to the plugin should override the default config.

  8. +++ b/tests/src/Kernel/ParagraphsMigrationTest.php
    @@ -44,12 +66,217 @@ class ParagraphsMigrationTest extends MigrateDrupalTestBase {
    +  protected function executeMigrationDependencies(MigrationInterface $migration) {
    

    This would be nice to add the core MigrateDrupalTestBase. Please open an issue :)

    Actually, this is an problem today in a lot of places in core/contrib and having this in core would be a wonderful addition.

  9. +++ b/tests/src/Unit/migrate/FieldCollectionsFieldInstanceSettingsTest.php
    @@ -0,0 +1,112 @@
    +  public function getData() {
    +    $data = [
    +      [
    

    Can we give this test data an array key title/description?

  10. +++ b/tests/src/Unit/migrate/ParagraphsFieldInstanceSettingsTest.php
    @@ -0,0 +1,103 @@
    +  public function getData() {
    +    $data = [
    +      [
    

    Can we give this scenario an array key title/description?

mikelutz’s picture

Issue summary: View changes
StatusFileSize
new52.61 KB
new17.59 KB

On #2, not only should we use the constant, but we should declare it in one class only and use the same one everywhere.

On #4, I do have strong feelings, but not enough to bikeshed over this edge case. I made my argument in #10 above, and if you still think it's best to enable the new bundles, then I accept it. I've reduced the logic and adjusted the tests.

#7 is correct in the order (later arguments in ::mergeDeep overwrite the earlier ones, opposite of array addition), though the result was supposed to be assigned to the $configuration property, not returned. That might be why it looked strange.

mikelutz’s picture

Status: Needs work » Needs review
StatusFileSize
new537 bytes
new52.61 KB

Fixing a typo..

heddn’s picture

Issue tags: +Needs manual testing

For #28.4, if we want to create a follow-up and add a TODO, that might let us continue the conversation?

I want to do some more manual testing, but the actual code looks pretty solid at this point. Leaving in review status since there's nothing to fix, just needs more testing.

mikelutz’s picture

I'm inclined to let it go until/unless someone with an actual use case raises the issue. It's really only a thing if you have both Field Collections and Paragraphs enabled on a D7 site, and if you have that setup, then you have a mess anyway, and reconfiguring a couple field instances after the migration is probably the least of your problems. Be warned though, If I see someone post "#3189452 Migrating Field Collections and Paragraphs together enables the new field collection bundles on the old paragraph fields" a year from now, expect an 'I told you so'. :-p

I'm good with more manual testing, I'm doing some myself today. noel.delacruz ws running into an issue in the parent thread that suggested somewhere the prefix removal was happening twice, but I haven't been able to replicate the issue. He's using migrate_upgrade though, which I haven't tested this with (my non-test framework tests were all using drupal console's migrate:setup, along with migrate_plus and migrate_tools), but I think I'll give it a shot with migrate_upgrade against one of my bigger d7 projects and see if I can find the issue. I've found at least one issue with migrate_upgrade that will affect the next phase of this migration Migration Lookup source_ids array keys are not properly prefixed when creating migrations. maybe you could weigh in on that one if you have time.

heddn’s picture

Status: Needs review » Needs work

I think we need to make the test in the plugin alter a little less strict. See #2944597: Make the tests for plugins in plugins alter stricter. That might help with some the errors from my manual testing. Also some other findings after applying the patch and running things manually.

  1. +++ b/paragraphs.module
    @@ -8,7 +8,12 @@
    +use Drupal\field\Plugin\migrate\source\d7\FieldInstance;
    
    +++ b/src/Plugin/migrate/process/ParagraphsProcessOnValue.php
    @@ -0,0 +1,84 @@
    +    if ($source_value == $this->configuration['expected_value']) {
    

    This should be === me thinks to account for 0 and ''.

  2. +++ b/src/Plugin/migrate/process/ParagraphsProcessOnValue.php
    @@ -0,0 +1,84 @@
    +    if (is_null($source_value)) {
    

    Do we want to check for null, empty and zero?

  3. +++ b/src/Plugin/migrate/process/ParagraphsProcessOnValue.php
    @@ -0,0 +1,84 @@
    +      if (array_key_exists('plugin', $process)) {
    +        if (empty($process['source'])) {
    +          $process['source'] = $destination_property;
    +        }
    +      }
    +      else {
    +        if (empty($process[0]['source'])) {
    +          $process[0]['source'] = $destination_property;
    +        }
    

    This logic here could serve some comments. I'm not clear from looking at it what we are achieving.

For my manual testing:

  1. I applied the patch to paragraphs.
  2. Installed the d7 fixture provided by paragraphs php ./db-tools.php import --database d7_settings.php_db_key ../../modules/paragraphs/tests/fixtures/drupal7.php.
  3. Installed D8 with minimal profile drush si -y minimal.
  4. Enabled the following modules: drush en -y node paragraphs file image link text telephone block_content comment filter migrate_drupal options path toolbar update node paragraphs file image link text telephone migrate_upgrade migrate_tools taxonomy datetime datetime_range
  5. Ran the migrate_upgrade command to generate all migration configurations drush migrate-upgrade --legacy-db-key=d7_settings.php_db_key --legacy-root=http://drupal.localhost --configure-only
  6. Exported the config so I could review it. drush cex -y
  7. Ran the whole migration drush mim -y --all

The resulting config for field:

uuid: 7dfb51a9-89da-413c-9fe3-9e0f9ccf2b30
langcode: en
status: true
dependencies: {  }
id: upgrade_d7_field
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: processField
cck_plugin_method: null
migration_tags:
  - 'Drupal 7'
  - Configuration
migration_group: migrate_drupal_7
label: 'Field configuration'
source:
  plugin: d7_field
  constants:
    status: true
    langcode: und
process:
  entity_type:
    -
      plugin: get
      source: entity_type
    -
      plugin: static_map
      map:
        field_collection_item: paragraph
        paragraphs_item: paragraph
      bypass: true
  status: constants/status
  langcode: constants/langcode
  field_name: field_name
  type:
    plugin: process_field
    source: type
    method: getFieldType
  cardinality: cardinality
  settings:
    plugin: d7_field_settings
destination:
  plugin: 'entity:field_storage_config'
migration_dependencies:
  required: {  }
  optional: {  }

And for field instance:

uuid: cc3a0eeb-3b55-4201-b00e-54b665ae7d7b
langcode: en
status: true
dependencies: {  }
id: upgrade_d7_field_instance
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: processFieldInstance
cck_plugin_method: null
migration_tags:
  - 'Drupal 7'
  - Configuration
migration_group: migrate_drupal_7
label: 'Field instance configuration'
source:
  plugin: d7_field_instance
  constants:
    status: true
process:
  type:
    plugin: process_field
    source: type
    method: getFieldType
  entity_type:
    -
      plugin: get
      source: entity_type
    -
      plugin: static_map
      map:
        field_collection_item: paragraph
        paragraphs_item: paragraph
      bypass: true
  field_name: field_name
  bundle:
    -
      plugin: static_map
      source: bundle
      bypass: true
      map:
        comment_node_forum: comment_forum
    -
      plugin: paragraphs_process_on_value
      source_value: entity_type
      expected_value: field_collection_item
      process:
        plugin: substr
        start: 6
  label: label
  description: description
  required: required
  status: constants/status
  settings:
    plugin: d7_field_instance_settings
    source:
      - settings
      - widget
      - field_definition
  default_value_function: ''
  default_value:
    plugin: d7_field_instance_defaults
    source:
      - default_value
      - widget
  translatable: translatable
destination:
  plugin: 'entity:field_config'
migration_dependencies:
  required:
    - upgrade_d7_field
  optional:
    - upgrade_d7_node_type
    - upgrade_d7_comment_type
    - upgrade_d7_taxonomy_vocabulary
    - upgrade_d7_field_collection_type
    - upgrade_d7_paragraphs_type

And the errors:

$ drush mim --all
Processed 50 items (46 created, 0 updated, 0 failed, 4 ignored) - done with 'upgrade_d7_field'                       [status]
Processed 7 items (7 created, 0 updated, 0 failed, 0 ignored) - done with 'upgrade_d7_node_type'                     [status]
Processed 7 items (7 created, 0 updated, 0 failed, 0 ignored) - done with 'upgrade_d7_comment_type'                  [status]
Processed 4 items (4 created, 0 updated, 0 failed, 0 ignored) - done with 'upgrade_d7_taxonomy_vocabulary'           [status]
Processed 3 items (3 created, 0 updated, 0 failed, 0 ignored) - done with 'upgrade_d7_field_collection_type'         [status]
Processed 2 items (2 created, 0 updated, 0 failed, 0 ignored) - done with 'upgrade_d7_paragraphs_type'               [status]
Missing bundle entity, entity type paragraphs_type, entity id collection_test.                                       [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Missing bundle entity, entity type paragraphs_type, entity id collection_test.                                       [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
The field_any_paragraph entity reference field (entity_type: node, bundle: paragraphs_test) no longer has any valid  [error]
bundle it can reference. The field is not working correctly anymore and has to be adjusted.
Missing bundle entity, entity type paragraphs_type, entity id _fc_outer.                                             [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Missing bundle entity, entity type paragraphs_type, entity id _fc_inner.                                             [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Processed 74 items (62 created, 0 updated, 4 failed, 8 ignored) - done with 'upgrade_d7_field_instance'              [status]
upgrade_d7_field_instance Migration - 4 failed.                                                                      [error]
Processed 7 items (7 created, 0 updated, 0 failed, 0 ignored) - done with                                            [status]
'upgrade_d7_comment_entity_form_display_subject'
Processed 9 items (9 created, 0 updated, 0 failed, 0 ignored) - done with 'upgrade_d7_view_modes'                    [status]
Missing bundle entity, entity type paragraphs_type, entity id collection_test.                                       [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Missing bundle entity, entity type paragraphs_type, entity id collection_test.                                       [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Missing bundle entity, entity type paragraphs_type, entity id _fc_outer.                                             [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Missing bundle entity, entity type paragraphs_type, entity id _fc_inner.                                             [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Processed 86 items (69 created, 0 updated, 4 failed, 13 ignored) - done with 'upgrade_d7_field_formatter_settings'   [status]
upgrade_d7_field_formatter_settings Migration - 4 failed.                                                            [error]
Missing bundle entity, entity type paragraphs_type, entity id collection_test.                                       [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Missing bundle entity, entity type paragraphs_type, entity id collection_test.                                       [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Missing bundle entity, entity type paragraphs_type, entity id _fc_outer.                                             [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Missing bundle entity, entity type paragraphs_type, entity id _fc_inner.                                             [error]
(/home/lucas/websites/drupal/core/lib/Drupal/Core/Entity/EntityType.php:910)
Processed 74 items (62 created, 0 updated, 4 failed, 8 ignored) - done with                                          [status]
'upgrade_d7_field_instance_widget_settings'
mikelutz’s picture

Status: Needs work » Needs review
StatusFileSize
new56.59 KB
new20.39 KB

Okay, it turns out that using migrate_upgrade and migrate_plus causes the alter hook to run twice on each migration, causing the added process plugins to be doubled up. This caused all the errors with the instance, widgets, and formatters. I've used migration tags to ensure our alters are only run one time. I also moved and refactored the tests slightly, just so we aren't installing a ton of modules for the easier tests that don't require so many.

I also went through the ==s, issets, empties, and is_nulls in the process on value and adjusted them, and they should be right now. the source_value configuration key must not be empty, the actual retrieved value must not be null, but can be 0 or '', expected value must be set and not null, but it also can now be empty, and the === will properly check for 0, false, and '' (not that we are using that plugin with any of those edge cases, but nice to have them buttoned up)

heddn’s picture

Status: Needs review » Needs work
+++ b/paragraphs.module
@@ -454,35 +454,40 @@ function paragraphs_library_info_alter(&$libraries, $extension) {
+    if (empty($migration['migration_tags']) || !in_array('paragraphs-processed', $migration['migration_tags'])) {
...
+      $migration['migration_tags'][] = 'paragraphs-processed';

I'm not a fan of this. Tagging with embedded meaning like this is fragile. Can we do this another way?

mikelutz’s picture

I'm not a fan of it either, it's a workaround for the way migrate_plus double runs the alter. Ideally I'd like to guarantee the alter hook would only be run once per migration, but at the moment I can't guarantee that, so I have to account for it. I'm open to other options on how to store that state, but this seems the most effective way to ensure we don't double process the migrations. If you are just using core migrate_drupal_ui migration then the whole thing is run once and it doesn't matter, but I agree it is fragile. The whole concept of migration alters is fragile. If you look at commerce_migrate, they straight up overwrite these process arrays assuming that they are the only module that might need to alter a field migration. If you run paragraphs and commerce_migrate migration together , we set our tag the first go around, but on the second, commerce blows our changes away again but leaves our tag, and we don't make the alterations again assuming we are good.

The only other thing I can think of to do is try to scan the process array for our specific modifications to see if they have already been added. While I'm sure I can do that in a way that passes tests and works, I'm not sure I can anticipate every edge case where another module may have processed it further, and we still end up being fragile, and less readable.

I'm very much open to suggestions here, but I don't want to leave this pending forever if we have code that will work fo 99% of use cases. Ultimately we are fighting other modules for this alter, and there's no structured way to share.

mikelutz’s picture

Fixing my broken tests

colan’s picture

How about if we do it this way for now, and then handle #35 in a follow-up once we've settled on a more general solution to this problem? And by "we" I mean the Migrate community as a whole, as we're not just dealing with Paragraphs here.

mikelutz’s picture

Status: Needs work » Needs review
StatusFileSize
new56.32 KB
new4.16 KB

So, after talking with @heddn on slack, I figured out a much less fragile solution. I've keyed all the process plugins that I'm adding to the process arrays in the alter. Now if the alter runs multiple times, we just overwrite the same process instead of pushing a new one to the array. I've ran through several tests, both automated and real world and it seems to work just fine.

heddn’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs manual testing

This has gone through a ton of testing. I think we are ready for prime time now. Onward and upward.

miro_dietiker’s picture

Status: Reviewed & tested by the community » Fixed

Yippie, awesome stuff :-) Committed.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.