Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Field mapping bug - forgets related fields #23534

Closed

Conversation

eileenmcnaughton
Copy link
Contributor

Overview

Fix Field mapping bug - forgets related fields

I hit this during r-run on imports and initially thought it was a regression but it is there in 5.48 too.

To replicate

  1. do an import with a contact where some of the fields are for a related contact - here is the csv I used for testing
  2. do NOT save the mapping, do not collect $200
  3. once you hit the preview field go back (still no $200)
  4. check if the related contact's field is still populated

Before

image

After

image

Technical Details

I think I got to the bottom of what the js was doing & switched to a simple jquery hide (well a whole tonne of them - I'm sure it could be more elegant at the jquery end but figuring out this much was a lot....

Comments

@civibot
Copy link

civibot bot commented May 21, 2022

(Standard links)

@civibot civibot bot added the master label May 21, 2022
@eileenmcnaughton eileenmcnaughton force-pushed the import_check branch 3 times, most recently from 0f8ce5a to 13be8cc Compare May 24, 2022 06:30
@aydun
Copy link
Contributor

aydun commented May 24, 2022

The first time the mapping page is shown, I get additional empty dropdowns after some fields:
image

If I then 'continue' to the preview and then 'previous' to the mapping I correctly get:
image

I added a phone field to trigger the 4th dropdown. The mappings are preserved when returning to the mapping screen from the preview:
image

One small anomaly: when selecting 'street address' for the main contact, the default location is 'Primary', but when selecting 'child of', 'street address', the default location is 'Home' instead of 'Primary'.

@aydun
Copy link
Contributor

aydun commented May 24, 2022

I see you've pushed more changes - those results were from before that push.

@eileenmcnaughton
Copy link
Contributor Author

@aydun no - just a rebase - there weren't any changes in it - just keeping it fresh

@eileenmcnaughton
Copy link
Contributor Author

@aydun I'm assuming your anomaly is unchanged by this PR?

foreach ([1, 2, 3] as $columnNumber) {
if (empty($mapperValues[$columnNumber])) {
$rowNumber = str_replace('mapper[', '', substr($row, 0, -1));
$js[] = "cj('#mapper_{$rowNumber}_{$columnNumber}').hide();";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't 'cj' deprecated? How about:
$js[] = "CRM.\$('#mapper_{$rowNumber}_{$columnNumber}').hide();";

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah - I'm always confused about that - it doesn't seem to work on some pages - @colemanw what should I use (Or are we just happy with 'a bit better' here?)

@aydun
Copy link
Contributor

aydun commented May 24, 2022

@aydun I'm assuming your anomaly is unchanged by this PR?

Yes - this also happens without this PR. I just noticed it while testing this, so not a problem for this PR.

@eileenmcnaughton
Copy link
Contributor Author

@aydun I think the 'Primary' behaviour is better - so I might yet circle round to that (not necessarily in this PR)

}
else {
$js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n";
if ($hasColumnNames) {
// do array search first to see if has mapped key
$columnKey = array_search($this->_columnNames[$i], $this->getFieldTitles());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to do a PR on a PR ... but the line just below this that reads:
$defaults["mapper[$i]"] = $columnKey;
can be changed to:
$defaults["mapper[$i]"] = [$columnKey];
to avoid the problem on the initial display of the mapping page that I noted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aydun - I can add that in - but do you want to confirm if it works with the other change you suggested? - if so I can include that too

(I think you can actually suggest changes in review & I can commit them through th eUI too)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aydun well I've pushed up tht change - so no pressure on checking the other

@aydun
Copy link
Contributor

aydun commented May 24, 2022

See: eileenmcnaughton#14

@eileenmcnaughton
Copy link
Contributor Author

@aydun - I merged your commit in & rebased to cheer up Jenkins - but now it's defaulting to 'Home' for both rather than 'Primary' for both

@aydun
Copy link
Contributor

aydun commented May 24, 2022

but now it's defaulting to 'Home' for both rather than 'Primary' for both

That's odd ... it's showing Primary for me on initial display:

image

@eileenmcnaughton
Copy link
Contributor Author

I'll try again

@eileenmcnaughton
Copy link
Contributor Author

@aydun - OK it IS showing 'Primary' for main contact - but when I changed 2 fields to related I got 'Home'

image

if (!empty($sel2[$key]) && is_array($sel2[$key])) {
reset($sel2[$key]);
$subkey = key($sel2[$key]);
$def[] = $subkey;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "Primary" is set here when the form is first loaded and the mapping is guesstimated. But after the form is built there is no default set for $sel2 so "Home" is selected as that is the first item in the select list.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just rebased this - do you see a fix for ^^ @darrick - it was kind of a tangent on this PR - so I'm also happy to drop that bit out of scope for this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "Primary" is set here when the form is first loaded and the mapping is guesstimated.

Yes, 'Primary' is selected because it is first in the php list.

When you change to a relationship ('spouse of') then the JS `swapoptions()' function is called. No value is initially set so the dropdown shows the first option.

The array ordering in php is:

"Primary":"Primary",
"5":"Billing",
"1":"Home",
"3":"Main",
"4":"Other",
"2":"Work"

However, although the php values are passed in the same order, when the JS function loops through the array to add the select options, it does so based on the ordering of the keys, not the order added, and so the options are ordered as:

1: "Home"
​​2: "Work"
​​3: "Main"
​​4: "Other"
​​5: "Billing"
​​Primary: "Primary"

To fix the default, we could explicitly pass through a default value and use that when creating the select options in swapoptions()

If we wanted to solve the more general issue of making the JS options match the PHP order, I think we would need to assign new key values when we pass from PHP to JS, but then also map back from from JS keys to PHP keys. That sounds like a lot of effort for something few people are likely to notice!

This looks like it goes well beyond the scope of the initial aim of this PR.

eileenmcnaughton and others added 4 commits May 26, 2022 17:49
default mappings.

Use CRM.$ instead of cj for new jQuery

Fix an assignment to $this->fieldUsed.  Not sure why it exists but
lets update the right key.
@eileenmcnaughton
Copy link
Contributor Author

@colemanw - this should be mergeable based on ^^ - ie this is an agreed fix - discussion focusses on a tangential bug that came up in review

@darrick
Copy link
Contributor

darrick commented May 28, 2022

This is a bit out of my skill set. But most things are. But I did step through the code a bit the other night. To me it seemed like setDefault was for setting form defaults when the page is first loaded. And this bug has more to do with the form state being lost or mangled when hitting the back button.

Also, the csv file used for testing has duplicate header field. I recall theleague/cvs throws an error for that. Maybe that should be caught in Validation earlier.

I just tested real quick. If I do the following (i.e. just don't set any defaults if the mapper is set). Then it works fine except the extra selects not be hidden. This is based off master.

    $mapper = $this->getSubmittedValue('mapper');

    for ($i = 0; $i < $this->_columnCount; $i++) {
      $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', [1 => $i]), NULL);
      $sel->setOptions([$sel1, $sel2, $sel3, $sel4]);

      // Don't set any defaults if we are going to the next page.
      // ... or coming back.
      // But do add the js.
      if (!empty($mapper)) {
        //$sel->setValue($mapper[$i]);
        //$js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n";
      }
      else if ($this->getSubmittedValue('savedMapping') && $processor->getFieldName($i)) {
        $defaults["mapper[$i]"] = $processor->getSavedQuickformDefaultsForColumn($i);
        $js .= $processor->getQuickFormJSForField($i);
      }
      else {
        $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n";
        if ($hasColumnNames) {
          // do array search first to see if has mapped key
          $columnKey = array_search($this->_columnNames[$i], $this->getFieldTitles());
          if (isset($this->_fieldUsed[$columnKey])) {
            $defaults["mapper[$i]"] = $columnKey;
            $this->_fieldUsed[$key] = TRUE;
          }
          else {
            // Infer the default from the column names if we have them
            $defaults["mapper[$i]"] = [
              $this->defaultFromColumnName($this->_columnNames[$i]),
              0,
            ];
          }
        }
        else {
          // Otherwise guess the default from the form of the data
          $defaults["mapper[$i]"] = [
            $this->defaultFromData($this->getDataPatterns(), $i),
            //                     $defaultLocationType->id
            0,
          ];
        }
      }

    }

image

@darrick
Copy link
Contributor

darrick commented May 28, 2022

The following solves the problem on my end: c9cd1c1

Adding this $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n"; is what was causing the second, third and fourth options to get hosed. As that function rebuilds the options for the select lists above the select list it was called from. Usually it's called from the onchange event for the appropriate select list. But above is hard coded to the first select list.

For what @aydun is mentioning about having a default location type maybe the swapOption js could be modified to use a default as it's currently hardcoded to false. Setting it in buildForm will only work for inferred defaults.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants