Skip to content

Commit

Permalink
Merge pull request #28730 from software-mansion-labs/ts-migration/Report
Browse files Browse the repository at this point in the history
[TS migration] Migrate 'Report.js' lib to TypeScript
  • Loading branch information
NikkiWines authored Dec 12, 2023
2 parents ca8b9b0 + 33cb7fc commit 4029eed
Show file tree
Hide file tree
Showing 22 changed files with 1,074 additions and 995 deletions.
3 changes: 2 additions & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,12 +453,13 @@ type OnyxValues = {
[ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT]: string;
[ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES]: number;
[ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE]: boolean;
[ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING]: boolean;
[ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING]: OnyxTypes.ReportUserIsTyping;
[ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM]: boolean;
[ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup;
[ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction;
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags;
[ONYXKEYS.COLLECTION.SELECTED_TAB]: string;
[ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string;

// Forms
[ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM]: OnyxTypes.AddDebitCardForm;
Expand Down
4 changes: 2 additions & 2 deletions src/components/ArchivedReportFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import * as ReportUtils from '@libs/ReportUtils';
import useThemeStyles from '@styles/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetails, Report, ReportAction} from '@src/types/onyx';
import type {PersonalDetailsList, Report, ReportAction} from '@src/types/onyx';
import Banner from './Banner';

type ArchivedReportFooterOnyxProps = {
/** The reason this report was archived */
reportClosedAction: OnyxEntry<ReportAction>;

/** Personal details of all users */
personalDetails: OnyxEntry<Record<string, PersonalDetails>>;
personalDetails: OnyxEntry<PersonalDetailsList>;
};

type ArchivedReportFooterProps = ArchivedReportFooterOnyxProps & {
Expand Down
37 changes: 10 additions & 27 deletions src/libs/EmojiUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,13 @@ import {Emoji, HeaderEmoji, PickerEmojis} from '@assets/emojis/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {FrequentlyUsedEmoji} from '@src/types/onyx';
import {ReportActionReaction, UsersReactions} from '@src/types/onyx/ReportActionReactions';
import {SupportedLanguage} from './EmojiTrie';

type HeaderIndice = {code: string; index: number; icon: React.FC<SvgProps>};
type EmojiSpacer = {code: string; spacer: boolean};
type EmojiPickerList = Array<EmojiSpacer | Emoji | HeaderEmoji>;
type ReplacedEmoji = {text: string; emojis: Emoji[]; cursorPosition?: number};
type UserReactions = {
id: string;
skinTones: Record<number, string>;
};
type UserReactionsWithTimestamps = UserReactions & {
oldestTimestamp: string;
};
type UsersReactionsList = {
createdAt: string;
users: Record<string, UserReactions>;
};
type TimestampedUsersReactions = Record<string, UserReactionsWithTimestamps>;
type EnrichedUserReactions = {
createdAt: string;
oldestTimestamp: string;
users: TimestampedUsersReactions;
};

let frequentlyUsedEmojis: FrequentlyUsedEmoji[] = [];
Onyx.connect({
Expand Down Expand Up @@ -442,9 +426,9 @@ function suggestEmojis(text: string, lang: keyof SupportedLanguage, limit = CONS
/**
* Retrieve preferredSkinTone as Number to prevent legacy 'default' String value
*/
const getPreferredSkinToneIndex = (val: string | number): number | string => {
if (val !== null && Number.isInteger(Number(val))) {
return val;
const getPreferredSkinToneIndex = (value: string | number | null): number => {
if (value !== null && Number.isInteger(Number(value))) {
return Number(value);
}

return CONST.EMOJI_DEFAULT_SKIN_TONE;
Expand Down Expand Up @@ -473,7 +457,7 @@ const getPreferredEmojiCode = (emoji: Emoji, preferredSkinTone: number): string
* array of emoji codes, that represents all used variations of the
* emoji, sorted by the reaction timestamp.
*/
const getUniqueEmojiCodes = (emojiAsset: Emoji, users: TimestampedUsersReactions): string[] => {
const getUniqueEmojiCodes = (emojiAsset: Emoji, users: UsersReactions): string[] => {
const emojiCodes: Record<string, string> = Object.values(users ?? {}).reduce((result: Record<string, string>, userSkinTones) => {
Object.keys(userSkinTones?.skinTones ?? {}).forEach((skinTone) => {
const createdAt = userSkinTones.skinTones[Number(skinTone)];
Expand All @@ -492,12 +476,11 @@ const getUniqueEmojiCodes = (emojiAsset: Emoji, users: TimestampedUsersReactions
/**
* Given an emoji reaction object and its name, it populates it with the oldest reaction timestamps.
*/
const enrichEmojiReactionWithTimestamps = (emoji: UsersReactionsList, emojiName: string): EnrichedUserReactions => {
const enrichEmojiReactionWithTimestamps = (emoji: ReportActionReaction, emojiName: string): ReportActionReaction => {
let oldestEmojiTimestamp: string | null = null;

const usersWithTimestamps: Record<string, UserReactionsWithTimestamps> = {};
Object.keys(emoji.users ?? {}).forEach((id) => {
const user = emoji?.users?.[id];
const usersWithTimestamps: UsersReactions = {};
Object.entries(emoji.users ?? {}).forEach(([id, user]) => {
const userTimestamps = Object.values(user?.skinTones ?? {});
const oldestUserTimestamp = userTimestamps.reduce((min, curr) => (curr < min ? curr : min), userTimestamps[0]);

Expand Down Expand Up @@ -527,7 +510,7 @@ const enrichEmojiReactionWithTimestamps = (emoji: UsersReactionsList, emojiName:
* Uses the NEW FORMAT for "emojiReactions"
* @param usersReactions - all the users reactions
*/
function hasAccountIDEmojiReacted(accountID: string, usersReactions: TimestampedUsersReactions, skinTone?: number) {
function hasAccountIDEmojiReacted(accountID: number, usersReactions: UsersReactions, skinTone?: number) {
if (skinTone === undefined) {
return Boolean(usersReactions[accountID]);
}
Expand All @@ -541,7 +524,7 @@ function hasAccountIDEmojiReacted(accountID: string, usersReactions: Timestamped
/**
* Given an emoji reaction and current user's account ID, it returns the reusable details of the emoji reaction.
*/
const getEmojiReactionDetails = (emojiName: string, reaction: UsersReactionsList, currentUserAccountID: string) => {
const getEmojiReactionDetails = (emojiName: string, reaction: ReportActionReaction, currentUserAccountID: number) => {
const {users, oldestTimestamp} = enrichEmojiReactionWithTimestamps(reaction, emojiName);

const emoji = findEmojiByName(emojiName);
Expand Down
4 changes: 2 additions & 2 deletions src/libs/GroupChatUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Onyx, {OnyxEntry} from 'react-native-onyx';
import ONYXKEYS from '@src/ONYXKEYS';
import {PersonalDetails, Report} from '@src/types/onyx';
import {PersonalDetailsList, Report} from '@src/types/onyx';
import * as OptionsListUtils from './OptionsListUtils';
import * as ReportUtils from './ReportUtils';

let allPersonalDetails: OnyxEntry<Record<string, PersonalDetails>> = {};
let allPersonalDetails: OnyxEntry<PersonalDetailsList> = {};
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (val) => (allPersonalDetails = val),
Expand Down
3 changes: 2 additions & 1 deletion src/libs/Notification/LocalNotification/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {ImageSourcePropType} from 'react-native';
import {OnyxEntry} from 'react-native-onyx';
import {Report, ReportAction} from '@src/types/onyx';

type PushParams = {
Expand All @@ -11,7 +12,7 @@ type PushParams = {
};

type ReportCommentParams = {
report: Report;
report: OnyxEntry<Report>;
reportAction: ReportAction;
onClick: () => void;
};
Expand Down
2 changes: 1 addition & 1 deletion src/libs/PersonalDetailsUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function getAccountIDsByLogins(logins) {
* Given a list of accountIDs, find the associated personal detail and return related logins.
*
* @param {Array<number>} accountIDs Array of user accountIDs
* @returns {Array} - Array of logins according to passed accountIDs
* @returns {Array<string>} - Array of logins according to passed accountIDs
*/
function getLoginsByAccountIDs(accountIDs) {
return _.reduce(
Expand Down
3 changes: 1 addition & 2 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import Str from 'expensify-common/lib/str';
import {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {PersonalDetails, Policy, PolicyMembers, PolicyTag, PolicyTags} from '@src/types/onyx';
import {PersonalDetailsList, Policy, PolicyMembers, PolicyTag, PolicyTags} from '@src/types/onyx';
import {EmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject';

type MemberEmailsToAccountIDs = Record<string, number>;
type PersonalDetailsList = Record<string, PersonalDetails>;
type UnitRate = {rate: number};

/**
Expand Down
34 changes: 26 additions & 8 deletions src/libs/Pusher/pusher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {LiteralUnion, ValueOf} from 'type-fest';
import Log from '@libs/Log';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {OnyxUpdateEvent, OnyxUpdatesFromServer} from '@src/types/onyx';
import {OnyxUpdateEvent, OnyxUpdatesFromServer, ReportUserIsTyping} from '@src/types/onyx';
import DeepValueOf from '@src/types/utils/DeepValueOf';
import TYPE from './EventType';
import Pusher from './library';
Expand All @@ -24,12 +24,25 @@ type Args = {

type PushJSON = OnyxUpdateEvent[] | OnyxUpdatesFromServer;

type UserIsTypingEvent = ReportUserIsTyping & {
userLogin?: string;
};

type UserIsLeavingRoomEvent = Record<string, boolean> & {
userLogin?: string;
};

type PusherEventMap = {
[TYPE.USER_IS_TYPING]: UserIsTypingEvent;
[TYPE.USER_IS_LEAVING_ROOM]: UserIsLeavingRoomEvent;
};

type EventData<EventName extends string> = EventName extends keyof PusherEventMap ? PusherEventMap[EventName] : PushJSON;

type EventCallbackError = {type: ValueOf<typeof CONST.ERROR>; data: {code: number}};

type ChunkedDataEvents = {chunks: unknown[]; receivedFinal: boolean};

type EventData = {id?: string; chunk?: unknown; final?: boolean; index: number};

type SocketEventCallback = (eventName: SocketEventName, data?: States | EventCallbackError) => void;

type PusherWithAuthParams = InstanceType<typeof Pusher> & {
Expand Down Expand Up @@ -139,13 +152,13 @@ function getChannel(channelName: string): Channel | undefined {
/**
* Binds an event callback to a channel + eventName
*/
function bindEventToChannel(channel: Channel | undefined, eventName: PusherEventName, eventCallback: (data: PushJSON) => void = () => {}) {
function bindEventToChannel<EventName extends PusherEventName>(channel: Channel | undefined, eventName: EventName, eventCallback: (data: EventData<EventName>) => void = () => {}) {
if (!eventName) {
return;
}

const chunkedDataEvents: Record<string, ChunkedDataEvents> = {};
const callback = (eventData: string | Record<string, unknown> | EventData) => {
const callback = (eventData: EventData<EventName>) => {
if (shouldForceOffline) {
Log.info('[Pusher] Ignoring a Push event because shouldForceOffline = true');
return;
Expand Down Expand Up @@ -207,7 +220,12 @@ function bindEventToChannel(channel: Channel | undefined, eventName: PusherEvent
* Subscribe to a channel and an event
* @param [onResubscribe] Callback to be called when reconnection happen
*/
function subscribe(channelName: string, eventName: PusherEventName, eventCallback: (data: PushJSON) => void = () => {}, onResubscribe = () => {}): Promise<void> {
function subscribe<EventName extends PusherEventName>(
channelName: string,
eventName: EventName,
eventCallback: (data: EventData<EventName>) => void = () => {},
onResubscribe = () => {},
): Promise<void> {
return new Promise((resolve, reject) => {
// We cannot call subscribe() before init(). Prevent any attempt to do this on dev.
if (!socket) {
Expand Down Expand Up @@ -307,7 +325,7 @@ function isSubscribed(channelName: string): boolean {
/**
* Sends an event over a specific event/channel in pusher.
*/
function sendEvent(channelName: string, eventName: PusherEventName, payload: Record<string, unknown>) {
function sendEvent<EventName extends PusherEventName>(channelName: string, eventName: EventName, payload: EventData<EventName>) {
// Check to see if we are subscribed to this channel before sending the event. Sending client events over channels
// we are not subscribed too will throw errors and cause reconnection attempts. Subscriptions are not instant and
// can happen later than we expect.
Expand Down Expand Up @@ -394,4 +412,4 @@ export {
getPusherSocketID,
};

export type {EventCallbackError, States, PushJSON};
export type {EventCallbackError, States, PushJSON, UserIsTypingEvent, UserIsLeavingRoomEvent};
2 changes: 1 addition & 1 deletion src/libs/PusherUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function triggerMultiEventHandler(eventType: string, data: OnyxUpdate[]): Promis
* Abstraction around subscribing to private user channel events. Handles all logs and errors automatically.
*/
function subscribeToPrivateUserChannelEvent(eventName: string, accountID: string, onEvent: (pushJSON: PushJSON) => void) {
const pusherChannelName = `${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${accountID}${CONFIG.PUSHER.SUFFIX}`;
const pusherChannelName = `${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${accountID}${CONFIG.PUSHER.SUFFIX}` as const;

function logPusherEvent(pushJSON: PushJSON) {
Log.info(`[Report] Handled ${eventName} event sent by Pusher`, false, pushJSON);
Expand Down
30 changes: 14 additions & 16 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import CONST from '@src/CONST';
import {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {Beta, Login, PersonalDetails, Policy, PolicyTags, Report, ReportAction, Session, Transaction} from '@src/types/onyx';
import {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, PolicyTags, Report, ReportAction, Session, Transaction} from '@src/types/onyx';
import {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
import {ChangeLog, IOUMessage, OriginalMessageActionName} from '@src/types/onyx/OriginalMessage';
import {Message, ReportActions} from '@src/types/onyx/ReportAction';
import {ChangeLog, IOUMessage, OriginalMessageActionName, OriginalMessageCreated} from '@src/types/onyx/OriginalMessage';
import {NotificationPreference} from '@src/types/onyx/Report';
import {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction';
import {Receipt, WaypointCollection} from '@src/types/onyx/Transaction';
import DeepValueOf from '@src/types/utils/DeepValueOf';
import {EmptyObject, isEmptyObject, isNotEmptyObject} from '@src/types/utils/EmptyObject';
Expand Down Expand Up @@ -187,10 +188,8 @@ type OptimisticClosedReportAction = Pick<
'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'message' | 'originalMessage' | 'pendingAction' | 'person' | 'reportActionID' | 'shouldShow'
>;

type OptimisticCreatedReportAction = Pick<
ReportAction,
'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'pendingAction'
>;
type OptimisticCreatedReportAction = OriginalMessageCreated &
Pick<ReportActionBase, 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'pendingAction'>;

type OptimisticChatReport = Pick<
Report,
Expand Down Expand Up @@ -315,7 +314,6 @@ type DisplayNameWithTooltips = Array<Pick<PersonalDetails, 'accountID' | 'pronou

type OptionData = {
alternateText?: string | null;
pendingAction?: PendingAction | null;
allReportErrors?: Errors | null;
brickRoadIndicator?: typeof CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR | '' | null;
tooltipText?: string | null;
Expand Down Expand Up @@ -1427,7 +1425,7 @@ function getDisplayNameForParticipant(accountID?: number, shouldUseShortForm = f
}

function getDisplayNamesWithTooltips(
personalDetailsList: PersonalDetails[] | Record<string, PersonalDetails>,
personalDetailsList: PersonalDetails[] | PersonalDetailsList,
isMultipleParticipantReport: boolean,
shouldFallbackToHidden = true,
): DisplayNameWithTooltips {
Expand All @@ -1441,7 +1439,7 @@ function getDisplayNamesWithTooltips(
const avatar = UserUtils.getDefaultAvatar(accountID);

let pronouns = user.pronouns;
if (pronouns && pronouns.startsWith(CONST.PRONOUNS.PREFIX)) {
if (pronouns?.startsWith(CONST.PRONOUNS.PREFIX)) {
const pronounTranslationKey = pronouns.replace(CONST.PRONOUNS.PREFIX, '');
pronouns = Localize.translateLocal(`pronouns.${pronounTranslationKey}` as TranslationPaths);
}
Expand Down Expand Up @@ -2394,7 +2392,7 @@ function getParsedComment(text: string): string {
return text.length <= CONST.MAX_MARKUP_LENGTH ? parser.replace(text) : lodashEscape(text);
}

function buildOptimisticAddCommentReportAction(text?: string, file?: File & {source: string; uri: string}): OptimisticReportAction {
function buildOptimisticAddCommentReportAction(text?: string, file?: File): OptimisticReportAction {
const parser = new ExpensiMark();
const commentText = getParsedComment(text ?? '');
const isAttachment = !text && file !== undefined;
Expand Down Expand Up @@ -3074,7 +3072,7 @@ function buildOptimisticChatReport(
oldPolicyName = '',
visibility: ValueOf<typeof CONST.REPORT.VISIBILITY> | undefined = undefined,
writeCapability: ValueOf<typeof CONST.REPORT.WRITE_CAPABILITIES> | undefined = undefined,
notificationPreference: string | number = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
parentReportActionID = '',
parentReportID = '',
welcomeMessage = '',
Expand Down Expand Up @@ -4302,12 +4300,12 @@ function shouldAutoFocusOnKeyPress(event: KeyboardEvent): boolean {
* Navigates to the appropriate screen based on the presence of a private note for the current user.
*/
function navigateToPrivateNotes(report: Report, session: Session) {
if (isEmpty(report) || isEmpty(session)) {
if (isEmpty(report) || isEmpty(session) || !session.accountID) {
return;
}
const currentUserPrivateNote = report.privateNotes?.[String(session.accountID)]?.note ?? '';
const currentUserPrivateNote = report.privateNotes?.[session.accountID]?.note ?? '';
if (isEmpty(currentUserPrivateNote)) {
Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, String(session.accountID)));
Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, session.accountID));
return;
}
Navigation.navigate(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID));
Expand Down Expand Up @@ -4485,4 +4483,4 @@ export {
shouldAutoFocusOnKeyPress,
};

export type {OptionData};
export type {OptionData, OptimisticChatReport};
5 changes: 2 additions & 3 deletions src/libs/SidebarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ function getOptionData(

const result: ReportUtils.OptionData = {
alternateText: null,
pendingAction: null,
allReportErrors: null,
brickRoadIndicator: null,
tooltipText: null,
Expand Down Expand Up @@ -285,7 +284,7 @@ function getOptionData(
result.isExpenseRequest = ReportUtils.isExpenseRequest(report);
result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report);
result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : null;
result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : undefined;
result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors;
result.brickRoadIndicator = Object.keys(result.allReportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '';
result.ownerAccountID = report.ownerAccountID;
Expand All @@ -304,7 +303,7 @@ function getOptionData(
result.hasOutstandingChildRequest = report.hasOutstandingChildRequest;
result.parentReportID = report.parentReportID ?? '';
result.isWaitingOnBankAccount = report.isWaitingOnBankAccount;
result.notificationPreference = report.notificationPreference ?? '';
result.notificationPreference = report.notificationPreference;
result.isAllowedToComment = ReportUtils.canUserPerformWriteAction(report);
result.chatType = report.chatType;

Expand Down
Loading

0 comments on commit 4029eed

Please sign in to comment.