-
Notifications
You must be signed in to change notification settings - Fork 6
Type-aware where #109
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
Type-aware where #109
Conversation
Codecov Report
@@ Coverage Diff @@
## feature/where #109 +/- ##
=================================================
- Coverage 94.85% 94.46% -0.40%
=================================================
Files 49 58 +9
Lines 1595 1878 +283
Branches 344 406 +62
=================================================
+ Hits 1513 1774 +261
- Misses 79 101 +22
Partials 3 3
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
| }); | ||
| } | ||
|
|
||
| get buttonData() { |
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.
This would be best to do in the @api. so add get/set to the buttonLabels and do it in the setter. Then just render buttonData. It's best to keep as much looping as possible outside of the render loop of LWC, since we don't know how often it attempts to re-render itself.
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.
You could also apply the class names in a function called from the template.
class={getClassName(idx)}
This would allow the button to dynamically restyle whenever the selectedIndex is changed. Altering the selected index will trigger a re-render and calculate the new styles...the most important being which is selected.
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.
Ok I put the className in the buttonData so everything needed to render the template is in that object. Then on selection I update the style. Since the buttonData is tracked LWC knows to re-render.
| @api selectedIndex = '-1'; | ||
|
|
||
| renderedCallback() { | ||
| const buttonElements = this.template.querySelectorAll('button'); |
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.
Opposite of my last comment, this could be best to do in the render loop from html. You can call a function from the template with class={applyStyle(index)} in the template.
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.
Would love to do this, but you can't have a call with arguments in the template.
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.
Sorta kinda do this now with keeping the classlist in the buttondata.
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.
Yeah. the limitations of LWC often catch me as a surprise coming from Angular / React's much richer template syntax.
| align-self: flex-start; | ||
| } | ||
| .btn { | ||
| height: 1.5em; |
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 generally like to use rem vs em personally. Because em is dynamic as compared to it's parent. So that can be funky as text up the dom possibly gets set to "larger" or "smaller", it affects what an em is on the button. Where as rem is based on the root font size, which is just easier to think about logically for me.
Normally, it's not a big issue, but i wanted to mention it from my experience.
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 that better. I made the change.
| buttonGroup.addEventListener('selection__changed', handler); | ||
| document.body.appendChild(buttonGroup); | ||
|
|
||
| const buttonElements = buttonGroup.shadowRoot.querySelectorAll('button'); |
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 think you can just do
const button0 = buttonGroup.shadowRoot.querySelectorAll('button')[0];
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.
Thanks. Done.
| } | ||
|
|
||
| @api get selection() { | ||
| return parseInt(this.selectedIndex); |
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.
How do you see this being used vs the event? I noticed you are using it in testing, however, outside of testing, i think the event will likely drive the app.
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 agree. It's gone now.
| export const error_incompleteFrom = 'Incomplete FROM clause. The FROM clause requires an object.'; | ||
| export const error_incompleteLimit = 'Incomplete LIMIT clause. The LIMIT keyword must be followed by a number.'; | ||
|
|
||
| export const error_fieldInput_boolean = 'Value must be TRUE or FALSE'; |
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.
Does case matter here? Must be all caps?
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.
Case does not actually matter, but all-caps is consistent with the way the documentation (https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql.htm) writes elements of SOQL syntax.
| export const error_incompleteLimit = 'Incomplete LIMIT clause. The LIMIT keyword must be followed by a number.'; | ||
|
|
||
| export const error_fieldInput_boolean = 'Value must be TRUE or FALSE'; | ||
| export const error_fieldInput_currency = 'Currency value is not valid'; |
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.
Any guidelines we can add here?
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.
There's a 3 letter currency code followed by the numeric value according to the ISO standard. So $100 US is USD100. I think the currency value can also be just a floating point number. So the most concise we can get is maybe...
"Value must be a number or a three-letter ISO currency code followed by a number"
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 feel like including the three-letter ISO currency code makes this feel more complicated than necessary.
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.
They are all a bit complicated - so let's just leave them as @jgellin-sf wrote them. Thanks for answering my questions in such detail.
|
|
||
| export const error_fieldInput_boolean = 'Value must be TRUE or FALSE'; | ||
| export const error_fieldInput_currency = 'Currency value is not valid'; | ||
| export const error_fieldInput_date = 'Date value is not valid'; |
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.
Can we provide valid date format or some other guidance? like: You must enter the date as YYYY-MM-DD (or something like that)
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.
Date is tricky. Because you can write date as:
- Date only: 2021-01-13
- UTC date and time: 2021-01-13T12:00:00Z
- Date and time with timezone offset: 2021-01-13T12:00:00-05:00
- Any of the many range literals built into SOQL (TODAY, TOMORROW, NEXT_WEEK, LAST_YEAR, etc)
- Any of the many parameterized date range literals build into SOQL (NEXT_N_DAYS:5, LAST_N_YEARS:20, etc.)
See https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_dateformats.htm?search_text=currency
| export const error_fieldInput_boolean = 'Value must be TRUE or FALSE'; | ||
| export const error_fieldInput_currency = 'Currency value is not valid'; | ||
| export const error_fieldInput_date = 'Date value is not valid'; | ||
| export const error_fieldInput_float = 'Value must be numeric'; |
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.
Does this mean "numbers only"? decimals?
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.
Right. So numeric values, containing decimal or not, are fine.
ekapner
left a comment
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.
A couple questions before I can finalize the validation messages
contraction Co-authored-by: Emily Kapner <ekapner@salesforce.com>
…ng into jg/typeAwareWhere
| modelService: ToolingModelService; | ||
| messageService: IMessageService; | ||
| theme = 'light'; | ||
| _sobjectTypeUtils: SObjectTypeUtils; |
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.
Is there a reason to use a getter/setter here?
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 don't think it's really necessary.
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.
This is irrelevant following the pass sobject metadata rather than utils object commit
| document.body.appendChild(modifierGroup); | ||
|
|
||
| const { criteriaInputEl } = getModifierElements(); | ||
| criteriaInputEl.value = 'FALSE'; |
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.
would this test pass if criteriaInputEl.value was lower case 'false'?
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.
yes
|
|
||
| export class IntegerValidator extends Validator { | ||
| public validate(input: string): ValidateResult { | ||
| const isValid = /^[+-]?[0-9]+$/.test(input.trim()); |
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.
There's also Number.isInteger() or parseInt(). But i don't know if they are more inclusive than you want.
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.
Those will return a integer for any string starting with an integer, so parseInt() will return 134 for " 134hello".
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.
yeah, i guess that's a false positive.
| type, | ||
| picklistValues | ||
| }; | ||
|
|
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 notice we are checking 2 validations here, but only displaying 1 error message. In your experience with this, is it best to check the input validation for the field first, and if that passes, show check operator validation?
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 don't have any special insight to the order of the validations. It felt right. 😂
| @api selectedOperator: string; | ||
| @api isLoading = false; | ||
| @api index; | ||
| @api sobjectTypeUtils: SObjectTypeUtils; |
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'm thinking we should pass the metadata and then process it here instead of with app.ts. using a setter to process the data with sobjectutils
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.
Made this change in pass sobject metadata rather than utils object commit
| import { Soql } from '@salesforce/soql-model'; | ||
|
|
||
| export class SObjectTypeUtils { | ||
| constructor(protected sobjectMetadata: any) { |
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.
To limit the looping/filtering in this utility, we could use a map so that the key is the field name ( possibly lowercased ). Something like this:
{
'id' : { name: 'Id', type: 'id', picklistValues: [] },
'name' : { name: 'Name', type: 'string', picklistValues: [] },
'accountsource': { name: 'AccountSource', type: 'picklist', picklistValues: [{ value: 'apple' }, { value: 'banana' }, { value: 'cherry' }] },
'annualrevenue': { name: 'AnnualRevenue', type: 'currency', picklistValues: [] },
'billingaddress' : { name: 'BillingAddress', type: 'address', picklistValues: [] },
...
}
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 updated this code in the Refactor sobject utils for easier map lookups commit.
|
This is amazingly cool work JG! |
dehru
left a comment
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.
None of my comments are change requests. Just possible enhancements and/or thinking out loud. Address any you see fit.
We could even merge this so that I can attempt to beautify the message while you address any of the comments.
…ql-tooling into jg/typeAwareWhere
* Jhork: Support for Simple WHERE Expressions (#101) * move rule to css common (#108) * Preventing webview re-rendering when soql statement has not changed (#105) * Revert "Disable WHERE in model (#113)" * Type-aware where (#109) * Recognize recoverable where errors (#116) * Operator and Criteria Error Tooltips (#122) * Support IN, NOT IN, INCLUDES, EXCLUDE operators (#123)
* Utility to return field type from SObject metadata * type-aware input validation * boolean input initialization * integer and float input validation * Validation for picklists and dates * validation of currency literals
* Jhork: Support for Simple WHERE Expressions (#101) * move rule to css common (#108) * Preventing webview re-rendering when soql statement has not changed (#105) * Revert "Disable WHERE in model (#113)" * Type-aware where (#109) * Fix preview not updating when autosave is off - issue 120 (#125) * Recognize recoverable where errors (#116) * Operator and Criteria Error Tooltips (#122) * Support IN, NOT IN, INCLUDES, EXCLUDE operators (#123) * Add smarter completin on GROUP BY clause and WHERE literals (#126) * Feature/custom select (#148) * Jhork/custom select fields (#111) * Jhork/custom select from (#130) * Jhork/custom select order by (#134) * set the error overlay to be on top of all other form elements (#150) * prepare to publish 0 .0.30 (#151) * Feature/where (#154) * Jhork: Support for Simple WHERE Expressions (#101) * move rule to css common (#108) * Preventing webview re-rendering when soql statement has not changed (#105) * Revert "Disable WHERE in model (#113)" * Type-aware where (#109) * Recognize recoverable where errors (#116) * Operator and Criteria Error Tooltips (#122) * Support IN, NOT IN, INCLUDES, EXCLUDE operators (#123) * Revert "Feature/where (#154)" (#157) * Add support for line comments ( `//` ) at the top of SOQL files (#132) * Jhork/where custom select (#161) * Fix: 'undefined' in SOQL string (#162) * Clear tooltip when object changes, fix empty soql statement error blocker (#165) * Track to force render on condition change - fix rebase conflict (#166) * do not send event when user still adding input' (#169) * Jhork/clear modifier field bug fix (#168) * handle a option selection event (#178) * cler tooltip error when X is clicked (#180) * Jhork/workflow updates (#163) * Bump versions for release (#181) Co-authored-by: Jonny Hork <jhork@salesforce.com> Co-authored-by: dehru <599418+dehru@users.noreply.github.com> Co-authored-by: Dehru Cromer <dehru.cromer@salesforce.com> Co-authored-by: Fernando Dobladez <fernandodobladez@salesforce.com>
What does this PR do?
This PR:
This PR does not:
What issues does this PR fix or reference?
@W-8555377@