-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Adds new ReportUtils for Violations #31448
Changes from all commits
a917e06
3756ef9
3cc2e0f
4db7207
35fd77e
7942b39
89997ce
a98b3a1
f7dc731
93ea5b7
1cfeec0
1f9d645
2a03cc9
813c856
6d47be5
6a16af0
5849803
f7fa4bf
9ce9595
02fa714
63ab544
bef0d73
c6dca6a
c69c1df
5e122ef
e53902e
c49b03f
375eabb
b6c24db
8bee286
e1ca1ac
ccd3eb3
1d2cfa2
bcd1ee8
cae1e0d
59ba57e
90b7138
35de23f
bbfd1fa
20a0fc3
2c52edf
73971f0
1234b88
9223374
9e1753b
4366d64
5716800
adf1933
da8f376
cda1aee
cd6586a
03f9d96
6478b94
1736c21
16a0a9a
feb3596
c7aa6c4
69b767b
9535a2b
99d3c0d
0720910
7faf50b
bf401af
f0a360b
4088e8f
766d37e
561a4d4
abad008
563ca7b
32fb912
2e5c39b
30a77c9
2e3d3e7
9503748
2871cd8
5dd7d01
d169a43
aa7822e
d86616d
ccb12cc
e3d4ae8
37e0f2e
d467853
fdab084
bf37bb0
db5fa36
ba5e940
e873fa4
68f933a
b41a3de
2492bf9
90101f2
ea6bf2e
4d4c476
ec2429a
04ed595
aa14258
0214a12
a89d888
cd4e987
93d08a0
c24db94
b3b367d
00ce2cf
a5ecebe
2f4a14a
93a300b
289d00f
0bba695
c2667a8
d1a6ee1
6cabad0
ea487ba
c8ebe6f
ec7dc4f
aacb894
9e57641
79ac93a
13fe960
4a26a24
66c181e
2f75e74
e110f14
00a8bec
0512719
6968650
ad15ca6
85fa56d
2452d31
44a96ca
192899c
0c41aec
891c3d2
09d08e2
ae525eb
4547321
3b66eeb
da7bf97
a5833d0
44ef928
587a44c
c732ddd
3eaf3d4
27e8315
96b9ff7
de97f71
8e0250c
00fefc1
9498292
e3882e7
64ab957
ae91d30
7305a48
6e8ebca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -8,10 +8,12 @@ import _ from 'underscore'; | |||||||
import participantPropTypes from '@components/participantPropTypes'; | ||||||||
import transactionPropTypes from '@components/transactionPropTypes'; | ||||||||
import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; | ||||||||
import usePermissions from '@hooks/usePermissions'; | ||||||||
import useThemeStyles from '@hooks/useThemeStyles'; | ||||||||
import compose from '@libs/compose'; | ||||||||
import * as OptionsListUtils from '@libs/OptionsListUtils'; | ||||||||
import * as ReportUtils from '@libs/ReportUtils'; | ||||||||
import {transactionViolationsPropType} from '@libs/Violations/propTypes'; | ||||||||
import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; | ||||||||
import reportPropTypes from '@pages/reportPropTypes'; | ||||||||
import stylePropTypes from '@styles/stylePropTypes'; | ||||||||
|
@@ -63,8 +65,13 @@ const propTypes = { | |||||||
|
||||||||
/** The transaction from the parent report action */ | ||||||||
transactions: PropTypes.objectOf(transactionPropTypes), | ||||||||
|
||||||||
/** List of draft comments */ | ||||||||
draftComments: PropTypes.objectOf(PropTypes.string), | ||||||||
|
||||||||
/** The list of transaction violations */ | ||||||||
transactionViolations: transactionViolationsPropType, | ||||||||
|
||||||||
...withCurrentReportIDPropTypes, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As new lines are added above, let's add here too
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||||||||
}; | ||||||||
|
||||||||
|
@@ -78,6 +85,7 @@ const defaultProps = { | |||||||
personalDetails: {}, | ||||||||
transactions: {}, | ||||||||
draftComments: {}, | ||||||||
transactionViolations: {}, | ||||||||
...withCurrentReportIDDefaultProps, | ||||||||
}; | ||||||||
|
||||||||
|
@@ -98,8 +106,10 @@ function LHNOptionsList({ | |||||||
transactions, | ||||||||
draftComments, | ||||||||
currentReportID, | ||||||||
transactionViolations, | ||||||||
}) { | ||||||||
const styles = useThemeStyles(); | ||||||||
const {canUseViolations} = usePermissions(); | ||||||||
/** | ||||||||
* Function which renders a row in the list | ||||||||
* | ||||||||
|
@@ -137,10 +147,26 @@ function LHNOptionsList({ | |||||||
onSelectRow={onSelectRow} | ||||||||
preferredLocale={preferredLocale} | ||||||||
comment={itemComment} | ||||||||
transactionViolations={transactionViolations} | ||||||||
canUseViolations={canUseViolations} | ||||||||
/> | ||||||||
); | ||||||||
}, | ||||||||
[currentReportID, draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], | ||||||||
[ | ||||||||
currentReportID, | ||||||||
draftComments, | ||||||||
onSelectRow, | ||||||||
optionMode, | ||||||||
personalDetails, | ||||||||
policy, | ||||||||
preferredLocale, | ||||||||
reportActions, | ||||||||
reports, | ||||||||
shouldDisableFocusOptions, | ||||||||
transactions, | ||||||||
transactionViolations, | ||||||||
canUseViolations, | ||||||||
], | ||||||||
); | ||||||||
|
||||||||
return ( | ||||||||
|
@@ -189,5 +215,8 @@ export default compose( | |||||||
draftComments: { | ||||||||
key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, | ||||||||
}, | ||||||||
transactionViolations: { | ||||||||
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of curiosity, is there a reason we're taking all the transaction violations, and not setting this to a function that takes a transactionID and returns the transaction violations for a specific transaction? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we able to determine the transactionId within the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cead22 -- this I'll have to look into a bit more as this is my first contact with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this work? export default compose(
withCurrentReportID,
withOnyx({
// ..
transactionViolations: {
key: ({currentReportId}) => `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${currentReportId}`,
},
}),
)(LHNOptionsList); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why need to filter out with currentReportId here? This is OptionsList, not OptionRow |
||||||||
}, | ||||||||
}), | ||||||||
)(LHNOptionsList); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,10 @@ import _ from 'underscore'; | |
import participantPropTypes from '@components/participantPropTypes'; | ||
import transactionPropTypes from '@components/transactionPropTypes'; | ||
import * as ReportActionsUtils from '@libs/ReportActionsUtils'; | ||
import * as ReportUtils from '@libs/ReportUtils'; | ||
import SidebarUtils from '@libs/SidebarUtils'; | ||
import * as TransactionUtils from '@libs/TransactionUtils'; | ||
import {transactionViolationsPropType} from '@libs/Violations/propTypes'; | ||
import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; | ||
import * as Report from '@userActions/Report'; | ||
import CONST from '@src/CONST'; | ||
|
@@ -42,6 +44,9 @@ const propTypes = { | |
/** The transaction from the parent report action */ | ||
transaction: transactionPropTypes, | ||
|
||
/** Any violations associated with the transaction */ | ||
transactionViolations: transactionViolationsPropType, | ||
|
||
...basePropTypes, | ||
}; | ||
|
||
|
@@ -73,6 +78,8 @@ function OptionRowLHNData({ | |
receiptTransactions, | ||
parentReportAction, | ||
transaction, | ||
transactionViolations, | ||
canUseViolations, | ||
...propsToForward | ||
}) { | ||
const reportID = propsToForward.reportID; | ||
|
@@ -85,9 +92,19 @@ function OptionRowLHNData({ | |
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [fullReport.reportID, receiptTransactions, reportActions]); | ||
|
||
const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction); | ||
|
||
const optionItem = useMemo(() => { | ||
// Note: ideally we'd have this as a dependent selector in onyx! | ||
const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction); | ||
const item = SidebarUtils.getOptionData({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we go back to individual params instead of a single object param? Or can you share why this change was made? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a number of benefits to using an object for params over a long list of parameters. For one, it means that the order of the parameters no longer matters, so if you need to add or remove a parameter in the future, it becomes a lot faster an easier, and when calling the function, you don't need to remember the order of the parameters. Also it means that every parameter is labeled with what it is. Which becomes important when there are long lists that include, for instance, boolean flags. To understand But with: getNextAvailableDate({
startDate: newDate('2024-09-01'),
useWeekends: true,
allowDoubleBooking: false,
cancelConflictingMeetings: false
}) It's very clear what each of those values mean. It also lets default values for boolean flags be omitted because you can define them as an optional boolean if they are rarely used. So the call can be reduced to: getNextAvailableDate({
startDate: newDate('2024-09-01'),
useWeekends: true
}) In typescript you will get type checking on properties of objects so if something is not optional, then the compuiler will raise a type error if you omit a property from the object, the same as you would if you omitted a parameter. On the other end, destructuring lets us access the params exactly as if they were written as separate params, and it maes things like default values a little clearer: function getNextAvailableDate({
startDate = newDate(),
useWeekends,
cancelConflictingMeetings,
allowDoubleBooking
}:GetNextAvailableDateParams) {
// ...
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fair, but the main reason I'm pushing back on this is because I'm trying to get all these PRs focused on the functionality and not add scope by refactoring. That said, I won't block on this, as I want to get this merged (which is another reason to limit the scope of the PR)
Doesn't TS have named params?
Do we encourage this kinda magic? I think I would prefer always having those values passed, or set with explicit destructuring, than magically have them set to false or to undefined which is falsy and have the code rely on that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This wasn't refactoring for the sake of refactoring, I had to do this to be able to work with these functions. Having to chase the flow of data through the various levels of callers was made far more annoying by dealing with long lists of params. The next person is going to have the same problem.
This is how we do that in typescript.
There's no magic there -- this is bog standard JS/TS. But i'll make all the params required. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel you, cause I use VIM. I assumed knowing the positions and names of parameters wasn't a problem for most people because they're editors make this easy. I actually downloaded visual studio code recently to make working in App a bit easier for myself There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I use webstorm which has parameter labels, but I still find it much easier to work with functions when the order of the params doesn't matter. For an extreme case say you have a function with 10 boolean params and you delete the seventh one. The labels are all wrong now, but there won't be any type errors or issues. And you need to go to each and count to the seventh param, and then make sure the seventh one is the right one to delete. But if you make a mistake counting at any point you've deleted the wrong thing and might not even notice until later. The labels wont help here because you've already deleted the param from the function, so there is no label for it anymore. When the params are in an object you can just follow the red squiggles and delete the one you know is the right one. Even passing in named variables can be wrong or worse misleading, because if the types are compatible, the compiler will accept it, but with an object using shorthand properties, you know that the variable with that name is being passed in the right place and will be consumed correctly by the called function. With something like VSCode lables you need to do the work of matching each label to each variable name, and the compiler won't help you out. |
||
report: fullReport, | ||
reportActions, | ||
personalDetails, | ||
preferredLocale, | ||
policy, | ||
parentReportAction, | ||
hasViolations, | ||
}); | ||
if (deepEqual(item, optionItemRef.current)) { | ||
return optionItemRef.current; | ||
} | ||
|
@@ -96,7 +113,7 @@ function OptionRowLHNData({ | |
// Listen parentReportAction to update title of thread report when parentReportAction changed | ||
// Listen to transaction to update title of transaction report when transaction changed | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction]); | ||
}, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction, transactionViolations, canUseViolations]); | ||
|
||
useEffect(() => { | ||
if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** | ||
* @returns A mock of the usePermissions hook. | ||
*/ | ||
const usePermissions = () => ({ | ||
canUseViolations: true, | ||
}); | ||
export default usePermissions; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1292,6 +1292,7 @@ function getOptions( | |
recentlyUsedTags = [], | ||
canInviteUser = true, | ||
includeSelectedOptions = false, | ||
transactionViolations = {}, | ||
includePolicyTaxRates, | ||
policyTaxRates, | ||
}, | ||
|
@@ -1357,7 +1358,22 @@ function getOptions( | |
const searchValue = parsedPhoneNumber.possible ? parsedPhoneNumber.number.e164 : searchInputValue.toLowerCase(); | ||
|
||
// Filter out all the reports that shouldn't be displayed | ||
const filteredReports = _.filter(reports, (report) => ReportUtils.shouldReportBeInOptionList(report, Navigation.getTopmostReportId(), false, betas, policies)); | ||
const filteredReports = _.filter(reports, (report) => { | ||
const {parentReportID, parentReportActionID} = report || {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When would There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure -- it happened once while I was building this but that may have been an artifact of some bad state. Reports here is an object, so it's possible that one of the properties of that object could be null. Especially in javascript. So it's mostly for safety, as it will cause the app to crash if you try and destructure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I get the safety argument, but is it possible for report not to be defined, since it comes from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't know if it's possible, I don't think we should add the fallback, at least not without some logging to understand why it happens. If we know it's possible, then let's add a comment explaining when that can happen There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the guard There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah here is where it is null: #31448 (comment) Not sure why -- i guess this is called when creating the list for new chats. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add a comment explaining why it's necessary to have the fallback |
||
const canGetParentReport = parentReportID && parentReportActionID && allReportActions; | ||
const parentReportAction = canGetParentReport ? lodashGet(allReportActions, [parentReportID, parentReportActionID], {}) : {}; | ||
const doesReportHaveViolations = betas.includes(CONST.BETAS.VIOLATIONS) && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction); | ||
|
||
return ReportUtils.shouldReportBeInOptionList({ | ||
report, | ||
currentReportId: Navigation.getTopmostReportId(), | ||
betas, | ||
policies, | ||
doesReportHaveViolations, | ||
isInGSDMode: false, | ||
excludeEmptyChats: false, | ||
}); | ||
}); | ||
|
||
// Sorting the reports works like this: | ||
// - Order everything by the last message timestamp (descending) | ||
|
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as outdated.
Sorry, something went wrong.