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

Map gender fields on CSV import #2401

Merged
merged 7 commits into from
Dec 10, 2024
Merged

Conversation

k-nut
Copy link
Collaborator

@k-nut k-nut commented Dec 2, 2024

Description

This PR allows users to properly map the gender fields form their CSV data to the gender definition that Zetkin understands

Screenshots

[Add screenshots here]

Changes

Inspired by the enum and org mappings, this adds config and preview components for the gender field.

Notes to reviewer

The null handling is pretty ugly right now (just replacing null with "null" so that it can also be used in translations). I'm also unsure how it should generally be handled and how much translations should be duplicated. Any input is appreciated.

Related issues

Resolves #2348

@richardolsson
Copy link
Member

@ziggabyte can you have a look at this and provide some feedback to @k-nut? Especially around the handling of null, which is an allowed value for gender but difficult to use as value in components.

@ziggabyte
Copy link
Contributor

ziggabyte commented Dec 2, 2024

Hi @k-nut ! :D So glad you have taken on this task, it's been very needed!

I've played with the deployed version of the code, and also experimented around a bit in your code to give some feedback.

I agree with you that the handling of null as "null" needs a bit of work. My thought is that basically, there are three gender options in the Zetkin universe, OR null, which is not a gender option but a value the gender field can have when you don't know the gender of the person. So in that line of thought I think the experimental and interesting null-as-a-string version, could be replaced by making the type Gender contain only the strings f, m and o and then typing things to be Gender | null:

bild

This means the Select in GenderMappingRow would look like this (adding an option with the value unknown which represents setting the value to null)

bild

Does this approach make sense to you?

@k-nut
Copy link
Collaborator Author

k-nut commented Dec 5, 2024

Thank you for your thoughts. I pushed a version inspired by your proposal, let me know what you think!

@richardolsson
Copy link
Member

This PR is still in draft mode, but I think it's ready for review so I'm going to mark it as such. Let me know if that was a mistake and we can change it back.

@richardolsson richardolsson marked this pull request as ready for review December 7, 2024 10:23
@k-nut
Copy link
Collaborator Author

k-nut commented Dec 7, 2024

Oh yes, I forgot. Thank you!

Copy link
Contributor

@ziggabyte ziggabyte left a comment

Choose a reason for hiding this comment

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

Hi! :D Looking nice with some updates! I have given a bit of a more detailed reading of the code and some feedback, if something does not seem right please write your thoughts and we can discuss it!

I also noticed a little bit of logic is missing, that you need to "hook up to" by including GENDER as a configurable column in this array in MappingRow.tsx. This will show a button to open the config if you wanna get back to it after going to different fields, and also have a warning message if the mapping is unfinished:
bild

Comment on lines 61 to 71
{!showSelect && (
<Button onClick={() => setMapping(true)}>
<Msg
id={
messageIds.configuration.configure.genders
.showGenderSelectButton
}
/>
</Button>
)}
{showSelect && (
Copy link
Contributor

Choose a reason for hiding this comment

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

This logic, to have a button in place of the Select, was created in the organization mapping because loading and rendering each Select with organisations took a really really long time. So this button was placed there to only load/render each select "on demand". With genders, since no loading of data is happening and it's a small set of values, i think it would be nice to remove the button and just show the select!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nice, I thought this was for a nicer UI. Removed 🧹

Comment on lines 84 to 86
// selecting `unknown` is like deselecting
onDeselectGender();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure I agree (and I might very well be wrong!) that it should be the same to deselect as to select to import a value as unknown (null).

What I mean is, that when selecting unknown, I want there to be information added to my object of mapping that says "value x should be mapped to null". But deselecting means removing the information related to "value x" in my object of mapping. This comes with the undesired side effect that if I open the gender mapping, and the first value i want to map i select should map to "unknown" - this is not recognised as me having done any mapping at all. Our validation for the mapping will believe no mapping for gender has happened, and it will be forbidden to move on to the validation step.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

I might be misunderstanding, so please ignore if does not make sense.

I believe that another aspect of this decision is what happens to people for where there is no mapping in the import, but do have gender data in the database.

Not mapping anything doesn't change them in the database (the data object will not include gender), while mapping to "unknown" will overwrite whatever is already in the database (setting data.gender to null).

So IMO not mapping, and mapping to unknown is two separate actions where the former is a no-op and the latter is destructive.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I changed the implementation to something that should now work for any of the "regular" genders, a deliberate "we want to set this to null" and a "ignore this value in the import"

Comment on lines 25 to 42
<PreviewGrid columnHeader={'Gender'} rowValue={null} unmappedRow={true} />
);
}

