-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Implement ViolationUtils lib #31083
Comments
@lindboe @cdanwards can you comment here so I can assign you please? |
Commenting for assignment |
@cdanwards Uh oh! This issue is overdue by 2 days. Don't forget to update your issues! |
Beginning work on this |
Hi @cead22 I'm from Infinite Red and I've been assigned this issue from @cdanwards -- I had a quick question about approach. tl;dr Are you ok with me using a more idiomatic typescript approach (string literal unions/Record types) to defining the possible values for violation names and fields? The spec says to:
and
As I read it, the goal of these objects is to:
In that case are you ok with me using string-literal unions and Record types to accomplish this? It's a more idiomatic typescript approach that will have the same result, and provide stronger type safety. For instance: First we declare the string-literal unions like this: /**
* Names of Fields where violations can occur
*/
type ViolationField =
| 'merchant'
| 'amount'
| 'category'
| 'date'
| 'tag'
| 'comment'
| 'billable'
| 'receipt'
| 'tax';
/**
* Names of the various Transaction Violation types
*/
type ViolationName =
| "perDayLimit"
| "maxAge"
| "overLimit"
| "overLimitAttendee"
| "overCategoryLimit"
| "receiptRequired"
| "missingCategory"
| "categoryOutOfPolicy"
| "missingTag"
| "tagOutOfPolicy"
| "missingComment"
| "taxRequired"
| "taxOutOfPolicy"
| "billableExpense"; Then we use them to define the type of the map: /**
* Map from Violation Names to the field where that violation can occur
*/
const violationFields: Record<ViolationName, ViolationField> = {
perDayLimit: 'amount',
maxAge: 'date',
// ...
}; When defined this way, trying to add an unknown violation name will cause a red squiggle: and the same if you try to assign an invalid field to a valid ViolationName Even in Javascript files, the typechecker will still throw a warning: |
📣 @trevor-coleman! 📣
|
@trevor-coleman Hi Trevor, nice to meet you and thanks for taking this over and helping with this. What you're proposing sounds good to me, but let's see what @hayata-suenaga thinks since he's more well versed on TS types than me |
I agree with @trevor-coleman. Your approach to use an union of string literals seems much simpler approach. Let's go with that plan 👍 |
Perfect -- It's pretty much done, I've just gotta run the checklist and I'll have a PR for you monday. 🚀 |
@cdanwards Uh oh! This issue is overdue by 2 days. Don't forget to update your issues! |
Quick question: Should These examples show the function returning a single string: getViolationForField([{name: 'overLimit', type: 'violation'}], 'amount')
// should return => translate('overLimit')
getViolationForField([{name: 'maxAge', type: 'violation'}], 'amount')
// should return => ''
getViolationForField([{name: 'maxAge', type: 'violation'}], 'date')
// should return => translate('maxAge') but then the provided function returns an array of strings function getViolationForField(transactionViolations, field) {
const {translate} = useLocalize();
const fieldViolations = _.filter(transactionViolations, (violation) => violations[violation.name] === field);
return fieldViolations.map(violation => translate(violation.name));
}
// Examples:
getViolationForField([{name: 'overLimit', type: 'violation'}], 'amount')
// would return => [translate('overLimit')]
getViolationForField([{name: 'maxAge', type: 'violation'}], 'amount')
// should return => ['']
// etc. I'm guessing from the singluar name that you want to return only a single value, but wanted to check before getting too far along with the change. Are there any cases where a given field would have multiple violations? My fix is to just return the first element of the array -- this will be getViolationForField(transactionViolations: TransactionViolation[], field: ViolationField, translate: (key: string) => string): string {
const fieldViolations = transactionViolations.filter((violation) => possibleViolationsByField[field]?.includes(violation.name)).map((violation) => translate(violation.name));
return fieldViolations[0];
}, |
Great question, and yes the reason for this is at first I thought we'd only have 1 violation max per field, but we're going to put more than 1 under the receipt, so to answer your question: it should return an array of strings, and if there's aren't any violations, an empty array. In the code that uses it, we'd check if there's a non-empty array. and if so display whatever is in the array |
…ation-utils Implement ViolationUtils lib Expensify#31083
Edit: Going to ask this question on the display ticket (see this comment) |
I replied in the other issue. Feel free to bring up these questions on slack and tag me, since I imagine I'll be quicker to respond that way |
@cdanwards is working on this |
@cdanwards Eep! 4 days overdue now. Issues have feelings too... |
Opened a PR here: #31656 |
Triggered auto assignment to @marcaaron, see https://stackoverflow.com/c/expensify/questions/7972 for more details. |
@cead22 -- should this be reassigned to me? |
@trevor-coleman nvm. @marcaaron is internal engineer and was assigned just to review PR |
@cead22 can you please reopen and add |
Triggered auto assignment to @johncschuster ( |
Bump @johncschuster, can you handle payment for the review please? |
Sure thing! I'm catching up on the PR now |
@johncschuster can we close this one? |
Not paid yet. I accepted offer on Feb 13 |
Sorry for the delay! This has now been paid! Thank you! |
getViolationForField(object transactionViolation, string field) : string
getViolationForField
will checktransactionViolation
and return an an array of strings with the translated copies of the violations for that field, egmissingCategory
and return thatgetViolationsOnyxData(Transaction transaction, object transactionViolations, bool policyRequiresTags, object[] policyTags, bool policyRequiresCategories, object[] policyCategories)
that calculates the new transaction violations (by adding/removing those that have been addressed with the current user changes), and returns an object that we can save into onyx. TheupdatedTransactionViolations
variable shown in the code snippet below will be set like so:policyRequiresCategories === true
, the transactionViolations array doesn't have one withname: categoryOutOfPolicy
and the passed transaction contains a category, and the policyCategories collection doesn’t have that category with its enabled value set to true, then it’ll add the object{‘name’:’categoryOutOfPolicy’, ‘type’: ‘violation’}
toupdatedTransactionViolations
policyRequiresCategories === true
, the passed transaction contains a category that is enabled in the policy, and the onyx data had amissingCategory
violation, we’ll clear it fromupdatedTransactionViolations
using_.reject(transactionViolations, e => e.name === 'missingCategory')
policyRequiresTag === true
, the transactionViolations array doesn't have one withname: tagOutOfPolicy
and the passed transaction contains a tag, and the policyTags collection doesn’t have that tag with its enabled value set to true, then it’ll add the object{‘name’:’categoryOutOfPolicy’, ‘type’: ‘violation’}
toupdatedTransactionViolations
policyRequiresTag === true
, the passed transaction contains a tag that is enabled in the policy, and the onyx data had amissingTag
violation, we’ll clear it fromupdatedTransactionViolations
using `_.reject(transactionViolations, e => e.name === 'missingTag')const formFields = {merchant: 'merchant', amount: 'amount', category: 'category', ...}
const violationField = {missingCategory: 'category', categoryOutOfPolicy: 'category', ...}
with a mapping of violation names to the name of the field where the violation is shown (more in code snippet belowExample
The text was updated successfully, but these errors were encountered: