-
Notifications
You must be signed in to change notification settings - Fork 36
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
base: production
Are you sure you want to change the base?
Conversation
Triggered by fed7786 on branch refs/heads/issue-5503
Triggered by db695a6 on branch refs/heads/issue-5503
specifyweb/frontend/js_src/lib/components/QueryBuilder/Formatter.tsx
Outdated
Show resolved
Hide resolved
specifyweb/frontend/js_src/lib/components/QueryBuilder/Formatter.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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.
Triggered by a9e9de4 on branch refs/heads/issue-5503
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! |
There was a problem hiding this 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!
specifyweb/frontend/js_src/lib/components/QueryBuilder/Formatter.tsx
Outdated
Show resolved
Hide resolved
…er.tsx Co-authored-by: Sharad S <[email protected]>
There was a problem hiding this 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
There was a problem hiding this 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!
There was a problem hiding this 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:

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:

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.
specifyweb/frontend/js_src/lib/components/QueryBuilder/Formatter.tsx
Outdated
Show resolved
Hide resolved
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 |
Triggered by 65009e7 on branch refs/heads/issue-5503
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. |
There was a problem hiding this 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
There was a problem hiding this 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!
There was a problem hiding this 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(', ')}`, |
There was a problem hiding this comment.
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
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(', ')}`, |
There was a problem hiding this comment.
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,
}));
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 }; | ||
} |
There was a problem hiding this comment.
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?
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]); |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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
Fixes #5503
Checklist
self-explanatory (or properly documented)
Testing instructions
Any
toEqual