const value = fields?.[fieldKey];
if (value !== 'm' && value !== 'f' && value !== 'o' && value !== 'null') {
return (
<PreviewGrid
columnHeader={messages.configuration.preview.columnHeader.gender()}
emptyLabel={messages.configuration.preview.noValue()}
rowValue={null}
unmappedRow={false}
/>
);
}
return (
<PreviewGrid
columnHeader={'Gender'}
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 columnHeader needs to be localised in all the places :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. Added 👍

Comment on lines 24 to 44
// Check if there is already a map for this row value to an org ID
const map = column.mapping.find((map) => map.value == value);
// If no map for that value
if (!map) {
const newMap = { gender, value: value };
dispatch(
// Add value to mapping for the column
columnUpdate([
columnIndex,
{
...column,
mapping: [...column.mapping, newMap],
},
])
);
} else {
// If there is already a map, replace it
// Find mappings that are not for this row value
const filteredMapping = column.mapping.filter((m) => m.value != value);
// New orgId for that row value
const updatedMap = { ...map, gender };
Copy link
Contributor

Choose a reason for hiding this comment

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

Some little copy-paste mistakes here in the comments, do you wanna updated them to relate to gender mapping and not org mapping? :)

Copy link
Member

Choose a reason for hiding this comment

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

I think this is a perfect example of the reasons why I try to avoid comments. They exist to make the code easier to read, but it's so easy to make mistakes like this where the code actually becomes more confusing because of the comment.

Generally, if I feel like a comment is needed, I will try to rewrite the code (and especially name things) so that the code itself becomes easier to read and the comment isn't needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think the code was actually way too complicated for what it was doing. I changed the implementation. Let me know what you think!

Comment on lines 66 to 74
genders: {
gender: m('Gender'),
genders: {
f: m('female'),
m: m('male'),
null: m('unspecified'),
o: m('other'),
},
header: m('Gender'),
Copy link
Contributor

Choose a reason for hiding this comment

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

Here is both a key called header, and gender on the same level translated to the same thing, "Gender". I'm thinking that both could be using the same one - where I prefer header since it communicates better what the transaltion is needed for?

Also an object strucutre thing - genders.genders does not feel like the optimal naming, could the inner object maybe be called "genderSelectLabels" or something like that, to more communicate what they are used for?

I also think the genders need to have uppercase in the first letter to look nice in the Select

In other parts of the code we use the word "unknown" instead of "unspecified", I think this could be good for consistency.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good call, I've changed them to be more descriptive I hope.

Copy link
Contributor

@ziggabyte ziggabyte left a comment

Choose a reason for hiding this comment

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

Noticed only a small thing when playing around with it right now - that the values in the select are still all lower case :)

Comment on lines 69 to 72
f: m('female'),
m: m('male'),
o: m('other'),
unknown: m('unspecified'),
Copy link
Contributor

Choose a reason for hiding this comment

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

Tiny thing here - still in need of uppercase on the options, and I think "unspecified" should be changed to "unknown" as the value! :D

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oops, it did feel like I was forgetting something 🙈

f: m('female'),
m: m('male'),
o: m('other'),
unknown: m('unspecified'),
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also changed ✅

Copy link
Contributor

@ziggabyte ziggabyte left a comment

Choose a reason for hiding this comment

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

Playing with this feels like it behaves the way i expect it to! I'm very happy that you added some very neat simplifications to the code that I wrote for org mapping, it's super fun to have you on board contributing :D

Giving you the seal of approval: 🦭 !

@ziggabyte ziggabyte merged commit 2c0177a into zetkin:main Dec 10, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Import: Gender field has no mapping configuration
3 participants