Problem/Motivation

The Commerce XLS Import Module has a UI that allows the user to set the fields that the import would use. A similar thing would be useful for migrations with a CSV source, provide a UI so that the mapping of migration source properties to CSV columns can be changed.

Proposed resolution

Make a new form as described and add a hook migration_plugin_alter to change the column_names in the source plugin configuration.
Use a temp private store to save the values for the hook to access.
The changes are stored in temp store so will only persist for the default length of time, which is 1 week.

The URL used is: /admin/structure/migrate/manage/{migration_group}/migrations/{migration}/source_csv
A screenshot of the form with a CSV source.

Screenshot of message displayed when the source plugin is not of the CSV class.

Remaining tasks

Decide on the URL for the form
Decide where to link to it
Review
Commit

User interface changes

Yes, a new form for modifying the column_name to source property association.

CommentFileSizeAuthor
#54 2942373-54.patch28.23 KBheddn
#54 interdiff_49-54.txt531 bytesheddn
#48 interdiff.txt495 bytesquietone
#48 2942373-49.patch28.24 KBquietone
#47 2942373-47.patch28.24 KBquietone
#47 interdiff.txt291 bytesquietone
#45 interdiff.txt288 bytesquietone
#45 2942373-45.patch28.24 KBquietone
#43 interdiff.txt946 bytesquietone
#43 2942373-43.patch28.23 KBquietone
#41 interdiff.txt1.7 KBquietone
#41 2942373-41.patch28.2 KBquietone
#36 interdiff_33-36.txt1.48 KBheddn
#36 2942373-36.patch28.18 KBheddn
#33 interdiff_29-33.txt1.09 KBheddn
#33 2942373-33.patch27.09 KBheddn
#29 2942373-29.patch26.78 KBquietone
#29 interdiff.txt15.18 KBquietone
#27 Schermafbeelding 2018-03-15 om 21.09.40.png77.68 KBmegachriz
#26 interdiff.txt3.31 KBquietone
#26 2942373-26.patch22.61 KBquietone
#25 interdiff.txt3.42 KBquietone
#25 2942373-25.patch21.86 KBquietone
#24 interdiff.txt4.99 KBquietone
#24 2942373-24.patch20.98 KBquietone
#23 interdiff.txt5.95 KBquietone
#23 2942373-23.patch21.09 KBquietone
#20 interdiff.txt2.9 KBquietone
#20 2942373-20.patch20.66 KBquietone
#17 interdiff.txt397 bytesquietone
#17 2942373-17.patch20.48 KBquietone
#16 interdiff.txt3.62 KBquietone
#16 2942373-16.patch20.52 KBquietone
#14 interdiff.txt5.62 KBquietone
#14 2942373-14.patch20.58 KBquietone
#11 interdiff.txt693 bytesquietone
#11 2942373-11.patch14.3 KBquietone
#9 Selection_007.png11.09 KBquietone
#8 Screenshot from 2018-03-12 13-30-20.png70.06 KBquietone
#8 interdiff.txt8.02 KBquietone
#8 2942373-9.patch14.3 KBquietone
#7 interdiff.txt7.38 KBquietone
#7 2942373-7.patch14.24 KBquietone
#5 interdiff.txt519 bytesquietone
#5 2942373-5.patch12.71 KBquietone
#2 2942373-2.patch12.13 KBquietone

Comments

quietone created an issue. See original summary.

quietone’s picture

StatusFileSize
new12.13 KB

Here is a start and it was working locally.

However, I don't know forms and there is no way to get there from the UI, you need to enter the URL, /admin/structure/migrate/manage/{migration_group}/migrations/{migration}/source_csv, which I found easiest to do by navigating to the View->Source for the migration and then modifying the URL. Obviously, this needs to be fixed. Ideas? Since I don't know forms a patch would be best or, at least, an example.

This does not change the migration in config, it does a hook alter, so the changes are not permanent.

heddn’s picture

Status: Needs review » Needs work

The last submitted patch, 2: 2942373-2.patch, failed testing. View results

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new12.71 KB
new519 bytes

This add links so that you can get to the new edit form. Still needs work to fix the failing tests and it should only show the new edit form for CSV sources.

Status: Needs review » Needs work

The last submitted patch, 5: 2942373-5.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new14.24 KB
new7.38 KB

