Skip to content
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

[$250] Send invoice - System message for invoice edit is missing "Reply in thread" for invoice sender #43797

Closed
6 tasks done
lanitochka17 opened this issue Jun 15, 2024 · 106 comments
Assignees
Labels
Bug Something is broken. Auto assigns a BugZero manager. Daily KSv2 External Added to denote the issue can be worked on by a contributor

Comments

@lanitochka17
Copy link

lanitochka17 commented Jun 15, 2024

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Version Number: 1.4.84-0
Reproducible in staging?: Y
Reproducible in production?: Y
If this was caught during regression testing, add the test name, ID and link from TestRail: N/A
Issue reported by: Applause - Internal Team

Action Performed:

  1. Go to staging.new.expensify.com
  2. Go to FAB > Send invoice
  3. Send invoice to User B
  4. Go to transaction thread
  5. Edit Amount
  6. [User A and User B] Right click on the system message
  7. Note that "Reply in thread" is missing for invoice sender but present for invoice receiver
  8. [User B] Send a reply to the system message
  9. [User A] Open the reply in thread
  10. {User A] Click on the thread header subtitle
  11. {User A] Click on the thread header subtitle again

Expected Result:

In Step 7, "Reply in thread" should be available for both invoice sender and receiver
In Step 10, the invoice thread should have no visual issue
In Step 11, user should be navigated to the main chat

Actual Result:

In Step 7, "Reply in thread" is missing for invoice sender but present for invoice receiver
In Step 10, the invoice details appear broken above existing invoice details
In Step 11, user is navigated to the same invoice thread instead of the main chat

Workaround:

Unknown

Platforms:

Which of our officially supported platforms is this issue occurring on?

  • Android: Native
  • Android: mWeb Chrome
  • iOS: Native
  • iOS: mWeb Safari
  • MacOS: Chrome / Safari
  • MacOS: Desktop

Screenshots/Videos

Add any screenshot/video evidence

Bug6514451_1718472036865.send_invoice_reply_in_thread.mp4

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~0111ac375ebfe2baa1
  • Upwork Job ID: 1803175554822916116
  • Last Price Increase: 2024-08-08
  • Automatic offers:
    • rayane-djouah | Reviewer | 103213577
    • truph01 | Contributor | 103213579
Issue OwnerCurrent Issue Owner: @dylanexpensify
@lanitochka17 lanitochka17 added Daily KSv2 Bug Something is broken. Auto assigns a BugZero manager. labels Jun 15, 2024
Copy link

melvin-bot bot commented Jun 15, 2024

