Follow-up to #2225775: Migrate Drupal 6 core node translation to Drupal 8

Problem/Motivation

In D8, a node revision has data for all languages. In D6 and D7, each node revision has data for just one language. This means currently D6 -> D8 and D7 -> D8 migrations yield a revision history where each node revision has only one language present, even if previous revisions had translations. This is strange and likely to confuse users.

Priority is given to the D7 migration because the D6 one might be effected by the term node migrations.

This is for migrating core translation revisions, migrating revisions for entity translation is now being done in #3076447: Migrate D7 entity translation revision translations. It is intended that this issue is completed first and then the approach used here is also used for entity translations.

The following notes use the term 'master' which was the original term what is now the 'complete' node migration.

Meeting notes

Things to consider, taken from notes of meeting with alexpott, Gábor Hojtsy, heddn, mikelutz and quiteone

  1. Migration dependencies
    • Possible: easy to do
    • BC concerns: none
    • BC what about contrib? Commerce / metatag
      • Possible incompatible
  2. How do we figure out you need the new complex master migration?
    • Possible: moderate to do
    • BC concerns: none
    • Don’t effect expose new d*_node_master unless explicitly enabled to existing sites
    • This handles incremental, no UI option available to enable
    • For new installs, give an option to upgraders to enable disable the new d*_node_master migration and use the old method.
  3. What about migration lookups on dn_node etc.
    • Possible: easy to do
    • BC concerns: none
  4. What about incremental migrations that have already begun
    • Not allowed. You can only use the new approach on a site that has not run migrations. So we need some of flag where existing installs use the old migrations.
  5. Contrib/Custom
  6. Does it make sense to use d*_node_master instead of d*_node migrations?

Proposed resolution

The current focus is to get the D7 version working and then move those changes to D6.

Using the existing method where the final revision is migrated first doesn't work instead the revisions need to be migrated in order. The first proof of concept patch to show this is in #121, thanks to mikelutz. This new approach is called the Complete node migration and the migrations and files use that name.

The migrate_drupal_ui functional tests use the complete node migration, as this is now the default. The exception is d6/NodeClassicTest which as the name suggests uses the classic node migration.

Do not deprecate the classic node migrations
"Removing it [the classic node migrations] will break the whole ecosystem. Deprecating it means that it’s not used anymore which in and of itself will break the eco system, and for what benefit? To remove it in Drupal 10, a year after D7 is EOL? At that point we are looking at deprecating the entire D7 upgrade path anyway (Probably for removal in 11, not 10) We really don’t want to break the migration ecosystem right now at this critical time when we are trying to get everyone off of D7 in the next 18-20 months". by mikelutz in #migration on Drupal Slack). See #3109819: [PP-1] Deprecate classic node migration plugin in Drupal 9 for removal in Drupal 10 for discussion of when deprecation of classic node migrations would happen.

Run Complete or classic node migrations
However, the Complete approach doesn't integrate with the current migrations, now being called Classic, that is the one either runs the existing node migrations or the new complete one, not both. Choosing between the two methods is done at run time by checking the node migration map tables in MigrationConfigurationTrait() and migrate_drupal_migration_plugins_alter(). In the trait the classic method is selected if any of the classic node migrate_map tables has data (using processedCount) and in the plugins_alter it is selected if any classic node migrate_map exists and there are no complete node migrate_maps.

The selection of node migration method also allows tests to decide which method to use. The test does this by adding either 'node_migrate_classic' or 'node_migrate_complete' to $modules.

Destination IDs
The entity complete destination plugin creates up to three IDs. First is the entity ID key. Then the revision key and the langcode, if they exist.

Altered migrations
There are several migrations that use migration_lookup on the node migration. The processes and dependencies for these are altered when the complete node migration is being run. The changes include new processes to convert the 3 destination IDs returned by complete node to the correct destination ID expected when using the classic migration.
The migrations altered are: d6_term_node, d6_term_node_revision, d6_term_node_translation, d6_comment, d6_url_alias,d7_comment, d7_url_alias, statistics_node_counter, node_translation_menu_links.

Incremental migrations
For the core UI, if map tables for the classic mode migrations exist and at least one has a row then the classic migrations will run and not the complete node migrations. This is determined at run time. Therefor, existing incremental migration should continue to function.

From the original IS
D6 and D7 node revisions do not directly indicate which other translated node revisions they are associated with. But we can attempt to guess this, based on the order of revisions.

Remaining tasks

As of 2020-05-11, the only remaining task is to update the patch for 8.8.x so that classic migrations are the default (set in settings.php).

Fix tests, add comments
Review

For Reviewers

  • All level of code reviews welcome.
  • There are BC issues raised in meeting notes, above

For Testers

  1. Use the lastest patch patch in this issue.
  2. There are instructions in the Issue summary of the META issue #2208401: [META] Remaining multilingual migration paths

From #55

For developers

  1. Make this work with a multilingual source and a non multilingual source
  2. Discuss and document implications for drush, especially drush migrate-upgrade --all
  3. Make sure the d6 and d7 tests includes testing a fields on each revision
  4. Handle the case where the vid of the first revision is higher the vid of a later revision. This is true for both d6 and d7.
  5. Make sure that the use of migration_tag 'translation' is documented. It is used in
    • d6_node_translation.yml
    • d7_node_entity_translation.yml
    • d7_node_translation.yml
    • d7_node_revision_translation.yml
    • d7_user_entity_translation.yml
    • d7_taxonomy_term_entity_translation.yml/li>
    • d7_comment_entity_translation.yml
    • d6_node_revision_translation.yml
  6. Make sure that the source plugin 'translation' property is documented
  7. Make sure that the destination plugin 'translations' property is documented

Release notes snippet

A new node migration, d*_node_complete.yml, referred to as the 'complete node migration', now exists that will migrate all nodes, the node revisions, including translated nodes and the translated node revisions. The complete node migration will eventually replace the existing trio of node migrations, d*_node, d*_node_revision and d*_node_translation, now collectively referred to as the classic node migrations. See https://www.drupal.org/node/3105503 for more information.

CommentFileSizeAuthor
#465 interdiff-2746541-451-463.txt1.73 KBbenjifisher
#463 2746541-463.patch188.59 KBquietone
#463 interdiff-461-463.txt8.83 KBquietone
#461 2746541-461.patch192.06 KBquietone
#461 interdiff-457-461.txt7.98 KBquietone
#457 2746541-457.patch188.1 KBquietone
#457 interdiff-455-457.txt526 bytesquietone
#455 2746541-455.patch188.1 KBquietone
#455 interdiff-445-455.txt513 bytesquietone
#451 interdiff-445-452-8.8.txt4.78 KBquietone
#451 2746541-8.8.x-451.patch188.1 KBquietone
#445 2746541-8.8.x-445.patch186.02 KBquietone
#445 interdiff-428-445-8.8.txt15.96 KBquietone
#442 2746541-440.patch191.15 KBheddn
#441 interdiff_428-440.patch5.43 KBheddn
#440 interdiff_428-440.patch5.43 KBheddn
#440 2746541-440.patch191.15 KBheddn
#428 2746541-8.8.x-428.patch185.71 KBalexpott
#428 423-438-interdiff.txt1 KBalexpott
#423 interdiff-9.0.x-397-423.txt6.86 KBquietone
#423 2746541-9.0.x-423.patch187.04 KBquietone
#423 interdiff-8.9.x-397-423.txt8.63 KBquietone
#423 2746541-8.9.x-423.patch185.21 KBquietone
#423 interdiff-8.8.x-397-423.txt8.63 KBquietone
#423 2746541-8.8.x-423.patch185.71 KBquietone
#421 interdiff-419-421.txt877 bytesquietone
#421 2746541-421.patch185.21 KBquietone
#419 interdiff-417-419.txt1.88 KBquietone
#419 2746541-419.patch184.24 KBquietone
#417 interdiff-397-417.txt6.06 KBquietone
#417 2746541-8.9.x-417.patch182.9 KBquietone
#407 drupal_8_node_field_revision_table.png468.89 KBfirewaller
#406 drupal_7_node_revision_table_2.png235.86 KBfirewaller
#405 drupal_8_node_revision_table.png173.08 KBfirewaller
#405 drupal_7_node_revision_table.png65 bytesfirewaller
#403 drupal_8_revisions.png127.47 KBfirewaller
#403 drupal_7_revisions.png299.51 KBfirewaller
#397 2746541-9.0.x-397.patch187.96 KBalexpott
#397 2746541-8.9.x-397.patch188.08 KBalexpott
#397 2746541-8.8.x-397.patch188.58 KBalexpott
#397 393-397-interdiff.txt4.17 KBalexpott
#393 2746541-8.9.x-393.patch186.38 KBquietone
#393 interdiff-380-393-8.9.txt17.55 KBquietone
#393 2746541-393-8.8.x.patch186.88 KBquietone
#393 interdiff-380-393-8.8.txt17.55 KBquietone
#391 2746541-9.0.x-391.patch186.26 KBalexpott
#391 390-391-interdiff.txt885 bytesalexpott
#390 2746541-9.0.x-390.patch186.26 KBalexpott
#390 387-390-interdiff.txt9.46 KBalexpott
#387 2746541-9.0.x-387.patch190.09 KBalexpott
#387 385-387-interdiff.txt1.04 KBalexpott
#385 2746541-9.0.x-385.patch189.45 KBalexpott
#385 383-385-interdiff.txt1.01 KBalexpott
#383 Screenshot 2020-03-02 at 11.24.12.png175.83 KBalexpott
#383 2746541-9.0.x-383.patch189.46 KBalexpott
#383 382-383-interdiff.txt20.94 KBalexpott
#382 2746541-9.0.x-382.patch186.81 KBalexpott
#382 pseudo-interdiff.txt1.13 KBalexpott
#380 2746541-380-8.9.x.patch186.93 KBquietone
#380 interdiff-377-380-8.9.txt816 bytesquietone
#380 2746541-380-8.8.x.patch187.43 KBquietone
#380 interdiff-377-380-8.8.txt816 bytesquietone
#377 2746541-377-8.9.x.patch186.94 KBquietone
#377 interdiff-373-377.8.9.txt4.46 KBquietone
#377 2746541-377-8.8.x.patch187.44 KBquietone
#377 interdiff-376-377.8.8.txt4.46 KBquietone
#376 2746541-376-8.8.x.patch182.8 KBquietone
#376 interdiff-357-376-8.8.txt44.38 KBquietone
#373 2746541-373.patch182.3 KBquietone
#373 interdiff-370-373.txt3.74 KBquietone
#370 2746541-370.patch181.67 KBquietone
#370 interdiff-364-370.txt1.71 KBquietone
#368 interdiff-364-368.txt2.15 KBquietone
#368 2746541-368-8.9.x.patch180.24 KBquietone
#366 2746541-366-8.9.x.patch182.17 KBquietone
#366 interdiff-364-366.txt459 bytesquietone
#364 interdiff-361-364.txt25.43 KBquietone
#364 2746541-364-8.9.x.patch182.11 KBquietone
#361 2746541-361-8.9.x.patch184.55 KBquietone
#361 interdiff-359-361-8.9.txt852 bytesquietone
#359 2746541-359-8.9.x.patch184.55 KBquietone
#359 interdiff-358-359-8.9.txt13.08 KBquietone
#358 2746541-358-8.9.x.patch185.59 KBquietone
#358 interdiff-357-358-8.9.txt970 bytesquietone
#357 interdiff-355-357-8.9.txt612 bytesquietone
#357 2746541-357-8.9.x.patch185.56 KBquietone
#357 interdiff-352-357-8.8.txt784 bytesquietone
#357 2746541-357-8.8.x.patch185.62 KBquietone
#355 interdiff-352-355-8.9.txt640 bytesquietone
#355 2746541-355-8.9.x.patch185.82 KBquietone
#353 interdiff-350-352-8.9.txt1.67 KBquietone
#353 2746541-352-8.9.x.patch186.1 KBquietone
#353 interdiff-350-352-8.8.txt1.67 KBquietone
#353 2746541-352-8.8.x.patch186.16 KBquietone
#350 interdiff-343-350-8.8.txt17.97 KBquietone
#350 2746541-350-8.8.x.patch186.25 KBquietone
#350 interdiff-346-350-8.9.txt1.91 KBquietone
#350 2746541-350-8.9.x.patch186.19 KBquietone
#347 2746541-346.patch185.67 KBquietone
#347 interdiff-342-346.txt15.86 KBquietone
#343 2746541-342-8.8.x.patch185.19 KBwim leers
#342 interdiff-340-342.txt4.08 KBquietone
#342 2746541-342.patch185.13 KBquietone
#340 interdiff-338-340.txt2.52 KBquietone
#340 2746541-340.patch181.77 KBquietone
#338 2746541-338.patch180.22 KBquietone
#338 interdiff-337-338.txt578 bytesquietone
#337 2746541-337.patch180.31 KBquietone
#337 interdiff-335-337.txt2.64 KBquietone
#335 2746541-335.patch180.3 KBquietone
#335 interdiff-333-335.txt1.94 KBquietone
#333 2746541-333.patch179.12 KBquietone
#333 interdiff-332-333.txt204.4 KBquietone
#332 2746541-332.patch179.66 KBquietone
#332 interdiff-330-332.txt6.68 KBquietone
#330 2746541-330.patch181.22 KBquietone
#330 interdiff-328-330.txt3.19 KBquietone
#328 2746541-328.patch181.26 KBquietone
#328 interdiff-326-328.txt2.31 KBquietone
#326 2746541-326.patch181.11 KBquietone
#326 interdiff-324-326.txt922 bytesquietone
#324 2746541-324.patch181.04 KBquietone
#324 interdiff-323-324.txt720 bytesquietone
#323 2746541-323.patch180.75 KBquietone
#323 interdiff-321-323.txt29.76 KBquietone
#321 2746541-321.patch171.93 KBquietone
#321 interdiff-315-321.txt4.73 KBquietone
#315 2746541-315.patch172.43 KBquietone
#315 interdiff-314-315.txt1.48 KBquietone
#314 2746541-314.patch172.39 KBquietone
#314 interdiff-310-314.txt5.65 KBquietone
#310 2746541-310.patch173.03 KBquietone
#310 interdiff-308-310.txt5.29 KBquietone
#308 interdiff-306-308.txt453 bytesquietone
#308 2746541-308.patch172.87 KBquietone
#306 interdiff-305-306.txt3.28 KBquietone
#306 2746541-306.patch172.87 KBquietone
#305 2746541-305.patch171.55 KBwim leers
#305 interdiff.txt8.43 KBwim leers
#302 2746541-302.patch171.79 KBquietone
#302 interdiff-300-302.txt625 bytesquietone
#300 interdiff-299-300.txt2.41 KBquietone
#300 2746541-300.patch171.79 KBquietone
#299 2746541-299.patch171.46 KBwim leers
#299 interdiff.txt1.82 KBwim leers
#298 2746541-298.patch171.66 KBwim leers
#298 interdiff.txt7.58 KBwim leers
#297 2746541-297.patch171.11 KBquietone
#297 interdiff-287-297.txt15.93 KBquietone
#292 Operation on blocks failed.png83.97 KBMadhura BK
#292 No content items.png71.86 KBMadhura BK
#292 Log messages.png280.19 KBMadhura BK
#292 Upgraded.png70.87 KBMadhura BK
#287 2746541-6-287.patch168.55 KBalexpott
#287 285-287-interdiff.txt1.73 KBalexpott
#286 Patch does not apply.png166.47 KBMadhura BK
#285 2746541-285.patch175.89 KBquietone
#285 interdiff-283-285.txt1.6 KBquietone
#283 2746541-283.patch175.95 KBquietone
#281 pl-after.png66.3 KBquietone
#281 it-after.png49.07 KBquietone
#281 en-after.png55.5 KBquietone
#281 pl-before.png74.13 KBquietone
#281 it-before.png45.65 KBquietone
#281 en-before.png50.19 KBquietone
#278 database-tables.txt14.62 KBquietone
#278 test3-pl-after.png34.53 KBquietone
#278 test3-it-after.png35.37 KBquietone
#278 test3-en-after.png40.03 KBquietone
#278 test3-pl-before.png28.88 KBquietone
#278 test3-it-before.png28.08 KBquietone
#278 test3-en-before.png32.05 KBquietone
#276 2746541-276.patch177 KBquietone
#276 interdiff-275-276.txt9.99 KBquietone
#275 interdiff-271-275.txt3.53 KBquietone
#275 2746541-275.patch174.11 KBquietone
#272 2746541-272-for-testers.patch171.44 KBquietone
#272 interdiff-271-272.txt1 KBquietone
#271 interdiff-269-271.txt21.08 KBquietone
#271 2746541-271.patch171.2 KBquietone
#269 2746541-269.patch174.5 KBquietone
#269 interdiff-267-269.txt1.41 KBquietone
#267 interdiff-263-267.txt1.91 KBquietone
#267 2746541-267.patch175.09 KBquietone
#263 2746541-5-263.patch173 KBalexpott
#263 261-263-interdiff.txt640 bytesalexpott
#261 2746541-261.patch172.73 KBquietone
#261 interdiff-259-261.txt1.37 KBquietone
#259 2746541-259.patch173.28 KBquietone
#259 interdiff-257-259.txt10.76 KBquietone
#257 2746541-257.patch172.18 KBquietone
#257 interdiff-254-257.txt3.41 KBquietone
#254 2746541-254.patch168.71 KBquietone
#254 interdiff-252-254.txt19.13 KBquietone
#252 2746541-252.patch154.8 KBquietone
#252 interdiff-250-252.txt9.05 KBquietone
#250 interdiff-249-250.txt12.21 KBquietone
#250 2746541-250.patch154.87 KBquietone
#249 2746541-249.patch153.33 KBquietone
#249 interdiff-246-249.txt6.7 KBquietone
#246 2746541-246.patch152.61 KBquietone
#246 interdiff-245-246.txt1.93 KBquietone
#245 2746541-4-245.patch151.96 KBalexpott
#245 243-245-interdiff.txt4.38 KBalexpott
#243 2746541-243.patch151.73 KBquietone
#243 interdiff-242-243.txt834 bytesquietone
#242 2746541-242.patch152.8 KBquietone
#242 interdiff-241-242.txt2.15 KBquietone
#241 2746541-3-241.patch152.89 KBalexpott
#241 240-241-interdiff.txt8.49 KBalexpott
#240 2746541-3-240.patch150.36 KBalexpott
#240 239-240-interdiff.txt999 bytesalexpott
#239 2746541-239.patch150.71 KBquietone
#239 interdiff-232-239.txt909 bytesquietone
#233 interdiff-231-232.txt3.02 MBquietone
#233 2746541-232.patch151.6 KBquietone
#231 2746541-231.patch194.43 KBquietone
#231 2746541-231-review-only.txt133.26 KBquietone
#229 2746541-229.patch199.77 KBquietone
#229 interdiff-227-229.txt5.42 KBquietone
#227 interdiff-225-227.txt10.41 KBquietone
#227 2746541-227.patch203.48 KBquietone
#225 2746541-225.patch198.7 KBquietone
#225 interdiff-222-225.txt17.02 KBquietone
#222 interdiff-218-222.txt2.32 KBquietone
#222 2746541-222.patch184.29 KBquietone
#219 interdiff-218-219.txt2.1 KBquietone
#219 2746541-219.patch183.72 KBquietone
#218 diff-211-218.txt834 bytesquietone
#218 2746541-218.patch183.91 KBquietone
#211 2746541-211.patch184.4 KBquietone
#211 interdiff-209-211.txt2.83 KBquietone
#209 2746541-209.patch181.49 KBquietone
#209 interdiff-207-209.txt4.88 KBquietone
#207 2746541-207.patch180.51 KBquietone
#207 interdiff-206-207.txt858 bytesquietone
#206 2746541-206.patch180.42 KBquietone
#206 interdiff-203-206.txt7.6 KBquietone
#203 interdiff-200-203.txt1.07 KBquietone
#203 2746541-203.patch178.16 KBquietone
#200 2746541-200.patch178.49 KBquietone
#200 interdiff-199-200.txt743 bytesquietone
#199 interdiff-197-199.txt1.31 KBquietone
#199 2746541-199.patch178.49 KBquietone
#197 2746541-197.patch178.49 KBquietone
#197 interdiff-195-197.txt10.26 KBquietone
#195 2746541-195.patch175.79 KBquietone
#195 interdiff-192-195.txt13.11 KBquietone
#192 2746541-192.patch175.44 KBquietone
#192 interdiff-183.192.txt2.98 KBquietone
#183 2746541-183.patch172.46 KBquietone
#183 interdiff-179-183.txt6.13 KBquietone
#179 2746541-179.patch167.26 KBquietone
#179 diff-177-179.txt1.63 KBquietone
#177 diff-174-177.txt10.1 KBquietone
#177 2746541-177.patch166.02 KBquietone
#174 2746541-174-review-only.txt104.04 KBquietone
#174 2746541-174.patch160.88 KBquietone
#174 interdiff-172-174.txt4.44 KBquietone
#172 2746541-172.patch126.41 KBquietone
#172 interdiff-169-172.txt4.72 KBquietone
#171 2746541-171.patch126.52 KBquietone
#171 interdiff-169-171.txt6.16 KBquietone
#169 2746541-169.patch124.02 KBquietone
#169 interdiff-167-169.txt3.88 KBquietone
#167 2746541-167.patch123.3 KBquietone
#167 interdiff-165-167.txt5.26 KBquietone
#165 2746541-165.patch122.12 KBquietone
#165 interdiff-159-165.txt8.08 KBquietone
#160 2746541-159.patch124.17 KBquietone
#160 interdiff-157-159.txt3.02 KBquietone
#157 interdiff-156-157.txt5.99 KBquietone
#157 2746541-157.patch123.99 KBquietone
#156 interdiff-153-156.txt6.02 KBquietone
#156 2746541-156.patch117.48 KBquietone
#154 2746541-153.patch116.41 KBalexpott
#153 150-153-interdiff.txt5.11 KBalexpott
#153 Screenshot 2019-09-18 at 13.54.40.png146.36 KBalexpott
#153 Screenshot 2019-09-18 at 13.54.54.png43.83 KBalexpott
#150 interdiff-148-150.txt1.71 KBquietone
#150 2746541-150.patch116.45 KBquietone
#148 diff-145-148.txt9.47 KBquietone
#148 2746541-148.patch115.59 KBquietone
#145 interdiff-142-145.txt4.96 KBquietone
#145 2746541-145.patch115.35 KBquietone
#142 interdiff-139-142.txt39.97 KBquietone
#142 2746541-142.patch112.36 KBquietone
#139 2746541-139.patch98.65 KBquietone
#139 interdiff-137-139.txt724 bytesquietone
#137 interdiff-132-134.txt4.67 KBquietone
#137 2746541-137.patch98.62 KBquietone
#134 interdiff-132-134.txt4.67 KBquietone
#134 2746541-134.patch95.9 KBquietone
#132 2746541-132.patch96.7 KBquietone
#132 interdiff-125-132.txt2.16 KBquietone
#127 2746541-126.patch97.91 KBquietone
#125 interdiff-124-125.txt3.96 KBquietone
#125 2746541-125.patch97.82 KBquietone
#125 Screenshot from 2019-08-30 17-07-25.png30.58 KBquietone
#125 Screenshot from 2019-08-30 17-06-50.png26.09 KBquietone
#124 2746541-124.patch93.6 KBquietone
#124 interdiff-121-124.txt15.25 KBquietone
#121 interdiff.2746541.117-121.txt66.81 KBmikelutz
#121 2746541-121.drupal.Migrate-D6-and-D7-node-revision-translations-to-D8.patch99.11 KBmikelutz
#117 2746541-117-drush-testing-only.patch37.12 KBquietone
#114 revision-comparison.txt4.57 KBquietone
#107 2746541-107-d7-only.patch36.19 KBquietone
#101 diff-88-101.txt23.8 KBquietone
#101 2746541-101.patch59.25 KBquietone
#88 interdiff-81-88.txt9.01 KBquietone
#88 2746541-88.patch54.6 KBquietone
#86 Screenshot from 2019-08-07 09-54-30.png6.66 KBquietone
#86 Screenshot from 2019-08-07 09-52-33.png20.51 KBquietone
#83 Screenshot from 2019-08-06 12-21-36.png13.96 KBquietone
#81 2746541-81.patch63.61 KBquietone
#79 2746541-79.patch219.7 KBquietone
#77 2746541-77.patch219.39 KBquietone
#74 2746541-74.patch50.78 KBquietone
#72 2746541-72.patch50.82 KBquietone
#70 2746541-70.patch52.7 KBquietone
#68 2746541-68.patch1.01 MBquietone
#68 interdiff-67-68.txt621 bytesquietone
#67 2746541-67.patch1.01 MBquietone
#65 2746541-65.patch77.07 KBquietone
#64 2746541-64-d6-only.patch19.63 KBquietone
#59 interdiff-51-59.txt3.73 KBquietone
#59 2746541-59.patch44.52 KBquietone
#58 2746541-58.patch33.37 KBquietone
#58 interdiff-51-58.txt3.85 KBquietone
#51 interdiff-45-51.txt1.2 KBquietone
#51 2746541-51.patch44.58 KBquietone
#49 2746541-49-D8-migration-results-FI-revisions.PNG129.95 KBmasipila
#49 2746541-49-D8-migration-results-EN-revisions.PNG124.22 KBmasipila
#49 2746541-49-D6.PNG160.27 KBmasipila
#49 2746541-49-D8-standard-behavior.PNG209.18 KBmasipila
#48 2746541-48.PNG42.72 KBmasipila
#45 interdiff-43-45.txt4.14 KBquietone
#45 2746541-45.patch44.54 KBquietone
#43 2746541-43.patch43.51 KBquietone
#43 interdiff-37-43.txt19.28 KBquietone
#40 2746541-37.patch44.2 KBquietone
#40 interdiff-33-37.txt16.03 KBquietone
#33 2746541-33.patch38.46 KBquietone
#25 2746541-EN-revision-list-showing-current-FI-revision.PNG46.92 KBmasipila
#24 2746541-revision-history-of-translations.PNG73.52 KBmasipila
#22 2746541-22.patch39.9 KBmaxocub
#22 2746541-22-without-fixtures.patch14.33 KBmaxocub
#22 interdiff-2746541-21-22.txt9.91 KBmaxocub
#21 2746541-21.patch34.21 KBjofitz
#21 interdiff-2746541-19-21.txt1.42 KBjofitz
#19 2746541-19.patch33.89 KBmaxocub
#19 2746541-19-without-fixtures.patch13.16 KBmaxocub
#18 interdiff-2746541-17-18.txt1.82 KBmaxocub
#18 2746541-without-fixtures-18.patch13.52 KBmaxocub
#18 2746541-with-2921661-18.patch41.64 KBmaxocub
#18 2746541-without-2921661-18.patch33.67 KBmaxocub
#17 2746541-without-fixtures-17.patch13.09 KBmaxocub
#17 2746541-with-2921661-17.patch41.05 KBmaxocub
#17 2746541-without-2921661-17.patch33.06 KBmaxocub
#9 2746541-9.patch3.5 KBmaxocub

Comments

vasi created an issue. See original summary.

catch’s picture

Make sure the current revision of each node has the correct data in it. This must be true even if the current revision is not the highest one (ie: revision was reverted).

Reverting a revision in Drupal 6/7 creates a new revision based on the older one, so the only case where the current revision is not the highest one would be in the case of a not-yet-published forward revision.

vasi’s picture

@catch: Can you unpublish a single revision in D6/7? I think 'status' lives in the node table...

mikeryan’s picture

Status: Active » Postponed
gábor hojtsy’s picture

@vasi: some contrib modules implement forward revisions with some core hacks, allowing for the "latest revision" to not necessarily be the published revision. See https://www.drupal.org/project/revisioning, Workbench moderation, etc, eg. https://www.drupal.org/files/images/workbench-moderation-screenshot.png (note difference between current vs. published revision -- the published revision state is orthogonal to the published node status). While these are not core features, entities encountered in the migration may have gone through these modules earlier.

maxocub’s picture

Status: Active » Needs review
StatusFileSize
new3.5 KB

Here's a failing test to start with.

heddn’s picture

maxocub’s picture

StatusFileSize
new33.06 KB
new41.05 KB
new13.09 KB

I know this is postponed, but I wanted to make sure that what was being done in #2921661: Add support to migrate multilingual revisions was going to help here, and it does.
So here's what I've got so far.

maxocub’s picture

StatusFileSize
new33.67 KB
new41.64 KB
new13.52 KB
new1.82 KB

Fixing the tests

maxocub’s picture

Status: Postponed » Needs review
StatusFileSize
new13.16 KB
new33.89 KB

#2921661: Add support to migrate multilingual revisions is in, unpostponing. First, it needs a reroll.

jofitz’s picture

StatusFileSize
new1.42 KB
new34.21 KB

Correct 1 of the test failures (the rest confuse me!)

maxocub’s picture

Status: Needs work » Needs review
StatusFileSize
new9.91 KB
new14.33 KB
new39.9 KB

Let's try this to fix the remaining tests.

masipila’s picture

Status: Needs review » Needs work
StatusFileSize
new73.52 KB

I did quite a lot of manual testing with D6-D8 and ran into two different issues.

This fist one is easily reproducible:
1. Have 2 languages installed
2. Have a content type (e.g. Story) that has multilingual support enabled
3. Create a Story node in English
4. Translate the node to Finnish
5. Create a Rev 2 revision in Finnish
6. Revert to the original Finnish revision
7. Run the upgrade to D8
8. Notice that the original Finnish translation is not shown in the revision list. One picture tells more than a thousand words so have a look at the screenshot below.

Screenshot of revision history

The second issue that I found is more trickier to reproduce but it leads to fatal error. I have a hunch on the root cause but I don't want to speculate before I have exact steps to reproduce. Once I have, I'll post the findings as a separate comment for the sake of clarity.

Cheers,
Markus

Edit: typo

masipila’s picture

Ok, here we go with further test results. For the sake of clarity, I post each different test to a separate comment.

1. Have 2 languages installed
2. Have a content type (e.g. Story) that has multilingual support enabled
3. Add taxonomy to Story so that the taxonomy does **not** have multilingual support.
3. Create a Story node in English and make sure that you have a taxonomy term associated with the node.
4. Translate the node to Finnish and ensure that the term is associated.
5. Create a Rev 2 revision in Finnish
6. Revert to the original Finnish revision
7. Create a Rev 2 revision in English
8. Run the upgrade to D8
9. Notice that the current Finnish revision is shown on the English revision list. One picture tells more than a thousand words so have a look at the screenshot below.

Screenshot of revision history

Note: this is not yet the fatal error issue I reported in #24. I'll post that as the next comment.

Edit: typo

masipila’s picture

And here's the third issue I found in my manual tests. This leads to a fatal error, details below.

1. Have 2 languages installed
2. Have a content type (e.g. Story) that has multilingual support enabled
3. Add taxonomy to Story so that the taxonomy has 'Localized' multilingual setting enabled.
3. Create a Story node in English and make sure that you have a taxonomy term associated with the node.
4. Translate the node to Finnish and ensure that the term is associated.
5. Create a Rev 2 revision in Finnish
6. Revert to the original Finnish revision
7. Create a Rev 2 revision in English
8. Run the upgrade to D8

After the upgrade, go to admin/content on your D8 site to see the migrated nodes.
A. Try to access the Finnish version of the node. It will be otherwise OK except the 'localized' term is not associated with the node. This is because of #2886609: Migrate translations for D6 i18n taxonomy 'localized' terms and more specifically because of #2859297: Migrate taxonomy term references for D6 Node translations. The latter issue (2859297) is about making the association between the translated node and localized term.

B. Try to access the English version of the node. You'll get HTTP 500.

  • Web server log shows a PHP Fatal error: Call to a member function isTranslatable() on null in /path/to/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php on line 1161.
  • In addition to this, Drupal recent log messages shows a PHP Notice: Undefined index: forums in Drupal\Core\Entity\Sql\SqlContentEntityStorage->loadFromDedicatedTables() (line 1161 of /path/to/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php)

EDIT / Additional information

The fatal error occurs also when there are no revisions i.e. when there is only the English D6 node which is translated to Finnish and they both have the 'localized' term. Let's move this part to be handled as part of #2859297: Migrate taxonomy term references for D6 Node translations instead of this issue as this seems to have nothing to with revisions migration.

Edit 2

The fatal error is caused by the fact that the term record in taxonomy_term_field_data does not have a langcode as it should. So this is definitely out of scope of this node revision migrate issue.

masipila’s picture

Status: Needs work » Postponed

I was testing this more. This is highly dependent on the multilingual taxonomy term migrations. I tested so that I had the following patches applied:
- 2746541#22 (this issue)
- 2975509-50
- 2886609-86

The results were better but still not complete.

When using 'localized' terms, the results seemed to be OK

When using 'Per language' terms, the EN revision list was showing the latest FI revision as 'current' and the current EN revision was not shown as current.

When using 'Fixed language' terms, the revision lists were OK but the Finnish term was not associated with the Finnish version of the node.

This is way too complex to test without the term migrations being done first, let's postpone this on #2975509: Migrate D6 vocabulary language settings and #2886609: Migrate translations for D6 i18n taxonomy 'localized' terms.

Markus

quietone’s picture

Issue summary: View changes

Put the issues this is postponed on at the top of the IS for convenience.

masipila’s picture

Re-parenting so that all multilingual migrations can be found from the same meta. Moved original parent to related issues.

masipila’s picture

Status: Postponed » Active

Unpostponed as this landed: #2886609: Migrate translations for D6 i18n taxonomy 'localized' terms. D7 part may need a separate follow-up.

quietone’s picture

Status: Active » Needs review
StatusFileSize
new38.46 KB

Let's reroll this.

Aside from the reroll, the changes include moving the migrations to content_translation, creating a property for a variable in both source plugins. The node revision translation tests fail, but let's see what else may fail.

quietone’s picture

Testing for the error reported in #24. I was able to reproduce the problem and it is not related to translations. The error occurs when there has been a 'revert' action on a content type.

Tested by making a new content type, with out translations and with revisions. Added a node of the content type, then made a revision, then reverted that revision. Then I ran 'd6_node' and 'd6_node_revision'. When looking at the revision history on Drupal 8, the original revision is not listed, just like what masipila reported.

quietone’s picture

Tracked down the problem with the display to the field revision_translation_affected field. It is NULL for the 1st revision migrated and if I change it to 1, the display shows all the revisions. And thus solves the problem reported in #24. The question remains, how to migrate that value?

This field is a bit mysterious. Finding documentation on it isn't easy. Eventually found #2933518: The semantics of the "revision_translation_affected" field are unclear to Decoupled Drupal developers (REST/JSON API/GraphQL) users, improve this, and in there was a link to Entity.api.php Lin 102 which is the documentation. In that it states

* If a translation has changes in a certain revision, the translation is considered
* "affected" by that revision, and will be flagged as such via the
* "revision_translation_affected" field

So with that one would assume, at least I do, that revision_translation_affected is set only when a translation has changes in a revision. That is not true.

I created a D8 site with the standard profile, did not install any translation module or language, created a new content type that is revision-able and added a few revisions. Then, looked at the db tables node_field_data/node_field_revision, and revision_translation_affected is set to 1. There are no translations yet the field is set.

Related to this is the revision_default flag for each revision. That same documentation states

The "default" revision is the canonical variant of
* the entity,

Every revision for the test content type has revision_default set to 1.

So, I made another content type and that is not revision-able and made some content. Just like before revision_translation_affected is set to 1 even though this is a content type that is not revision-able and on a single language site with no translation modules enabled.

It is a really hot day here any maybe I'm not thinking clearly or done something wrong in my tests but if I haven't this is really whacky.

From a migration perspective, the solution is to set revision_translation_affected to TRUE and have a followup to determine what to do about that field value.

masipila’s picture

Whoa...

@quietone, can you summarize what would be the result if we would do this as you suggested?

From a migration perspective, the solution is to set revision_translation_affected to TRUE and have a followup to determine what to do about that field value.

Markus

quietone’s picture

@masipila, Yea, seems radical. The result is that migrate is duplicating what I see D8 doing in my testing. If I can make a non revision-able, non translatable content type that has revision_translation_affected set to 1 and that content type behaves normally, then it shouldn't break a migration for a non revision-able and non translatable content type. And since the field is supposed to be set for revision-able content type and revision-able and translatable content types it should not break those either.

masipila’s picture

Ok, sounds logical to try that approach!

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new16.03 KB
new44.2 KB

A patch implementing the idea in #36. Haven't worked on the failing MigrateUploadTest

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new19.28 KB
new43.51 KB

Reroll

quietone’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new44.54 KB
new4.14 KB

Change the order of migrations in MigrateContent so that d6_node_revision_translation only runs after d6_node_translation and d6_node_revision.

Also, removed the 'vid: vid', from MigrateUploadTest which is an indication of a larger issue with revision numbers. I'm thinking that the migration of vid needs to be removed from all migrations except perhaps d6_node.yml. More on that later, it is too late now.

mikelutz’s picture

Assigned: Unassigned » mikelutz

Assigning to myself to review this week. I want to take a look at the revision_translation_affected stuff and the revision ids.

quietone’s picture

Woke up thinking that it might be worthwhile to move the vid issue to it's own issue and focus on the revision_translation_affected here. What do you think?

masipila’s picture

Assigned: mikelutz » Unassigned
Status: Needs review » Needs work
StatusFileSize
new42.72 KB

I was testing the patch #45 manually on a break between the games. The patch breaks something quite badly, see the attached screenshot from D8 /admin/content with the patch applied:

WTF

I gotta go to play another game now so I can't troubleshoot this more right now but I did test the migration with and without patch #45. Clean 8.7.x -- the /admin/content looks normal
With patch ¤45 -- the /admin/content shows corrupted data as illustrated in the screenshot above.

Cheers,
Markus

p.s. I unassigned @mikelutz who had assigned self for review. Now that I kicked this back to NW, the assignment would indicate that @mikeluz would work on the actual patch.

Edit:
I'm not able to easily reproduce this issue with the /admin/content view.
I orignally had an English story node in D6 without any revisions.
I also had a Finnish story node which was linked as a translation to the English node
The Finnish node had a revision and I had then reverted the original as current.
When I migrated to D8, this was the result on the /admin/content

However, I then later added a revision also to the English node and reverted the original as the current. When I did this, the /admin/content view looked normal.

I tried to repro the issue shown in the screenshot above by deleting the English revisions on my D6 site but at least in this setup the /admin/content looked normal.

This might have something to do with the fact that originally I had the English node which did not have any revisions + a linked Finnish node which did have revisions. I need to go to bed now so I'm not able to try to repro this from the scratch.

masipila’s picture

D8 standard behavior
I chose to go back to basics and double check how D8 shows the revisions of multilingual content types when the data is created in D8. This is what we should achieve with migrations.

D8 standard behavior

  • Content type Article has 'Enable translations'
  • I created an article in D8 in English and translated it to Finnish. Both language versions share the same nid (nid 2 in the example above)
  • I made an edit to the English language version and then reverted to the original.
  • I made an edit to the Finnish language version and then reverted to the original.
  • The revisions in D8 are language aware i.e. only the revisions of the current language are displayed.

D6 data
I have similar data setup in D6, look at the screenshot below.

D6

  • Content type Story has multilingual support 'Enabled, with translations'
  • I created a node in English and translated it to Finnish. Both language versions are nodes of their own i.e. they have different nids. The nodes are linked to each other as translations of each other.
  • I made an edit to the English node and then reverted to the original.
  • I made an edit to the Finnish node and then reverted to the original.
  • Since these are two different nodes, the revisions are obviously language aware i.e. only the revisions of the current language are displayed.

Migration results with patch #45 applied.

English revisions after the migration
The screenshot below shows the English revisions after the migration.
D8 EN revisions

There are two problems:
1. The Finnish revisions are displayed on the English revisions list.
2. The current English revision is not displayed as the 'Current' revision because the current Finnish revision is displayed as current.

Finnish revisions after the migration
The screenshot below shows the Finnish revisions after the migration.
D8 Finnish revisions

There is one problem:
3. The English revisions are displayed on the Finnish revision list.

quietone’s picture

@masipila, I followed your instructions on how to create the nodes in D6, ran the migration via the UI and got different results. The English revisions were OK but there was one revision missing in the Italian revisions list. I'll look into that but I'd really like to reproduce your results. Can you post the Drupal 6 node and node_revisions tables you used? Hopefully, you still have it around.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new44.58 KB
new1.2 KB

Ah, found that vid:vid was still being used. I have removed them it the migrations are working for me, at least for my test cases.

masipila’s picture

Manual Drupal 6 tests
Re #48: I'm not able to reproduce this issue anymore. I'm suspecting that I managed to screw up my D6 test data somehow in D6 which triggered this weirdness.

Re #49: I re-tested patch #51. My migration results with the nodes shown in #49 are quite weird but having a closer look in D6, it's even more weird there i.e. I somehow managed to trigger some D6 bug.

So I created completely new set of test data in D6 as follows:

English

  • I created a new English node
  • Created a revision to the English node
  • Then reverted to the original English node

Finnish

  • Created a Finnish translation to the English node
  • Created a revision to the Finnish translation
  • Then reverted to the original Finnish node.

The D6-D8 migration results were all OK.

  • The D8 English revision list was showing the expected English revisions only
  • The D8 Finnish revision list was showing the expected Finnish revisions only

Drupal 7 / node translation tests

  • I have content type which has multilingual settings as 'Enabled, with translation'
  • Same test scenario than what I used above for D6-D8

Results for D7 node translation tests were all OK.

  • The D8 English revision list was showing the expected English revisions only
  • The D8 Finnish revision list was showing the expected Finnish revisions only

Drupal 7 / entity translation tests
Drupal 7 also supports entity translation concept so I tested what happens with that.

In Drupal 7, the 'Revisions' tab shows a list of all revisions of the node

  • When viewing en/node/21/revisions, I'm seeing all revisons regardless of the language
  • When viewing fi/node/21/revisions, I'm seeing all revisons regardless of the language

Entity translation results in D8:

  • When viewing en/node/21/revisions, I'm seeing all revisions regardless of the language
  • When viewing fi/node/21/revisions, I'm seeing only two Finnish revisions

I'm close to have a brain meltdown trying to think what does the node revision conceptually mean in the context of entity translation and languages, because the different fields may be translatable or then not. I would be tempted to say that this is the best that we can achieve but others may present better ideas if you're able to explain a concrete example.

Conclusion:

  • My manual tests were all OK but I can see that there were 4 failures in the automated tests.
  • Leaving NW to address those
  • I did not have time yet to do a full review to the patch and the test coverage we have there

Cheers,
Markus

masipila’s picture

I queueud this to PostgreSQL again to see if we are now down to 4 errors like on MySQL and SQLite. I assume that the difference in the error count was due to the fact that the whole 8.7.x was not passing with PostgreSQL but that seems to be fixed now. Let's see.

masipila’s picture

I read the patch #51 on my way home today.

1. Question: Could you please describe the background why we needed to remove the vid:vid mapping? I can see that it was removed also from d6_upload.yml which wasn't obvious to me. Do we have something similar for D7 that should be taken into account?

2. I checked the test coverage. Could we add a test to D7 fixture for a content type that uses entity translation? Point being that this is complex and if the migration result ever changes because of some other issue, we would notice it?

Cheers,
Markus

masipila’s picture

Re: #54. Just as I assumed and hoped, the PostgreSQL has now the same amount of fails than other database backends.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new3.85 KB
new33.37 KB

Fixing the failed tests requires changing the revision ids.

quietone’s picture

StatusFileSize
new44.52 KB
new3.73 KB

Hmm, looked at the patch in #58 and it is not right, ignore it. This starts again from #51

quietone’s picture

Still to do is to address the question in #55

catch’s picture

Priority: Major » Critical
Issue tags: +Drupal 9
quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new19.63 KB

Rerolling the patch in #59 but excluding the D7 work because there are changes to the source fixture that do not apply and that is too much to deal with just now.

quietone’s picture

StatusFileSize
new77.07 KB

Now add the d7 stuff. Rerolling the d7 test fixture was a disaster so I remade it and removed many, but not all, of the unnecessary changes. I haven't run all the tests, so lets see what may fail.

Oh and no interdiff since this is just adding the D7 code.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new1.01 MB

Might have a working d7 fixture now. There is no interdiff since this contains an untrimmed version of the fixture. Reducing the size will come later once this passes tests.

quietone’s picture

StatusFileSize
new621 bytes
new1.01 MB

Try again.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new52.7 KB

Let's try this fixture, it has been trimmed a bit as well.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new50.82 KB

Try again, only changes are to the fixture.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new50.78 KB

And update the new d7 MigrateNodeRevisionTranslationTest to use the correct text for the title fields.

quietone’s picture

Issue summary: View changes
Status: Needs review » Needs work

Update IS
NW for the remaining tasks

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new219.39 KB

This is an attempt to add revisions to the test content type which uses entity translation. I expect lots of test failures.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new219.7 KB

Remove more changes from the fixture. Plus modify a test since the field field_text is now translatable.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new63.61 KB

This trims the d7 fixture some more and add tests. Specifically adding to d6/MigrateNodeRevisionTest, d6/MigrateNodeRevisionTranslationTest, d7/MigrateNodeRevisionTranslationTest and creating a d7/MigrateNodeRevisionTest. No interdiff because of all the changes to the fixture make it full of noise.

quietone’s picture

Issue summary: View changes
StatusFileSize
new13.96 KB

Did some manual testing for d6 and d7. The d7 one created extra nodes and crashed when trying to view a node, so pretty bad there. The d6 one looks much better. I noticed the following problems.

1) The page content type is not translatable and it should be, it has translated nodes in d6.
2) The tag field in the page content type is a required field but the drop down has no selections so you can't enter data. I deleted the field to keep testing.
3) The revisions listed for the french translation of the page are in vid order which is not in the date entered order.

quietone’s picture

Manual testing of d6 I discovered that the node revisions for node 1, which does not have translations, are NOT listed in the revisions tab. So, I deleted and started over this time without the translation modules enabled and ran the migration from the UI. Same results. What would prevent revisions from being listed in the revisions tab?

amateescu’s picture

@quietone, looking at \Drupal\node\Controller\NodeController::revisionOverview(), it seems that the revisions tab only displays the revisions which have the revision_translation_affected field set to 1 in the database. Maybe the migration process doesn't populate that field?

If that's not the problem, can you give me some pointers (or a link to a docs page) on how to replicate your manual testing process? :)

quietone’s picture

@amateescu, revision_translation_affected is set to true in the d6 and d7 node_revision_translation.yml. But there is a problem with the revisions without translation enabled. The d6 fixture has node 1 with 3 revisions and post migration only the latest one shows up on the revisions tab, node/1/revisions. That needs to be fixed first before adding the translations on top.

Right, so for testing. First, I just checked and didn't see a docs page for how to test. So, here goes.

  1. Add an entry in settings.local.php for the d6 database dump.
  2. import the fixture using db-tools, php core/scripts/db-tools.php import --database=d6_dump core/modules/migrate_drupal/tests/fixtures/drupal6.php
  3. enable migrate_drupal_ui. Don't enable the translation modules.
  4. navigate to /upgrade and complete the form and run the migration
  5. then goto /node/1/revisions and there will be one listed not the three it should be

Screenshots:
Drupal 6, node/1/revisions

Drupal8, node/1/revisions

This problem with the node revisions, without translation, should be moved to it's own issue. Hopefully, I will get to that before the end of the day.

quietone’s picture

Created a new issue for the problem with the node revision tab without translations. #3073088: d6 term node migration changes revision log on wrong revision.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new54.6 KB
new9.01 KB

Remove changes for node revisions tests which are now in #3073088: d6 term node migration changes revision log on wrong revision

gábor hojtsy’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

This is a bit over my head to review. Can you update the issue summary in terms of what is the before/after migration scenario? There are a lot of data updates in the dumps to make this clear on review to me sorry.

+++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeRevisionTranslationTest.php
@@ -0,0 +1,131 @@
+    // Translation revisions are not migrated.
+    $ids = [14, 16, 23, 2009];
+    foreach ($ids as $id) {
+      $revision = $this->nodeStorage->loadRevision($id);
+      $this->assertNull($revision);
+    }

What does "translation revision" mean here?

mikelutz’s picture

I'm working on reviewing this, but It's going to take me a couple days. I have a pretty good understanding of what is going on, and my initial test show some things in the migrated database that don't seem exactly right to me. I'm working on seeing what we need to adjust to make it work.

quietone’s picture

Issue summary: View changes

Item 2 of remaining task: Could you please describe the background why we needed to remove the vid:vid mapping?
I removed that change because honestly I don't remember all the details. And it does not seem to have caused any problems. So removing from the remaining task list.

quietone’s picture

I am really tempted to split this into a d6 an d7 version because the d6 has the added complication of the effect of the term node migrations and then d7 has the entity translation which d6 doesn't.

Any objections?

mikelutz’s picture

Issue summary: View changes

I think splitting them off is a good idea. I suspect removing vid:vid was necessary in d7 entity translation migrations because each vid has multiple translations, but we are trying to save them one at a time, which the entity API doesn't like. Problem with that is that we are then saving past revisions with higher revision IDs than default revisions, and that can cause problems in the system. We may need to go back to scratch and rework the source and destination
such that we can save a whole revision at once somehow.

quietone’s picture

Issue summary: View changes

Talked to mikelutz and heddn about this today and we decided to make a new issue for working solely on the d7 entity translation revisions. And that here, the priority is the D7 migration. It is much that same as D6 and since with D6 there is a possible complication with the term node migration it is best for right now to just get the D7 working correctly.

quietone’s picture

I've been staring and the revisions before and after a D7 migration and found there are 'extra' revisions made. Then I remembered the entity translation. Yes, d7_entity_reference_translation is migrating data for the article content type, which should not be using entity translation. Is there yet another problem with the fixture?

edit: Hmm, that is a follow up migration and is adjusting entity references. that is not causing the extra revisions

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new59.25 KB
new23.8 KB

This patch only changes the d7 migration.

Spent quite a bit of time running the d7 migrations in different order and looking at the resulting tables. For now, going with d7_node, d7_node_revision, d7_node_translation then d7_node_revision_translation. And I added vid:vid back to the node translation migrations and that seems to work for d7 where the current revision is the highest revision id.

One problem is the revision tab post migration were incorrect, the revision tab for the english node was showing an icelandic revision. Tracked that to the d7_node_translation migration which will creates two revisions for the one icelandic row and both have the revision_translation_affected flag set TRUE. The only place I could see to set that to FALSE for the english revision is in onPostRowSave in NodeTranslatoinMigrateSubscriber, which has to load that revision, set the value FALSE, and then save again. It is unfortunate to do that extra work.

The migration dependencies haven't been changed yet and the migration of a field that changes on the revision (field_text_plain) needs to be checked.

quietone’s picture

Status: Needs work » Needs review

The errors in the patch are all d6 related so that is fine. Right now just focusing on D7. Anyone reviewing, the next step is to check the results that are explained here.

Now to explain the results for d7.

The d7 source has 4 revisions, 2 'en' and 2 'is', with the following nid/vid.
d7 source
2/2 en first rev
3/3 is first rev
2/11 en 2nd rev
3/12 is 2nd rev

The d7 results has 6 revisions, 4 'en' and 2 'is'. They are shown below along with the migration that creates them. The translation migrations do not specifically migrate the 'en' versions, exactly where that is happening I don't know. There is only 1 row in the map table but two revisions are created. The 'is' translations and there fields are correct. The "extra" 'en' revisions are always the latest revision and have revision_translation_affected set to 0 or NULL so they don't display in the revisions tab.

d8 results
The values are nid/vid/langcode, revision, revision_translation_affected

d7_node
  2/11 --> 2/11/en, the 2nd revision, 1
d7_node_revision
  2/2 --> 2/2/en, the 1st revision, 1
d7_node_translation
  3/12 --> 2/12/is, the 2nd revision, 0
      |--> 2/12/en, the 2nd revision, 1
d7_node_revision_translation
  3/3 --> 2/3/is, the 1st is revision, 1
      |--> 2/3/en, the 2nd revision, NULL

Is this an acceptable solution? What problems might it cause?

edit: formatting

mikelutz’s picture

This is close, but not quite right.

There should be 4 en and 3 is when is all said and done. It should have

2/2/en/the 1st revision 1
2/3/en/the 1st revision NULL
2/3/is/the 1st revision 1
2/11/en/the 2nd revision 1
2/11/is/the1st revision NULL
2/13/en/the 2nd revision NULL
2/13/is/the 2nd revision 1

That being said, the entity api is really going to fight us trying to get that structure saved with the final default revision being saved first.

quietone’s picture

Right, that is what D8 does, which isn't possible to produce using the current way migrate works. Is it necessary to match the revisioning that D8 would do or is it sufficient to keep all the data and display it in the correct order?

Highlighting differences between what D8 does (as shown in above comment) and current migrated results

2/2/en, the 1st revision, 1
2/3/en, the 2nd revision, NULL ---> this is the the first revision in non migrated D8
2/3/is, the 1st is revision, 1
2/11/en, the 2nd revision, 1
2/11/is/the 1st revision NULL ----> missing from migrated version
2/12/en, the 2nd revision, 1 ---> revision translated affected NULL in non migrated D8
2/12/is, the 2nd revision, 0 ---> revision translated affected 1 in non migrated D8

edit: add comparison of results

quietone’s picture

So, is the solution in #103 sufficient. mikelutz disagrees but local testing has convinced me it isn't going to get any better. Anyone is welcome to prove me wrong.

Getting all the revisions to match what D8 would do out of the box doesn't seem possible in the current environment. The dN_node_revision migration will migrate ONLY the default language revision, there will NOT be a translated revision with that vid. Whereas D8 doesn't do that. When I make a new revision of a node in the default language (en) and save, the result is that both a default language (en) and a translated (it) version are saved. That is why this translation "2/11/is/the 1st revision NULL ----> missing from migrated version" is missing in the example above. Now, functionally I think that will work, at least in my local testing.

I had an idea of another approach and discussed with mikelutz and heddn on slack. The idea was to not use the existing node, node_revision and node_translation migrations. Instead, we need to ask the user if they want to run multilingual with or without revisions and then when they want revision translation we use a new migration (or migrations) with a new fancy source plugin that does what is needed (details TBD) and unset the dN_node* migrations that aren't needed. That will hopefully give us the ability to save the revision translations correctly.

mikelutz thought this "would work just fine for the core one button upgrade" but that it wouldn't for custom incremental migrations.

But as heddn pointed out unsetting the dN_node family of migration is a problem. They are used in migration_lookups in core and in migration_dependencies. Most of those are dN_node so that problem could be alleviated if dN_node was still run.

quietone’s picture

StatusFileSize
new36.19 KB

Here is a version of the patch with just D7 changes. Let's get this one sorted then we can go back and update the D6 version.

This the the node_field_revision table post migration in the test node/tests/src/Kernel/Migrate/d7/MigrateNodeRevisionTranslationTest.php. If the same database is used as the source for running the migration from the UI, the results are likely to be different since the migration may run in a different order.

MariaDB [test]> select nid,vid,langcode,title,revision_translation_affected from test44775560node_field_revision where nid=2 or nid=3;
+-----+-----+----------+---------------------------------------------+-------------------------------+
| nid | vid | langcode | title                                       | revision_translation_affected |
+-----+-----+----------+---------------------------------------------+-------------------------------+
|   2 |   2 | en       | The thing about Deep Space 9 (1st rev)      |                             1 |
|   2 |   3 | en       | The thing about Deep Space 9                |                          NULL |
|   2 |   3 | is       | is - The thing about Deep Space 9 (1st rev) |                             1 |
|   2 |  11 | en       | The thing about Deep Space 9                |                             1 |
|   2 |  12 | en       | The thing about Deep Space 9                |                             0 |
|   2 |  12 | is       | is - The thing about Deep Space 9           |                             1 |
+-----+-----+----------+---------------------------------------------+-------------------------------+
6 rows in set (0.001 sec)
quietone’s picture

Issue summary: View changes

The IS proposed that "for each D6 and D7 revision, the migration source should yield every translation that we think goes along with it. This means multiple rows per revision, which is slightly confusing. But it will provide a mostly-sane revision history.". I don't see how to make that work. First there needs to be some logic to determine which nid/vids goes along with a translation. But then you have to manage multiple node translations in a single Row and the mapping? Seems custom destination and idmap would be needed?

gábor hojtsy’s picture

@quietone: so my mental model is languages are columns on a table where rows are revisions. When a new revision is saved, Drupal saves translations as well, so when you load the latest revision you get all translations in their most up to date form in that revision. If migrations don't save with this mental model into the revisions database tables then how does loading translations work?

I don't personally think we necessarily need to merge the revision rows creatively. The vids are globally consecutive (across entities) so all the migrations would need to do is to save revisions in vid order and rely on the entity API saving the other translations with the right value that was already available. That way merging various entities into one should result in the right order of revisions and all revisions should contain the most up to date translations at the time, no? Something like:

Node 1 French translation: revisions 4, 7, 12
Node 2 English translation: revisions 3, 8, 11
Node 3 Spanish translation: revisions 5, 6

(Node 1, 2 and 3 should all have unique vid values since it was not possible in D6/D7 to save a vid across entities).

So then we can order by vid and merge accordingly. Resulting merged node should contain revisions:

  • 3 (only English at this point)
  • 4 (English carries over and French added)
  • 5 (English and French carry over and Spanish added)
  • 6 (All languages carry over, Spanish updated)
  • 7 (All languages carry over, French updated)
  • 8 (All languages carry over, English updated)
  • 11 (All languages carry over, English updated)
  • 12 (All languages carry over, French updated)

If we cannot/don't want the vid to stay the same, that does not matter in terms of the local history of the entity, we can renumber them. As long as we saved with the original vid order, we should merge them perfectly on top of each other, no?

mikelutz’s picture

This is what I mean when I say that the migrations should be combined into one, and we need to use the entity api to 'replay' the data in the node revision table in the same order it was created to avoid fighting the entity API. Let the entity API do the merging for us.

quietone’s picture

I have managed to run a migration by doing the revisions in order, just for the article content type in the test fixture. The results of that are the same as the results in #103 but not taking into account the revision_translated_affected field which I am not worrying about right now. The key thing is that when migrating a revision that is not a translation the result is different than when adding such a revision through the UI in D8. When adding a English revision in D8 the revision table will have a new English and Italian revision with the same vid. When migrating an English revision the revision table will have only a new English revision. As such, I don't see how it is possible to match the D8 structure exactly. I don't see it is possible to achieve the results in #104.

I have not tested with a source like the example in #110.

Now, I do agree that it is better to run this in revision order and would like to pursue that and see what problems we run into but first, do we agree with the results I am getting.

edit: Forgot to add that using the UI post migration the revision tabs are wrong.

masipila’s picture

I believe I followed the logic of #112 but would it be possible to have screenshots or diagrams that highlight the conceptual differences between D8 and D6/D7? For example similar diagrams that we have been using here:
https://www.drupal.org/docs/8/upgrade/upgrading-multilingual-drupal-6-to...

I mean, the amount of energy that has gone into digging into this is so exessive that it is worth summarizing it so that we can minimize the risk of needing get back to this monster and tear up the whole thing...

Cheers,
Markus

quietone’s picture

StatusFileSize
new4.57 KB

I'm not good at making diagrams and screen shots are not sufficient to show the differences. I have settled on showing the database tables for the source D7, D8 post migration and D8 made via the UI. What I made via the UI should reflect the Drupal 7 source. All this is in the attached text file.

Are we making the assumption that since the post migration database tables does not match what is done via that UI that it is broken? What if the differences are OK and the site works just fine? I admit I have done limited testing but it did seem to be working with the patch in #88. Has anyone else tried?

gábor hojtsy’s picture

Thanks for the details. I think this is a problem:

DRUPAL 8 POST MIGRATION

Now let's look at the same table post migration of Drupal 7 data which should be the same scenario. The Drupal 7 tables are at the end for you to check. Here I want to try to show the difference between the Drupal 8 UI and post migration. Of course the nid and vid are different, it is the pattern that is of importance.

The revisions post migrations are not the same as through the UI. The nid/vid pairings are different in two situations. One is when an English revision is added and the second is when the French translation is added. In these two cases the import of the migrated row does not generate the revisions in the other languages like what happens when saving via the UI.

MariaDB [d8]> select nid, vid, langcode, title from node_field_revision where nid=2 order by nid, vid;

+-----+-----+----------+-----------------------------------+
| nid | vid | langcode | title                             |
+-----+-----+----------+-----------------------------------+
|   2 |   2 | en       | The thing about Deep Space 9      | migrate first English node
|   2 |   3 | en       | The thing about Deep Space 9      | migrate Icelandic translation
|   2 |   3 | is       | is - The thing about Deep Space 9 |
|   2 |  11 | en       | The thing about Deep Space 9      | migrate English revision
|   2 |  12 | en       | The thing about Deep Space 9      | migrate Icelandic revision
|   2 |  12 | is       | is - The thing about Deep Space 9 |
|   2 |  15 | en       | The thing about Deep Space 9      | migrate 1st French translation
|   2 |  15 | fr       | fr - The thing about Deep Space 9 |
+-----+-----+----------+-----------------------------------+

8 rows in set (0.001 sec)

A. Your latest revision does not have an Icelandic translation. When you edit it does it even appear? How would Drupal know it was not intentionally removed? (There can be any number of independent changes in a revision).

B. Also, what happens when you revert back to revision 11, that also does not have the Icelandic translation. Ok it does not have French either but that was not yet supposed to have it. This B case may be solved with the revision translation affected logic.

I'll ask Francesco to chime in if he can as well, he knows how A and B expect data and where are the bounds of acceptable data.

gábor hojtsy’s picture

Revert does not seem to be a problem, because revert only reverts the state of the translation being acted on, not the whole entity, as per @hchonov in #2766957-2: Forward revisions + translation UI can result in forked draft revisions https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Form%21N... well, at least a node revert. How does loading/editing code with translations not being present in the latest revision?

quietone’s picture

StatusFileSize
new37.12 KB

and finally what I have been testing with. No point running tests but you are welcome to have a go migrating with drush.

There are two migrations to run to migrate the revision translations. d7_node_first_revision.yml followed by d7_node_revision_translation.yml. Both use the Node.php source plugin, which is modified to use some added configuration keys to make the desired query. It was the quickest way I could think of to test getting the revisions in the correct order.

hchonov’s picture

It is definitely a serious problem when a new revision is created, which does not contain all the translations. As seen in #114 the last revision contains only 2 instead of 3 revisions, which might considered data loss. Even if the data is in the database it is not present on all revisions.

There might be 2 reasons for that:

  1. The wrong revision is retrieved for a base when creating a new one.
  2. For some reason only a subset of translations are retrieved from the current revision.

I've seen something strange that I don't understand in \Drupal\migrate\Plugin\migrate\destination\EntityRevision::getEntity() where the new entity revision is flagged as a non-default revision. When an entity revision is saved in a non-default revision, then this revision will not be returned by $storage::load(), but only by $storage::loadRevision() with the right revision ID.

In order to understand better what exactly is happening it would be better if every revision has an unique change on a specific translatable field - for example a counter. We need the source data and the migrated data with this counter. This should confirm whether we are retrieving the correct revision and using it for creating a new one.

Also could you please test by removing the following line from \Drupal\migrate\Plugin\migrate\destination\EntityRevision::getEntity():
$entity->isDefaultRevision(FALSE);

---

I also don't see a place where we're actively removing translations, therefore I would say that for some reason the wrong revisions are being used as a base.

gábor hojtsy’s picture

How does migrations ensure that the default revisions are accurate and have all the translations and why can that not be used to do the same for revisions? They would need to save over each other for the various languages. In more discussion with @hchonov, he said one of the possible problems is "you cannot pick a revision and simply change the content language and see how this revision looks in a different language". Also while reverting a revision with the UI would work, using the Entity API could still lead to data loss.

mikelutz’s picture

Status: Needs work » Needs review
StatusFileSize
new99.11 KB
new66.81 KB

Here is the relevant proof of concept master migrations for D6 and D7. There is a lot of work left to do to actually integrate these into the migrations, and make sure we are handling non-translatable and non-revisionable nodes correctly. Additionally, this works with core translations, and would probably fail with entity translations. Those will be trickier, but I think should also be done in one migration in a similar fashion.

I had to adjust the D7 fixture as it's currently invalid, using node 1 vid 1 as the current default instead of vid 2001 like it should.

I dropped the test suite down to just the two migration tests, and put an export of the node_field_data and node_field_revision tables in the test files for both so you can see what the migration should end up looking like.

There may be some additional work around making sure the fields and everything are correct across revisions too, I was just worried about the main node tables being correct.

I think we can also fix content translation source to the default translation, we don't really have the concept of a translation source in d6 or d7.

quietone’s picture

StatusFileSize
new15.25 KB
new93.6 KB

@mikelutz, thanks. That is much nicer, clearing I was on the wrong path. The removes any remaining files for that wrong patch and adds the assertRevision method to the d7 MigrateNodeMasterTest. That adds two things to the test that mike wrote, a) it load the revision to test the fields and b) it actually has a plain text field that has data that states the revision it is associated with. And that is encouraging.

Testing this with drush and it works very well. Although I do seem to be able to break it in some way that the is translations wind up on the revision tab of the english version. But I was also doing a fair bit of rolling back and remigrating so that may not mean a whole lot.

What definitely fails is running this from the UI. It is failing somewhere in the auditing done for the IdConflictForm'.

The website encountered an unexpected error. Please try again later.

LogicException: Cannot determine the highest migrated ID without an integer ID column in Drupal\migrate\Plugin\migrate\id_map\Sql->Drupal\migrate\Plugin\migrate\id_map\{closure}() (line 978 of core/modules/migrate/src/Plugin/migrate/id_map/Sql.php).
array_filter(Array, Object) (Line: 980)
Drupal\migrate\Plugin\migrate\id_map\Sql->getHighestId() (Line: 36)
Drupal\migrate\Audit\IdAuditor->audit(Object) (Line: 52)
Drupal\migrate\Audit\IdAuditor->auditMultiple(Array) (Line: 74)
quietone’s picture

This version has changes so that this would run from the UI. There are two changes.
One is cobbled together code in migrate_drupal_migrations_plugin_alter to not run d7_node, d7_node_translation and d7_revision. I'm still leaning to having a question in the UI that asks if they want to run multilingual migration with node revisions. Not sure about drush though.

The other change is to Sql::getHighestId() to prevent the exception shown in the previous comment. Original the getHighest method checked that every id had a type on integer. Obviously that doesn't work with a langcode id. (Which, btw has a type of 'language' - where is that defined?) Now, getHighest method should throw the exception only if an id with an integer type is not found.

Fortunately, the results are the as with drush.

The revision tabs post migration are not yet correct.
English revision tab
Icelandic revision tab

mikelutz’s picture

The reason that all revisions show up on both trans for node 2 in d7 is due to the untranslatable taxonomy fixed field. It's set in revision 2, removed in revision 3, and restored in revision 11 and removed again in revision 12. I can compensate depending on what that should is supposed to be doing, but based on that each revision affects each language, which is why they all show up.

quietone’s picture

StatusFileSize
new97.91 KB

Add a few things to the migration. Add the Multilingual tag, a migration for content_translation_source, declare the destination module, add a dependency on language migration. MigrateNodeMasterTest is updated with the content_translation_source values and what I think are the correct values for revision_translation_affected. The latter causes the test to fail.

Edit: only changed d7, not d6

mikelutz’s picture

The discrepancies in revision_translation_affected for D7 node 2 is the same issue as the revision tabs described in #126. (And in fact are simply two descriptions of the same thing, as the revision tab display IS the revision_translation_affected)

The untranslatable field is only stored on the original node, and isn't pulled for the iceandic copy
The Icelandic translation added in revision 3 removes field_vocab_fixed, which is an untranslatable field. This mean that when we are add revision 3 in icelanding we are affecting the english translation, as we cannot modify field_vocab_fixed in one translation and not the other. Revision 11 has a value in the D7 fixture for field_vocab_fixed, so the field is reset in revision 11 (again affecting both translations) Finally it is removed again in revision 12 (still affecting both translations) .

I can't quite seem to figure out What is supposed to be happening here. field_vocab_localize and field_tags both also come across as untranslatable fields, but they have entries in the d7 fixture for both languages that happen to match, so they are fine.

Is there something in the configuration of field_vocab_fixed such that is only has a record in the default language and for the translations we should go get the value from the original node? If we can find the condition that declares it in the D7 database we can either adjust the source to include the field data from the original node (which the entity api will correctly detect as no change for English, and thus set the rta flags correctly), or we can set the untranslated field to match the original language in the destination, again causing the entity api to manage the rta flag correctly. I just need to understand how D7 is interpreting the field, and why it's different in the fixture than the other untranslated fields.

quietone’s picture

Issue summary: View changes

Yes, the vocabulary fixed is problematic and behaves differently for me in D7 than in D8. I found that out when working on #3035392: Migrate vocabulary translations and taxonomy term references for Drupal 7 node translations. See Comment #18.

mikelutz’s picture

Interesting. I'll fire up a d7 instance with the fixture and play with it and pick Gabor's brain on exactly what data state we want on that type of field. I'm certain I can adjust the master migration to handle it once I know what we want the d8 version of that type of field to look like. Otherwise, I'm really happy with how the one shot approach is working though. I definitely think it's the right solution to pursue.

quietone’s picture

Status: Needs work » Needs review
Issue tags: -Needs issue summary update
StatusFileSize
new2.16 KB
new96.7 KB

As per discussion with mikelutz on slack I'm going to look into fixing tests. This patch restores drupalci.yml and changes the @group on the two new MigrateNodeMaster tests. Now, lets see what fails besides the functional test.

Oh, and I did an IS update so removing that tag. Hope it is sufficient for now.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new95.9 KB
new4.67 KB

Move the check for a multilingual source database to MigrationConfigurationTrait.

catch’s picture

@quietone

The IS proposed that "for each D6 and D7 revision, the migration source should yield every translation that we think goes along with it. This means multiple rows per revision, which is slightly confusing. But it will provide a mostly-sane revision history.". I don't see how to make that work.

So for Drupal 7 field translation this seems like the 'simplest' option since the Drupal 8 entity structure is similar.

But... is it possible for that approach to co-exist with a Drupal 6/7 tnid-style source?

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new98.62 KB
new4.67 KB

Now update the failing D6 Node tests to use the values in the current fixture. Move the node master migrations to content_translation. And coding standard fixes. That should fix a few more tests.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new724 bytes
new98.65 KB

In EntityContentBase instead of setting new revision true if the entity is revisionable set it if it revisionable and IsNewRevision. Found this while looking into the MigrateUploadTest was failing, where on import it was making a new revision when the content type has new_revision as false.

@catch, not really, and instead of that method we are trying what mikelutz proposes, in #121.

plach’s picture

@Gábor Hojtsy asked my feedback on #114.

I just went through the whole thread (I didn’t read any code though) and I agree with the issues @Gábor Hojtsy and @hchonov brought up. In general I don’t think we can avoid matching the D8 data model strictly: a missing record would mean that the related translation does not exist in that revision. Since we don’t track parents, that may either mean that the translation was removed or that the “parent” revision had no such translation in the first place. In any case that outcome would be wrong, since the translation should always be there, as we wouldn’t be migrating it otherwise (the entire d6/d7 node would have been deleted in that case).

The revision_translation_affected flag acts as a poorman’s “parent” field, as it allows to reconstruct the translation history by connecting the revisions affecting it.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new112.36 KB
new39.97 KB

@plach, thanks for the input.

This patch has more test fixes and add brings the d6 migration and d6 tests up to date. And hanged the MigrateNodeMasterTests to be easier to read, at least that is the intention.

quietone’s picture

I have found a problem with this approach and the idAuditor. The auditor will search for the highestId() on the destination and compare that to the highestId in the relevant migrate_map table or tables. Because the destination of dN_node_master is node, we are not checking the node revisions and we can't inform the user if they existing revision ids that will conflict with the ones to be migrated.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new115.35 KB
new4.96 KB

Here is a hack that adds auditing code to IdConflictForm so that it will audit node revisions.

quietone’s picture

OK, I was wrong earlier about the tests being easy to fix. This is a bit of a rabbit hole. Looks like we will need to test fixtures per Drupal version, one to be used for i18n and one not. And when it is i18n use the node_master migration and not run the others in the dN_node family, and alter all the processes and dependencies to use node_master, and then add a new process to account for the fact that node_master returns 3 ids not 1 like the dN_node family. Fun eh but possible.

All that does nothing to get the revision_translation_affected field to be correct.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new115.59 KB
new9.47 KB

This needed a reroll because of changes to the fixture in #3031727: Migrate D7 synchronized fields. That change uses the field_text_plain field which is also used here. To avoid the conflict I changed this patch to use the field, field_text_long_plain. And then, of course, the MigrateNodeMasterTest needed to be changed.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new116.45 KB
new1.71 KB

For some reason this reroll is giving me a headache.

Missed some changes to the test fixture.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new43.83 KB
new146.36 KB
new5.11 KB

Here's a fix for the failing kernel test. Note I have only fixed the test - I've not checked that the test assertions are actually correct.

The migrate UI upgrade fails are interesting - the test could be more helpful and tell us what migrations are failing. Here are screenshots of what is going on the D6 migrate upgrade UI test:

alexpott’s picture

StatusFileSize
new116.41 KB

Here's my patch :)

quietone’s picture

StatusFileSize
new117.48 KB
new6.02 KB

This just adds some comments.

The migrate UI upgrade fails are interesting - the test could be more helpful and tell us what migrations are failing.

Yes, that is a pain and should be a new issue.

For d6 I'm showing failures in these migrations, Books, Comments, File uploads, Term/node relationships, Term/node relationship revisions.

Edit; new issue made #3082211: Migrate UI upgrade tests should provide the complete log

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new123.99 KB
new5.99 KB

To make sure that dN_node_master was being used everywhere for multilingual I tried altering all the migrations. Seems to work locally.

Now alter any migration_lookup in core that has a source migration of dN_node* to use dN_node_master and add a plugin to that pipeline that will retrieve the desired IDs. That is needed because dN_node_master has 3 source IDs but dN_node has 1 source ID and dN_node_translation has two. For example, a lookup on d7_node will return the nid but a lookup on d7_node_translation expects two ids to be return, the nid and the language. The new process plugins handle that.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new3.02 KB
new124.17 KB

Some fixes and code cleanup. Only alter migrations that are not dN_node family.

The changes so for for migration_lookups won't work. More on that later - I have to run to catch a bus.

heddn’s picture

I'm concerned that we might run into BC issues. If we didn't have to concern ourselves w/ existing migrations, this would be a lot easier.

That said, the proposed solution is probably the *only* path forward. And it might be a good reason to keep the migrate_drupal_multilingual module though as the way to tell core and migrate_upgrade to use the master migration in place of the existing "normal" migrations.

TLDR; +1 on architecture. Need more discussions on actual implementation.

quietone’s picture

@heddn, thanks good to have confirmation of mikelutz's node_master work.

Using migrate_drupal_multilingual to tell core and migrate_upgrade to use the master migration in place of the existing "normal" migrations is perhaps changing the meaning of that module. Right now that module only says 'this is multilingual' whereas for node_master we need something that says 'this is multilingual with revision translations'. Now for core UI that may be fine, after all the idea is to migrate a copy of the original site which should include revisions. That's OK, just want to be clear on the point.

But the real challenge is the existing migrations. Hopefully it is limited to the subset where migration_lookup on dn_node/node_revision/node_translation migrations. I think we need to alter those migrations to use a new smart wrapper around migration lookup that will check both the dn_node/node_revision/node_translation table or dn_node_master to find the destination ids. We could avoid altering those migrations by creating 'copies' of the affected migrations and change migration_lookups to use the new smart wrapper around migration lookup and do some unsetting of migrations to remove the ones we don't want to run. Make sense?

Are there other cases where the existing migrations will fail?

mikelutz’s picture

You know, random thought. I didn't actually get round to building anything out for #3061676-2: Create an audit plugin class/manager for migrations, but theoretically, that might be an excellent place to detect the existence of revisions+translations in d(6|7), ask the user if they even want to migrate revisions, and if so, disable the standard node migrations, enable the master one, and even tweak any other dependant migrations that might need a process adjustment.

I think migration_lookup issues will be helped by #3004927: Create Migration Lookup and Stub services and possibly a more advanced lookup plugin based on that service. The service has better handling of lookups with a subset of the source keys, and would be useful if we are running a master migration and need to lookup based only on nid for example.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new8.08 KB
new122.12 KB

Yes, I am looking forward to the migration lookup service.

This is a better way to do the migration lookup, a process plugin that will check both the original dn_node migration and if not found check the dn_node_master. It should be smarter and check the migration property so that if it is dn_node_master it checks that first.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new5.26 KB
new123.3 KB

And this is an even better way to change the migration_lookups. The Upgrade7Test now has two failures, d7_book and d7_comment. I haven't run Upgrade6Test locally.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new3.88 KB
new124.02 KB

I accidentally removed all the code in the migrations_plugin_alter that was altering the destination and the dependencies. This restores that code. And a change to the _isMultilinugalSource function in used in the alter so that it checks that the definition of the migration 'system_site' exists before using it.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new6.16 KB
new126.52 KB

Add the patch from #3082719: migrate_drupal_migration_plugins_alter() should only alter definitions that exist for a problem I discovered while working on this. And fix coding standard errors.

There will still be failures.

quietone’s picture

StatusFileSize
new4.72 KB
new126.41 KB

I made a small mistake in that patch.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new4.44 KB
new160.88 KB
new104.04 KB

One way to fix the Kernel tests is to create a copy of the drupal6 and drupal7 test fixtures and change the status of the 'i18n' module to 0/uninstalled. That way the master_node migrations are not used and the test can proceed as it did before. It would be nice to use migrateDumpAlter() to do this but that happens after the migration_plugin_alter. I've only changed the source fixture on the failing tests. This may not be the best solution but it is a step forward.

The interdiff excludes changes to the fixtures.

quietone’s picture

Well, the Kernel tests are passing now, that just leaves the Functional tests.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new166.02 KB
new10.1 KB

Needs a reroll.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new1.63 KB
new167.26 KB

Fix d7/MigrateNodeRevisionTest.php and remove unused use statement.

quietone’s picture

Now, back to fixing the functional tests. The Upgrade7Test looks straightforward but I need to bring up a D7 site and run the full migration to compare results before making a new patch. Unfortunately, Upgrade6Test is failing on an error I don't recall seeing before so will take some time.

1) Drupal\Tests\migrate_drupal_ui\Functional\d6\Upgrade6Test::testMigrateUpgradeExecute
Exception: TypeError: Argument 1 passed to Drupal\forum\ForumIndexStorage::updateIndex() must implement interface Drupal\node\NodeInterface, null given, called in /var/www/html/core/modules/forum/forum.module on line 264
Drupal\forum\ForumIndexStorage->updateIndex()() (Line: 93)
quietone’s picture

The d6 error is happening here

  if ($comment->getCommentedEntityTypeId() == 'node') {
    \Drupal::service('forum.index_storage')->updateIndex($comment->getCommentedEntity());
  }

and is related to comments which is a nuisance as I haven't worked with the forum or the comment migrations before. Anyone have some pointers?

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new6.13 KB
new172.46 KB

OK, D7 failures are from followupMigrations. So make sure the followup migrations run when the node_master migration is being run.

quietone’s picture

Status: Needs work » Needs review

That fixed Upgrade7Test leaving one failing test, Upgrade6Test.
Anyone keen to test this with Drupal 7, go right ahead. You will have to use the UI for testing.

quietone’s picture

I have run the failing test, Upgrade6, 3 times over the past few times and it always passes. I do not know why it is failing with the test bot.
I have also run the upgrade manually, setting up the D8 site the same as in the test. Again, the migration complete successfully and the Forum node display correctly with all the comments.
I am going to start a retest, not that I expect anything different. Ideas welcome.

quietone’s picture

Yicks new disk and rebuild most of dev environment and Upgrade6Test takes way too long, "The test run finished in 1 hour 21 min.".
It does fail locally. Now to run it again an preserve the tables.

quietone’s picture

Ran the test again and saved the tables. No migrate messages in forum or comment migrations and nothing in watchdog either. Anyone have a suggestion?

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new2.98 KB
new175.44 KB

Ah, now have a failing migration test for the problem. No solution, just the test.

quietone’s picture

Issue summary: View changes

Trying to fix formatting of list in IS with help from tbradbury

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new13.11 KB
new175.79 KB

Yes, it was d6_term_node causing the test to fail. This patch should fix it by going back to the way the migration lookups were done in #160. If you read the code the comments are not all updated - I've had a tough day with my local test servers losing their ip address in the middle of running tests.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new10.26 KB
new178.49 KB

Right, of course, can't change all the processes to use node_master because the tests run with a multilingual source so the pipeline needs to change so the migration tests and the Upgrade tests work. So, let's try this.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new178.49 KB
new1.31 KB

Fix two typos.

quietone’s picture

StatusFileSize
new743 bytes
new178.49 KB

It is a day for typos. Found another one.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new178.16 KB
new1.07 KB

Fix entity count on Upgrade 7 test.

quietone’s picture

Status: Needs work » Needs review

More that disappointing that Upgrade6 is still failing. Any help appreciated.

quietone’s picture

StatusFileSize
new7.6 KB
new180.42 KB

Since node master will be the default we need to check if a migration has already been run on this site using the dN_node. Just checking if one has run isn't sufficient because it would prevent the running of future incrementals using node master. Instead check the counts in the migrate_map tables for node and node_migration.

I'm looking at the comment tests for the failure in Upgrade6.

quietone’s picture

StatusFileSize
new858 bytes
new180.51 KB

Lots of failing tests so I aborted the test run. Just add a check that the table exists before accessing it.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new4.88 KB
new181.49 KB

I learned about schema()->findTables so that is now used in migrate_drupal_migrations_plugin_alter to help determine if the master migrations are to be used. But then there was still no way to know if the master migration was to be used in a kernel test so nothing better came to mind other than making a module (sort of like migate_drupal_multilingual). I've been having a bit of a bother running functional tests so will have to use the test bot.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new2.83 KB
new184.4 KB

A few small adjustments to correctly use the master migration in the functional test.

quietone’s picture

The message table, d6_node_master__story, has "Invalid translation language (und) specified. (/opt/sites/d8/core/lib/Drupal/Core/Entity

Seems that 'und' is not valid for story content type. Don't know why.

quietone’s picture

Stepped through Upgrad6Test on HEAD and found that the language for node/1 is 'und' in the destination but somewhere in the import process it gets changed to 'en'. With this patch that does not happen and the language remains 'und' and fails later when forum.module tries to access node/1, which failed to migrate.

quietone’s picture

Now with the failing test:

So in EntityContentBase it checks if this is a translation destination, which it is. And when $entity->addTranslation($language) is executed an InvalidArgumentException("Invalid translation language 'und' specified.") is thrown.

The difference is that without this patch when node/1 is imported the destination is from d6_node which is not configured as a translation destination.

This is happening for the story content type, not for the other types. Why? Questions questions...

quietone’s picture

In SqlContentEntityStorage::mapFromStorageRecords the bundle for node/1 vid/1 is set to page when it should be story. No idea why yet.

quietone’s picture

There is an existing entry in the node table before any migrations start. I didn't expect this but finally the light bulb went off. Before the test starts content is created \Drupal\Tests\migrate_drupal\Traits\CreateTestContentEntitiesTrait::createContent(), for both Upgrade6 and Upgrade7, to allow checking for existing conflicts.

I don't have the whole picture yet but for Update6, it is something like this. On import of vid/1, type 'story', it is discovered that there is an existing vid/1, type 'page'. The existing vid/1 is loaded and the to be imported row is set to type 'page' and then it is treated as a translation. The saving of the translation fails because 'und' is not a valid translation language.

What makes Upgrade7 different? One, there are no nodes in the fixture of type 'page' and two, nid/1, vid/1 is of type 'test_content_type'. After that I am not sure why it doesn't fail as well.

MariaDB [d8]> select * from test85010523node;
+-----+------+------+--------------------------------------+----------+
| nid | vid  | type | uuid                                 | langcode |
+-----+------+------+--------------------------------------+----------+
|   1 |    1 | page | dc5004aa-c6a2-48b8-95f2-e553b9842490 | en       |
+-----+------+------+--------------------------------------+----------+
1 row in set (0.000 sec)
quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new183.91 KB
new834 bytes

First, a reroll

quietone’s picture

StatusFileSize
new183.72 KB
new2.1 KB

Now, lets try making each revision a new revision. Seems worth a try and I am about to leave for night class.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new184.29 KB
new2.32 KB

Ignore #219.

This starts from #218 and changes d6 and d7 MigrateNodeMasterTest to create content and thus will show the fail. Finally.

quietone’s picture

Status: Needs work » Needs review

So, existing content on the site is causing the failure. When there is existing content on the site and a vid of the existing data is the same as a vid of an imported row, that row will fail. As will all rows that have that node id.

One thing that happens is that the content type is being changed, the same is happening in D6 but it tries to add the row as a translation so there is a different error message than the one show here for d7.

1) Drupal\Tests\node\Kernel\Migrate\d7\MigrateNodeMasterTest::testNodeMasterMigration
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
     0 => Array (
         'nid' => '1'
         'vid' => '1'
-        'type' => 'test_content_type'
+        'type' => 'page'

Right now I don't have a fix in mind but it is late. If anyone has ideas, please speak up.

quietone’s picture

StatusFileSize
new17.02 KB
new198.7 KB

Thinking about this I thought the best way forward was treat the upgrade as we always said, that it was to be to an empty D8 site, minimal install. Since then I've chatted with mikelutz in Slack who come to the same conclusion.

@mikelutz @heddn:
    Can you shine some light on how to fix the failure in node rev translations due to existing content? https://www.drupal.org/project/drupal/issues/2746541#comment-13292069
mikelutz:iconinteractive: 
    The bigger question for me is, how was the system ever working before with existing content?
mikelutz:iconinteractive:
    I mean, I realize we threw up a warning about content being overwritten and everything, but how did the system ever deal with content types changing in the overwritten nodes, or worse when an existing vid mapped to a different existing nid than what is coming in?
quietone:
    It has to do with the fact that d6_node ran first and imported that latest vid for that nid. I think it is nid/1, vid/2001 in this case. So, when importing the revision vid/1 it would handle it correctly, it would figure out it is a new revision, determine the content_type correctly as well as determine that it is not a translation.
mikelutz:iconinteractive: 
    But that’s pure luck in that the test only created one piece of content.  If the existing content in the site happened to have vid 2001 assigned to node 12, then the old system would have failed too.
mikelutz:iconinteractive:
   I suspect the whole concept of running these migrations with existing content is completely fubared already.
mikelutz:iconinteractive:
   I think the only reasonable thing to do with migrate_drupal_UI is to $node_storage->delete($node_storage->loadMultiple()); before running the migrations.
quietone:
   Maybe. But certainly not if they have already run a migration.

This patch begins to do that, to delete the added nodes. It is done in the MigrateNodeMaster Kernel tests and started in the functional tests. The Upgrade6 and Upgrad7 tests will have failures because of the id conflict. Those functional tests are not easy to change and, well, cleaning them up needs its own issue.

edit: tried to improve the formatting in the Slack discussion

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new203.48 KB
new10.41 KB

Cleaning up the failing tests.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new5.42 KB
new199.77 KB

Bother, the namespace for the tests got mixed up. Also removed some testing that does not need to be done here.
But did you notice that Upgrade6Test passed. Yea!

sinasalek’s picture

I check this issue on daily basis. Great job @quietone can't wait to see this land

quietone’s picture

StatusFileSize
new133.26 KB
new194.43 KB

@sinasalek, thanks. Some testing of this would be most welcome. Are you able to do some and report back?

Just removing a file that I thought would be needed but isn't and I forgot to delete it and adding a review only patch.

Now that we have passing tests, I want to update the IS to explain the changes. I think the patch here should be merged into the entity translation revision issue as well, to make sure all is well there.

quietone’s picture

Version: 8.9.x-dev » 8.8.x-dev
StatusFileSize
new151.6 KB
new3.02 MB

Now, remove test files. There were made to help chase down problems. Often while hunting for the cause of failures in Upgrade6Test and while using the state of the i18n module in the source to determine the migration is of type master or not. No longer strictly needed. Of course, we can always add them back.

  • d6/MigrateCommentTest.php
  • d7/MigrateCommentTest.php
  • d6/MigrateForumMasterTest.php
  • d6/MigrateDrupal6NoI18nTestBase.php
  • d7/MigrateDrupal7NoI18nTestBase.php
  • d6/MigrateTermNodeTranslationTest.php
  • Changes to /migrate_drupal/tests/src/Kernel/dependencies/MigrateDependenciesTest.php
  • Although maybe we need a test with node master.

The interdiff is > 3MB but here it is anyway.

quietone’s picture

Version: 8.9.x-dev » 8.8.x-dev

While reviewing the files in the patch to find test files no longer needed I made a list (I think in lists). Here it is for your reading pleasure.

  1. The new migrations
    • d6_node_master.yml, d7_node_master.yml
  2. Node Master destination
    • /migrate/src/Plugin/migrate/destination/EntityContentBase.php,
      /migrate/src/Plugin/migrate/destination/EntityContentMaster.php and
      /migrate/src/Plugin/Derivative/MigrateEntityMaster.php
  3. Node Master source plugins
    • d6/NodeMaster.php and d7//NodeMaster
  4. Significant changes to use Node Master by default
    • /migrate_drupal/src/MigrationConfigurationTrait.php - a) unset the d*_node migrations when this is a
      master
      migration and b) change the processes that do a migration_lookup on d*_node to also use d*node_master.
    • /migrate_drupal/migrate_drupal.module - a)change d*_node* to d*_node_master in the destination and
      migration
      dependences and b) change the processes that do a migration_lookup on d*_node to also use d*node_master.
  5. Process used to get the correct id to return. Node_master uses 3 ids, nid/vid/langcode where d*_node and
    d*node_translation use nid and d*_node_revision uses vid.
    • /migrate_drupal/src/Plugin/migrate/process/NodeMasterNodeLookup.php, NodeMasterNodeRevisionLookup.php
      and
      NodeMasterNodeTranslationLookup.php
  6. Fixture changes to drupal6.php and drupal7.php
  7. Tests
    • d6/FollowUpMigrationsTest.php and d7/FollowUpMigrationsTest.php
      • Add test of master migration.
    • d6/MigrateDrupal6AuditIdsTest.php and d7/MigrateDrupal7AuditIdsTest.php
      • Add migration to a list for the test. Been awhile don't remember the details.
    • IdConflictForm.php
      • If this is a master migration then get the correct audit results for node revisions.
    • /d6/Upgrade6Test.php, d7/Upgrade7Test.php
      • Delete content added for idConflict page test.
    • node_migrate_master
      • A module to indicate if the test is using the master method.
    • d6/MigrateNodeMasterTest.php and d7//MigrateNodeMasterTest.php
      • Test master node methods.
    • /d6/MigrateNodeTest.php, d7/MigrateNodeTest.php, d6/MigrateNodeRevisionTest.php and
      d7/MigrateNodeRevisionTest.php
      • Reflect fixture changes.
  8. /migrate/src/Plugin/migrate/id_map/Sql.php
  9. /migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php, d6/IdConflictTest.php,
    d7/IdConflictTest.php

Edit; trying to fix formatting

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new909 bytes
new150.71 KB

Missed removing some changes to a test file

alexpott’s picture

StatusFileSize
new999 bytes
new150.36 KB

Rerolled and removed an unneeded file.

alexpott’s picture

StatusFileSize
new8.49 KB
new152.89 KB

