diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js
index 7889dcb0b703..152eaadcf709 100644
--- a/src/components/AvatarWithDisplayName.js
+++ b/src/components/AvatarWithDisplayName.js
@@ -17,6 +17,9 @@ import compose from '../libs/compose';
import * as OptionsListUtils from '../libs/OptionsListUtils';
import Text from './Text';
import * as StyleUtils from '../styles/StyleUtils';
+import Navigation from '../libs/Navigation/Navigation';
+import ROUTES from '../ROUTES';
+import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
const propTypes = {
/** The report currently being looked at */
@@ -52,6 +55,7 @@ const defaultProps = {
function AvatarWithDisplayName(props) {
const title = props.isAnonymous ? props.report.displayName : ReportUtils.getDisplayNameForParticipant(props.report.ownerAccountID, true);
const subtitle = ReportUtils.getChatRoomSubtitle(props.report);
+ const parentNavigationSubtitle = ReportUtils.getParentNavigationSubtitle(props.report);
const isExpenseReport = ReportUtils.isExpenseReport(props.report);
const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policies);
const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs([props.report.ownerAccountID], props.personalDetails);
@@ -88,6 +92,22 @@ function AvatarWithDisplayName(props) {
textStyles={[props.isAnonymous ? styles.headerAnonymousFooter : styles.headerText, styles.pre]}
shouldUseFullTitle={isExpenseReport || props.isAnonymous}
/>
+ {!_.isEmpty(parentNavigationSubtitle) && (
+ {
+ Navigation.navigate(ROUTES.getReportRoute(props.report.parentReportID));
+ }}
+ accessibilityLabel={subtitle}
+ accessibilityRole="link"
+ >
+
+ {parentNavigationSubtitle}
+
+
+ )}
{!_.isEmpty(subtitle) && (
diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js
index a0e0fc3b70ad..3ecc854951e0 100644
--- a/src/components/MoneyRequestHeader.js
+++ b/src/components/MoneyRequestHeader.js
@@ -90,6 +90,11 @@ function MoneyRequestHeader(props) {
const shouldShowSettlementButton = !isSettled && !props.isSingleTransactionView && isPayer;
const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport);
const shouldShowPaypal = Boolean(lodashGet(props.personalDetails, [moneyRequestReport.managerID, 'payPalMeAddress']));
+ const report = props.report;
+ if (props.isSingleTransactionView) {
+ report.ownerAccountID = lodashGet(props, ['parentReport', 'ownerAccountID'], null);
+ report.ownerEmail = lodashGet(props, ['parentReport', 'ownerEmail'], '');
+ }
return (
`From ${rootReportName}${workspaceName ? ` in ${workspaceName}` : ''}`,
},
qrCodes: {
copyUrlToClipboard: 'Copy URL to clipboard',
diff --git a/src/languages/es.js b/src/languages/es.js
index 27b51cd73492..f765ce0270df 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -1893,7 +1893,7 @@ export default {
lastReply: 'Ăšltima respuesta',
replies: 'Respuestas',
reply: 'Respuesta',
- from: 'De',
+ parentNavigationSummary: ({rootReportName, workspaceName}) => `De ${rootReportName}${workspaceName ? ` en ${workspaceName}` : ''}`,
},
qrCodes: {
copyUrlToClipboard: 'Copiar URL al portapapeles',
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index 72f8f297344e..aa3f600cb9d7 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -461,7 +461,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, {
result.isArchivedRoom = ReportUtils.isArchivedRoom(report);
result.isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report);
result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
- result.isThread = ReportUtils.isThread(report);
+ result.isThread = ReportUtils.isChatThread(report);
result.isTaskReport = ReportUtils.isTaskReport(report);
result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report);
result.allReportErrors = getAllReportErrors(report, reportActions);
@@ -644,7 +644,7 @@ function getOptions(
return;
}
- const isThread = ReportUtils.isThread(report);
+ const isThread = ReportUtils.isChatThread(report);
const isChatRoom = ReportUtils.isChatRoom(report);
const isTaskReport = ReportUtils.isTaskReport(report);
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report);
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index 4ddbf032867c..5de8abb15ed6 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -378,13 +378,23 @@ function getBankAccountRoute(report) {
}
/**
- * Returns true if report has a parent and is therefore a Thread.
+ * Returns true if report has a parent
*
* @param {Object} report
* @returns {Boolean}
*/
function isThread(report) {
- return Boolean(report && report.parentReportID && report.parentReportActionID && report.type === CONST.REPORT.TYPE.CHAT);
+ return Boolean(report && report.parentReportID && report.parentReportActionID);
+}
+
+/**
+ * Returns true if report is of type chat and has a parent and is therefore a Thread.
+ *
+ * @param {Object} report
+ * @returns {Boolean}
+ */
+function isChatThread(report) {
+ return isThread(report) && report.type === CONST.REPORT.TYPE.CHAT;
}
/**
@@ -394,7 +404,7 @@ function isThread(report) {
* @returns {Boolean}
*/
function isConciergeChatReport(report) {
- return lodashGet(report, 'participantAccountIDs', []).length === 1 && Number(report.participantAccountIDs[0]) === CONST.ACCOUNT_ID.CONCIERGE && !isThread(report);
+ return lodashGet(report, 'participantAccountIDs', []).length === 1 && Number(report.participantAccountIDs[0]) === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report);
}
/**
@@ -480,9 +490,15 @@ function isArchivedRoom(report) {
* @param {String} report.policyID
* @param {String} report.oldPolicyName
* @param {String} report.policyName
+ * @param {Boolean} [returnEmptyIfNotFound]
* @returns {String}
*/
-function getPolicyName(report) {
+function getPolicyName(report, returnEmptyIfNotFound = false) {
+ const noPolicyFound = returnEmptyIfNotFound ? '' : Localize.translateLocal('workspace.common.unavailable');
+ if (report === undefined) {
+ return noPolicyFound;
+ }
+
if ((!allPolicies || _.size(allPolicies) === 0) && !report.policyName) {
return Localize.translateLocal('workspace.common.unavailable');
}
@@ -491,7 +507,7 @@ function getPolicyName(report) {
// // Public rooms send back the policy name with the reportSummary,
// // since they can also be accessed by people who aren't in the workspace
- return lodashGet(policy, 'name') || report.policyName || report.oldPolicyName || Localize.translateLocal('workspace.common.unavailable');
+ return lodashGet(policy, 'name') || report.policyName || report.oldPolicyName || noPolicyFound;
}
/**
@@ -734,7 +750,7 @@ function getIcons(report, personalDetails, defaultIcon = null, isPayer = false,
result.source = Expensicons.DeletedRoomAvatar;
return [result];
}
- if (isThread(report)) {
+ if (isChatThread(report)) {
const parentReportAction = ReportActionsUtils.getParentReportAction(report);
const actorEmail = lodashGet(parentReportAction, 'actorEmail', '');
@@ -1030,7 +1046,7 @@ function getReportPreviewMessage(report, reportAction) {
*/
function getReportName(report) {
let formattedName;
- if (isThread(report)) {
+ if (isChatThread(report)) {
const parentReportAction = ReportActionsUtils.getParentReportAction(report);
if (ReportActionsUtils.isTransactionThread(parentReportAction)) {
return getTransactionReportName(parentReportAction);
@@ -1072,17 +1088,33 @@ function getReportName(report) {
}
/**
- * Recursively navigates through parent to get the root reports name only for DM reports.
+ * Recursively navigates through thread parents to get the root report and workspace name.
+ * The recursion stops when we find a non thread or money request report, whichever comes first.
* @param {Object} report
- * @returns {String|*}
+ * @returns {Object}
*/
-function getDMRootReportName(report) {
- if (isThread(report) && !getChatType(report)) {
+function getRootReportAndWorkspaceName(report) {
+ if (isThread(report) && !isMoneyRequestReport(report)) {
const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]);
- return getDMRootReportName(parentReport);
+ return getRootReportAndWorkspaceName(parentReport);
+ }
+
+ if (isIOUReport(report)) {
+ return {
+ rootReportName: lodashGet(report, 'displayName', ''),
+ };
+ }
+ if (isMoneyRequestReport(report)) {
+ return {
+ rootReportName: lodashGet(report, 'displayName', ''),
+ workspaceName: isIOUReport(report) ? CONST.POLICY.OWNER_EMAIL_FAKE : getPolicyName(report, true),
+ };
}
- return getReportName(report);
+ return {
+ rootReportName: getReportName(report),
+ workspaceName: getPolicyName(report, true),
+ };
}
/**
@@ -1091,23 +1123,8 @@ function getDMRootReportName(report) {
* @returns {String}
*/
function getChatRoomSubtitle(report) {
- if (isThread(report)) {
- if (!getChatType(report)) {
- return `${Localize.translateLocal('threads.from')} ${getDMRootReportName(report)}`;
- }
-
- let roomName = '';
- if (isChatRoom(report)) {
- const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]);
- if (parentReport) {
- roomName = lodashGet(parentReport, 'displayName', '');
- } else {
- roomName = lodashGet(report, 'displayName', '');
- }
- }
-
- const workspaceName = getPolicyName(report);
- return `${Localize.translateLocal('threads.from')} ${roomName ? [roomName, workspaceName].join(' in ') : workspaceName}`;
+ if (isChatThread(report)) {
+ return '';
}
if (!isDefaultRoom(report) && !isUserCreatedPolicyRoom(report) && !isPolicyExpenseChat(report)) {
return '';
@@ -1125,6 +1142,24 @@ function getChatRoomSubtitle(report) {
return getPolicyName(report);
}
+/**
+ * Gets the parent navigation subtitle for the report
+ * @param {Object} report
+ * @returns {String}
+ */
+function getParentNavigationSubtitle(report) {
+ if (isThread(report)) {
+ const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]);
+ const {rootReportName, workspaceName} = getRootReportAndWorkspaceName(parentReport);
+ if (_.isEmpty(rootReportName)) {
+ return '';
+ }
+
+ return Localize.translateLocal('threads.parentNavigationSummary', {rootReportName, workspaceName});
+ }
+ return '';
+}
+
/**
* Get the report for a reportID
*
@@ -1143,7 +1178,7 @@ function getReport(reportID) {
function navigateToDetailsPage(report) {
const participantAccountIDs = lodashGet(report, 'participantAccountIDs', []);
- if (isChatRoom(report) || isPolicyExpenseChat(report) || isThread(report)) {
+ if (isChatRoom(report) || isPolicyExpenseChat(report) || isChatThread(report)) {
Navigation.navigate(ROUTES.getReportDetailsRoute(report.reportID));
return;
}
@@ -1961,7 +1996,7 @@ function shouldReportBeInOptionList(report, currentReportId, isInGSDMode, iouRep
if (
!report ||
!report.reportID ||
- (_.isEmpty(report.participantAccountIDs) && !isThread(report) && !isPublicRoom(report) && !isArchivedRoom(report) && !isMoneyRequestReport(report) && !isTaskReport(report))
+ (_.isEmpty(report.participantAccountIDs) && !isChatThread(report) && !isPublicRoom(report) && !isArchivedRoom(report) && !isMoneyRequestReport(report) && !isTaskReport(report))
) {
return false;
}
@@ -2021,7 +2056,7 @@ function getChatByParticipants(newParticipantList) {
newParticipantList.sort();
return _.find(allReports, (report) => {
// If the report has been deleted, or there are no participants (like an empty #admins room) then skip it
- if (!report || !report.participantAccountIDs || isThread(report)) {
+ if (!report || !report.participantAccountIDs || isChatThread(report)) {
return false;
}
@@ -2201,7 +2236,7 @@ function canRequestMoney(report) {
*/
function getMoneyRequestOptions(report, reportParticipants, betas) {
// In any thread, we do not allow any new money requests yet
- if (isThread(report)) {
+ if (isChatThread(report)) {
return [];
}
@@ -2307,7 +2342,7 @@ function shouldReportShowSubscript(report) {
return false;
}
- if (isPolicyExpenseChat(report) && !isThread(report) && !isTaskReport(report) && !report.isOwnPolicyExpenseChat) {
+ if (isPolicyExpenseChat(report) && !isChatThread(report) && !isTaskReport(report) && !report.isOwnPolicyExpenseChat) {
return true;
}
@@ -2372,6 +2407,7 @@ export {
isUserCreatedPolicyRoom,
isChatRoom,
getChatRoomSubtitle,
+ getParentNavigationSubtitle,
getPolicyName,
getPolicyType,
isArchivedRoom,
@@ -2440,6 +2476,7 @@ export {
getWhisperDisplayNames,
getWorkspaceAvatar,
isThread,
+ isChatThread,
isThreadParent,
isThreadFirstChat,
shouldReportShowSubscript,
diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js
index ee53cd9df69a..f722f0cd1572 100644
--- a/src/libs/SidebarUtils.js
+++ b/src/libs/SidebarUtils.js
@@ -263,7 +263,7 @@ function getOptionData(reportID) {
const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(report.participantAccountIDs, personalDetails));
const personalDetail = participantPersonalDetailList[0] || {};
- result.isThread = ReportUtils.isThread(report);
+ result.isThread = ReportUtils.isChatThread(report);
result.isChatRoom = ReportUtils.isChatRoom(report);
result.isTaskReport = ReportUtils.isTaskReport(report);
if (result.isTaskReport) {
diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js
index 59a84182b803..3d19a632a634 100644
--- a/src/libs/actions/Task.js
+++ b/src/libs/actions/Task.js
@@ -57,6 +57,7 @@ function createTaskAndNavigate(currentUserEmail, currentUserAccountID, parentRep
// Create the CreatedReportAction on the task
const optimisticTaskCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(optimisticTaskReport.reportID);
const optimisticAddCommentReport = ReportUtils.buildOptimisticTaskCommentReportAction(taskReportID, title, assignee, assigneeAccountID, `Created a task: ${title}`, parentReportID);
+ optimisticTaskReport.parentReportActionID = optimisticAddCommentReport.reportAction.reportActionID;
const currentTime = DateUtils.getDBTime();
diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js
index 26588481f53a..cb727d9e3eae 100644
--- a/src/pages/ReportDetailsPage.js
+++ b/src/pages/ReportDetailsPage.js
@@ -63,7 +63,7 @@ function ReportDetailsPage(props) {
const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy), [policy]);
const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(props.report), [props.report]);
const isChatRoom = useMemo(() => ReportUtils.isChatRoom(props.report), [props.report]);
- const isThread = useMemo(() => ReportUtils.isThread(props.report), [props.report]);
+ const isThread = useMemo(() => ReportUtils.isChatThread(props.report), [props.report]);
const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(props.report), [props.report]);
const isArchivedRoom = useMemo(() => ReportUtils.isArchivedRoom(props.report), [props.report]);
diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js
index cdcaabcfe34d..6d9bdbffba54 100755
--- a/src/pages/ReportParticipantsPage.js
+++ b/src/pages/ReportParticipantsPage.js
@@ -93,7 +93,9 @@ function ReportParticipantsPage(props) {
1;
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, isMultipleParticipant);
- const isThread = ReportUtils.isThread(props.report);
+ const isChatThread = ReportUtils.isChatThread(props.report);
const isChatRoom = ReportUtils.isChatRoom(props.report);
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.report);
const isTaskReport = ReportUtils.isTaskReport(props.report);
- const reportHeaderData = !isTaskReport && !isThread && props.report.parentReportID ? props.parentReport : props.report;
+ const reportHeaderData = !isTaskReport && !isChatThread && props.report.parentReportID ? props.parentReport : props.report;
const title = ReportUtils.getReportName(reportHeaderData);
const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData);
+ const parentNavigationSubtitle = ReportUtils.getParentNavigationSubtitle(reportHeaderData);
const isConcierge = participants.length === 1 && _.contains(participants, CONST.ACCOUNT_ID.CONCIERGE);
const isAutomatedExpensifyAccount = participants.length === 1 && ReportUtils.hasAutomatedExpensifyAccountIDs(participants);
const guideCalendarLink = lodashGet(props.account, 'guideCalendarLink');
@@ -174,7 +175,7 @@ function HeaderView(props) {
) : (
)}
@@ -184,35 +185,31 @@ function HeaderView(props) {
tooltipEnabled
numberOfLines={1}
textStyles={[styles.headerText, styles.pre]}
- shouldUseFullTitle={isChatRoom || isPolicyExpenseChat || isThread || isTaskReport}
+ shouldUseFullTitle={isChatRoom || isPolicyExpenseChat || isChatThread || isTaskReport}
/>
- {(isChatRoom || isPolicyExpenseChat || isThread) && !_.isEmpty(subtitle) && (
- <>
- {isThread ? (
- {
- Navigation.navigate(ROUTES.getReportRoute(props.report.parentReportID));
- }}
- style={[styles.alignSelfStart, styles.mw100]}
- accessibilityLabel={subtitle}
- accessibilityRole="link"
- >
-
- {subtitle}
-
-
- ) : (
-
- {subtitle}
-
- )}
- >
+ {!_.isEmpty(parentNavigationSubtitle) && (
+ {
+ Navigation.navigate(ROUTES.getReportRoute(props.report.parentReportID));
+ }}
+ accessibilityLabel={parentNavigationSubtitle}
+ accessibilityRole="link"
+ >
+
+ {parentNavigationSubtitle}
+
+
+ )}
+ {!_.isEmpty(subtitle) && (
+
+ {subtitle}
+
)}
{brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && (
diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js
index 8e48f06c3528..b0f108ed7d39 100644
--- a/src/pages/home/report/ReportActionsList.js
+++ b/src/pages/home/report/ReportActionsList.js
@@ -116,7 +116,7 @@ function ReportActionsList(props) {
({item: reportAction, index}) => {
// When the new indicator should not be displayed we explicitly set it to null
const shouldDisplayNewMarker = reportAction.reportActionID === newMarkerReportActionID;
- const shouldDisplayParentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && ReportUtils.isThread(report);
+ const shouldDisplayParentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED && ReportUtils.isChatThread(report);
const shouldHideThreadDividerLine =
shouldDisplayParentAction && sortedReportActions.length > 1 && sortedReportActions[sortedReportActions.length - 2].reportActionID === newMarkerReportActionID;
return shouldDisplayParentAction ? (
diff --git a/src/pages/settings/Report/ReportSettingsPage.js b/src/pages/settings/Report/ReportSettingsPage.js
index 4c8f82c625cd..02ab518d999b 100644
--- a/src/pages/settings/Report/ReportSettingsPage.js
+++ b/src/pages/settings/Report/ReportSettingsPage.js
@@ -92,9 +92,9 @@ class ReportSettingsPage extends Component {
}
render() {
- const shouldShowRoomName = !ReportUtils.isPolicyExpenseChat(this.props.report) && !ReportUtils.isThread(this.props.report);
+ const shouldShowRoomName = !ReportUtils.isPolicyExpenseChat(this.props.report) && !ReportUtils.isChatThread(this.props.report);
const linkedWorkspace = _.find(this.props.policies, (policy) => policy && policy.id === this.props.report.policyID);
- const shouldDisableRename = this.shouldDisableRename(linkedWorkspace) || ReportUtils.isThread(this.props.report);
+ const shouldDisableRename = this.shouldDisableRename(linkedWorkspace) || ReportUtils.isChatThread(this.props.report);
const notificationPreference = this.props.translate(`notificationPreferencesPage.notificationPreferences.${this.props.report.notificationPreference}`);
const shouldDisableWelcomeMessage = this.shouldDisableWelcomeMessage(linkedWorkspace);
const writeCapability = this.props.report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL;