-
Notifications
You must be signed in to change notification settings - Fork 37
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
Improve Preparation form links to Interactions #6110
base: production
Are you sure you want to change the base?
Changes from all commits
d76789b
daa990c
4800238
eaf9a6c
0c27b37
81067b5
416dfb2
702e118
33caf9b
aa4dfda
268110e
634aef1
b7f1896
bd0efb5
59802e1
0371f02
34b0e89
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 | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -100,74 +100,149 @@ export function ShowLoansCommand({ | |||||||
domainFilter: false, | ||||||||
}).then(({ records }) => records.map(deserializeResource)) | ||||||||
: undefined, | ||||||||
exchanges: hasTablePermission('ExchangeOutPrep', 'read') | ||||||||
exchangeOuts: hasTablePermission('ExchangeOutPrep', 'read') | ||||||||
? fetchCollection('ExchangeOutPrep', { | ||||||||
limit: DEFAULT_FETCH_LIMIT, | ||||||||
preparation: preparation.get('id'), | ||||||||
domainFilter: false, | ||||||||
}).then(({ records }) => records.map(deserializeResource)) | ||||||||
: undefined, | ||||||||
exchangeIns: hasTablePermission('ExchangeInPrep', 'read') | ||||||||
? fetchCollection('ExchangeInPrep', { | ||||||||
limit: DEFAULT_FETCH_LIMIT, | ||||||||
preparation: preparation.get('id'), | ||||||||
domainFilter: false, | ||||||||
}).then(({ records }) => records.map(deserializeResource)) | ||||||||
: undefined, | ||||||||
disposals: hasTablePermission('Disposal', 'read') | ||||||||
? fetchCollection('DisposalPreparation', { | ||||||||
limit: DEFAULT_FETCH_LIMIT, | ||||||||
preparation: preparation.get('id'), | ||||||||
domainFilter: false, | ||||||||
}).then(({ records }) => records.map(deserializeResource)) | ||||||||
: undefined, | ||||||||
}), | ||||||||
[preparation] | ||||||||
), | ||||||||
true | ||||||||
); | ||||||||
|
||||||||
const hasAnyInteractions = data && [ | ||||||||
data.openLoans, | ||||||||
data.resolvedLoans, | ||||||||
data.gifts, | ||||||||
data.exchangeOuts, | ||||||||
data.exchangeIns, | ||||||||
data.disposals, | ||||||||
].some(interactions => Array.isArray(interactions) && interactions.length > 0); | ||||||||
|
||||||||
return typeof data === 'object' ? ( | ||||||||
<Dialog | ||||||||
buttons={commonText.close()} | ||||||||
header={interactionsText.interactions()} | ||||||||
icon={icons.chat} | ||||||||
onClose={handleClose} | ||||||||
> | ||||||||
<H3 className="flex items-center gap-2"> | ||||||||
<TableIcon label name={tables.Loan.name} /> | ||||||||
{interactionsText.openLoans({ | ||||||||
loanTable: tables.Loan.label, | ||||||||
})} | ||||||||
</H3> | ||||||||
<List | ||||||||
displayFieldName="loanNumber" | ||||||||
fieldName="loan" | ||||||||
resources={data.openLoans ?? []} | ||||||||
/> | ||||||||
<H3 className="flex items-center gap-2"> | ||||||||
<TableIcon label name={tables.Loan.name} /> | ||||||||
{interactionsText.resolvedLoans({ | ||||||||
loanTable: tables.Loan.label, | ||||||||
})} | ||||||||
</H3> | ||||||||
<List | ||||||||
displayFieldName="loanNumber" | ||||||||
fieldName="loan" | ||||||||
resources={data.resolvedLoans ?? []} | ||||||||
/> | ||||||||
<H3 className="flex items-center gap-2"> | ||||||||
<TableIcon label name={tables.Gift.name} /> | ||||||||
{interactionsText.gifts({ | ||||||||
giftTable: tables.Gift.label, | ||||||||
})} | ||||||||
</H3> | ||||||||
<List | ||||||||
displayFieldName="giftNumber" | ||||||||
fieldName="gift" | ||||||||
resources={data.gifts ?? []} | ||||||||
/> | ||||||||
{Array.isArray(data.exchanges) && data.exchanges.length > 0 && ( | ||||||||
{!hasAnyInteractions ? ( | ||||||||
<> | ||||||||
<H3> | ||||||||
{interactionsText.exchanges({ | ||||||||
exhangeInTable: tables.ExchangeIn.label, | ||||||||
exhangeOutTable: tables.ExchangeOut.label, | ||||||||
})} | ||||||||
</H3> | ||||||||
<List | ||||||||
displayFieldName="exchangeOutNumber" | ||||||||
fieldName="exchange" | ||||||||
resources={data.exchanges} | ||||||||
/> | ||||||||
{interactionsText.noInteractions({ | ||||||||
preparationTable: String(tables.Preparation.label).toLowerCase(), | ||||||||
})} | ||||||||
</> | ||||||||
) : ( | ||||||||
<> | ||||||||
{Array.isArray(data.openLoans) && data.openLoans.length > 0 && ( | ||||||||
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. data.openLoans.length > 0 With this assertion, there is actually a slight change in UX compared to the previous implementation, was this intentional? An empty array is a valid prop to pass to the List component, and it used to show specify7/specifyweb/frontend/js_src/lib/components/FormCommands/ShowTransactions.tsx Lines 47 to 49 in 36f7a7e
Now, if no results are returned, the Interaction is not shown at all. |
||||||||
<> | ||||||||
<H3 className="flex items-center gap-2"> | ||||||||
<TableIcon label name={tables.Loan.name} /> | ||||||||
{interactionsText.openLoans({ | ||||||||
loanTable: tables.Loan.label, | ||||||||
})} | ||||||||
</H3> | ||||||||
<List | ||||||||
displayFieldName="loanNumber" | ||||||||
fieldName="loan" | ||||||||
resources={data.openLoans} | ||||||||
/> | ||||||||
</> | ||||||||
)} | ||||||||
{Array.isArray(data.resolvedLoans) && data.resolvedLoans.length > 0 && ( | ||||||||
<> | ||||||||
<H3 className="flex items-center gap-2"> | ||||||||
<TableIcon label name={tables.Loan.name} /> | ||||||||
{interactionsText.resolvedLoans({ | ||||||||
loanTable: tables.Loan.label, | ||||||||
})} | ||||||||
</H3> | ||||||||
<List | ||||||||
displayFieldName="loanNumber" | ||||||||
fieldName="loan" | ||||||||
resources={data.resolvedLoans} | ||||||||
/> | ||||||||
</> | ||||||||
)} | ||||||||
{Array.isArray(data.gifts) && data.gifts.length > 0 && ( | ||||||||
<> | ||||||||
<H3 className="flex items-center gap-2"> | ||||||||
<TableIcon label name={tables.Gift.name} /> | ||||||||
{interactionsText.gifts({ | ||||||||
giftTable: tables.Gift.label, | ||||||||
})} | ||||||||
</H3> | ||||||||
<List | ||||||||
displayFieldName="giftNumber" | ||||||||
fieldName="gift" | ||||||||
resources={data.gifts} | ||||||||
/> | ||||||||
</> | ||||||||
)} | ||||||||
{Array.isArray(data.disposals) && data.disposals.length > 0 && ( | ||||||||
<> | ||||||||
<H3 className="flex items-center gap-2"> | ||||||||
<TableIcon label name={tables.Disposal.name} /> | ||||||||
{interactionsText.disposals({ | ||||||||
disposalTable: tables.Disposal.label, | ||||||||
})} | ||||||||
</H3> | ||||||||
<List | ||||||||
displayFieldName="disposalNumber" | ||||||||
fieldName="disposal" | ||||||||
resources={data.disposals} | ||||||||
/> | ||||||||
</> | ||||||||
)} | ||||||||
{Array.isArray(data.exchangeOuts) && data.exchangeOuts.length > 0 && ( | ||||||||
<> | ||||||||
<H3 className="flex items-center gap-2"> | ||||||||
<TableIcon label name={tables.ExchangeOut.name} /> | ||||||||
{interactionsText.exchangeOut({ | ||||||||
exchangeOutTable: tables.ExchangeOut.label, | ||||||||
})} | ||||||||
</H3> | ||||||||
<List | ||||||||
displayFieldName="exchangeOutNumber" | ||||||||
fieldName="exchangeOut" | ||||||||
resources={data.exchangeOuts} | ||||||||
/> | ||||||||
</> | ||||||||
)} | ||||||||
{Array.isArray(data.exchangeIns) && data.exchangeIns.length > 0 && ( | ||||||||
<> | ||||||||
<H3 className="flex items-center gap-2"> | ||||||||
<TableIcon label name={tables.ExchangeIn.name} /> | ||||||||
{interactionsText.exchangeIn({ | ||||||||
exchangeInTable: tables.ExchangeIn.label, | ||||||||
})} | ||||||||
</H3> | ||||||||
<List | ||||||||
displayFieldName="exchangeInNumber" | ||||||||
fieldName="exchangeIn" | ||||||||
resources={data.exchangeIns} | ||||||||
/> | ||||||||
</> | ||||||||
)} | ||||||||
</> | ||||||||
)} | ||||||||
</Dialog> | ||||||||
) : null; | ||||||||
} | ||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,7 +104,10 @@ export function PrepDialog({ | |
<Button.DialogClose>{commonText.cancel()}</Button.DialogClose> | ||
<Button.Info | ||
disabled={!canSelectAll} | ||
title={interactionsText.selectAllAvailablePreparations()} | ||
title= | ||
{interactionsText.selectAllAvailablePreparations({ | ||
preparationTable: String(tables.Preparation.label).toLowerCase(), | ||
})} | ||
onClick={(): void => | ||
setSelected(preparations.map(({ available }) => available)) | ||
} | ||
|
@@ -133,7 +136,10 @@ export function PrepDialog({ | |
</> | ||
) | ||
} | ||
header={interactionsText.preparations()} | ||
header= | ||
{interactionsText.preparations({ | ||
preparationTable: tables.Preparation.label, | ||
})} | ||
Comment on lines
+140
to
+142
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. |
||
onClose={handleClose} | ||
> | ||
<Label.Inline className="gap-2"> | ||
|
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.
While probably not in the scope of the PR, there are a few things I noticed about the implementation of the ShowLoansCommand feature.
Conceptually, we are fetching the first 20 InteractionPreparation records associated with the Preparation (e.g., we're fetching al ExchangeInPrep records related to the Preparation in the highlighted section of code), and then we are iterating over each of the InteractionPreparation records and fetching their related Interactions:
specify7/specifyweb/frontend/js_src/lib/components/FormCommands/ShowTransactions.tsx
Lines 34 to 42 in 36f7a7e
(With the highlighted code of this comment, this would be iterating over all of the ExchangeInPrep records and fetching their related ExchangeIn record).
The problem with approach becomes apparent because we do not omit "duplicate" Interaction records in the list of Interactions for a Preparation.
Put more precisely, the Show Transactions Dialog can repeat the same resource multiple times if there are multiple InteractionPreps
Here is a setup demonstrating the Issue: there are 20 Preparations included in
ExchangeOut #1
, and 1 Preparation included inExchangeOut #2
. Specify fetches the first 20 ExchangeOutPreps related to the Preparation (i.e., all of the ExchangeOutPreps inExchangeOut #1
), then fetches the ExchangeOut related to each of those ExchangeOutPreps and uses that resource to populate the Dialogfetching_issues.mov
I would assume the purpose of this Dialog is to show related Interaction records? In reality it is showing the InteractionPreparations which all link to the Interaction record.
Besides not showing identical Interaction records, I also think the fetching logic here can be improved!
Currently, the method of fetching the Interaction after fetching the InteractionPreparations can be expensive if each InteractionPreparation exists for a different Interaction: we need a separate network request for each Interaction (this is after the static network request to fetch the InteractionPreparations):
network_requests.mov
We can actually directly use the API to fetch the related Interaction records for a table in a single network request.
Currently, we are constructing a query like
/api/specify/interactionprep/?preparation=<PREP_ID>
, then iterating over the returned records and constructing queries for/api/specify/interaction/<INTERACTION_ID>/
from the relationship name to the Interaction.But we can use a query like
/api/specify/interaction/?relationshipToInteractionPrep__preparation=<PREP_ID>
(e.g.,/api/specify/loan/?loanpreparations__preparation=<PREP_ID>
) to directly return the collection of Interaction records given a specific preparation id.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 have actually worked on a branch - issue-6108-b - to address this problem. It is functionally a complete reimplementation of the Show Transactions Dialog: take a look through the branch and see if there's any ideas you'd like to carry over to this implementation!
Here are some videos comparing production, this branch, and issue-6108-b:
production
production.mov
The error here is a bug in production which occurs whenever there are ExchangeOut records associated with a Preparation
issue-6108
issue-6108.mov
issue-6108-b
issue-6108-b.mov