Triggered auto assignment to @dylanexpensify (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

@lanitochka17
Copy link
Author

@dylanexpensify FYI I haven't added the External label as I wasn't 100% sure about this issue. Please take a look and add the label if you agree it's a bug and can be handled by external contributors

@truph01
Copy link
Contributor

truph01 commented Jun 17, 2024

Proposal

Please re-state the problem that we are trying to solve in this issue.

In Step 7, "Reply in thread" is missing for invoice sender but present for invoice receiver
In Step 10, the invoice details appear broken above existing invoice details
In Step 11, user is navigated to the same invoice thread instead of the main chat

What is the root cause of that problem?

When we create a send invoice, we don't pass the ID of iouAction as a parameter in the SendInvoice API. It leads after the API is complete, the iou report has two IOU action

const parameters: SendInvoiceParams = {

And then getOriginalReportID returns the wrong reportID for the system message because we get all reportActions of the iou report in Onyx which makes getOneTransactionThreadReportID function returns undefined.

const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(reportID, reportActions ?? ([] as ReportAction[]));

It doesn't happen in the ReportScreen because when we get the report actions here, it doesn't include the optimistic iou action

return ReportActionsUtils.getContinuousReportActionChain(sortedAllReportActions, reportActionIDFromRoute);

What changes do you think we should make in order to solve the problem?

We should pass the reportActionID of iouAction as a parameter of the SendInvoice API and BE will use this reportActionID to create the iouAction and return data for the user.

const parameters: SendInvoiceParams = {

To do that we should

  1. In getSendInvoiceInformation function, we will return iouReportActionID: iouAction.reportID here
    return {

What alternative solutions did you explore? (Optional)

  1. Get the iouReportActionID when we call getSendInvoiceInformation function here
    const {senderWorkspaceID, receiver, invoiceRoom, createdChatReportActionID, invoiceReportID, reportPreviewReportActionID, transactionID, transactionThreadReportID, onyxData} =

and then pass it as a parameter of SendInvoice API here

const parameters: SendInvoiceParams = {

  1. We need a BE change here that will use iouReportActionID param to generate the the iouAction.

OPTIONAL: The created action of the transaction thread report is also duplicated. The RCA is the same with the iouAction, we can add the same fix for this action as well.

@tsa321
Copy link
Contributor

tsa321 commented Jun 17, 2024

Proposal

Please re-state the problem that we are trying to solve in this issue.

Send invoice - System message for invoice edit is missing "Reply in thread" for invoice sender and some visual issues related to transaction details.

What is the root cause of that problem?

When user send an invoice to the server returns new IOU report action which has different report action id than optimistic report action.


Basically because there are two IOU report action after user send invoice (one from optimistic action, one from server, this two IOU have different report action ids). The report view/list success to display correct report actions, this is because the optimistic IOU report action is filtered by this function, so it is not displayed in report actions list:

return ReportActionsUtils.getContinuousReportActionChain(sortedAllReportActions, reportActionIDFromRoute);

When report actions in view/list only have 1 IOU report actions it will display transaction details (this is already correct).
If it is more than one it will displayed as REPORTPREVIEW for each IOU report actions.

Also, when only 1 IOU report actions is displayed, the child transactions details thread reportactions will be merged into parent report report actions(which is the report which don't has "Reply in thread").
So basically the report actions of report which doesn't have a "Reply in thread" consist of report actions from that report and child transaction thread report.

Because of the getContinuousReportActionChain which is success to filter the optimistic IOU report action, but when user right click to show report acitons context menu item it fails to display Reply in Thread. This is because:

const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction);
const originalReport = ReportUtils.getReport(originalReportID);
showContextMenu(

The getOriginalReportID fails to get the correct child (original) report id / the transaction thread report which is the report that show the system message about money request update / the report which is the report actions are merged into parent if parent only has 1 IOU report action.

This is because:

App/src/libs/ReportUtils.ts

Lines 6066 to 6070 in f618a94

function getOriginalReportID(reportID: string, reportAction: OnyxInputOrEntry<ReportAction>): string | undefined {
const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`];
const currentReportAction = reportActions?.[reportAction?.reportActionID ?? '-1'] ?? null;
const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(reportID, reportActions ?? ([] as ReportAction[]));
if (Object.keys(currentReportAction ?? {}).length === 0) {

getOneTransactionThreadReportID :

if (!iouRequestActions.length || iouRequestActions.length > 1) {
return;
}

Fails to returns correct childreportid / trasaction thread report id, because early return in that if clause.
This is because there are two IOU report actions (one from optimistic update and one from backend, these two report actions have different reportActionID).

Because of incorrect originalReportID, the reportActions here:

const reportAction: OnyxEntry<ReportAction> = useMemo(() => {
if (isEmptyObject(reportActions) || reportActionID === '0' || reportActionID === '-1') {
return;
}
return reportActions[reportActionID];
}, [reportActions, reportActionID]);
const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, reportAction), [reportID, reportAction]);
const shouldEnableArrowNavigation = !isMini && (isVisible || shouldKeepOpen);
let filteredContextMenuActions = ContextMenuActions.filter(
(contextAction) =>
!disabledActions.includes(contextAction) &&
contextAction.shouldShow(type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat, isUnreadChat, !!isOffline, isMini),

the reportAction here will be undefined, and ContextMenuActions.filter will filter Reply In Thread context menu because the context menu give false result in contextAction.shouldShow.

What changes do you think we should make in order to solve the problem?

We could remove onyx optimistic IOU report actions because server returns new/different IOU report actions, in here:

App/src/libs/actions/IOU.ts

Lines 1035 to 1041 in b3f688b

[iouCreatedAction.reportActionID]: {
pendingAction: null,
errors: null,
},
[iouAction.reportActionID]: {
pendingAction: null,
errors: null,

inside successData in buildOnyxDataForInvoice,

We set the

[transactionThreadCreatedReportAction.reportActionID]: null
....

[iouCreatedAction.reportActionID]: null,
[iouAction.reportActionID]: null,

We set it to null, to remove the optimistic iou report actions...


For the navigation and header issue, we should use the parent of parent header and back to report link, if the parent of parent report is one trasaction / only has 1 IOU report action.

alternative solution:

Use getContinuousReportActionChain inside getOneTrasactionReportID to filter out the optmistic IOU report action.

@dylanexpensify
Copy link
Contributor

reproing today!

@dylanexpensify dylanexpensify added the External Added to denote the issue can be worked on by a contributor label Jun 18, 2024
@melvin-bot melvin-bot bot changed the title Send invoice - System message for invoice edit is missing "Reply in thread" for invoice sender [$250] Send invoice - System message for invoice edit is missing "Reply in thread" for invoice sender Jun 18, 2024
Copy link

melvin-bot bot commented Jun 18, 2024

Job added to Upwork: https://www.upwork.com/jobs/~0111ac375ebfe2baa1

@melvin-bot melvin-bot bot added the Help Wanted Apply this label when an issue is open to proposals by contributors label Jun 18, 2024
Copy link

melvin-bot bot commented Jun 18, 2024

Triggered auto assignment to Contributor-plus team member for initial proposal review - @rayane-djouah (External)

@dylanexpensify
Copy link
Contributor

added to project

@rayane-djouah
Copy link
Contributor

rayane-djouah commented Jun 20, 2024

@truph01, @tsa321
Thank you, everyone, for the proposals.
Both proposals do not have a clear RCA.
Can you please clearly explain in detail:

  1. Why is the "Reply in thread" option disabled for invoice sender?
  2. Why is there a duplicate report action for the invoice details?
  3. Why is the user navigated to the same invoice thread instead of the main chat?

@tsa321
Copy link
Contributor

tsa321 commented Jun 20, 2024

@rayane-djouah

Why is the "Reply in thread" option disabled for invoice sender?

Basically because there are two IOU report action after user send invoice (one from optimistic action, one from server, this two IOU have different report action ids).
The optimistic IOU is filtered by this function, so it is not displayed in report actions list:

return ReportActionsUtils.getContinuousReportActionChain(sortedAllReportActions, reportActionIDFromRoute);

When report actions in view/list only have 1 IOU report actions it will display transaction details (this is already correct).
If it is more than one it will displayed as REPORTPREVIEW for each IOU report actions.

Also, when only 1 IOU report actions is displayed, the child transactions details thread reportactions will be merged into parent report report actions(which is the report which don't has "Reply in thread").
So basically the report actions of report which doesn't have a "Reply in thread" consist of report actions from that report and child transaction thread report.

The getContinuousReportActionChain success to filter out the optimistic IOU report action, but when user right click to show report actions context menu item it fails to display Reply in Thread. This is because:

const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction);
const originalReport = ReportUtils.getReport(originalReportID);
showContextMenu(

The getOriginalReportID fails to get the correct child (original) report id / the transaction thread report which is the report that show the system message about money request update / the report which is the report actions are merged into parent if parent only has 1 IOU report action.

This is because:

App/src/libs/ReportUtils.ts

Lines 6066 to 6070 in f618a94

function getOriginalReportID(reportID: string, reportAction: OnyxInputOrEntry<ReportAction>): string | undefined {
const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`];
const currentReportAction = reportActions?.[reportAction?.reportActionID ?? '-1'] ?? null;
const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(reportID, reportActions ?? ([] as ReportAction[]));
if (Object.keys(currentReportAction ?? {}).length === 0) {

getOneTransactionThreadReportID :

if (!iouRequestActions.length || iouRequestActions.length > 1) {
return;
}

Fails to returns correct childreportid / trasaction thread report id, because early return in that if clause.
This is because there are two IOU report actions (one from optimistic update and one from backend, these two report actions have different reportActionID).

Because of incorrect originalReportID, the reportActions here:

const reportAction: OnyxEntry<ReportAction> = useMemo(() => {
if (isEmptyObject(reportActions) || reportActionID === '0' || reportActionID === '-1') {
return;
}
return reportActions[reportActionID];
}, [reportActions, reportActionID]);
const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, reportAction), [reportID, reportAction]);
const shouldEnableArrowNavigation = !isMini && (isVisible || shouldKeepOpen);
let filteredContextMenuActions = ContextMenuActions.filter(
(contextAction) =>
!disabledActions.includes(contextAction) &&
contextAction.shouldShow(type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID, isPinnedChat, isUnreadChat, !!isOffline, isMini),

the reportAction here will be undefined, and ContextMenuActions.filter will filter Reply In Thread context menu because the context menu give false result in contextAction.shouldShow.

I have updated my proposal in RCA section.

Why is there a duplicate report action for the Why is there a duplicate report action for the invoice details??

Because after user send invoice the report action of type CREATE and IOU from optimistic update and from back end are preserved/stored. Both have different report action ids. The details is representation of CREATE report action, when there are two create report action, there is a glitch when the APP try to display two invoice details.

Why is the user navigated to the same invoice thread instead of the main chat?

because the system message of IOU request upate/edit are from child thread transaction report. The system message is displayed/merged into parent report because the parent has exactly one IOU report action (because report screen success filtering the optimistic IOU report action).
basically the system message is from child transaction thread report.

@truph01
Copy link
Contributor

truph01 commented Jun 20, 2024

@rayane-djouah

Why is the "Reply in thread" option disabled for invoice sender?

This because the originalReportID is wrong as I mentioned in my proposal and then we can't get the current reportAction in BaseReportActionContextMenu

Why is there a duplicate report action for the invoice details?

Because BE generate an iou action with a random reportActionID instead of using the reportActionID from front-end, after SendInvoice API is complete we have two iou action, one from server, one is optimistic iou action.

Why is the user navigated to the same invoice thread instead of the main chat?

This is not the same chat. The first chat is the transaction thread report that is the current parent report of the system message thread. The second is the invoice room but this report has only one transaction and then this report display as combine report that will display all report actions of invoice room and all report actions of the transaction thread. So this case isn't a bug.

@rayane-djouah
Copy link
Contributor

@truph01 - I think you included the "What alternative solutions did you explore? (Optional)" title in the middle of your solution by mistake?

@rayane-djouah
Copy link
Contributor

@tsa321, @truph01 Thank you for your explanations.

I confirmed that we are having duplicate iouAction and createdActionForIOUReport for the invoice sender; below my investigation:

  1. I sent an invoice

  2. Result from :

const [optimisticCreatedActionForChat, optimisticCreatedActionForIOUReport, iouAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] =

Screenshot 2024-06-20 at 8 09 14 PM
  1. Invoice sender onyx data:
Screenshot 2024-06-20 at 8 09 26 PM
  1. Invoice receiver onyx data:
Screenshot 2024-06-20 at 8 09 59 PM
  1. The "Reply in thread" option is missing for the sender.

  2. When I deleted site data in the invoice sender session and logged in again, the "Reply in thread" option was available.

  3. I retried the scenario and sent the reply in a thread from the receiver; the invoice details appear duplicated in the sender session.

  4. When I deleted site data in the invoice sender session and logged in again, the invoice details appeared correct.

  • Conclusion: the root bug cause of the bug is that we have duplicate reportActionID, createdIOUReportActionID, and createdReportActionIDForThread. Therefore, similar to RequestMoneyParams and other IOU API endpoints, we should includereportActionID, createdIOUReportActionID, and createdReportActionIDForThread in SendInvoiceParams, and BE should use this IDs to create the report actions and return data with correct IDs.

Both proposals have a correct RCA, but @truph01 was the first to identify the root cause and provide the correct solution based on what we established as a pattern in IOU flows like Track Expense and Request Money...

@tsa321 - Removing optimistic IOU report actions inside successData in buildOnyxDataForInvoice could work, but I think it's not the optimal solution and does not follow the established pattern in other IOU flows.

Why is the user navigated to the same invoice thread instead of the main chat?
This is not the same chat. The first chat is the transaction thread report that is the current parent report of the system message thread. The second is the invoice room but this report has only one transaction and then this report display as combine report that will display all report actions of invoice room and all report actions of the transaction thread. So this case isn't a bug.

I will let the assigned internal engineer decide whether we should fix the header link so we can navigate directly to the invoice room when we are in one transaction report instead of navigating to the iou report and then to the invoice report. I think we can address this in the PR.

@truph01's proposal looks good to me.

🎀👀🎀 C+ reviewed

Copy link

melvin-bot bot commented Jun 20, 2024

Triggered auto assignment to @cristipaval, see https://stackoverflow.com/c/expensify/questions/7972 for more details.

@dylanexpensify
Copy link
Contributor

@cristipaval to review and confirm

@cristipaval
Copy link
Contributor

I think @truph01 can work on new issues once they merge all the ongoing ones. @dylanexpensify, can you confirm? 🙏

Copy link

melvin-bot bot commented Jun 24, 2024

@cristipaval, @dylanexpensify, @rayane-djouah Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

@melvin-bot melvin-bot bot added the Overdue label Jun 24, 2024
@rayane-djouah
Copy link
Contributor

@dylanexpensify ^^

@melvin-bot melvin-bot bot removed the Overdue label Jun 24, 2024
Copy link

melvin-bot bot commented Jun 25, 2024

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

@cristipaval
Copy link
Contributor

Waiting for the PR to get deployed.

@melvin-bot melvin-bot bot removed the Overdue label Aug 26, 2024
@rayane-djouah
Copy link
Contributor

@cristipaval - Reminder that the old reports with 2 CREATED report actions still need to be cleaned up. Are you tracking this internally?

@dylanexpensify
Copy link
Contributor

Bump @cristipaval 🙇‍♂️

@cristipaval
Copy link
Contributor

@cristipaval - Reminder that the old reports with 2 CREATED report actions still need to be cleaned up. Are you tracking this internally?

I don't think it makes sense to spend time on cleaning that as we don't have active users using the invoice rooms yet.

@madmax330, do you think we should run some custom queries to clean the DB, or should we deal with it if it becomes a real problem?

@melvin-bot melvin-bot bot added the Overdue label Aug 30, 2024
@rayane-djouah
Copy link
Contributor

⚠️ We just got confirmation on Slack that the Deploy Checklist: New Expensify 2024-08-26 which includes the PR of this issue was only deployed to production in Deploy Checklist: New Expensify 2024-08-28. More context on why this happened can be found in this Slack thread and this Slack comment.

Given the context above, this issue should be on [HOLD for Payment 2024-09-6] according to today's production deploy from Deploy Checklist: New Expensify 2024-08-28.

cc @dylanexpensify

@madmax330
Copy link
Contributor

do you think we should run some custom queries to clean the DB, or should we deal with it if it becomes a real problem?

@cristipaval are they real users or test users?
Let's clean them up anyway so we don't spend time looking into it later?

@rayane-djouah
Copy link
Contributor

@madmax330 - It looks like @cristipaval is on parental leave, could you please create an internal issue for this? Thanks!

Copy link

melvin-bot bot commented Sep 2, 2024

@madmax330, @cristipaval, @dylanexpensify, @rayane-djouah, @truph01 Eep! 4 days overdue now. Issues have feelings too...

@dylanexpensify
Copy link
Contributor

@madmax330 shall we get a new engineer on this? Or are you on it?

@melvin-bot melvin-bot bot removed the Overdue label Sep 4, 2024
@madmax330
Copy link
Contributor

@dylanexpensify actually I think we can just close this out since it was fixed already and the 2 report actions don't cause much of a problem

@melvin-bot melvin-bot bot added the Overdue label Sep 6, 2024
@rayane-djouah
Copy link
Contributor

@dylanexpensify This is due payment - #43797 (comment) Thanks!

@madmax330
Copy link
Contributor

Cool is there a payment summary already?

Copy link

melvin-bot bot commented Sep 9, 2024

@madmax330, @cristipaval, @dylanexpensify, @rayane-djouah, @truph01 Huh... This is 4 days overdue. Who can take care of this?

@dylanexpensify
Copy link
Contributor

Payment summary:

Contributor: @truph01 $250
Contributor+: @rayane-djouah $250

Please apply/request!

@melvin-bot melvin-bot bot removed the Overdue label Sep 10, 2024
@rayane-djouah
Copy link
Contributor

rayane-djouah commented Sep 10, 2024

Here is the pull request that resolves this issue:

Here are the issues fixed by the PR:


Checklist

  • The PR that introduced the bug has been identified. Link to the PR: Implement Send Invoice flow from Global Create #40015
  • The offending PR has been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake. Link to comment: https://github.com/Expensify/App/pull/40015/files#r1751709178
  • A discussion in #expensify-bugs has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner. Link to discussion: N/A
  • Determine if we should create a regression test for this bug. Yes
  • If we decide to create a regression test for the bug, please propose the regression test steps to ensure the same bug will not reach production again.

Regression Test Proposal

Test case 1:

  1. Go to FAB > Send invoice.
  2. Send invoice to User B.
  3. Go to transaction thread. Verify there is no visual issue.
  4. Edit Amount.
  5. [User A and User B] Right click on the system message.
  6. Verify that "Reply in thread" is displayed in both invoice sender and invoice receiver.

Test case 2:

  1. Navigate to FAB > Send invoice
  2. Add amount, select user and workspace
  3. Add merchant and send the invoice
  4. Go to invoice details and change the date
  5. Notice the notification appears. Attempt to copy it, and verify that is copied successfully.

Test case 3:

  1. [User A] Send invoice to User B
  2. [User A and B] Go to invoice thread
  3. [User B] Pay the invoice
  4. [User A] Quickly change the amount
  5. [User A] Dismiss the error under the amount edit system message
  6. Verify that User A will be able to dismiss the error under the amount edit system message

Test case 4:

  1. Go to FAB > Send invoice
  2. Enter amount and select a receiver
  3. Enter merchant and send the invoice
  4. In invoice chat, click on the preview
  5. Verify that the invoice report will not appear broken

Do we agree 👍 or 👎

@melvin-bot melvin-bot bot added the Overdue label Sep 12, 2024
Copy link

melvin-bot bot commented Sep 13, 2024

@madmax330, @cristipaval, @dylanexpensify, @rayane-djouah, @truph01 Whoops! This issue is 2 days overdue. Let's get this updated quick!

@rayane-djouah
Copy link
Contributor

@dylanexpensify Friendly bump

Copy link

melvin-bot bot commented Sep 17, 2024

@madmax330, @cristipaval, @dylanexpensify, @rayane-djouah, @truph01 6 days overdue. This is scarier than being forced to listen to Vogon poetry!

@dylanexpensify
Copy link
Contributor

on it now.

@melvin-bot melvin-bot bot removed the Overdue label Sep 17, 2024
@dylanexpensify
Copy link
Contributor

done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something is broken. Auto assigns a BugZero manager. Daily KSv2 External Added to denote the issue can be worked on by a contributor
Projects
Status: Done
Development

No branches or pull requests

8 participants