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

Ensure Correct COT Selection in QB When Sharing Formatters #6252

Open
wants to merge 9 commits into
base: production
Choose a base branch
from

Conversation

Areyes42
Copy link
Contributor

@Areyes42 Areyes42 commented Feb 17, 2025

Fixes #5503

Checklist

  • Self-review the PR after opening it to make sure the changes look good and
    self-explanatory (or properly documented)
  • Add automated tests
  • Add relevant issue to release milestone
  • Add relevant documentation (Tester - Dev)

Testing instructions

  1. Create two CollectionObjectTypes with the same Catalog Number Format Name which differs from that of the Collection's default Catalog Number format
  2. Create a new Collection Object query, add Catalog Number to the query, and switch the type from Any to Equal
  3. Try to select both COTs
  • Verify that it selects the proper COT regardless of the Catalog Number format

Triggered by fed7786 on branch refs/heads/issue-5503
@CarolineDenis CarolineDenis added this to the 7.10.2 milestone Feb 21, 2025
@Areyes42 Areyes42 marked this pull request as ready for review February 21, 2025 19:15
@Areyes42 Areyes42 requested a review from a team February 21, 2025 19:15
Copy link
Contributor

@sharadsw sharadsw left a comment

Choose a reason for hiding this comment

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

I like your approach. Functionally your code looks good, just pointing out some minor code smells.

@Areyes42
Copy link
Contributor Author

I like your approach. Functionally your code looks good, just pointing out some minor code smells.

Good point, I've added some helpers to separate the logic and hopefully make it a little more intuitive. Let me know if you think this looks better!

@Areyes42 Areyes42 requested a review from sharadsw February 21, 2025 22:24
Copy link
Contributor

@sharadsw sharadsw left a comment

Choose a reason for hiding this comment

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

Looks good, nice work!

@CarolineDenis CarolineDenis requested review from a team and CarolineDenis February 24, 2025 14:10
Copy link
Collaborator

@lexiclevenger lexiclevenger left a comment

Choose a reason for hiding this comment

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

  • Verify that it selects the proper COT regardless of the Catalog Number format

Looks good!

Screen.Recording.2025-02-24.at.11.42.46.AM.mov

@lexiclevenger lexiclevenger requested a review from a team February 24, 2025 17:44
Copy link
Contributor

@pashiav pashiav left a comment

Choose a reason for hiding this comment

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

  • Verify that it selects the proper COT regardless of the Catalog Number format

Works as expected!

Copy link
Contributor

@melton-jason melton-jason left a comment

Choose a reason for hiding this comment

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

Nice work on this! I really like the implementation; pretty clever idea! 🚀

I will mention that while the CollectionObjectType the user selects while the page is loaded will always be respected, it will always revert to the first CollectionObjectType in the list with the same format when re-rendered!

Screen.Recording.2025-02-26.at.9.04.25.PM.mov

This is because only the format name (nothing of the CollectionObjectType) is saved to the SpQueryField in the database; when the page is rendered initially, it isn't formatted with the formatterSeparator and index and is just the name of the formatter itself.
So Specify has no clue which CollectionObjectType it can be referring to.

There's also a somewhat smaller Issue where changing the CollectionObjectType to another which shares the same format doesn't trigger a change on the field (because nothing has actually changed on the SpQueryField!)

Screen.Recording.2025-02-26.at.9.40.58.PM.mov

We're relatively having a lot of Issues regarding this feature (#6239, #6253, etc. no thanks to my initial implementation in the first place!), and I think that's usually an indicator to take a step back and re-evaluate the core problem and potential solutions.

@specify/ux-testing, @specify/dev-testing

Instead of having a separate option for each CollectionObjectType, could we instead have an option for each Format and have the title of the option be a combination of all the COT names with that format?

It could look something like this:

Screenshot 2025-02-26 at 9 30 38 PM

Or we can go even more descriptive to try and be as clear as possible: the user isn't selecting/filtering on COT, they're only applying a different formatter for the field:

Screenshot 2025-02-26 at 10 18 36 PM

Alternatively, we could move away from the SpQueryField -> FormatName approach entirely and come up with something different which would allow the user to select an individual COT name.

@emenslin
Copy link
Collaborator

I agree with Jasons comment and I think that grouping them together might make the most sense since it does not necessarily matter which specific one you use. I also think since we don't really have the gear other places aside from when you have something mapped as 'formatter' or 'aggregator' that adding 'Format As: ...' in this instance makes sense but we can go with something else if people have other opinions @specify/ux-testing

@Areyes42
Copy link
Contributor Author

Areyes42 commented Mar 3, 2025

@melton-jason @emenslin

Great suggestions! I've updated the logic to group COTs with identical formatters together. This should mitigate issues related to re-rendering and state changes in the form, since it no longer compares against the same formatter. Also, I've added the "Format As:" identifier to improve clarity, but we can adjust it if it seems too confusing.

Copy link
Collaborator

@emenslin emenslin left a comment

Choose a reason for hiding this comment

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

  • Verify that it selects the proper COT regardless of the Catalog Number format

Looks good! I ran into an issue where the query results were incorrect (I set the filter to equal and instead it showed all results), I couldn't recreate it but I would love if we could get one more testing review to verify that it is not an issue

@emenslin emenslin requested a review from a team March 4, 2025 16:32
Copy link
Contributor

@pashiav pashiav left a comment

Choose a reason for hiding this comment

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

  • Verify that it selects the proper COT regardless of the Catalog Number format=

Looks good!

@emenslin I didn't run into what you mentioned, it's working on my end!

Copy link
Contributor

@melton-jason melton-jason left a comment

Choose a reason for hiding this comment

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

Nice work!
Coming along nicely 👌


return Array.from(formattersMap.values(), ({ name, isDefault, cotNames }) => ({
name,
title: `Format As: ${cotNames.join(', ')}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't forget to localize the Format As part of this string before merging this!
For other non-english languages this would currently always display as Format As.

If you're unfamiliar with how localization works in Specify 7 or just need a refresher, be sure to checkout the Localization Guidelines for Programmers README.md!

Just as an example, you could add a new string to a WebLate dictionary (resolver) (such as the query builder-specific strings in query.ts), which would look something like the following:

// in https://github.com/specify/specify7/blob/production/specifyweb/frontend/js_src/lib/localization/query.ts

  formatInputAs: {
    comment: `
      Used to indicate a text field will be formatted with a specific format.
      If a format can be identified/named in more than one way, names can be 
      displayed in a comma-separated format

      Example: Format As: Ichthyology
      Example: Format As: Rock, Mineral
    `,
    'en-us': 'Format As: {commaSeparatedFormats:string}',
  },

It can be used like the following:

import { queryText } from '../../localization/query';

const title = queryText.formatInputAs({
  commaSeparatedFormats: cotNames.join(', '),
});

// Assuming cotNames is ['Cot1', 'Cot2', 'Cot3'], title is now: 
// Format As: Cot1, Cot2, Cot3

Comment on lines +116 to +135
const formattersMap = filterArray(cots).reduce((map, cot) => {
const format = cot.get('catalogNumberFormatName') ?? schema.catalogNumFormatName;
const cotName = cot.get('name');

if (!map.has(format)) {
map.set(format, {
name: format,
title: format,
isDefault: format === schema.catalogNumFormatName,
cotNames: []
});
}

map.get(format)!.cotNames.push(cotName);
return map;
}, new Map<string, FormatterWithCOTs>());

return Array.from(formattersMap.values(), ({ name, isDefault, cotNames }) => ({
name,
title: `Format As: ${cotNames.join(', ')}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

(just a note)
Nice work on this! 🚀
I had the exact same idea of using reduce.

Just for a different perspective, here's what I had come up with:

const cotsByFormat = filterArray(cots).reduce(
  (cotsByFormatAccum, cot) => {
    const format =
      cot.get('catalogNumberFormatName') ?? schema.catalogNumFormatName;
    return {
      ...cotsByFormatAccum,
      [format]: [...(cotsByFormatAccum[format] ?? []), cot.get('name')],
    };
  },
  {} as IR<RA<string>>
);

return Object.entries(cotsByFormat).map(([formatName, cotNames]) => ({
  name: formatName,
  title: `Format As: ${cotNames.join(', ')}`,
  isDefault: false,
}));

Comment on lines +30 to +43
export const formatterSeparator = '|||';

export function createCompositeFormatter(name: string, index: number): string {
return `${name}${formatterSeparator}${index}`;
}

export function parseFormatterValue(value: string): { readonly name: string; readonly index: number } | null {
const [name, indexString] = value.split(formatterSeparator);
const index = f.parseInt(indexString);

if (!name || index === undefined) return null;

return { name, index };
}
Copy link
Contributor

Choose a reason for hiding this comment

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

With the grouping of COTs by format name, are these utilities needed anymore?

If I'm understanding the code correctly, these were needed to differentiate the "PickList" options which had the same value by their index.

There should now never be a case where more than one option has the same value (format name) because they will be grouped, right?

Comment on lines +174 to +190
const [selectedIndex, setSelectedIndex] = React.useState<number | null>(null);
const id = useId('formatters-selection');

const compositeValue = React.useMemo(() => {
if (availableFormatters && availableFormatters.length > 0) {
if (selectedIndex !== null && availableFormatters[selectedIndex]) {
return createCompositeFormatter(availableFormatters[selectedIndex].name, selectedIndex);
}
const foundIndex = availableFormatters.findIndex(
(f) => f.name === currentFormat
);
if (foundIndex !== -1) {
return createCompositeFormatter(availableFormatters[foundIndex].name, foundIndex);
}
}
return '';
}, [selectedIndex, availableFormatters, currentFormat]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same thing as the prior comment! With the grouping of the CollectionObjectTypes by format, do we need to keep track of which option the user has selected anymore?

<option value="" />
{availableFormatters.map((formatter, index) => (
<option key={index} value={createCompositeFormatter(formatter.name, index)}>
{formatter.title}
Copy link
Contributor

Choose a reason for hiding this comment

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

In production, the default format would have a (default) appended next to the title.
It seems that this has been removed in this branch. Was this an intentional change?

While the requirements of the UI for the CatalogNumber CollectionObjectType Format selection seem a little more flexible in this PR, I am mostly referring to the other place this component is rendered: selecting a specific formatter or aggregator in the QueryBuilder for tables!

Production

production.mov

Issue-5503

issue-5503.mov

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

Successfully merging this pull request may close these issues.

Query Builder: Can't select a COT for Catalog Number if it shares a formatter with another COT
7 participants