Add a fix for the test as well as comments and code standard fixes.

The test is fixed by adding a if in migrate_plugins_alter to check that the stored value exists before using it.
+  if (isset($store) && $store->get('source_configuration')) {
+    foreach ($store->get('source_configuration') as $key => $value) {
+      if (isset($value['updated'])) {
+        $migrations[$key]['source']['column_names'] = $value['updated'];
+      }
quietone’s picture

Issue summary: View changes
StatusFileSize
new14.3 KB
new8.02 KB
new70.06 KB

If the source plugin is not of class CSV then it is not editable and a message is now displayed. Testing found test code lying about and typos, all that is fixed now. And the SourceCsvForm:buildForm() is reworked so error cases are checked in the beginning.

quietone’s picture

Issue summary: View changes
StatusFileSize
new11.09 KB

Status: Needs review » Needs work

The last submitted patch, 8: 2942373-9.patch, failed testing. View results

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new14.3 KB
new693 bytes

Add another check in the plugin alter.

Started to add a test but stuck on schema errors.

Drupal\Core\Config\Schema\SchemaIncompleteException: Schema errors for migrate_plus.migration.csv_source_test with the following errors: migrate_plus.migration.csv_source_test:source.path missing schema, migrate_plus.migration.csv_source_test:source.header_row_count missing schema, migrate_plus.migration.csv_source_test:source.enclosure missing schema, migrate_plus.migration.csv_source_test:source.keys missing schema, migrate_plus.migration.csv_source_test:source.column_names missing schema
quietone’s picture

Issue summary: View changes

Update the IS.

Ready for initial review and testing.

heddn’s picture

Perhaps you can give #2913592: Missing schema a spin and see if that fixes your issues. Love what I'm seeing in the screenshots.

quietone’s picture

StatusFileSize
new20.58 KB
new5.62 KB

I switched to using the existing test migration in migrate_tools and just changing the source plugin and process. I don't yet know why the previous one failed. I might look at it one more time.

This patch adds a functional test and in making the test I discovered two errors. They aren't fixed yet, I want to see the failures here.

Status: Needs review » Needs work

The last submitted patch, 14: 2942373-14.patch, failed testing. View results

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new20.52 KB
new3.62 KB

Perhaps it needs @requires.

quietone’s picture

StatusFileSize
new20.48 KB
new397 bytes
quietone’s picture

The new test isn't running on the testbot. Anyone know why?

heddn’s picture

Status: Needs review » Needs work
  1. +++ b/tests/src/Functional/CSVSourceEditTest.php
    @@ -0,0 +1,116 @@
    +    'migrate_source_csv',
    

    I think we need to either 1) add a require --dev for drupal/migrate_source_csv to composer.json or 2) add a test_dependencies. Or both. This should be on the main migrate_tools project, not the test module.

  2. +++ b/migrate_tools.module
    @@ -28,3 +28,20 @@ function migrate_tools_entity_type_build(array &$entity_types) {
    +  if (isset($store) && (\Drupal::currentUser()->id()) && ($store->get('source_configuration'))) {
    

    Is this check of UID to see if authenticated? I think we can do that in a cleaner way, no? BTW, the drush user is always anonymous unless we do something special, so counting on an authenticated user is difficult.

  3. +++ b/migrate_tools.routing.yml
    @@ -122,3 +122,10 @@ migrate_tools.execute:
    +    _permission: 'administer migrations'
    

    I think the access should be tied to permission and a _module_dependencies route check on migrate_source_csv. See https://www.drupal.org/docs/8/api/routing-system/structure-of-routes

  4. +++ b/src/Form/SourceCsvForm.php
    @@ -0,0 +1,395 @@
    +    $this->currentRouteMatch = $currentRouteMatch;
    ...
    +    $migration_name = $this->currentRouteMatch->getParameter('migration');
    

    I think we can get the migration passed into the form directly using route parameters arguments. The benefit there is you don't have to do the error checking if the migration exists.

  5. +++ b/src/Form/SourceCsvForm.php
    @@ -0,0 +1,395 @@
    +    if (!is_a($source, CSV::class)) {
    

    If we do an access check on the route, then this isn't necessary. When doing the access check, also check on the source. This will require a custom access check I think.

  6. +++ b/src/Form/SourceCsvForm.php
    @@ -0,0 +1,395 @@
    +    unset($this->sourceConfiguration[$this->id]);
    +    try {
    +      $this->store->set('source_configuration', $this->sourceConfiguration);
    

    Can we delete things from the store? Wouldn't that be easier/cleaner?

  7. +++ b/src/Form/SourceCsvForm.php
    @@ -0,0 +1,395 @@
    +      $row = fgetcsv($handle);
    

    I think when opening the file, we can limit the number of lines read in. Let's do that and use the header column config option. That way we don't open a full 10mb csv just to read the first line.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new20.66 KB
new2.9 KB

1. migrate_source_csv added to composer.json
2. Yes, I was just responding to the errors. What is a better way?
3. Added +module_dependencies
4. Fixed and learned how to do that.
5. Haven't changed this because I prefer a helpful error message to access denied for these two cases.
6. No, because all the source configurations use the same key 'source_configurations'. Maybe that should change.
7. Maybe I'm not understanding this, when no length is given fgets will read until it reaches the end of the line.

Status: Needs review » Needs work

The last submitted patch, 20: 2942373-20.patch, failed testing. View results

heddn’s picture

19/20 feedback:

2: \Drupal::currentUser()->isAuthenticated(). However, depending on if a user flushes cache from drush or web UI, it won't work the way you expect. Because drush runs as anonymous. We'll have to think about this one. Because the alter runs at cache clear time I think, no?
5: If you are building links and menu items using the drupal api for links, it will check access for you and hide the link if the access doesn't exist. This (I feel) is better than posterior printing a message that the url won't work for them. Just don't make it available in the first place. If someone starts playing with the URL themselves, they already know enough and wouldn't be surprised with a 403.
6: Maybe it should change. This seems unexpected and only supports a single migration at a time? Teach me what this is doing.
7: Ignore my comment. I hadn't read the code in question closely enough and my thought is nonsense.

  1. +++ b/src/Form/SourceCsvForm.php
    @@ -171,24 +173,18 @@
    +      $this->messenger->addError($this->t('Source plugin migration @migration not found.', ['@migration' => $migration]));
    ...
    +        '#description' => $this->t('The migration source for :migration can not be edited.', [':migration' => $migration]),
    

    Won't this result trying to print an object? Wouldn't it be better to use $migration->label()

  2. +++ b/migrate_tools.info.yml
    @@ -6,3 +6,4 @@ core: 8.x
    +  - migrate_source_csv:migrate_source_csv (>=8.2-dev)
    

    This should not be listed here as a required dependency. Test dependency, yes.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new21.09 KB
new5.95 KB

2. OK The alter runs after the cache clear. During test of the plugins alter I need to drush cr, then do a drush ms for the alter to run and debug it, if necessary.
5. I'll get back to you on this. This patch makes a small change, adding a module dependency in routing.yml and removing the corresponding code from the form.
6. Yes, it should change. It was just the first go. I want to improve this next.
7. NP

1. Here, $migration is the plugin id, it isn't bringing in the object.
2. Fixed.

This patch fixes the things that came up while I was writing the test. The test runs locally but not yet on the testbot but when it does hopefully it will pass.

quietone’s picture

StatusFileSize
new20.98 KB
new4.99 KB

Don't let the green fool you, the new test still isn't running.

#22.6 This changes to the key used for the store to the plugin_id. Passes tests locally so seems to be fine. It much simpler and the comments updated as well.

quietone’s picture

StatusFileSize
new21.86 KB
new3.42 KB

Now I remember the reason all the change migration information was stored in one key, source_configurations. It made it easy at the time for the alter to find the migrations that were changed. It would get the 'source_configuration' from the store, which had a key for each migration changed. Now that the changes are stored by migration_id, the alter has to loop through all migrations and check if the id is in the private store. Instead of that there is now a simple array of the changed migrations in the store.

Changed the index 'updated' to 'changed' throughout because I prefer it.

Still to do is #22.2 and #22.5.

quietone’s picture

StatusFileSize
new22.61 KB
new3.31 KB

22.5 Should be fixed in this patch.

megachriz’s picture

StatusFileSize
new77.68 KB

I haven't looked into this issue at all, but I saw the screenshot at the top of the page and I would say that it would be nice if the mapping UI was in multiple columns as is the case in Feeds:

megachriz’s picture

I have some more UI designs here, though some may only make sense in Feeds context: #2917405: UI designs.

quietone’s picture

StatusFileSize
new15.18 KB
new26.78 KB

@MegaChriz, thanks for stopping by and commenting. On a first look, having a source and a target column seems a better way to present this information. I haven't read through the UI designs issue yet, thanks for the link.

This patch has quite a few changes:
The test for the currentUser in migrate_tools.module has been removed. I guess the addition of the Access checking has eliminated the problem.
The URL is now '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/source/edit'. Before it didn't include 'edit'.
Fixed a typo in the name of the migration_changed key
Reworked the test module migration to work for a taxonomy vocabulary destination so that a migration can be executed and tested
Execute the migration twice in the test to verify different results are optioned when the aliases are changed.

heddn’s picture

@quietone should we merge this and later improve the UI or attempt to make changes on the UI before landing it. The code itself looks pretty solid at this point. I'm sure I could pull out some nits, but wanted to know if we're at that point in the review cycle or if you are just wanting to see if tests pass on the testbot. Because tests still aren't running :(

quietone’s picture

Status: Needs review » Needs work

Yes, lets get this in and move the UI improvements to a follow up.

The work to do here is to get the new test running on the testbot and review, no more features/additions planned here.

To start, what is wrong with the test?

quietone’s picture

heddn’s picture

StatusFileSize
new27.09 KB
new1.09 KB

Let's see how this is treated.

heddn’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 33: 2942373-33.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

heddn’s picture

Status: Needs work » Needs review
StatusFileSize
new28.18 KB
new1.48 KB

Status: Needs review » Needs work

The last submitted patch, 36: 2942373-36.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

heddn’s picture

Yeah, tests are recognized now. On to fixing things. I'll leave that to you @quietone.

quietone’s picture

@heddn, thank you.

The form needs the \SplFileObject object for the source CSV file in order to determine the things like the header row count etc. This is already being calculated in the CSV:setupFile protected method. If that was made public then the form would not have to duplicate that setup code. Is there any reason not to make CSV:setupFile public? It should be noted that setupFile is called from initializeIterator() which is public, but that won't work as it is not guaranteed to return an SplfileObject. In the case when the source plugin uses yeild it returns a Generator.

Can the form be used to pass the SplFileObject?

edit: add initializeIterator

quietone’s picture

A getter for the file in CSV class will work.

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new28.2 KB
new1.7 KB

Getting closer now. Now gets the file with all the properties setup via the new getter in migrate_source_csv. I also added another try catch in the cancel function, to match the existing one, on this->store->set. Not sure how to handle those execptions. Maybe just display the returned error?

Status: Needs review » Needs work

The last submitted patch, 41: 2942373-41.patch, failed testing. View results

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new28.23 KB
new946 bytes

Change the version of migrate_source_csv in the test dependency to -dev. Add display of error message if TempStoreException is caught in the cancel method.

Status: Needs review » Needs work

The last submitted patch, 43: 2942373-43.patch, failed testing. View results

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new28.24 KB
new288 bytes

Change composer.json to use migrate_source_cs 2.x-dev

Status: Needs review » Needs work

The last submitted patch, 45: 2942373-45.patch, failed testing. View results

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new291 bytes
new28.24 KB

Remove the carat

quietone’s picture

StatusFileSize
new28.24 KB
new495 bytes

Yay, the test is running now. Boo, I found a typo.

The changes since #40 are to get the file properties initialized and then get the SplFileObject for the file. This is now down with the following code.

    // Get the source file after the properties are initialized.
    $source->initializeIterator();
    $this->file = $source->getFile();

And to display any error messages if the actions on the store throw an exception during when the cancel button is clicked.

And to use the dev version of migrate_source_csv

quietone’s picture

Issue tags: +Needs manual testing

this should be tested by someone other than me.

irinaz’s picture

@quietone, I am happy to test, but now sure what is setup. Should I apply patch 2942373-49.patch to Migrated tools module or do I need to make any other changes?
thanks, Irina

quietone’s picture

@irinaz, thanks for the offer!

Yes, apply the patch. And you will need an existing migration that uses a CSV file for import. You may have one from a project or the example here should work, https://github.com/heddn/d8_custom_migrate. The process is to run the migration, verify the results, rollback the migration, change the column assignments, run the migration, verify the results. We need to be sure that the changes made to the column assignments really work.

I wrote this in steps to help make sure I didn't miss anything.

  1. Go to the /admin/structure/migrate execute the migration.
  2. Verify on the site that the migration and that the correct data is displayed
  3. Go to the /admin/structure/migrate rollaback the migration.
  4. Go to /admin/structure/migrate click on the name of same migration.
  5. Click on the 'Edit' tab
  6. Click on 'Source'
  7. You should now be able to change the column aliases for the fields in that migration. That is you can change it so that the migration uses column 2 this time for field A instead of column 1 for field A.
  8. Make some changes to the columns assignments
  9. Click Submit
  10. Then, as before, run the migration.
  11. Verify on the site that the migration and that data displayed is from the changed columns.

I hope that make sense. If you get stuck, ask here.

irinaz’s picture

@heddn, thanks for pointing me to https://www.drupal.org/project/migrate_source_csv.

@quietone, I setup test site http://demo-drupal-8-content-migration.pantheonsite.io/admin/modules and added migrate-related modules, including CSV source migration.

In /admin/structure/migrate I see migrations that come from module migrate_example, but not from /modules/migrate_source_csv/tests/modules/migrate_source_csv_test. I plan to go into back end and configure this migration via drush, but I wanted to make sure that I am not missing something obvious in the web interface.
Thanks for your help with this!!!

quietone’s picture

@irinaz, sorry I ddn't see this earlier. Ah, looks like a missing step which is to upload the csv. I am working from memory but on, /admin/structure/migrate, there should be a button in the upper right to upload your source file. Upload your source file first and then it should work.

Good luck!

heddn’s picture

StatusFileSize
new531 bytes
new28.23 KB

Still waiting on manual testing, or should we proceed with merging it? It still applies cleanly.

+++ b/migrate_tools.info.yml
@@ -6,3 +6,5 @@ core: 8.x
+  - migrate_source_csv:migrate_source_csv (>=8.x-2.x-dev)

Can we pin this on a specific version now? Doing that now. See updated patch.

irinaz’s picture

It looks good, let's merge, and if there are any conflicts after merge we can report them. Thanks for making this happen :)

heddn’s picture

Status: Needs review » Reviewed & tested by the community

Based on #55, let's RTBC this thing.

  • heddn committed 52f8274 on 8.x-4.x authored by quietone
    Issue #2942373 by quietone, heddn, irinaz: Add UI to alter column_names...
heddn’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -Needs manual testing
joelpittet’s picture

+++ b/migrate_tools.module
@@ -28,3 +28,24 @@ function migrate_tools_entity_type_build(array &$entity_types) {
+  $tempStoreFactory = \Drupal::service('tempstore.private');
+  $store = $tempStoreFactory->get('migrate_tools');
...
+  $migrationsChanged = $store->get('migrations_changed');

These get() assume a set() and unfortunately with drush that throws an exception:

Error: Call to a member function getId() on null in /core/lib/Drupal/Core/TempStore/PrivateTempStore.php on line 222 #0 /core/lib/Drupal/Core/TempStore/PrivateTempStore.php(212): Drupal\Core\TempStore\PrivateTempStore->getOwner()

This could be exposing a core bug? getOwner doesn't check for null or start a session like it does in session for anonymous users.

How would you like to fix this? Try/Catch? Core bug report? Separate issue?

joelpittet’s picture

Actually starting the session seems to work... not sure if this is the right way though.

I added this for a quick fix:

function migrate_tools_migration_plugins_alter(array &$migrations) {
  $session = \Drupal::service('session');
  \Drupal::request()->setSession($session);
  $session->start();
   ...

#2865991: provide an API to forcibly start a session

heddn’s picture

heddn’s picture

I've opened #2976862: PrivateTempStore throws exception if session is not started (drush users) to work around this in migrate tools while we fix upstream.

Status: Fixed » Closed (fixed)

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

yareckon’s picture

This feature makes drupal 8.5 the base requirement, as tempstore.private doesn't exist in 8.4

heddn’s picture

That's probably OK, seeing as 8.4 has limited support at this point. But we could probably open a new issue to pin the version in composer.json to 8.5+.

batigolix’s picture

Is this functionality available in the 5.x version of migrate_tools? It seems te be missing.