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
| Comment | File | Size | Author |
|---|---|---|---|
| #39 | interdiff.2911243.37-39.txt | 4.16 KB | mikelutz |
| #39 | 2911243-39.paragraphs.Create-field-collections-field-plugin.patch | 56.32 KB | mikelutz |
Comments
Comment #2
mikelutzHere 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. :-)
Comment #3
mikelutzAlright, 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.
Comment #4
mikelutzThis 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.
Comment #5
heddnLet's submit this for tests. And a review is up next.
Comment #6
heddnThis 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.
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'.
Can we create a plugin dedicated for paragraphs and another for field collections? Breaking these out makes more sense to me.
Nit: double periods.
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.
same applies here. prefix with paragraph_.
Comment #7
heddnAnd 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.
Comment #8
heddnWe 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.
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.
Comment #9
heddnI 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.fieldand 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_alterfor 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.Comment #10
mikelutzI'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.
Comment #11
mikelutzComment #12
mikelutzIt seems D8.5 requires new annotations on the Field Plugins, so this adds them for tests.
Comment #13
mikelutzThis 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!
Comment #15
heddnThe review patch in #13 didn't seem to have any tests? But things are looking good here.
Nit: Comment style should be two slashes and less than 80 characters per line.
Do we need to do an isset/empty check here?
Let's throw an \InvalidArgumentException instead. The migration is fine, just the arguments passed to it are a little broken.
Instead of calling this ProcessOnValue, we typically use the plugin id. "paragraphs_process_on_value"
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.
Comment #16
mikelutz@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.
Comment #17
mikelutzThis 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.
Comment #18
mikelutzThis 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.
Comment #20
mikelutzI'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.
Comment #21
mikelutzOkay, 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.
Comment #23
mikelutzgrr
Comment #24
mikelutzgrr again.. I knew I didn't need the @group annotation in my base test class..
Comment #26
mikelutzRebase against latest -dev
Comment #27
mikelutzComment #28
heddnWow, a real pleasure to review. Really only some very small nits. This is really close.
Nit: extra parenthesis isn't needed.
Should we use the class constant so as to explain more clearly what this magic number means? FieldCollectionFieldInstanceSettings::FIELD_COLLECTION_PREFIX_LENGTH
I don't think this is needed any more.
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.
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.
can we put a colon: after the key to distinguish it more clearly?
I think we want the other way around? The configuration set to the plugin should override the default config.
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.
Can we give this test data an array key title/description?
Can we give this scenario an array key title/description?
Comment #29
mikelutzOn #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.
Comment #30
mikelutzFixing a typo..
Comment #31
heddnFor #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.
Comment #32
mikelutzI'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.
Comment #33
heddnI 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.
This should be === me thinks to account for 0 and ''.
Do we want to check for null, empty and zero?
This logic here could serve some comments. I'm not clear from looking at it what we are achieving.
For my manual testing:
php ./db-tools.php import --database d7_settings.php_db_key ../../modules/paragraphs/tests/fixtures/drupal7.php.drush si -y minimal.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_rangedrush migrate-upgrade --legacy-db-key=d7_settings.php_db_key --legacy-root=http://drupal.localhost --configure-onlydrush cex -ydrush mim -y --allThe resulting config for field:
And for field instance:
And the errors:
Comment #34
mikelutzOkay, 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)
Comment #35
heddnI'm not a fan of this. Tagging with embedded meaning like this is fragile. Can we do this another way?
Comment #36
mikelutzI'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.
Comment #37
mikelutzFixing my broken tests
Comment #38
colanHow 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.
Comment #39
mikelutzSo, 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.
Comment #40
heddnThis has gone through a ton of testing. I think we are ready for prime time now. Onward and upward.
Comment #42
miro_dietikerYippie, awesome stuff :-) Committed.