Partial review and patch addressing some of it.

  1. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
    @@ -264,6 +264,9 @@ protected function updateEntity(EntityInterface $entity, Row $row) {
    +          if ($this->storage->getEntityType()->isRevisionable() && $entity->isNewRevision()) {
    +            $entity->setNewRevision(TRUE);
    +          }
    

    This needs a comment. It looks tautological ie. if new revision make it a new revision.

  2. +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
    @@ -971,14 +971,10 @@ protected function getMigrationPluginManager() {
    +    // Ensure that at least one Id is an integer.
    

    ID. Fixed in patch.

  3. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +90,201 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +  if (_useMasterNodeMigration($definitions) === 'MASTER') {
    ...
    +/**
    + * Determines is the node master migration is to be used.
    + *
    + * The node master migration is the default. It is not used when there
    + * are existing tables for dNnode.
    + *
    + * @return string
    + *   Indicator of the node migration map tables in use.
    + */
    +function _useMasterNodeMigration($definitions) {
    

    Let's mark this @internal and use Drupal naming standards. Fixed.

  4. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +90,201 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +/**
    + * Add dN_node_master to the migrations array of a migration_lookup plugin.
    + */
    +function _insert_migration($migrations) {
    

    Let's mark @internal. Fixed.

  5. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +90,201 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +    $useMasterNode = 'MASTER';
    ...
    +      $useMasterNode = 'BOTH';
    ...
    +      $useMasterNode = 'NOT_MASTER';
    

    FIXED: These should be constants.

  6. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +90,201 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +    // Save the migration type in state.
    +    $state = \Drupal::service('state');
    +    $state->set('migrate_drupal.master', $useMasterNode);
    
    +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -273,4 +296,52 @@ protected function getState() {
    +    // Save the migration type in state.
    +    $state = $this->getState();
    +    $state->set('migrate_drupal.master', $useMasterNode);
    

    As far as I can see this state value is never used. Do we need it?

quietone’s picture

StatusFileSize
new2.15 KB
new152.8 KB

@alexpott, thanks for the review and fixes.

#1. Ah, that one. I forgot to come back to it. That was meant to be a temporary for what I found in #139. For now, I have added a comment and todo.
#6. I think you are right. It was an earlier idea I played with. It is removed now.

So, let's confirm there are no errors with the removed code (#6). In the meantime I will look at the failing test related to #1.

quietone’s picture

StatusFileSize
new834 bytes
new151.73 KB

Now, remove the new code mentioned in #241-1 and let's see what errors we get.

quietone’s picture

Tests are passing, a good sign. This removes all changes to EntityContentBase.php that were introduced in this patch, which was simply to make a new revision whenever the entity is revisionable in updateEntity.

if ($this->storage->getEntityType()->isRevisionable()) {
  $entity->setNewRevision(TRUE);
}

This was causing failures in d6/MigrateUploadTest because it was making a new revision for each migrated node. When it saved the nodes of type page the version id was changed and then when the upload migration ran, the vid no longer matched the source and we get the error "Update existing 'node' entity revision while changing the revision ID is not supported."

alexpott’s picture

Issue tags: +Needs change record
StatusFileSize
new4.38 KB
new151.96 KB
  1. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
    @@ -0,0 +1,117 @@
    +   * @return \Drupal\Core\Entity\EntityInterface|false
    +   *   The entity or false if it can not be created.
    +   */
    +  protected function getEntity(Row $row, array $old_destination_id_values) {
    

    FIXED: Looking at the code I don't think this can ever return false.

  2. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
    @@ -0,0 +1,117 @@
    +    if (!empty($revision_id) && ($entity = $this->storage->loadRevision($revision_id))) {
    +      $entity->setNewRevision(FALSE);
    +    }
    +    else {
    +      if (($entity_id = $row->getDestinationProperty($this->getKey('id'))) &&
    +        ($entity = $this->storage->load($entity_id))) {
    +        $entity->enforceIsNew(FALSE);
    +        $entity->setNewRevision(TRUE);
    +      }
    +      else {
    +        // Attempt to ensure we always have a bundle.
    +        if ($bundle = $this->getBundle($row)) {
    +          $row->setDestinationProperty($this->getKey('bundle'), $bundle);
    +        }
    +
    +        // Stubs might need some required fields filled in.
    +        if ($row->isStub()) {
    +          $this->processStubRow($row);
    +        }
    +        $entity = $this->storage->create($row->getDestination());
    +        $entity->enforceIsNew();
    +      }
    +    }
    

    FIXED: We can simplify this a bit

  3. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +107,210 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +      if (!preg_match('/d([67])_(node|node_translation|node_revision)($|:.*)/', $definition['id'])) {
    +        $properties = ['destination', 'migration_dependencies'];
    +        foreach ($properties as $property) {
    +          if (isset($definition[$property])) {
    +            array_walk_recursive($definition[$property], function (&$value) {
    +              if (is_string($value)) {
    +                $value = preg_replace('/d([67])_(node|node_translation|node_revision)($|:.*)/', 'd$1_node_master$3', $value);
    +              }
    +              return $value;
    +            });
    +          }
    +        }
    +      }
    

    FIXED: This can be made a little bit simpler in terms of number of loops and conditions.

  4. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +107,210 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +      // Pipeline does not extract the migration_lookup return value.
    +      if (preg_match('/d6_term_node($|:.*)/', $definition['id'])) {
    +        $tmp = $definition['process']['nid'][0]['migration'];
    +        $definition['process']['nid'][0]['migration'] = _insert_migration($tmp);
    +        $definition['process']['nid'][2] = [
    +          'plugin' => 'node_master_node_lookup',
    +        ];
    +      }
    +      // Pipeline does not extract the migration_lookup return value.
    +      if (preg_match('/d6_term_node_revision($|:.*)/', $definition['id'])) {
    +        $tmp = $definition['process']['vid'][0]['migration'];
    +        $definition['process']['vid'][0]['migration'] = _insert_migration($tmp);
    +        $definition['process']['vid'][2] = [
    +          'plugin' => 'node_master_node_revision_lookup',
    +        ];
    +      }
    +      // Pipeline does not extract the migration_lookup return value.
    +      if (preg_match('/d6_term_node_translation($|:.*)/', $definition['id'])) {
    +        $tmp = $definition['process']['dest_nid'][0]['migration'];
    +        $definition['process']['dest_nid'][0]['migration'] = _insert_migration($tmp);
    +        $definition['process']['dest_nid'][2] = [
    +          'plugin' => 'node_master_node_translation_lookup',
    +        ];
    +      }
    +    }
    +
    +    // Pipeline does not extract the migration_lookup return value.
    +    if (isset($definitions['d6_comment'])) {
    +      $tmp = $definitions['d6_comment']['process']['entity_id'][0]['migration'];
    +      $definitions['d6_comment']['process']['entity_id'][0]['migration'] = _insert_migration($tmp);
    +      $definitions['d6_comment']['process']['entity_id'][2] = [
    +        'plugin' => 'node_master_node_lookup',
    +      ];
    +    }
    +    // Pipeline does not extract the migration_lookup return value.
    +    if (isset($definitions['d6_url_alias'])) {
    +      $tmp = $definitions['d6_url_alias']['process']['node_translation'][2]['migration'];
    +      $definitions['d6_url_alias']['process']['node_translation'][2]['migration'] = _insert_migration($tmp);
    +      $definitions['d6_url_alias']['process']['node_translation'][3] = [
    +        'plugin' => 'node_master_node_translation_lookup',
    +      ];
    +    }
    +    // Pipeline does not extract the migration_lookup return value.
    +    if (isset($definitions['d7_comment'])) {
    +      $tmp = $definitions['d7_comment']['process']['entity_id'][0]['migration'];
    +      $definitions['d7_comment']['process']['entity_id'][0]['migration'] = _insert_migration($tmp);
    +      $definitions['d7_comment']['process']['entity_id'][2] = [
    +        'plugin' => 'node_master_node_lookup',
    +      ];
    +    }
    +    // Pipeline does not extract the migration_lookup return value.
    +    if (isset($definitions['d7_url_alias'])) {
    +      $tmp = $definitions['d7_url_alias']['process']['node_translation'][2]['migration'];
    +      $definitions['d7_url_alias']['process']['node_translation'][2]['migration'] = _insert_migration($tmp);
    +      $definitions['d7_url_alias']['process']['node_translation'][3] = [
    +        'plugin' => 'node_master_node_translation_lookup',
    +      ];
    +    }
    +    // Pipeline does not extract the migration_lookup return value.
    +    if (isset($definitions['statistics_node_counter'])) {
    +      $tmp = $definitions['statistics_node_counter']['process']['nid'][0]['migration'];
    +      $definitions['statistics_node_counter']['process']['nid'][0]['migration'] = _insert_migration($tmp);
    +      $definitions['statistics_node_counter']['process']['nid'][2] = [
    +        'plugin' => 'node_master_node_lookup',
    +      ];
    +    }
    +    // Pipeline extracts the first item of the migration_lookup return value.
    +    if (isset($definitions['node_translation_menu_links'])) {
    +      $tmp = $definitions['node_translation_menu_links']['process']['new_nid'][4]['migration'];
    +      $definitions['node_translation_menu_links']['process']['new_nid'][4]['migration'] = _insert_migration($tmp);
    +    }
    

    These alters concern me. Doesn't this mean that contrib and custom are going to have to do a lot of work to support this? And what happens if _use_master_node_migration() detects that they should be using MASTER migrations but they got contrib / custom migrations that depend on the old ones?

  5. +++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/FollowUpMigrationsTest.php
    @@ -31,15 +31,32 @@ protected function setUp() {
    +  /**
    +   * Test entity reference translations using d6_node and d6_node_translation.
    +   */
    +  public function testEntityReferenceTranslations() {
    +    $this->executeMigrations([
           'd6_node',
           'd6_node_translation',
         ]);
    +    $this->entityTranslationTest();
    +  }
    +
    +  /**
    +   * Test entity reference translations using d6_node_master migration.
    +   */
    +  public function testEntityReferenceTranslationsNodeMaster() {
    +    $this->executeMigrations(['d6_node_master']);
    +    $this->entityTranslationTest();
       }
     
    
    +++ b/core/modules/migrate_drupal/tests/src/Kernel/d7/FollowUpMigrationsTest.php
    @@ -68,9 +66,28 @@ protected function getFileMigrationInfo() {
       public function testEntityReferenceTranslations() {
    +    $this->executeMigrations([
    +      'd7_node',
    +      'd7_node_translation',
    +    ]);
    +    $this->entityTranslationTest();
    +  }
    +
    +  /**
    +   * Test entity reference translations using d7_node_master migration.
    +   */
    +  public function testEntityReferenceTranslationsNodeMaster() {
    +    $this->executeMigrations(['d7_node_master']);
    +    $this->entityTranslationTest();
    +  }
    

    Clever and nice to see. One question is shouldn't we be able to test for a better migration when using the d*_node_master migrations. Or are we proving here that the migrations are equivalent in the simple case?

  6. +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6Test.php
    @@ -35,13 +35,26 @@ class Upgrade6Test extends MigrateUpgradeExecuteTestBase {
    +    $this->nodeStorage = $this->container->get('entity_type.manager')
    +      ->getStorage('node');
    +    $this->nodeStorage->delete($this->nodeStorage->loadMultiple());
    
    +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php
    @@ -37,13 +37,25 @@ class Upgrade7Test extends MigrateUpgradeExecuteTestBase {
    +    $this->nodeStorage = $this->container->get('entity_type.manager')
    +      ->getStorage('node');
    +    $this->nodeStorage->delete($this->nodeStorage->loadMultiple());
    

    It is really interesting that the current d*_node migrations work fine with existing content but this new one doesn't. What's happening on HEAD atm? At the very least I think this needs a comment.

  7. I think we need a really good change record here to describe the new node master migrations and how to use them
quietone’s picture

StatusFileSize
new1.93 KB
new152.61 KB

@alexpott, again, thanks for the review and cleanup.

245.4. Agree, this needs explanation. One thing I will point out now is that the altering is done to add node_master to the migration lookups. Migration lookup will search the map tables for all listed migrations until it finds a match and return that value. Core already uses that feature in some migrations.
245.5 This test is just proving that the FollowUpMigrations work with node master, nothing more. I am not sure what you mean by a 'better' migration but there is a question to answer about what to test both ways. Do we change all the migration Kernel tests to add a test with using node_master? That will be a big patch itself. Better, what subset, if any, do we want to see here? The Functional test of the full migration should be using node master.
245.6 This is because the full upgrade test has had everything thrown at it and it is doing to much. In this specific case, it is adding content to test the ID conflict form. That should be a separate test and is being done in #2974445: [Meta] Refactor Migrate Drupal UI Functional tests, which would be better to get committed before this issue. There is also an issue to refactor all the functional tests in migrate_drupal_ui, which I've finally started poking at. Comment added.

TODO:
254.4. More explanation needed
254.7 Add CR

edit: change formatting of 'add' to emphasis

quietone’s picture

254.4. 245.4. I think there are 4 cases to consider, migrations as configuration, running migrations with migrate_run or migrate_manifest, a one off migration via the UI, and incrementals via the UI. Did I miss one?

  1. migrations as configuration - Should be no impact here. The migration in use are in configuration. Choosing to reconfigure is an active decision by a developer. If they have modified any of the migrations being altered they are not going to reconfigure them anyway. If they bring them in anyway, the alter detects if the not node master was used and if it was then the migrations are not altered.
  2. running migrations with migrate_run or migrate_manifest - The alter detects if the not node master was used and if it was then the migrations are not altered.
  3. a one off migration via the UI - Node master will be used.
  4. incrementals via the UI
    • Post a node master migration via the UI - The alter detects if the not node master was used and if it was then the migrations are not altered.
    • Post a not node master migration via the UI - The alter detects if the not node master was used and if it was then the migrations are not altered.

This key here is the function _use_master_node_migration and method MigrationConfigurationTrait:useMasterNodeMigration which determines if this is a master or not master migration. They search for tables named migrate_map_d*_node* but not migrate_map_d*_node_master. If any are found then, and only then, will the migrations be altered.

Just as finished writing that I realize there should be a test of the not node master incremental, another version of Upgrade6Test and Upgrade7Test.php.

Oh and how about instead of NODE_MIGRATE_TYPE_NOT_MASTER we could use NODE_MIGRATE_TYPE_CLASSIC?

heddn’s picture

Status: Needs review » Needs work

I like NODE_MIGRATE_TYPE_CLASSIC.

In regards to 245.4, this shouldn't effect migrate_upgrade (and migrate_tools/migrate_plus config entities) because the alters to the migrations would happen prior to the config entities being generated. So we should be good to go there. Some feedback below:

  1. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -12,6 +12,23 @@
    +const NODE_MIGRATE_TYPE_MASTER = 'MASTER';
    ...
    +const NODE_MIGRATE_TYPE_NOT_MASTER = 'NOT_MASTER';
    ...
    +const NODE_MIGRATE_TYPE_BOTH = 'BOTH';
    

    I don't really like these as .module file constants. But I can't come up with a better location to store them. Maybe new interface in node module?

  2. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +107,215 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +  // If this source database is multilingual then we are only running the
    +  // dN_node_master migration and not any other dN_node* migration.  Alter all
    +  // instances of migration_lookup in core migrations to use dN_node_master.
    +  if (_use_master_node_migration($definitions) === NODE_MIGRATE_TYPE_MASTER) {
    

    Could we encapsulate all this into a new utility class? Then we could add the constants to it from above and have something self contained that could be utilized by others (contrib/custom), if it is needed.

    It would also make things more testable.

  3. +++ b/core/modules/migrate_drupal/src/Plugin/migrate/process/NodeMasterNodeLookup.php
    @@ -0,0 +1,36 @@
    + * When the source database has i18n enabled d7_node migrations are not run and
    + * when used in the migration process or dependencies they are replaced with the
    
    +++ b/core/modules/migrate_drupal/src/Plugin/migrate/process/NodeMasterNodeRevisionLookup.php
    @@ -0,0 +1,36 @@
    + * When the source database has i18n enabled d7_node migrations are not run and
    + * when used in the migration process or dependencies they are replaced with the
    

    The wording here seems complicated. Not sure I follow it. Might need some extra commas.

  4. +++ b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php
    @@ -40,6 +41,51 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +    // If dN_node_master migration is being used get the audit results for node
    

    being used, get the audit

  5. +++ b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php
    @@ -40,6 +41,51 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +    // revisions manually. We need to do this manually because the node master
    +    // map has the ids for both nodes and revisions and Sql::getHighestId()
    +    // only returns the highest migrated ID of the destination entity type,
    +    // which for node_master is node.
    +    $migration_plugin_manager = \Drupal::service('plugin.manager.migration');
    

    Instead of inserting all of this directly into the form, can we create a new class, say NodeMasterIdAuditor to encapsulate all this logic? We should probably add an auditor manager at some point, but that's a bigger change.

  6. +++ b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php
    @@ -40,6 +41,51 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +        // Make a migration based on node_master but with and entity_revision
    

    an entity_revision...

  7. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +107,215 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +    foreach ($bases as $base) {
    ...
    +      $base_tables = preg_grep('/^migrate_map_d' . $version . '_' . $base . '_{2}.*$/', $tables);
    

    Could we add some more comments (with an example) of what should and should not pass this check. I'm not sure what is the expected pass/fail of these conditions.

  8. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +107,215 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +      $exists = $base . '_exists';
    +      $$exists = FALSE;
    

    This fancy variable logic could use a comment.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new6.7 KB
new153.33 KB

Cool, another review! thanks.

Comment fixes only. #248: 3, 4, 6, 7 and 8.

quietone’s picture

StatusFileSize
new154.87 KB
new12.21 KB

Now changes for #248: 1 and 2. Not sure about these changes nor the names I've chosen.

Still to do 248.5.

heddn’s picture

Status: Needs review » Needs work

NW for 248.5. And a little feedback.

  1. +++ b/core/lib/Drupal/Core/Utility/MigrateType.php
    @@ -0,0 +1,115 @@
    +/**
    + * Provides a class to determine the type of migration.
    + */
    +class MigrateType implements MigrateTypeInterface {
    

    I'm hesitant to put this in the general namespace. Probably in Migrate Drupal or Node is fine. It would be interesting though if Node module isn't enabled, so I'm not sure how that would all shake out. And we probably don't need an interface as I can't think of any scenario where we'd want someone else to swap it out. In fact, the whole class can probably be Final since we don't want folks changing the logic.

    This is all about node migrations, so why not call the class NodeMigrationType.

  2. +++ b/core/lib/Drupal/Core/Utility/MigrateType.php
    @@ -0,0 +1,115 @@
    +      $use_master_node = MigrateType::NODE_MIGRATE_TYPE_MASTER;
    

    Here and elsewhere, inside this class, the constants can use static:: I believe.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new9.05 KB
new154.8 KB

Yes, that is a better idea. This patch moves that work, now called NodeMigrationType, to migrate drupal. I originally moved it to Node but then Kernel tests that were not in the node path were failing. For example. core/modules/system/tests/src/Kernel/Migrate/d6/MigrateSystemConfigurationTest.php, failed with

There was 1 error:

1) Drupal\Tests\system\Kernel\Migrate\d6\MigrateSystemConfigurationTest::testConfigurationMigration
Error: Class 'Drupal\Node\NodeMigrateType' not found

I'd appreciate an explanation of why the class is not found when it is in the node module. And, if for some reason we really wanted it to be in node, what would be the way to make it work?

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new19.13 KB
new168.71 KB

I'd say this would fix the tests but I found a problem in the plugin alter in that the destination was not being changed from 'entity:node' to 'entity:node_master'. This patch fixes that. I also added more tests to MigrationPluginAlterTest, including testing the destination and the migration_dependencies. Also, some comment updates.

quietone’s picture

Status: Needs work » Needs review

So, now we have migrate_drupal_migrations_plugin_alter doing what it should be doing and some better testing on it.

The failing tests are coming from the followup migrations, dn_entity_reference_translation.yml. That migration uses the d8 content_entity source plugin which returns one nid with an array of vids and passes those values straight through the pipeline. Not surprisingly, the destination plugin, EntityContentMaster, can't handle an array of vids. My first thought is to change the source plugin to return a unique row for each nid/vid/langcode. Is there a better approach?

quietone’s picture

StatusFileSize
new3.41 KB
new172.18 KB

Taking a close look at the d8 ContentEntity source plugin it was easy to add the revision id to the existing ids, and to do so only for revisionable entities. ContentEntityTest.php is changed to check for the revision id. So that piece is done. That can move to it's own issue.
But first, lets see what this does for the failing tests.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new10.76 KB
new173.28 KB

Looking further into the followup migration and I learned that because they are created after all the migration run, they do not get passed to migrate_drupal_migrations_plugin_alter(). There is the choice of adding the altering code to the EntityReferenceTranslationDeriver or having the driver call migrate_drupal_migrations_plugin_alter. I chose the latter so they use the same code.

Fixed a bug in the regex for replacing the classic node migrations with master node migration such that the d6_node_type destination was being changed. That is in the migrations_plugin_alter. And NodeMigrateType::useMasterNodeMigration() now correctly identifies the followup migrations.

Added two new test for testing the FollowupMigrations. New tests are needed so that the test module, 'node_migrate_master', is installed before any migrations run. Otherwise the migrations in the parent::setup() method run and there are failures.

Note: NodeMigrateType::useMasterNodeMigration() needs a name change since it returns a string not a boolean.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new1.37 KB
new172.73 KB

One coding standard fix and fix for the failing test d7\FollowUpMigrationsTest. The other two failures will have to wait.

If you are coming here to test, the migration test for Drupal 7 sources are passing, so you are welcome to test that. The failing test d7\FollowUpMigrationsTest does not use the node migrations that the node revision translation does, so it is unrelated.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new640 bytes
new173 KB

One test fix.

quietone’s picture

@alexpott, thanks. Unfortunately, while that test passes when node_migrate_master is installed, it needs to be run both with and without it. I'm hope to redesign that test today.

quietone’s picture

Just confirming that the remaining failure only affects Drupal6 sources. They are about the term node migrations, no surprise there.

Testing with Drupal 7 source is worthwhile.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new175.09 KB
new1.91 KB

Adding a test to show an error in d6_term_node.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new1.41 KB
new174.5 KB

Let's not alter the destination plugin.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new171.2 KB
new21.08 KB

Yep, no need to change the destination plugin, that was a mistake.
This is a rewrite of MifrationPluginAlterTest that is hopefully a bit easier to understand. I think there are sufficient test cases.
And I did a bit of renaming and updated doc blocks.

quietone’s picture

Issue summary: View changes
StatusFileSize
new1 KB
new171.44 KB

Testers - use this patch. Yes, despite the failure. There is a quick fix in here and the failures are expected.

Sorry for any confusion. The problem is in the logic of deciding if the migration should be of type master (the new one for node revision translations) or classic. And I have been testing so much with kernel tests that I didn't catch this. A proper fix is needed. This patch forces the migration to be the master type.

quietone’s picture

Issue summary: View changes

Clarify testing instructions

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new174.11 KB
new3.53 KB

Looking over the issue I found an item not addressed.
1) This makes a new class as per #248.5

quietone’s picture

StatusFileSize
new9.99 KB
new177 KB

Deciding if the node migration type is class or master is complicated by the fact that on a fresh install and in a test there will be no migration tables to aid the decision. When that happens via the UI we want to use the master migrations. But, when in a test, the test has to be able to decided which to use. I have finally gone for a not elegant solution where the test has declare the type of node migration. It does this by enabling one of two testing modules, node_migrate_classic or node_migrate_master, that are hidden and should not interfere with real migrations. Let's see what breaks.

quietone’s picture

StatusFileSize
new32.05 KB
new28.08 KB
new28.88 KB
new40.03 KB
new35.37 KB
new34.53 KB
new14.62 KB

Here are results of a simple test with a D7 source. One node, 3 languages, a few revisions, including a revert in each language. I didn't notice any problems with the node migration.

There was one error with the migration, 'The "block_content" entity type does not exist.', something I have not investigated.

gábor hojtsy’s picture

@queitone: sounds like the block_content problem should not be related unless this is applied to blocks?

quietone’s picture

Gábor Hojtsy, I agree it is not related. I want to do an incremental migration next.

quietone’s picture

StatusFileSize
new50.19 KB
new45.65 KB
new74.13 KB
new55.5 KB
new49.07 KB
new66.3 KB

This test includes an incremental and a revert. Again, just one node. Migrated the content, then added a revision to each language, en, it an pl, and a reverted pl to the first revision. The revision tabs for each match the d7 source.

quietone’s picture

StatusFileSize
new175.95 KB
quietone’s picture

Did manual testing with a multilingual database (en and zh-hans) that does NOT have revisions. The D8 site was a minimal install.

The errors were

The website encountered an unexpected error. Please try again later.

Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'd8.comment' doesn't exist: SELECT base_table.cid AS cid, base_table.cid AS base_table_cid FROM {comment} base_table GROUP BY base_table.cid ORDER BY base_table.cid DESC LIMIT 1 OFFSET 0; Array ( ) in Drupal\migrate\Plugin\migrate\destination\EntityContentBase->getHighestId() (line 409 of core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php).

This was from the IdConflict form login. I commented out that getHighestId() method in order for the migration to run.

After that the migration ran with no problems. The errors logged were about missing files (which I don't have), filter_null, ' Attempt to create a field without a field_name' (looks like one per content type).

Of those, getHighestId() needs to be fixed and I want to check which field did not have a field name.

The good news

The migration created tables only for d7_node_master and not d7_node, d7_node_translation and d7_node_revision.
Visuals checks of the source and destination node and node revision tables are OK.
Able to edit and save translated nodes.

quietone’s picture

StatusFileSize
new1.6 KB
new175.89 KB
Madhura BK’s picture

StatusFileSize
new166.47 KB

Hi,

I wanted to test this issue. So I started with the steps mentioned in "For Testers".
I have 8.9.x-dev version with minimal installation profile and I tried applying patch in #276.
But it does not apply.

alexpott’s picture

StatusFileSize
new1.73 KB
new168.55 KB

@Madhura BK here's a new patch - the very last commit on 8.9.x conflicts with this one as it is a small commit to make getting this one in a bit simpler. Also there is no need to upload an image of patch not applying - press the retest button the patch in #285 and that would tell everyone too...

Fixed some coding standards on the way too.

Madhura BK’s picture

@alexpott,

Can you please let me know on which minor version of drupal 8 I need to apply this patch.

I have tried applying patch #287 on Drupal 8.8.0.I get the following error:

error: patch failed: core/modules/migrate_drupal/tests/fixtures/drupal7.php:9037
error: core/modules/migrate_drupal/tests/fixtures/drupal7.php: patch does not apply
error: core/modules/migrate_drupal_ui/tests/src/Functional/d6/IdConflictTest.php: No such file or directory
error: core/modules/migrate_drupal_ui/tests/src/Functional/d7/IdConflictTest.php: No such file or directory

On Drupal 8.9.0 I get these errors:
error: core/modules/migrate_drupal_ui/tests/src/Functional/d6/IdConflictTest.php: No such file or directory
error: core/modules/migrate_drupal_ui/tests/src/Functional/d7/IdConflictTest.php: No such file or directory

alexpott’s picture

@Madhura BK you can see from the test run that this can be applied against the latest 8.9.x branch. There is no 8.9.0 as that has not been released yet.

quietone’s picture

Issue summary: View changes

@Madhura BK, thanks for starting to test this patch. I have correct the IS here so that it states to use the latest patch. You can also find support for testing this on Slack, #migrate. Just ping me.

quietone’s picture

The errors encountered in #284 are not related to this patch. They occur when testing without this patch and with the test fixture. See #3101438: getHIghestId queries tables that do not exist.

Madhura BK’s picture

StatusFileSize
new70.87 KB
new280.19 KB
new71.86 KB
new83.97 KB

Hi,

I have set up and run the migrations following the steps in "For Testers".

  • There are some errors displayed post migration.Have attached screenshot.
  • Post migration,there are no content items on Drupal 8 site
wim leers’s picture

This would really benefit from a change record.

(I discovered this earlier today, #2746517-15: Automatically derive auxiliary migrations pointed here as a reason to not do that issue anymore. Reading a >100 KB patch and a very long issue summary where it's not entirely clear what the BC implications are makes it hard to jump in and participate. A change record showing the current state of what changes would be communicated to the general public would help in gaining an understanding and participating here 🙏

quietone’s picture

Yes, this is complicated.

I have just started a CR but I have a bit of a headache so not making much progress.

firewaller’s picture

+1

wim leers’s picture

Status: Needs review » Needs work

Partial first review. Will continue tomorrow. Have also started actually testing this, but no conclusions/remarks for that yet. Below are mostly nits, but there's unfortunately a lot of them, that's why I'm already posting this partial review.

  1. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +91,118 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +    // are not altered..
    

    🔎 altered..altered.

  2. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +91,118 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +      // Pipeline does not extract the migration_lookup return value.
    ...
    +    // Pipeline does not extract the migration_lookup return value.
    

    🤔😅 I don't know what this means. Could we rephrase this slightly so that people who aren't migration system maintainers also understand it?

  3. +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -123,6 +123,29 @@ protected function getMigrations($database_state_key, $drupal_version) {
    +    if ($type === NodeMigrateType::NODE_MIGRATE_TYPE_MASTER) {
    ...
    +    if ($type === NodeMigrateType::NODE_MIGRATE_TYPE_CLASSIC) {
    ...
    +    if ($type === NodeMigrateType::NODE_MIGRATE_TYPE_BOTH) {
    

    🤓 Nit: this can/should be elseif, or perhaps better still: a switch statement?

  4. +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -273,4 +296,59 @@ protected function getState() {
    +   * @param array $migrations
    

    🤓 Nit: why not MigrationInterface[]?

  5. +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -273,4 +296,59 @@ protected function getState() {
    +   *   The legacy Drupal version.
    
    +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,151 @@
    +    // We need to get the version of the source database in order to check
    

    👎 Let's refer to this consistently — not "legacy Drupal version" in one place and "version of the source database" in another.

  6. +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -273,4 +296,59 @@ protected function getState() {
    +   *   Indicator of the node migration map tables in use.
    

    🤓 Nit: Shouldn't this say One of NodeMigrateType::…, NodeMigrateType::… or NodeMigrateType::….?

  7. +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -273,4 +296,59 @@ protected function getState() {
    +      // set it the default value, FALSE.
    

    🗣 Nit: "set it the default value" → something wrong here.

    (🗣 = English writing problem.)

  8. +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -273,4 +296,59 @@ protected function getState() {
    +      // Set the existence flag True when the migration exists and has a map
    

    🗣

  9. +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -273,4 +296,59 @@ protected function getState() {
    +    if ($node_exists && $node_master_exists) {
    +      $migrate_type = NodeMigrateType::NODE_MIGRATE_TYPE_BOTH;
    +    }
    +    if ($node_exists && !$node_master_exists) {
    +      $migrate_type = NodeMigrateType::NODE_MIGRATE_TYPE_CLASSIC;
    +    }
    

    🤔 This can be simplified.

  10. +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -273,4 +296,59 @@ protected function getState() {
    +    // This might be a test environment.
    

    🤔 Huh?

  11. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,151 @@
    +   * Indicates of the node migration map tables in use.
    

    🗣 "Indicates of" sounds pretty weird to my non-native speaker ears.

  12. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,151 @@
    +   *   An associative array of migration. The array is normally keyed by
    

    🗣"… array of migration."

  13. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,151 @@
    +  public function getNodeMigrateType(array $definitions) {
    

    🐛 This should be static, since it doesn't use $this, nor can it, because it's a trait.

  14. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,151 @@
    +      catch (\Exception $e) {
    +        // @todo: do something useful.
    +      }
    

    🐛 This TODO needs to be resolved still.

  15. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,151 @@
    +            if ($version_string && $version_string[0] == '1') {
    +              if ((int) $version_string >= 1000) {
    +                $version_string = '5';
    +              }
    +              else {
    +                $version_string = FALSE;
    +              }
    +            }
    

    🤯 This definitely needs a comment. Dark magic! 🧙‍♀️

  16. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,151 @@
    +    else {
    +      // Check the followup migrations.
    +      foreach ($definitions as $key => &$definition) {
    +        if ($definition['id'] == 'd7_entity_reference_translation' || $definition['id'] == 'd6_entity_reference_translation') {
    +          $version = substr($definition['id'], 1, 1);
    +        }
    +      }
    +    }
    

    🤯 Wait, so first we do check if system_site is in the list of migration definitions (which is pretty complicated), and if it isn't we check the follow-up migrations? Why is that okay? Why only these specific two?

  17. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,151 @@
    +    if ($version) {
    

    🤔 The second bulk of the logic in this method simply may or may not run? This method is really begging to be split up into multiple methods to make it easier to understand. 🙏

  18. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,151 @@
    +      // either the classic, 'dN_node', or the master, 'dN_node_master', or both
    

    🤔 How can it be both? Aren't we very much trying to avoid it being possible for both migrations to run?

    (Also: missing trailing period.)

  19. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,151 @@
    +            if ($connection->select($base_table)
    +              ->countQuery()
    +              ->execute()
    +              ->fetchField()) {
    

    🙈 This is some really weird code formatting.

  20. +++ b/core/modules/migrate_drupal/src/Plugin/migrate/EntityReferenceTranslationDeriver.php
    @@ -186,7 +186,9 @@ public function getDerivativeDefinitions($base_plugin_definition) {
    +    if ($this->derivatives) {
    +      migrate_drupal_migration_plugins_alter($this->derivatives);
    +    }
    

    🤔 Why is it okay to special case one particular deriver and then hardcode the invocation a specific alter hook implementation?

  21. +++ b/core/modules/migrate_drupal/tests/modules/node_migrate_classic/node_migrate_classic.info.yml
    @@ -0,0 +1,7 @@
    +name: Node migrate calssic
    

    🗣 calssicclassic

  22. +++ b/core/modules/migrate_drupal/tests/src/Kernel/MigrationPluginAlterTest.php
    @@ -25,12 +25,24 @@ protected function setUp() {
    +   *   The expected results..
    

    🤓 ...

  23. +++ b/core/modules/migrate_drupal/tests/src/Kernel/MigrationPluginAlterTest.php
    @@ -45,19 +57,16 @@ public function testMigrationPluginAlterNoTranslation($source, $expected) {
    +    // Tests migrations that should never be altered, once without an extra
    +    // modules installed, then with content_translation installed and finally
    

    🗣"without an extras modules installed"

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new15.93 KB
new171.11 KB

This patch has fixes for #296 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 15, 19, 21, 22, 23. There are mostly the comment improvements.

todo: 9, 10, 13, 14, 16, 17, 18, 20

For #9. I happen to like the explicit nature of those statements. What would you prefer?

wim leers’s picture

StatusFileSize
new7.58 KB
new171.66 KB

Thanks for the #297 interdiff, a lot of really solid comment improvements that I hope you agree will help future maintenance :)

Also, I've changed strategies: rather than pointing out nits, I'm raising them but fixing them myself, so that you can focus on the actual interesting questions (well, hopefully they're good questions 🤞!).

Interdiff review

  1. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -107,15 +107,55 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +    // process pipeline we are concerned with references to classic mode
    

    🤓 s/mode/node/

  2. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -107,15 +107,55 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +    // the correct ids. For example, the classic d6_node_revision migration
    

    🤓 s/ids/IDs/ (It's written this way everywhere else!)

  3. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -107,15 +107,55 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +    // has a single ID, the revision ID, but the d6_node_master has three IDs,
    +    // the node ID, the revision ID and the language. To ensure the pipeline
    +    // works for both migrations we need to insert a process to return only
    +    // the revision ID if the master node migration was used.
    

    🙏 NOW I ACTUALLY UNDERSTAND THIS! Thank you, great comment!

  4. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -107,15 +107,55 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +    //After
    

    🤓 Indentation nit.

  5. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -67,6 +67,9 @@ public function getNodeMigrateType(array $definitions) {
    +        // Determine the source database version. This is an excerpt from
    +        // getLegacyDrupalVersion for Drupal 5, 6 or 7 source databases.
    +        // See \Drupal\migrate_drupal\MigrationConfigurationTrait::getLegacyDrupalVersion
    

    🤔 Why are we copy/pasting some code instead of calling that existing code?

  6. +++ b/core/modules/migrate_drupal/src/Plugin/migrate/EntityReferenceTranslationDeriver.php
    @@ -186,6 +186,9 @@ public function getDerivativeDefinitions($base_plugin_definition) {
    +    // Ensure these migrations are also altered. This is done here because This
    

    🤓 s/This/this/

  7. +++ b/core/modules/migrate_drupal/src/Plugin/migrate/EntityReferenceTranslationDeriver.php
    @@ -186,6 +186,9 @@ public function getDerivativeDefinitions($base_plugin_definition) {
    +    // deriver is only used for the follow up migrations which are created after
    

    🤓 s/follow up/follow-up/

Continued patch review (again partial, still going!)

  1. +++ b/core/modules/content_translation/migrations/d6_node_master.yml
    @@ -0,0 +1,53 @@
    +  # If you are using this file to build a custom migration consider removing
    +  # the nid and vid fields to allow incremental migrations.
    

    🤔 This comment doesn't match the one in d6_node, whereas the equivalent comments in d7_node and d7_node_master are identical.

    Is there a reason for this deviation?

  2. +++ b/core/modules/content_translation/migrations/d6_node_master.yml
    @@ -0,0 +1,53 @@
    +  changed: timestamp
    
    +++ b/core/modules/content_translation/migrations/d7_node_master.yml
    @@ -0,0 +1,47 @@
    +  changed: timestamp
    

    🤔 In d*_node this is

      changed: changed
    

    Why is it different here?

  3. +++ b/core/modules/content_translation/migrations/d6_node_master.yml
    @@ -0,0 +1,53 @@
    +  content_translation_source: source_langcode
    

    🤔 This is the only additional field that is mapped compared to d6_node.

    But d6_node has the following comment:

    #  unmapped d6 fields.
    #  tnid
    #  translate
    #  moderate
    #  comment
    

    Why are we not retaining this comment?

  4. +++ b/core/modules/migrate/src/Audit/NodeMasterIdAuditor.php
    @@ -0,0 +1,68 @@
    +   * @return \Drupal\migrate\Audit\AuditResult[]
    +   *   Array of migration results.
    

    🤓 Comment is wrong.

  5. +++ b/core/modules/migrate/src/Audit/NodeMasterIdAuditor.php
    @@ -0,0 +1,68 @@
    +    // map has the ids for both nodes and revisions and Sql::getHighestId()
    +    // only returns the highest migrated ID of the destination entity type,
    +    // which for node_master is node.
    

    🤓 s/ids/IDs/ and 80 cols.

  6. +++ b/core/modules/migrate/src/Audit/NodeMasterIdAuditor.php
    @@ -0,0 +1,68 @@
    +        $map_table = $migration->getIdMap()->mapTableName();
    +
    +        $database = \Drupal::database();
    +        if (!$database->schema()->tableExists($map_table)) {
    +          continue;
    +        }
    +        // Get highest migrated node revision id.
    +        $query = $database->select($map_table, 'map')
    +          ->fields('map', ['destid2'])
    +          ->range(0, 1)
    +          ->orderBy('destid2', 'DESC');
    +        $ids[] = $query->execute()->fetchField();
    +        $max = (int) (max($ids));
    ...
    +        $highest = $destination->getHighestId();
    

    🤔 ✅ IMHO this code would be easier to understand and maintain if this would be moved into a helper method, because there are two different kinds of "highest ID" being computed here.

    Implemented that, because I don't think this is going to be controversial.

  7. +++ b/core/modules/migrate/src/Audit/NodeMasterIdAuditor.php
    @@ -0,0 +1,68 @@
    +          $results[$migration_id . '-revision'] = AuditResult::fail($revision_migration, [
    +            t('The destination system contains data which was not created by a migration.'),
    +          ]);
    

    🤔 Aha, so the goal here is to prevent node revision ID conflicts (which could happen if after the migration, more revisions were created on the destination).

    Should we clarify this in the class doc? And presumably add a similar comment to IdAuditor's class doc?

    Because Audits the node master migrations is super abstract. With only a single auditor in core, there was less of a need to explain things clearly, but with multiple, the distinction needs to be crystal clear.

  8. +++ b/core/modules/migrate/src/Plugin/Derivative/MigrateEntityMaster.php
    @@ -0,0 +1,25 @@
    +        'class' => 'Drupal\migrate\Plugin\migrate\destination\EntityContentMaster',
    

    🤓 Let's use FQCN.

  9. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
    @@ -0,0 +1,115 @@
    + * Master destination for migrating the entire entity revision table.
    

    🤔 "Master destination". I don't know how to interpret this. "Master" as opposed to what?

  10. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
    @@ -0,0 +1,115 @@
    +  /**
    +   * {@inheritdoc}
    +   *
    +   * @todo: Use EntityFieldDefinitionTrait instead.
    +   *   See https://www.drupal.org/project/drupal/issues/2937782.
    +   */
    +  protected static function getEntityTypeId($plugin_id) {
    +    // Remove entity_revision:
    +    return substr($plugin_id, 14);
    +  }
    

    Left a comment at #2937782-26: Create trait for getDefinitionFromEntity and raised attention with a core committer.

    EDIT: this got committer attention, addressed their concerns in #2937782-28: Create trait for getDefinitionFromEntity and #2937782-29: Create trait for getDefinitionFromEntity 😊

wim leers’s picture

StatusFileSize
new1.82 KB
new171.46 KB

Sorry, didn't get much further due to other obligations. Posting what I have for today.

  1. 🥳 #2937782: Create trait for getDefinitionFromEntity landed, so yay, #298.10 is now fixed!
  2. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
    @@ -0,0 +1,115 @@
    +    $revision_id = $old_destination_id_values ?
    +      $old_destination_id_values[1] :
    +      $row->getDestinationProperty($this->getKey('revision'));
    

    🤓We usually format this differently.

  3. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
    @@ -0,0 +1,115 @@
    +      $entity->setNewRevision(FALSE);
    ...
    +      $entity->setNewRevision(TRUE);
    

    🤔 The overriding of the new revision flag would be a good thing to document I think. I haven't been able to figure out the "why" behind this yet.

  4. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
    @@ -0,0 +1,115 @@
    +      $entity->enforceIsNew(FALSE);
    ...
    +      $entity->enforceIsNew();
    

    🤔 Why do we sometimes enforce it as a new thing?

  5. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
    @@ -0,0 +1,115 @@
    +      // Attempt to ensure we always have a bundle.
    

    🤔 Is it attempt or ensure?

  6. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
    @@ -0,0 +1,115 @@
    +          $entity->getTranslation($langcode)->setChangedTime($entity->getChangedTime());
    

    🤔 I think this should document why this uses \Drupal\Core\Entity\EntityChangedInterface::getChangedTime() and not \Drupal\Core\Entity\EntityChangedInterface::getChangedTimeAcrossTranslations().

quietone’s picture

StatusFileSize
new171.79 KB
new2.41 KB

Wasn't able to spend as much time on this as I hoped the good thing though is I found a bug. I migrated the d6 test site, without this patch applied, then added a node to the source, then applied the patch and ran an incremental migration. That results in an error in a follow up migration.

Message 	SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sourceid3' in 'field list': INSERT INTO @migrate_map_d6_entity_reference_translation__node__page (source_ids_hash, sourceid1, sourceid2, sourceid3, source_row_status, rollback_action, hash) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6); Array ( [:db_insert_placeholder_0] => debec8da10f1eb437171cf8f98e10a6b44ad9a1446777b8bd3b581320e2faaab [:db_insert_placeholder_1] => 10 [:db_insert_placeholder_2] => 13 [:db_insert_placeholder_3] => en [:db_insert_placeholder_4] => 2 [:db_insert_placeholder_5] => 0 [:db_insert_placeholder_6] => )
Severity 	Error

And here are a few responses.
#296. 16. The followup migrations are created after all other migrations are run. We can alter them before hand because they will not exist. So, we force a check of them when they are derived. I've improved the search for them by searching for the migration tag 'Follow up migration' to find them.

#298. Continued patch review (again partial, still going!)

1. Comments in d6 and d7 node_master now have the same text concerning the 'tnid'.

2. The 'timestamp' value is used because it is the timestamp of the revision whereas 'changed' is the timestamp for the latest revision.

See .https://git.drupalcode.org/project/drupal/blob/8.9.x/core/modules/node/s...

3. Copied comment from d6_node.yml to d6_node_master.yml and removed 'tnid' since it is actually mapped. For completeness, the comment is added to d7_node_master.yml.

6. Yes, good idea it is much more readable now.

Still to answer:
296: 10, 13, 14, 17, 18, 20
298. 5. And from the second half 7, 9
299 3, 4, 5, 6

quietone’s picture

StatusFileSize
new625 bytes
new171.79 KB

Try that again.

wim leers’s picture

StatusFileSize
new8.43 KB
new171.55 KB
  1. Found a bunch more nits that were sufficiently trivial for me to fix them and for not to bore y'all with the details. See the interdiff.
  2. #296. 16. The followup migrations are created after all other migrations are run. We can alter them before hand because they will not exist. So, we force a check of them when they are derived. I've improved the search for them by searching for the migration tag 'Follow up migration' to find them.

    Ah, that makes this generic instead of hardcoding those two. Excellent! That means contrib/custom migrations that add additional follow-up migrations will also be picked up 👍 That addresses the gravest concern I had here. But the overall if/else still is very tricky IMHO: why the system_site check exists isn't clear to me.

  3. 2. The 'timestamp' value is used because it is the timestamp of the revision whereas 'changed' is the timestamp for the latest revision.

    Interesting. So how will this still result in a correct changed timestamp in the node table?

  4. --- /dev/null
    +++ b/core/modules/content_translation/migrations/d7_node_master.yml
    

    I'm concerned about the complexity this introduces forever: this means that the d7_node_master migration (whose name suggests it's of an all-encompassing nature) will not be used on sites without content_translation turned on.

    On sites with content_translation, you get:

    1. d7_node_master

    On sites without content_translation, you get:

    1. d7_node
    2. d7_node_revision

    At least, that's …

    +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -89,6 +91,164 @@ function migrate_drupal_migration_plugins_alter(&$definitions) {
    +  // If this source database is multilingual then we are only running the
    +  // dN_node_master migration and not any other dN_node* migration.  Alter all
    +  // instances of migration_lookup in core migrations to use dN_node_master.
    

    … according to this.

    But

    +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -273,4 +299,61 @@ protected function getState() {
    +   *   One of NodeMigrateType::NODE_MIGRATE_TYPE_CLASSIC,
    +   *   NodeMigrateType::NODE_MIGRATE_TYPE_MASTER, or
    +   *   NodeMigrateType::NODE_MIGRATE_TYPE_BOTH
    

    "both" implies both "master" + "classic" will run, but all comments so far indicate the opposite? How can that occur?

    Also, "classic" implies "this is old and will be removed at some point", but that does not seem to be the case? Based on this, I think "monolingual" would be a better name?

    (I suspect when you started a migration prior to "master" existing, and then end up with both? But this is a complex edge case worth documenting I think. Related: why do we absolutely need to support this edge case?)

  5. +++ b/core/modules/migrate/src/Audit/NodeMasterIdAuditor.php
    @@ -0,0 +1,88 @@
    +class NodeMasterIdAuditor {
    ...
    +  public function audit($migrations) {
    
    +++ b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php
    @@ -39,6 +40,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +    $node_master_results = (new NodeMasterIdAuditor())->audit($migrations);
    

    🤔🤔 Now it's 100% certain that the interface/API that #2876085: Before upgrading, audit for potential ID conflicts introduced is inadequate. Or rather, it was too optimistic at the time to add an interface, because we only knew of a single use case.

    We can see that this actually does not implement the interface … it even violates it! The interface dictates:

    public function audit(MigrationInterface $migration);
    

    and here we do:

    public function audit(array $migrations);
    

    I'm not saying we need to fix this here. I'm just making the observation. I do think we should at least have a class-level comment on NodeMasterIdAuditor, to explain why it doesn't implement the interface.

  6. +++ b/core/modules/migrate_drupal/tests/fixtures/drupal6.php
    @@ -44473,10 +44473,10 @@
    -  'created' => '1388271197',
    +  'created' => '1390095702',
    
    @@ -45288,7 +45288,7 @@
    -  'timestamp' => '1420861423',
    +  'timestamp' => '1390095702',
    
    @@ -45526,11 +45526,11 @@
    -  'timestamp' => '1390095702',
    ...
    +  'timestamp' => '1420861423',
    
    +++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php
    @@ -41455,14 +41828,14 @@
    -  'changed' => '1471428152',
    +  'changed' => '1564543706',
    
    @@ -41471,14 +41844,14 @@
    -  'changed' => '1478755274',
    +  'changed' => '1564543810',
    
    @@ -41487,14 +41860,14 @@
    -  'changed' => '1478755314',
    +  'changed' => '1564543929',
    

    🤔 Why change the timestamps?

  7. +++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php
    @@ -7140,23 +7140,23 @@
    -  'field_link_title' => 'Home',
    -  'field_link_attributes' => 'a:0:{}',
    +  'field_link_title' => NULL,
    +  'field_link_attributes' => 'a:1:{s:5:"title";s:0:"";}',
    ...
    -  'field_link_title' => 'Home',
    +  'field_link_title' => NULL,
    

    🤔 These changes seem out of scope?

  8. +++ b/core/modules/migrate_drupal/tests/src/Kernel/MigrationPluginAlterTest.php
    @@ -25,12 +25,24 @@ protected function setUp() {
    +   * @param array $migrations
    +   *   An array of migrations.
    

    🤓 Nit.

    If it's an array of migrations, it should be \Drupal\migrate\Plugin\MigrationInterface[] . $migrations.

    If it's an array of migration definitions, it should be array $migration_definitions.

    It's the latter. Fixed. ✅

  9. +++ b/core/modules/migrate_drupal/tests/src/Kernel/d7/FollowUpMigrationsMasterTest.php
    @@ -0,0 +1,66 @@
    +  protected function getFileMigrationInfo() {
    +    return [
    +      'path' => 'public://sites/default/files/cube.jpeg',
    +      'size' => '3620',
    +      'base_path' => 'public://',
    +      'plugin_id' => 'd7_file',
    +    ];
    +  }
    

    🐛 ✅ This is exactly the same as the parent implementation, so can be removed. Done.

  10. +++ b/core/modules/node/src/Plugin/migrate/source/d7/NodeMaster.php
    --- /dev/null
    +++ b/core/modules/node/tests/modules/node_migrate_classic/node_migrate_classic.info.yml
    
    +++ b/core/modules/node/tests/modules/node_migrate_classic/node_migrate_classic.info.yml
    @@ -0,0 +1,7 @@
    +name: Node migrate
    ...
    +description: Use the master node migrations.
    

    🐛🐛

    🤔🤔🤔 BUT! Why do we have two different node_migrate_classic modules?! One at

    core/modules/node/tests/modules/node_migrate_classic, the other at
    core/modules/migrate_drupal/tests/modules/node_migrate_classic. This is super duper confusing!? 🤯

  11. +++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeMasterTest.php
    @@ -0,0 +1,1257 @@
    +    $result = (new IdAuditor())->auditMultiple($migration);
    

    🤔 We don't anything about $result. That seems wrong?

  12. +++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTest.php
    @@ -54,10 +54,10 @@ public function testNode() {
    -    $this->assertIdentical('1388271197', $node->getCreatedTime(), 'Node has the correct created time.');
    +    $this->assertIdentical('1390095702', $node->getCreatedTime(), 'Node has the correct created time.');
    

    🤔 Why are we changing the node created time here? Shouldn't we only be seeing revision- and translation-related changes in the patch?

  13. +++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeMasterTest.php
    @@ -0,0 +1,931 @@
    +    // Source node 1. This node uses entity translation which does not have a
    +    // migrate. See https://www.drupal.org/project/drupal/issues/3076447.
    +    $data = $this->expectedRevisionEntityData()[0];
    

    🗣 "Source node 1."?

    🗣 "[…] does not have a migrate."?

    I'm not sure what this is saying.

  14. +++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeMaster.php
    @@ -0,0 +1,64 @@
    +class MigrateTermNodeMaster extends MigrateDrupal6TestBase {
    

    🤔 Why are we testing this only for Drupal 6, and not also for Drupal 7?

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new172.87 KB
new3.28 KB

@Wim Leers, again, thanks for the reviews and patches.

Been thinking about the followup migrations and this error, from #300

Message SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sourceid3' in 'field list': INSERT INTO @migrate_map_d6_entity_reference_translation__node__page (source_ids_hash,

The error message can be fixed by modifying the migrate_map table for the d6_entity_reference_translations for nodes. This adds an update hook that should do that.

Also I think that altering them can be avoided by adding the master_node migration to the migration_lookups in the process pipeline when the migrations are created in the entityreferencetranslation deriver. If the master node migration does not exist the migration_lookup will throw a Plugindoesnotexist but continue on to the other migrations to find the lookup. Testing that manually is working, so let's find out what the test bot thinks of it.

quietone’s picture

Aborted the test since it looks like very migrate kernel test is failing.

Some answers for #305

#3. Presumably the entity save is doing this for us?
#4. Ah, right. Originally this was for multilingual migrations but somewhere along the way it became the default migration for new migrations. If the latter, then the migration needs to move to node module. However, this points needs to be double checked with, at least, the migrate maintainers.
#6. mikelutz found problems with the fixtures when he created the master node migration in #121. There are some other questions about the fixtures and will do those all together.
#10. Yes, I forgot to remove the one from migrate_drupal. Fixed in the patch in #306.
#14. There are no term node migrations for Drupal 7. I don't know the history of term node and didn't work on those migrations.

Still to answer:
296: 10, 13, 14, 17, 18, 20
298. 5. And from the second half 7, 9
299. 3, 4, 5, 6
305. 2, 5, 7, 11, 12, 13

quietone’s picture

StatusFileSize
new172.87 KB
new453 bytes

And fix the bad patch in #306

wim leers’s picture

So, comments #296 until #305 were me trying to make sense of things, and verbalizing all the things that stood out to me as hard to understand. Hopefully that helps this patch get to a point where it's committable.

Deeper concerns that I needed to double-check by running code locally and stepping through it with a debugger

  1. ✅ I was concerned that when using Migrate Drupal UI, I would see both the d7_node_master and d7_node migrations getting picked up under the hood (only under the hood, because this UI does not show any of that, it just runs all migration plugin instances in order in a single long-running batch). This worked fine, thanks to \Drupal\migrate_drupal_ui\Form\CredentialForm using \Drupal\migrate_drupal\MigrationConfigurationTrait::getMigrations().
  2. ✅ The next concern was that this would break the Drush + https://www.drupal.org/project/migrate_upgrade. This too works fine thanks to \Drupal\migrate_upgrade\MigrateUpgradeDrushRunner using \Drupal\migrate_drupal\MigrationConfigurationTrait::getMigrations() also.
  3. ❌ The next concern was that this would break Drush + https://www.drupal.org/project/migrate_tools. This does not work okay:
    vendor/bin/drush ms | grep d7_node
      Default     d7_node_settings           Idle     1       0          1                            
      Default     d7_node_type               Idle     7       0          7                            
      Default     d7_node_master:article     Idle     241     0          241                          
      Default     d7_node_master:blog        Idle     567     0          567                          
      Default     d7_node_master:client      Idle     17      0          17                           
      Default     d7_node_master:demo        Idle     12      0          12                           
      Default     d7_node_master:page        Idle     42      0          42                           
      Default     d7_node_master:project     Idle     47      0          47                           
      Default     d7_node_master:talk        Idle     85      0          85                           
      Default     d7_node:article            Idle     25      0          25                           
      Default     d7_node_translation:arti   Idle     0       0          0                            
      Default     d7_node:blog               Idle     108     0          108                          
      Default     d7_node_translation:blog   Idle     0       0          0                            
      Default     d7_node:client             Idle     12      0          12                           
      Default     d7_node_translation:clie   Idle     0       0          0                            
      Default     d7_node:demo               Idle     3       0          3                            
      Default     d7_node_translation:demo   Idle     0       0          0                            
      Default     d7_node:page               Idle     9       0          9                            
      Default     d7_node_translation:page   Idle     0       0          0                            
      Default     d7_node:project            Idle     32      0          32                           
      Default     d7_node_translation:proj   Idle     0       0          0                            
      Default     d7_node:talk               Idle     28      0          28                           
      Default     d7_node_translation:talk   Idle     0       0          0                            
      Default     d7_node_revision:article   Idle     216     0          216                          
      Default     d7_node_revision:blog      Idle     459     0          459                          
      Default     d7_node_revision:client    Idle     5       0          5                            
      Default     d7_node_revision:demo      Idle     9       0          9                            
      Default     d7_node_revision:page      Idle     33      0          33                           
      Default     d7_node_revision:project   Idle     15      0          15                           
      Default     d7_node_revision:talk      Idle     57      0          57                           
      Default     d7_node_title_label        Idle     7       0          7  
    

    See how both the "classic" and "master" migrations are listed! I'm not sure what the answer is here. Just documentation? Detect if the "master" migration ran and then refuse to run the "classic" migration, and vice versa? (This relates to the question I asked in #305.4 about the "both" case — this is probably the biggest architectural concern.)

    Because the row counts do line up nicely:

    wim.leers at Acquia in ~/Work/d8 on 8.9.x*
    $ vendor/bin/drush ms d7_node_master:article
     ------------------- ------------------------ -------- ------- ---------- ------------- --------------- 
      Group               Migration ID             Status   Total   Imported   Unprocessed   Last Imported  
     ------------------- ------------------------ -------- ------- ---------- ------------- --------------- 
      Default (default)   d7_node_master:article   Idle     241     0          241                          
     ------------------- ------------------------ -------- ------- ---------- ------------- --------------- 
    

    versus

    wim.leers at Acquia in ~/Work/d8 on 8.9.x*
    $ vendor/bin/drush ms d7_node:article
     ------------------- ----------------- -------- ------- ---------- ------------- --------------- 
      Group               Migration ID      Status   Total   Imported   Unprocessed   Last Imported  
     ------------------- ----------------- -------- ------- ---------- ------------- --------------- 
      Default (default)   d7_node:article   Idle     25      0          25                           
     ------------------- ----------------- -------- ------- ---------- ------------- --------------- 
    wim.leers at Acquia in ~/Work/d8 on 8.9.x*
    $ vendor/bin/drush ms d7_node_revision:article
     ------------------- -------------------------- -------- ------- ---------- ------------- --------------- 
      Group               Migration ID               Status   Total   Imported   Unprocessed   Last Imported  
     ------------------- -------------------------- -------- ------- ---------- ------------- --------------- 
      Default (default)   d7_node_revision:article   Idle     216     0          216                          
     ------------------- -------------------------- -------- ------- ---------- ------------- --------------- 
    

    241 for the "master" migration matches the 25 + 216 for the "classic" migration's cumulative entity + entity revision table migration plugin instance row counts. (This is for wimleers.com, which is monolingual.)

  4. 🤔🤔🤔 Which brings me once more to the concerns raised in #305.4: why are we insisting on keeping both "classic" and "master" around? Evidently we need to keep "classic" around to not break existing migration projects. But we could deprecate it. And then the "master" one would contain everything (entity + entity revisions + entity translations), always. If there's no translations or the content_translation module is not installed, no big deal, then "master" simply would not handle translations.

    That's conceptually much simpler.

    I think this is the most important question by far, because it seems there is a huge potential for simplification. If that's not true, then we need to thoroughly document why this simplification is not possible or not desirable.

Until the questions in this comment are answered, I don't think it makes sense for me to continue reviewing this.

quietone’s picture

StatusFileSize
new5.29 KB
new173.03 KB

This patch changes the way the migrate type is determined. Instead of checking for the existence of the classic and master migrate_map tables it now checks if the map table has rows. The map tables get created before the migration is run and an empty table just tells us that the migration exists when what we want to know is if the classic or master node migrations have run.

quietone’s picture

Just answering some of the questions from Wim Leers.

#296:10 - When a test is run NodeMigrateType executes before the migrate_map tables are created (in Sql::ensureTables()) adding a module
was the quickest and easiest way to indicate the type of migration. I suppose the proper way is to add a migrate_map table
to the d8 database and add a row as part of the setup. It was just a bridge too far at the time.

#296:13 - The change to static was made but now I see that the comment is for the method that is not in the trait. That will have to be fixed.

#296:14 Perhaps, a message the same as the one in CredentialForm could be used here?

        $msg = $this->t('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', ['%error' => $e->getMessage()]);

#296:18 - It is possible to have classic and master migrate_map tables with data if one is using drush.

#296:20 - Answered in #300.

#305:3 Presumably the entity save handles keeping the latest revision correct as each revision is saved during the migration. That seems to be that case in practice.

TODO:
296: 17
298. 5. And from the second half 7, 9
299. 3, 4, 5, 6
305. 2, 5, 7, 11, 12, 13
309. 3, 4

quietone’s picture

@Wim Leers, your review and the break I took from working on this has helped greatly to put this all together and find my errors. Thank you.

309:3. The drush ms command lists all the available migrations so in that sense the display is correct. The developer can then choose which they want to run. Should core be deciding for the developer which node migrations are available? I am not a user of migrate_tools so input from others on what they expect would be helpful.
And, as I write this I recall that there is an option that will run all the dependencies for a given migration. That dependencies on the node migrations should be checked. I doubt they are correct. But, again, need to know what is expected.

309.4 Excellent question. I now think that the BOTH case doesn't matter. All that matters is knowing if there is data in the classic node tables and if there are then continue to use the classic migrations. That is just for the UI, not drush.
And we need to keep in mind that we can not deprecate a migration. It is a serious blocker. :-( See #3039240: Create a way to declare a plugin as deprecated.

It may help to show the cases we are dealing with.

Before

Only classic node migrations exist

After

  • Both the classic and master node migrations exist and will be discovered. The default is master node migrations.
  • The ContentEntity source plugin now uses 3 IDs and not 2 IDS and the migrate_map tables need to be altered to add the column..
  • Core migrations that do a migration_lookup with a node migration need to be altered.
  • Follow up migrations. The process pipelines for these d8 to d8 migration use migration_lookup on the node migrations. These are modified so the migration source for the migration_lookup includes the master node migration.

Case A

Core UI /upgrade

  • Only node master migrations are run.

CASE B

Core UI migration first run before this patch and then after with this patch.

  • Both the classic and master node migrate_map tables exist and only the classic tables have data.
  • Do we know use only classic or master?

CASE C

drush

  • Both the classic and master node migrations are available. (getMigrations is only executed for the UI)
  • Both the classic and master node migrate_map tables exists and both can have data.
  • The developer will decide which ones to run. Right?/li>

TODO:
296: 17
298. 5. And from the second half 7, 9
299. 3, 4, 5, 6
305. 2, 5, 7, 11, 12, 13

quietone’s picture

Issue summary: View changes

Update IS with comment by mikelutz about deprecating the classic node migrations.

quietone’s picture

StatusFileSize
new5.65 KB
new172.39 KB

296: 17 - helper method created.
298: 5. - now uses getLegacyDrupalVersion from MigrationConfigurationTrait
298: from the second half, 9 - updated entitycontentmaste summary line.
299: 5 - Changed comment.
305: 11, 13 - Fixed.

TODO:
298. And from the second half, 7
299. 3, 4, 6
305. 2, 5, 7, 12

quietone’s picture

StatusFileSize
new1.48 KB
new172.43 KB

@mikelutz can you respond to #299. 3, 4 and 6? These are all about adding comments to the EntityContentMaster destination plugin.

This patch has fixes for the following, about the IdAuditor.
298. And from the second half, 7
305. 5. This one suggests adding comment to IdAuditor.php. Since that file is not changed in this patch and this patch is already large I refrained from making any changes. There are other issues about the auditor and perhaps improving the comments could be done in one of them.

TODO:
299. 3, 4, 6
305. 2, 7, 12

gábor hojtsy’s picture

Issue summary: View changes

The more I am reading comments on the issue, the more I feel I should raise the terminology question :) we moved off of "master" as a terminology in our database drivers (where it was used alongside "slave") due to our better awareness of how it relates to the colonial past. (We are cited in the Wikipedia article for it even: https://en.wikipedia.org/wiki/Master/slave_(technology)#Terminology_concerns). I am wondering if we can use a better term here as well before we land it.

In other news I opened #3109819: [PP-1] Deprecate classic node migration plugin in Drupal 9 for removal in Drupal 10 which we maybe can do in Drupal 9 later on. Let's discuss there. If we postpone that for deprecating for Drupal 11 like @mikelutz said that is also an option, but that is a different discussion from solving this missing migration coverage.

wim leers’s picture

#310:

  1. +++ b/core/modules/migrate_drupal/migrate_drupal.module
    @@ -93,10 +93,10 @@
    +  // If the master node migrate_map tables have data then the any migration
    +  // use a node migration in a migration_lookup needs to be altered to use
    

    🗣 "then the any migration use a node migration"

  2. +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -317,32 +317,33 @@
    -    $node_exists = FALSE;
    -    $node_master_exists = FALSE;
    +    $node_has_rows = FALSE;
    +    $node_master_has_rows = FALSE;
    

    👍 Much clearer :)


#311

  • #296.10: Ah, that makes sense! I suspect now is the appropriate time? Or perhaps we want sign-off from migration system maintainer Mike Lutz first?
  • #296.13: Sounds good! 👍
  • #296.14: Sounds good! 👍
  • #296.18: Hm … because then the safeguards that this patch is adding don't work, as I discovered myself in #309.3. Still not sure how to feel about that … 🤔

#312:

You're most welcome :) This is super complicated, and super important. Thank you for having pushed it so far!!! 🙏🥳

RE: #309.3: I defer to others on what is expected in this scenario. All I know is that as a new user of migrate_tools, I find this extremely confusing.
RE: #309.4: heh, thanks :) And … good point about #3039240! What you're saying is that this is really blocked on #3039240. Since this is critical, I've just bumped that issue to critical too: #3039240-29: Create a way to declare a plugin as deprecated.


#316++

gábor hojtsy’s picture

Re blockers in terms of deprecation infrastructure, @mikelutz said this on slack an is copied on the issue summary:

"Removing it [the classic node migrations] will break the whole ecosystem. Deprecating it means that it’s not used anymore which in and of itself will break the eco system, and for what benefit? To remove it in Drupal 10, a year after D7 is EOL? At that point we are looking at deprecating the entire D7 upgrade path anyway (Probably for removal in 11, not 10) We really don’t want to break the migration ecosystem right now at this critical time when we are trying to get everyone off of D7 in the next 18-20 months". by mikelutz in #migration on Drupal Slack)

We also cannot deprecate anything anymore for removal in Drupal 9 unless super-critical, we said we are done with the release of Drupal 8.8. So we can only deprecate in Drupal 9 for removal in Drupal 10 soonest. I opened #3109819: [PP-1] Deprecate classic node migration plugin in Drupal 9 for removal in Drupal 10 for that as per above (probably cross-post :). And deprecating something for removal in Drupal 10 or later cannot be done now as per current policy it needs to be filed against 9.1 and will only land on that branch, see https://groups.drupal.org/node/535670 so if that would block this issue then this would not get done before Drupal 9.1, which is a paradox :)

TL;DR: not blocked on deprecating anything.

quietone’s picture

Totally agree with Gábor Hojtsy in #136 about not using the term 'master'. And I can hear heddn saying 'naming is hard'!

Just has a look at some on-line thesauri and the best there was 'complete', which is accurate. A shorter version would be 'all'. Any suggestions?

And some more feedback on questions.
#296:10 Further to this, which is about the test modules for testing. The proper thing to do is to improve the tests so they are not needed. So, that is a todo.

#296:18 - For the UI, if we make the decision that if the classic tables exist then only the classic migrations run, there will never be both classic and non classic migrate map tables. That is easily done.

#309.3 Last time I used migration_tools `drush ms` will list all migrations discovered which will include Drupal 6 and Drupal 7 ones. That is, unless migrate_tools does something to filter the migrations. The `drush ms` command in migrate_run does list all migrations.

#309.4: This issue doesn't need to deprecate any migration yml so it is isn't blocked on that. I will defer to mikelutz and others on the timing of deprecating the classic node migrations.

gábor hojtsy’s picture

"Complete" sounds great :)

quietone’s picture

StatusFileSize
new4.73 KB
new171.93 KB

Fixing some comments and removing the BOTH migrate type.

wim leers’s picture

"Complete" sounds great :)

Agreed!

quietone’s picture

StatusFileSize
new29.76 KB
new180.75 KB

Didn't get as much time to work on this during the weekend as I had hoped.

This focuses on doing the testing properly. Instead of using modules to indicate the type of migration, tests can add a row in the migrate_map_d6_node__page table to indicate that a classic migration should be run. A new test trait is added with the helper functions for creating, removing and getting the number of the node migrate map tables. I hope it is clearer now what is happening.

So, far there is support for changing 'master' to 'complete'. If you have any other ideas for that add it here as I was planning on making that the next change.

quietone’s picture

StatusFileSize
new720 bytes
new181.04 KB

Missed a use statement causing most of the d7 Kernel tests to fail.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new922 bytes
new181.11 KB

I think some of the errors has to do with the fact that the new test trait, NodeMigrateTestTrait, creates a migrate_map_d*_node__page table (used to determine if this is classic migration) and sometimes that table is used in the test. So, lets use a random bundle name for the migrate_map table created in NodeMigrateTestTrait. This worked locally for MigrateDrupal7AuditIdsTest so should fix all the audit tests.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new2.31 KB
new181.26 KB

The name of the migrate_map created in the new trait needs to be preserved, otherwise the table is not removed. This also has code standard fixes.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new3.19 KB
new181.22 KB

This should fix two tests and hopefully leave 1 more to fix, a funcational test.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new6.68 KB
new179.66 KB

This should fix the failing test.

quietone’s picture

StatusFileSize
new204.4 KB
new179.12 KB

And now rename from master to complete.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new1.94 KB
new180.3 KB

Missed renaming in a file and somehow lost a file.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new2.64 KB
new180.31 KB

And missed a few more places to rename master to complete.

quietone’s picture

StatusFileSize
new578 bytes
new180.22 KB

If the complete node migration is to be the default then it should be in node. Let's see what fails.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new181.77 KB
new2.52 KB

I expected the Validate tests to fail, not sure about the Functional test though. Here are fixes for the Validation tests.

I've also updated the CR,

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new185.13 KB
new4.08 KB

Since the complete node migration is not just for Multilingual, the tag needs to be removed and some tests adjusted.

wim leers’s picture

StatusFileSize
new185.19 KB

@quietone You keep making excellent and very difficult contributions here — thank you so much for all this! 🙏😊🥳


#323 through #332 and then #333 through #337: that is indeed far less confusing, and more realistic 🙂 👍🙏 (Thanks for splitting up the mastercomplete rename into a separate patch sequence, that made it much easier to review!)

#338 + #342: YES! This is what I was worried about after seeing #323–#337: that we wouldn't make "complete" the new default, and would only use it in case of content_translation being installed. That's what I raised in #305.4 and #309.4.

#340: That must have felt sooooo satisfying 😄 So much work, just to be allowed to change those few highly impactful and meaningful lines of metadata! 👏


Remaining concerns:

  1. What about all the existing d7_node migration dependencies? In case of an optional dependency on d7_node (example: d7_menu_links), we can add an optional dependency on d7_node_complete. In case of a required dependency (examples: d7_node_revision, d7_book, d7_node_entity_translation) … well … I'm not sure.

    migrate_drupal_migration_plugins_alter() handles this dynamically (look at $replace_with_complete_migration). It only does this when migrate_drupal is enabled, but that's probably okay.

    Probably to preserve backwards compatibility? Let's document this magic explicitly in the change record.

    We definitely will need this magic for contrib/custom migrations, but … I'm not sure we need this in Drupal core? In Drupal core, I think we can update the migration definition YAML files instead?

  2. The migrate_lookup alters in migrate_drupal_migration_plugins_alter() update core migrations. But what about contrib migrations? I think the answer "oh but we don't remove d7_node so BC is not a concern" is only true for in-progress migrations. What about new migrations? All contrib modules that use migrate_lookup on d7_node would need to be auto-derived to d7_node_complete.
  3. Related to the previous two questions: why does the migrate_drupal module perform this alteration, instead of the node module? (As of #338 that would become possible.)
  4. Will be backport this to Drupal 8.8? Enabling more people to migrate to Drupal 8 now without having to wait for 8.9 would be very valuable. A good number of Drupal 7 sites are without a doubt waiting for this to be supported. Meanwhile, I've attached a 8.8 rebase of #342, because I'm curious to see how badly tests would fail 😁 The only conflict was in the D7 DB fixture.
  5. I do think this should still be signed off by at least one other migration system maintainer besides you, @quietone. You did the (excellent! :)) work, and I'm nowhere near the expertise level required to RTBC this patch. Especially because the consequences are significant.
quietone’s picture

@Wim Leers, thank you. Yes, that last round of changes was very satisfying!

I don't have time now to answer the concerns right now but I am pleased we are done to 5.

ps. I'm in the middle of rerolling #3076447: Migrate D7 entity translation revision translations.

sinasalek’s picture

A good number of Drupal 7 sites are without a doubt waiting for this to be supported

@wim-leers Yes we are and closely following this issue :))
I know how completed and huge this one is, thank you guys for the hard works you've put into this.
My website is a complicated case, so you can expect my test and feedback once this is finalized

gábor hojtsy’s picture

Tagging.

quietone’s picture

StatusFileSize
new15.86 KB
new185.67 KB

Only have time to make this patch

343.3 Moving the plugins alter from migrate_drupal to node.

wim leers’s picture

#347++ 🥳 Sounds like that was just an oversight and not intentional then! Great :)

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new186.19 KB
new1.91 KB
new186.25 KB
new17.97 KB

343.1 For core only I think we can modify the YAML. And if we do, how will contrib modify the migrations? I would like mikelutz and heddn to comment on this point. And they should have a look again at this due to the recent changes.

This patch fixes the forgotten test when moving the changes in migrate_drupal migration_plugins_alter to the node module. And I had a go at making a patch for 8.8.x too.

quietone’s picture

Issue summary: View changes

Changed terminology in the IS from master to complete.

There are still a few points that have not been answered. And I hope @mikelutz can respond to them because they are about changes he made when introducing what was then called the master node migration. There are #299. 3, 4, 6 and #305 7, 12

305.2 - When using the core UI the source database connection is know but what about contrib? In Commerce Migrate system_site is used because the system module will be enabled and that migration found and then from its source plugin we can get the connection. I recall that heddn suggested this method. @heddn, can you comment please.

wim leers’s picture

And if we do, how will contrib modify the migrations?

We'd need to keep the at-run-time altering. It just would only affect contrib migrations. I should've explicitly said that, sorry! Does my suggestion make more sense then? :)

quietone’s picture

StatusFileSize
new186.16 KB
new1.67 KB
new186.1 KB
new1.67 KB

Found a bug, the list of migration that are unset when classic is being run includes the regex pattern 'node_entity_translation' and it should be 'd7_node_entity_translation:'. And updating the comment as well. Having run any tests though.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new185.82 KB
new640 bytes

Only have time to change the entity count for 8.9.x

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new185.62 KB
new784 bytes
new185.56 KB
new612 bytes

And now patches for both versions with the entity count changes.

quietone’s picture

StatusFileSize
new970 bytes
new185.59 KB

Need to add node_entity_translation to the list of classic migration where the dependency is not altered.

quietone’s picture

StatusFileSize
new13.08 KB
new184.55 KB

In an earlier comment wim leers suggested changing the migration ymls and not using migration_plugin_alter. This is a test patch of that idea, there will be test failures. I don't think there are BC issues (correct me if I am wrong) but there is a problem with dependencies. For now, the dependencies on classic node migrations are moved from required to optional and added the complete migration as well. I don't really like that option but what else is there?

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new852 bytes
new184.55 KB

Fix some typos.

mikelutz’s picture

299.3

+++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
@@ -0,0 +1,115 @@
+      $entity->setNewRevision(FALSE);
...
+      $entity->setNewRevision(TRUE);

🤔 The overriding of the new revision flag would be a good thing to document I think. I haven't been able to figure out the "why" behind this yet.

The call to setNewRevision(FALSE) is necessary if we are re-running a migration with set revision ids, and the destination revision id already exists. In this case we want to update the existing revision, not create a new one. I believe the call to setNewRevision(TRUE) my be unnecessary, I believe I just added it in to be explicit there and to remind myself in which of those blocks we are intentionally creating a new revision.

299.4

+++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
@@ -0,0 +1,115 @@
+      $entity->enforceIsNew(FALSE);
...
+      $entity->enforceIsNew();

🤔 Why do we sometimes enforce it as a new thing?

Similar to above, the enforceIsNew() call is necessary to properly save a new entity while setting the id. Without it, the system would see that the id is already set and assume it is an update. The call to enforceIsNew(FALSE) I think is unnecessary, again, just a reminder to myself explicitly when we were creating a new entity versus an update.

+++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentMaster.php
@@ -0,0 +1,115 @@
+          $entity->getTranslation($langcode)->setChangedTime($entity->getChangedTime());

🤔 I think this should document why this uses \Drupal\Core\Entity\EntityChangedInterface::getChangedTime() and not \Drupal\Core\Entity\EntityChangedInterface::getChangedTimeAcrossTranslations().

getChangedTimeAcrossTranslations wouldn't make sense here. We are trying to build the revision table, and all we are doing is saying that if we updated an untranslated field, then set the changed time for all translations to match the current row that we are saving. in this context, getChangedTime() should return the value we just set in the updateEntity() call above.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new182.11 KB
new25.43 KB

Fixing the errors in the most recent patch. It was all a mistake on my part in that I shouldn't have mucked with the dependencies n the yml files. They are now modified in a migrations_pugin_alter in node.module and the test simplified.

@mikelutz, thanks for the explanations, which I haven't read thoroughly yet as I am on a short holiday. But I did see that you think that the call to setNewRevision(TRUE) my be unnecessary, I intend to run the tests without that and see what happens.

mikelutz’s picture

Status: Needs review » Needs work

305.7 Those changes do seem slightly off. We added revisions for nodes 2 and 3, hence the extra field link entries, but the field data table doesn't match the field revision table, so those should match.

305.12 This changed because the original d6 fixture was weird.
Originally, in the node table you had node 1, Revision id 1, created time 1388271197. changed time 1420861423
in the node revision table you had node 1 revision 1 timestamp 1420861423 and node 1, revision 2001, timestamp 1390095702. It made no sense, the latest revision timestamp was earlier than the first revision timestamp, and the node and revision tables don't match. I swapped the first and last timestamps, and set the node table to match, though I see now the node table still lists the revision for node 1 as revision 1 when it should be revision 2001, and I don't know how changing that will affect other tests.

I think the approach here is correct, and I fully sign off on the basics of the patch here as a maintainer, though I would like to clean up these details in the fixture. I think we should get this in and then focus on iterations on it, as this method has significant improvements and advantages over what exists currently.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new459 bytes
new182.17 KB

This makes the correction to the drupal6 test fixture stated in the second paragraph above.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new180.24 KB
new2.15 KB

As we see making that change in the drupal6 fixture causes test failures and in test unrelated to the changes here. I'd prefer to have that fixed on HEAD and not here.

This patch starts again from #364 but this time remove the changes to field_link in the drupal7 fixture. Lets see what changes now.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new1.71 KB
new181.67 KB

Silly me, I must have gotten too much sun today.

Start again from #364 and correct the entries in the drupal7 fixture (instead of removing them )for field_link and fix a coding standard error.

mikelutz’s picture

Status: Needs review » Reviewed & tested by the community

I am fine with additional iterations on the fixture being moved to follow-ups.

wim leers’s picture

#363: those all sound solid, but can we please add those as code comments instead of issue comments? That's all I asked for, really :) Not going to un-RTBC for that though; not important enough.

#359 + #364: 👍💯

quietone’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new3.74 KB
new182.3 KB

Adding comments as per #372. And in doing that I changed the logic of an if statement in node.module.

Then I discovered I found that 'statistics_node_counter.yml' still had dependencies on the complete node migrations. That should have been removed in #364.

So, there are two code changes here plus the comments.

quietone’s picture

And I've updated the change record.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

#373++
#374++

Thanks, @quietone! :) <3

quietone’s picture

StatusFileSize
new44.38 KB
new182.8 KB

Now try to make a patch for 8.8.x

edit: s/path/patch

quietone’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new4.46 KB
new187.44 KB
new4.46 KB
new186.94 KB

There was a test of the plugin alter that got removed when most of those changes were made directly to the migration ymls. Since node.module still uses hook migration_plugins_alter, the test should be restored. So, here it is for both 8.8.x and 8.9.x.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new816 bytes
new187.43 KB
new816 bytes
new186.93 KB

Oops, namespace error. And no need to install taxonomy in the test.

mikelutz’s picture

Status: Needs review » Reviewed & tested by the community
alexpott’s picture

StatusFileSize
new1.13 KB
new186.81 KB

We need a D9 patch too. There's a conflict in core/modules/migrate_drupal/migrate_drupal.install because of the removal of the update hooks and addition of migrate_drupal_update_last_removed().

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new20.94 KB
new189.46 KB
new175.83 KB
  1. +++ b/core/modules/migrate/src/Audit/NodeCompleteIdAuditor.php
    @@ -0,0 +1,88 @@
    +
    +/**
    + * Audits the revision ID for the node complete migration.
    + */
    +class NodeCompleteIdAuditor {
    ...
    +    $migration_plugin_manager = \Drupal::service('plugin.manager.migration');
    ...
    +    $database = \Drupal::database();
    

    Given this class has dependencies it seems funny it is not a service.

  2. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentComplete.php
    @@ -0,0 +1,118 @@
    +/**
    + * Provides a node destination for migrating the entire entity revision table.
    + *
    + * @MigrateDestination(
    + *   id = "entity_complete",
    + *   deriver = "Drupal\migrate\Plugin\Derivative\MigrateEntityComplete"
    + * )
    + */
    +class EntityContentComplete extends EntityContentBase {
    

    This is not a node destination. It can be used for other entity types, right?

  3. +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php
    @@ -0,0 +1,125 @@
    +  public function getNodeMigrateType(array $definitions, $version = NULL) {
    +    if (!$version) {
    +      // We need to get the version of the source database in order to check
    +      // if the classic or complete node tables have been used in a migration.
    +      if (isset($definitions['system_site'])) {
    +        // Use the source plugin of the system_site migration to get the
    +        // database connection.
    +        $migration = $definitions['system_site'];
    +        /** @var \Drupal\migrate\Plugin\migrate\source\SqlBase $source_plugin */
    +        $source_plugin = \Drupal::service('plugin.manager.migration')
    +          ->createStubMigration($migration)
    +          ->getSourcePlugin();
    +        $connection = NULL;
    +
    +        try {
    +          $connection = $source_plugin->getDatabase();
    +        }
    +        catch (\Exception $e) {
    +          // @todo: do something useful.
    +        }
    +        $version = FALSE;
    +
    +        if ($connection) {
    +          $version = $this->getLegacyDrupalVersion($connection);
    +        }
    +      }
    +    }
    +    return $this->migrateType($version);
    +  }
    

    This looks really odd. $definitions are only required if $version is NULL which is not required. I think caller should work out $version.

  4. +++ b/core/modules/migrate/src/Audit/NodeCompleteIdAuditor.php
    @@ -0,0 +1,88 @@
    +      if (preg_match('/node_complete/', $migration_id) === 1) {
    

    This seems a very fragile way to determine whether a migration is using the node complete migration. Is there are better way to determine this? In other places we seem to have a more specific regex being used.

  5. +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php
    @@ -121,8 +121,29 @@ protected function createDatabaseStateSettings(array $database, $drupal_version)
    +    $type = (new NodeMigrateType)->getNodeMigrateType([], $drupal_version);
    

    new NodeMigrateType() - but also this shows the awkwardness of this method passing in an empty array and the version.

Patch attached addresses these concerns by allowing destinations to say they can audit themselves, removing NodeCompleteIdAuditor.php, and refactoring \Drupal\migrate_drupal\NodeMigrateType::getNodeMigrateType() to be a static helper. The patch also fixes duplicate types on the ID conflict form (and tests the fix) - see screenshot of how this looks with #382 - https://www.drupal.org/files/issues/2020-03-02/Screenshot%202020-03-02%2...

I'll apply to the D8.9 and D8.8 patches once it is green on D9.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new1.01 KB
new189.45 KB

Whoops - too many $connection vars.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new1.04 KB
new190.09 KB
quietone’s picture

#383.1. While working on this I found limitations with the auditor and getHighest() because they assume that all IDs are integers and that the first one is the one to use for auditing. I choose to make the minimal changes to the audit functions necessary for this patch as this is already complicated enough. I'd rather have any more work on the auditor done in separate issues. There is already, #3061676: Create an audit plugin class/manager for migrations and #3091004: getHighestId() should be able to use any integer destination id.

#383.4. Sure, it could be, preg_match('/d([67])_node_complete($|:.*)/', $migration_id).

alexpott’s picture

@quietone so yep this patch does expose the limitations of the audit system but the implementation in #382 and earlier have several drawbacks. There are:

  • We produce an audit fail against a migration that does not actually exist
  • The existing IdAuditor doesn't produce a complete list of failed audits anymore - we have to tag on this new one.
    +++ b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php
    @@ -39,6 +40,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     
         $results = (new IdAuditor())->auditMultiple($migrations);
    +    $node_complete_results = (new NodeCompleteIdAuditor())->auditMultiple($migrations);
    +    $results += $node_complete_results;
    
  • The change is adding new API which we have to support going forward ie. the NodeCompleteIdAuditor class which is also pre-empting the improvements you're pointing to.
  • I think if we really want to not pre-empt those over improvements and maintain the current API then we should do the special casing inside \Drupal\migrate\Audit\IdAuditor::audit() to limit API creep.

alexpott’s picture

StatusFileSize
new9.46 KB
new186.26 KB

Here's #389 implemented. So no extension to the audit system - leaving that for follow-ups and a private method introduced so we can clean this up in those issues without worrying about BC and API implications while still getting this done without have to fix it.

alexpott’s picture

StatusFileSize
new885 bytes
new186.26 KB

Missed out an important !

quietone’s picture

StatusFileSize
new17.55 KB
new186.88 KB
new17.55 KB
new186.38 KB

@alexpott, nice changes. I guess these should be applied to the 8.8.x and 8.9.x versions?

alexpott’s picture

@quietone yep I was waiting till you +1'd the changes but #393 is even better. I consider all the points of my review in #383 resolved now and this could be back to rtbc.

masipila’s picture

Queued for sqlite and PostgreSQL just to be sure.

catch’s picture

sqlite fails are real:

PHPunit Test failed to complete; Error: PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\node\Kernel\Migrate\d6\MigrateNodeCompleteTest
F                                                                   1 / 1 (100%)

Time: 10.41 seconds, Memory: 7.49MB

There was 1 failure:

1) Drupal\Tests\node\Kernel\Migrate\d6\MigrateNodeCompleteTest::testNodeCompleteMigration
Failed asserting that two arrays are equal.
--- Expected

https://www.drupal.org/pift-ci-job/1599790

alexpott’s picture

StatusFileSize
new4.17 KB
new188.58 KB
new188.08 KB
new187.96 KB

Well that was fun. The node complete migration lookups without all the source keys are causing a new postgres fail :) It's explained in the code comment in the patch - see interdiff.

The interdiff is the same for all three branches.

quietone’s picture

Nice solution alexpott.

The test failures appear to be unrelated to this patch.

alexpott’s picture

I've opened #3117629: Postgres converts all tablenames to lowercase causing problems when dropping them to address the Postgres upper case table name fun.

firewaller’s picture

#397 works well for me on 8.8.x except the revision_translation_affected value is not always set for a revision an it gets hidden in the node revisions form (last discussed in #141).

masipila’s picture

Thanks for testing this @firewaller and taking the time to read the previous comments of this 400 comment long mega monster!

The revision_translation_affected thingy is quite creepy stuff. Are you able to point a specific scenario where you expected a different outcome than what was migrated?

Cheers,
Markus

quietone’s picture

Issue summary: View changes

I asked mikelutz about the new entity_complete destination plugin which creates from 1 to 3 destination IDs in light of the fact there is another issue to remove conditionals from all destination plugins #3004574: Remove conditional logic from all destination getIds(). Here is his response:

@quietone I don’t think you will be able to, and In retrospec I’d be inclined to mark 3004574 as ‘won’t fix’. The goal of that one was to essentially allow getIds be static and not depend on the the specific configuration or migration, but that is a pipe dream, particularly because all of these destinations are derived, and the returned ids depends on the specific derivation, along with the particular settings of that entity in the destination.

The only possible way around it is to always return all three potential ids, even if they don’t apply, but that would leave some as NULL and that would cause some issues, I suspect. The current system isn’t perfect, but I think it’s the best we can do right now.

I agree with mikelutz's explanation. And any further discussion can happen in #3004574: Remove conditional logic from all destination getIds().

firewaller’s picture

StatusFileSize
new299.51 KB
new127.47 KB

@masipila I've been following the progress of this for a while, great work to everybody so far!

Sorry for the redactions, but here's an example D7 vs D8 node revisions comparison:

Drupal 7 English Node Revisions
drupal_7_revisions

Drupal 8 English Node Revisions
drupal_8_revisions
*Note: I had to remove the pager limit and add the vid column manually in \Drupal\diff\Form\RevisionOverviewForm to display this appropriately.

As you can see, both revision IDs ****681 and ****671 are missing from the D8 node revisions page, but I have confirmed they have been migrated and have a NULL value in revision_translation_affected. From what I can tell, in D7 there is no significant difference between them and the revisions that have revision_translation_affected = 1 aside from the fact that ****681 and ****671 end up in draft in D7.

mikelutz’s picture

Looks like draft revisions are perhaps not showing correctly, but I'd be curious as to what the node_revisions table looks like for those rows in your database.

firewaller’s picture

StatusFileSize
new65 bytes
new173.08 KB

Here is a query result from the node_revision in both D7 and D8 (again sorry for redactions):

D7:
drupal_7_node_revision_table

D8
drupal_8_node_revision_table

Let me know if I can provide any further information!

firewaller’s picture

StatusFileSize
new235.86 KB

(Updating image above)

firewaller’s picture

StatusFileSize
new468.89 KB

Additionally here is a query result from the node_field_revision in D8:
drupal_8_node_field_revision_table

xjm’s picture

Issue tags: +beta target
quietone’s picture

@firewaller, thanks for the testing.

Unfortunately, the testing was done with moderation (which I gather is workbench_moderation in Drupal7) which I have no experience with and it is not used in the Drupal7 test fixture. So, I gather the next step it to add workbench moderation to the fixture and use it in some content types (entity translation and node translation). But will adding that break any existing test? I don't know, but I hope not.

For me, I would prefer to have confirmation from testing that this patch works without moderation first.

masipila’s picture

@firewaller, if the patch works without the D7 moderation module, it would be extremely beneficial if you could help narrow this down by trying to find full steps to reproduce the moderation steps that trigger the unexpected behavior.

Cheers,
Markus

catch’s picture

Status: Needs review » Reviewed & tested by the community

I've opened a follow-up for firewaller's issue at #3118625: Migrate sets revision_translation_affected incorrectly when different languages co-exist for 8.x un-translatable fields. This issue is quite unweildy at 400+ comments, and if it is due to workbench_moderation or a similar module, then while I agree we should try to account for that in the core migration assuming we can, it also shouldn't prevent this being committed and tested more widely.

Tentatively moving back to RTBC - I haven't reviewed the whole patch recently, but did review @alexpott's interdiff in #397 which fixed sqlite/postgres failures from the previously RTBC patch.

firewaller’s picture

Thanks everybody! It seems appropriate to split this out, I will follow up in the new thread above with any findings related to workbench.

alexpott’s picture

Assigned: Unassigned » alexpott

I'm doing final reviews looking to commit this. I discussed this issue yesterday with @catch and, whilst I have contributed fixes to the patch I'm not the patch author (that's @quietone) or the originator of the idea (I think that's @mikelutz), the size and complexity of the patch mean that the time I've spent working on this probably puts me in the best place to do the final review and commit.

quietone’s picture

Yes, the idea was mikelutz's.

alexpott’s picture

Assigned: alexpott » Unassigned
Status: Reviewed & tested by the community » Needs work
  1. What happens if you are in the process of building a big custom migration and have lots of custom migrations using d*_node? I.e. what's the impact of node_migration_plugins_alter()? Do we need a setting or something so that sites can force a classic migration if that's what they are using. The situation I'm concerned about if that someone has built a site and they are using migrate and they're in the test phase where they are starting from fresh and the beginning of every migration. As far as I can see node_migration_plugins_alter() will switch them to using the new COMPLETE migrations but their custom migrations might not expect that at all.

    I think the change record might need expanding to cover the change from the perspective of different use-cases. For example:

    • As a contributed module author I'm providing migrations from previous versions of Drupal. These migrations depend on d*_node. What changes do I have to make? For example, the library module or commerce migrate module
    • As a site development teams we've written a custom migrations from a Drupal 6 site to Drupal 8 - these migrations depend on d*_node. What changes might I have to make?
  2. +++ b/core/modules/migrate_drupal/migrate_drupal.install
    @@ -31,3 +33,56 @@ function migrate_drupal_update_8502() {
    +/**
    + * Add revision ID to entity reference translation migrate_map tables..
    + */
    +function migrate_drupal_update_8901(&$sandbox) {
    +  $schema = Database::getConnection()->schema();
    +  $table_expression = 'migrate_map%entity_reference_translation%node%';
    +  $tables = $schema->findTables($table_expression);
    

    This update function looks like it is untested. I'm a bit confused as to why it's needed. In #306 it says it's to fix an error from incremental migrations. But I thought if you have migrations in progress already we'd be using CLASSIC migrations because the map tables exist already - so why do we need an update?

    Or is this what the FollowUpMigrations is testing - I don't think it is because I can't see how it is.

heddn’s picture

Give a 188k patch to a new reviewer and you are bound to have something surface. So sorry for the late nits.

  1. re #415.1: I think there isn't any need for BC concerns in that regard. NodeMigrateType::getNodeMigrateType() seems to make that determination quite well.
  2. re #415.2: I think I have the same question. From looking at FollowUpMigrationsCompleteTest, it seems to explicitly test the opposite condition where we don't have any classic migrations.
  3. +++ b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/ContentEntityTest.php
    --- /dev/null
    +++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/FollowUpMigrationsCompleteTest.php
    
    +++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/FollowUpMigrationsCompleteTest.php
    +++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/FollowUpMigrationsCompleteTest.php
    @@ -0,0 +1,60 @@
    
    @@ -0,0 +1,60 @@
    +<?php
    +
    +namespace Drupal\Tests\migrate_drupal\Kernel\d6;
    +
    +use Drupal\migrate_drupal\NodeMigrateType;
    +use Drupal\user\Entity\User;
    +
    +/**
    + * Tests follow-up migrations.
    + *
    + * @group migrate_drupal
    + */
    +class FollowUpMigrationsCompleteTest extends FollowUpMigrationsTest {
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static $modules = [
    +    'content_translation',
    +    'language',
    +    'menu_ui',
    +    // A requirement for d6_node_translation.
    +    'migrate_drupal_multilingual',
    +  ];
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected function setUp() {
    +    MigrateDrupal6TestBase::setUp();
    +
    +    // Remove the classic node table made in setup.
    +    $this->removeNodeMigrateMapTable(NodeMigrateType::NODE_MIGRATE_TYPE_CLASSIC, '7');
    +
    

    There seems some oddness here. This says d6, but then lists version 7. And dreditor says this is for a d7 test. I'm confused.

  4. +++ b/core/modules/node/node.module
    @@ -1257,3 +1259,52 @@ function node_comment_delete($comment) {
    +      // @todo: do something useful.
    

    Either put an issue here or remove this todo, no? Maybe just logging is enough.

  5. +++ b/core/modules/node/node.module
    @@ -1257,3 +1259,52 @@ function node_comment_delete($comment) {
    +  // If this is complete node migration then for of migrations, except the
    +  // classic node migrations, replace the dependency on a classic node migration
    +  // with a dependency on the complete node migration.
    

    Nit: this seems like the coherency of the message could be more clear.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new182.9 KB
new6.06 KB

#415.1 - Yes, I see what you mean. There are 15 migrations that will have dependencies altered to use the complete node migrations and those will then fail to run in the custom environment envisaged because requirements will fail. Not sure how to address that yet.

#415.2 - Good spotting. The upgrade hook will be needed when moving from using classic node migrations to complete node migrations, if that is to happen. The hook was created in response to an error from a specific case. The steps to reproduce that from #300 are: "I migrated the d6 test site, without this patch applied, then added a node to the source, then applied the patch and ran an incremental migration. That results in an error in a follow up migration." But that scenario is not possible anymore with the UI. If one has classic node migrate maps with data then it will always use classic mode.

Discussion of moving from classic to complete is for a separate issue, in my opinion.

As for the FollowupMigrationsCompleteTests, they can be removed. It was mostly my unfamiliarity with the Followup migrations that I wrote those tests to be sure they worked. Since are not creating new kernel migration tests for all the complete node migrations there is no need to single these one out.

#416.3 - Fixed
#416.4 - Added the same error message used on the CredentialForm when the database is not found. Is there a better thing to do here?
#416.5 - I saw that same thing as well and have improved the comment.

catch’s picture

Yes, I see what you mean. There are 15 migrations that will have dependencies altered to use the complete node migrations and those will then fail to run in the custom environment envisaged because requirements will fail. Not sure how to address that yet.

Could we do something like add a $settings check to the alter to make it a no-op if it's set?

Custom migrations would then hit the problem described, but they'd have a choice of flipping the setting (a one line change), or converting to use the new migrations.

It is a bit of disruption, but don't think this is a detectable situation.

quietone’s picture

StatusFileSize
new184.24 KB
new1.88 KB

I was thinking along the same lines. How is this?

Status: Needs review » Needs work

The last submitted patch, 419: 2746541-419.patch, failed testing. View results

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new185.21 KB
new877 bytes

Fixing the test.

alexpott’s picture

Issue summary: View changes

I agree that adding a setting is probably the best way around this. I've updated the change record with this information and I've added a release note snippet. Now I think all we need are patches for 9.0.x and 8.8.x too.

quietone’s picture

Thanks alexpoptt.

And here are the patches. I made one for each version and made an interdiff against the last time patches were made for all three versions, #397, thinking it would be easier to review.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs change record
+++ b/sites/default/default.settings.php
@@ -741,6 +741,19 @@
+ * Migration node type.

This sounds odd to me? Shouldn't it be Node migration type.?

Not enough reason to hold this up though :) Also, this definitely has a change record already, so untagging.

alexpott’s picture

Version: 8.9.x-dev » 8.8.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

@Wim Leers good point. Fixed on commit.

@quietone yay you did it!

Committed dcd666c and pushed to 9.0.x. Thanks!
Committed 8002785 and pushed to 8.9.x. Thanks!

Leaving open against 8.8.x for discussion around the backport. I think it needs to go into 8.8.x as a migrate critical that allow people to update to Drupal 8 that were not able to before.

I've additionally credited @catch, @plach and @hchonov for issue review.

diff --git a/core/assets/scaffold/files/default.settings.php b/core/assets/scaffold/files/default.settings.php
index df48333f2c..bec626cd16 100644
--- a/core/assets/scaffold/files/default.settings.php
+++ b/core/assets/scaffold/files/default.settings.php
@@ -720,7 +720,7 @@
 $settings['entity_update_backup'] = TRUE;
 
 /**
- * Migration node type.
+ * Node migration type.
  *
  * This is used to force the migration system to use the classic node migrations
  * instead of the default complete node migrations. The migration system will
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index df48333f2c..bec626cd16 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -720,7 +720,7 @@
 $settings['entity_update_backup'] = TRUE;
 
 /**
- * Migration node type.
+ * Node migration type.
  *
  * This is used to force the migration system to use the classic node migrations
  * instead of the default complete node migrations. The migration system will

  • alexpott committed 8002785 on 8.9.x
    Issue #2746541 by quietone, alexpott, maxocub, Wim Leers, mikelutz,...

  • alexpott committed dcd666c on 9.0.x
    Issue #2746541 by quietone, alexpott, maxocub, Wim Leers, mikelutz,...
alexpott’s picture

Issue tags: +9.0.0 release notes, +8.9.0 release notes
StatusFileSize
new1 KB
new185.71 KB

Here's a patch with the settings update.

daffie’s picture

Create an followup for the removal the lowercase table naming in NodeMigrateTypeTestTrait::getTableName() for PostgreSQL, because #2986452: Database reserved keywords need to be quoted as per the ANSI standard has landed for Drupal 9.0. See: #3119710: Remove lowercase table naming in NodeMigrateTypeTestTrait::getTableName().

masipila’s picture

@quietone (and all other contributors to this patch), woop, woop! \o/

This must be the longest and most complex migrate issue I've seen...

Cheers,
Markus

darren oh’s picture

I have a Drupal 7 site that uses Organic Groups access control to allow multiple groups to each create their own private translation of a node. I have not been able to think of any way to migrate this to Drupal 8 translations.

xjm’s picture

Status: Patch (to be ported) » Needs review

I think this should actually be NR for #428 now.

quietone’s picture

I agree with the change in #428, it corrects an error I made in the comment. But I can't RTBC this one.

masipila’s picture

+ PostgreSQL & SQLite test executions

wim leers’s picture

In testing today, it seems that rolling back the d7_node_complete migration fails. Can anybody else reproduce this?

quietone’s picture

I've made an issue to fix the the rollback problem reported in #435 , #3123095: Rollback of complete node migration fails.

Plus retesting PostgreSQL

quietone’s picture

Reran the PostgreSQL test and it is passing now.

masipila’s picture

Status: Needs review » Reviewed & tested by the community

#423 was committed to 8.9.x and 9.0.x

@alexpott's change in #428 is only in docblock and the update makes sense.

Issues reported after #428 have their own follow-ups.

All tests are green. RTBC.

Happy Easter to all who are celebrating it!

Cheers,
Markus

heddn’s picture

Status: Reviewed & tested by the community » Needs work
Related issues: +#3125763: Hidden dependency on migrate_drupal from node module when only migrate.module is enabled

This needs work to incorporate #3125763: Hidden dependency on migrate_drupal from node module when only migrate.module is enabled. Otherwise we'd have a 8.8 release that is broken.

heddn’s picture

Status: Needs work » Needs review
StatusFileSize
new191.15 KB
new5.43 KB

Here's that re-roll that incorporates #3125763: Hidden dependency on migrate_drupal from node module when only migrate.module is enabled into this patch. It wasn't RTBC (yet), so back to NR for a little while. Ideally, we'd have any feedback on _it_ over in _its_ issue, then re-rolls to incorporate _it_ back here.

heddn’s picture

StatusFileSize
new5.43 KB

Oops, did that wrong. If this goes to RTBC, the testbot will try to test the interdiff. Let's head that off.

heddn’s picture

StatusFileSize
new191.15 KB

:blush:

masipila’s picture

Re: #439 I'm confused to the max. :)

According to the interdiff 423-428 the only change is the docblock change. I must be missing something?

Markus

heddn’s picture

re #443: I hadn't realized that this backport hadn't landed yet in 8.8. But currently 8.9 and 9.x are both broken rather badly. So we need to incorporate the bug fixes from that other issue into this one. Otherwise, 8.8 will also be badly broken. The fix is a one-line module_exists check on migrate_drupal in node.module.

I apologize for all the noise in uploading the merged patch.

quietone’s picture

StatusFileSize
new15.96 KB
new186.02 KB

Now the patch for 8.8.x.

This does not have the test for the hidden dependency from #3125763: Hidden dependency on migrate_drupal from node module when only migrate.module is enabled because that is intended to be committed first. As expected the interdiff with the previous patch here for 8.8.x shows that the migration_plugin_alter hook and the associated kernel test are moved from the node module to migrate_drupal.

heddn’s picture

Title: Migrate D6 and D7 node revision translations to D8 » PP-1: Migrate D6 and D7 node revision translations to D8
Status: Needs review » Postponed
catch’s picture

Status: Postponed » Needs work

Just committed the patch there. This will need the bug fix from that issue incorporated into the patch here now.

catch’s picture

Title: PP-1: Migrate D6 and D7 node revision translations to D8 » Migrate D6 and D7 node revision translations to D8
heddn’s picture

Status: Needs work » Reviewed & tested by the community

RTBC, because the only things here added since it was last RTBC and landed in 8.9 and 9.x are the fixes from #3125763: Hidden dependency on migrate_drupal from node module when only migrate.module is enabled.

benjifisher’s picture

  1. I compared the patches for 8.8.x and 8.9.x in #423. Can someone comment on the differences? There are several differences in the fixtures. Maybe I should not worry, but I prefer to start with the patch that was committed (to the 8.9.x branch). I guess both patches were RTBC in #424.

  2. I compared the patches for 8.8.x in #423 and #428. As expected, the only differences were the ones suggested in #424.

  3. I compared the patches for 8.8.x in #428 and #445. The differences are as expected: the new patch makes more changes to migrate_drupal.module, it adds the kernel test to the migrate_drupal module instead of the node module, and it makes no changes to node.module.

  4. I applied the patch to 8.8.x and compared the .module file and the kernel test in the migrate_drupal module to the versions in 8.9.x, where #3125763: Hidden dependency on migrate_drupal from node module when only migrate.module is enabled has been fixed. The files are identical.

I am really close to saying +1 for RTBC, but I would appreciate an answer to my question in (1).

quietone’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new188.1 KB
new4.78 KB

@benjifisher, your thoroughness never ceases to amaze me.

#450-1. Yes, I think you are worrying to much. However, I have made another 8.8.x patch, this one has the same fixture as 8.9.x except, of course, for changes to the fixture made in commits only made on 8.9.x.

I started by diffing 2746541-8.8.x-423.patch 2746541-8.9.x-423.patch which showed differences in the drupal7 fixture. I checked where the differences may have come from with git blame and that didn't turn up anything useful. I gave up. I used Meld to work with the 8.8.x and 8.9.x versions of drupal7.php and to create an 8.8.x version that is the same as the 8.9.x version (expect for patches only applied to 8.9.x).
Then from 8.8.x I applied 2746541-8.8.x-445.patch and overwrote drupal7.php with the version created using Meld. At this point I edited drupal7.php to fix some existing spacing errors.

So, lets see if this breaks any tests.

catch’s picture

Title: Migrate D6 and D7 node revision translations to D8 » [backport] Migrate D6 and D7 node revision translations to D8
benjifisher’s picture

Status: Needs review » Reviewed & tested by the community

@quietone, funny, I am likewise impressed by your energy.

I confirmed that the only changes from #445 to #451 are in the fixture.

I tried comparing the patches directly. That did not go well. So I applied the patch from #451 locally and compared the fixture to the version in 8.9.x. There are several differences, and I did not check them all, but the ones I did examine seem to come from patches that, as you say, were applied to 8.9.x but not 8.8.x. Especially

Extracting the parts of these patches that affect the fixture, the first does not apply to 8.8.x, but the second does. If the committers want a more thorough comparison, then I will start by applying that in order to make it easier.

At this point, I leave it up to the committers. You can accept the patch in #445 based on the the reviews in #449 and #450, or accept the patch in #451 based on this review, or request additional review or changes. I do hope that this issue gets into 8.8.x so that site owners have the option of using this improvement to migrate their older sites into 8.8.

benjifisher’s picture

Issue summary: View changes
Status: Reviewed & tested by the community » Needs work

We discussed this issue on Slack.

@catch suggested that we change the default for $settings['migrate_node_migrate_type_classic'] in 8.8. @heddn approves of this idea. Let's make it happen!

I am setting the status to NW. If someone else can supply a patch, then I will review it promptly.

I think we can leave the release-notes snippet as is, but we should update the CR when we make this change.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new513 bytes
new188.1 KB

OK, set the flag to FALSE.

Status: Needs review » Needs work

The last submitted patch, 455: 2746541-455.patch, failed testing. View results

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new526 bytes
new188.1 KB

Sigh, I forgot to change /core/assets/scaffold/files/default.settings.php. And we are setting 'migrate_node_migrate_type_classic' to TRUE not FALSE like I typed above.

Status: Needs review » Needs work

The last submitted patch, 457: 2746541-457.patch, failed testing. View results

benjifisher’s picture

@quietone: no worries. That is why we have automated tests.

It seems that several tests are failing. Do these tests assume that we are using the complete node migrations?

I do not know why NoMultilingualReviewPageTest fails: that tests the review form before any migrations have run, right?

Do we need additional test coverage (in the 8.9.x, 9.0.x, 9.1.x branches) for the case $settings['migrate_node_migrate_type_classic'] = TRUE;?

quietone’s picture

Status: Needs work » Needs review

All the functional tests in migrate_drupal_ui, except d6/NodeClassicTest.php, assume that the complete node migration is enabled. I would like to keep these test like that.

The NoMultilingualReviewPageTest is about IdConflicts. The auditor is deciding that there is a IdConflict when the complete node migration is enabled whereas it doesn't with the classic node migration. That is pretty basic answer and I'd have to look at the auditing code to provide a better answer.

Do we need additional test coverage (in the 8.9.x, 9.0.x, 9.1.x branches) for the case $settings['migrate_node_migrate_type_classic'] = TRUE;?

I really hope not because these tests are awful to work with and take a terribly long time to run locally. And yes, I wrote these tests. If I knew what I know now when I was creating them I would have done it different. And, there is an issue to refactor these tests, which I would like to finish.

Now, back to your question. There is a d6/NodeClassicTest which is the same as Upgrade6Test. They both run a full migration (one classic node and the other complete node) and they also test the IdConflict page, the ReviewPage and the final entity counts.

If it is deemed necessary to add more tests I will, of course, do so but I should not be the one to decide if they are needed.

quietone’s picture

StatusFileSize
new7.98 KB
new192.06 KB

This patch restores all the functional tests, except d6/NodeClassicTest, to using the complete node migration. This is done byt changing the $settings value in setUp(), though it took me a while to learn how to alter $setting.

benjifisher’s picture

Status: Needs review » Needs work

@quietone, thanks so much for your continued work on this issue! I think we all assumed this would be a really easy update. We can take comfort knowing that it was a shared mistake.

  1. +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/IdConflictTest.php
    @@ -2,6 +2,8 @@
     
     namespace Drupal\Tests\migrate_drupal_ui\Functional\d6;
     
    +use Drupal\Core\DrupalKernel;
    +use Drupal\Core\Site\Settings;
    

    We do not need these use statements. Sorry, but I have to set the status back to NW for this.

  2. Please confirm this, but if we set migrate_node_migrate_type_classic in MigrateUpgradeTestBase, then I think the only other place we have to change it (in the other direction) is NodeClassicTest. This would have the advantage that all of these tests would be decoupled from the actual setting in settings.php.

Maybe we can just update the base class and leave NodeClassicTest alone, since it overrides the setting by creating a migrate map table.

I am not sure, but I think we should have test coverage for the classic migrations. It seems to me that we did have such coverage before this issue, and we removed it. Since this issue has already been committed to the other branches, I think that test coverage should be added in a separate issue. I hope the core committers will disagree, but I do not think we can commit this patch to 8.8.x until we have restored that test coverage.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new8.83 KB
new188.59 KB

There is nothing easy about the migrate_drupal_ui functional test. ;-)

I made the change in all the test file because I have been bitten before in migration tests where the base class does too much. However, point taken and the change is made.

I am not sure, but I think we should have test coverage for the classic migrations

While it is painful to read that I know we should too. I just have to get over my snit about it and then write the tests. There is a walk in the bush in my future!

quietone’s picture

Issue summary: View changes

So, now the migrate_drupal_ui functional tests in the 8.8. backport use the complete node migration, the same as the is done in other branches. The exception being d6/NodeClassicTest.

Meant to add that I agree with benjifisher that restoring test coverage for the classic node migrations in functional tests should be done in a separate issue.

benjifisher’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new1.73 KB

Thanks for the updated patch. I compared it to the one in #451, and it makes the required changes. I am attaching an interdiff, since this comparison is easier than comparing either of these to the patch in #461.

I am afraid that it is too late to get the patch into 8.8, but I hope there is still time.

As I said in #462, I am afraid that this patch effectively removes existing test coverage for the classic node migrations. I hope that the added coverage in NodeClassicTest is enough to make up for that.

I am setting the status to RTBC. It is up to the release managers to decide these questions.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

benjifisher’s picture

Title: [backport] Migrate D6 and D7 node revision translations to D8 » Migrate D6 and D7 node revision translations to D8
Status: Reviewed & tested by the community » Fixed

Since this issue has already been fixed for 8.9.x and it is now too late to back-port it to 8.8.x, I am marking it Fixed.

chriscalip’s picture

Hello, I am trying to figure out how to proceed.
This patch introduced an edge case BC from drupal 8.8.x to 8.9.x.

My existing migrations was doing a drupal 8 source content_entity:node to a custom analytics database.
Initially these migrations only had 2 keys: nid & langcode. Drupal 8.9 migrate_drupal now adds another key in the mix, vid.
so all migrate_map_[xxx] stop working because it only has 2 sourceid1 and sourceid2.. but Drupal\migrate_drupal\Plugin\migrate\source\ContentEntity insist there should be a sourceid3; thus breaking existing migrations.

Example

Next Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42S22]:
Column not found: 1054 Unknown column 'sourceid3' in 'field list':
INSERT INTO {migrate_map_[XXX]}
(source_ids_hash, sourceid1, sourceid2, sourceid3, source_row_status,
rollback_action, hash) VALUES (:db_insert_placeholder_0,
:db_insert_placeholder_1, :db_insert_placeholder_2,
:db_insert_placeholder_3, :db_insert_placeholder_4,
:db_insert_placeholder_5, :db_insert_placeholder_6); Array
(

Looking at the problem briefly, I believe adding specific configuration keys should ameliorate this probem.
The configuration keys would be: do we use langcode as a sourceid key, and do we use revision id as a sourceid key.

chriscalip’s picture

I believe adding the source-key, revision-id, is in error.
Situation a content_entity:node to a destination sql table.

We want nid-8 to go to corresponding target nid-1008.

We want nid-8-lang-en to go to corresponding target nid-1008.
We want nid-8-lang-fr to go to corresponding target nid-1009.

We want nid-8-lang-en-vid-8 to go to corresponding target nid-1008.
We want nid-8-lang-en-vid-9 to ALSO go to corresponding target nid-1008.
We want nid-8-lang-fr-vid-9 to go to corresponding target nid-1009.

Having configuration keys: langcode as a sourceid key, revision id as a sourceid key; would give migrate-api users the flexibility needed.

Status: Fixed » Closed (fixed)

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

geek-merlin’s picture

Impressing work! We got a regression though.

drumm’s picture

The large number of files attached to this issue, and comments on it, have been causing Drupal.org to have out-of-memory issues. I am closing comments so we do not have to spend more time looking for memory-saving hacks.