Skip to content

Show "(guest)" by guest users' names, when setting enabled #5809

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

Merged
merged 22 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3cae733
msglist: Get dm-recipient-header names from user, not message, when able
chrisbobbe Dec 22, 2023
9ebb8c7
msglist: Get message sender's name from user, not message, when avail…
chrisbobbe Dec 21, 2023
ab219f7
lightbox: Get user's name from user data, not message, when available
chrisbobbe Dec 21, 2023
95ed1ba
api [nfc]: Make UserOrBot.role required, since we require Server 4.0
chrisbobbe Dec 20, 2023
b032519
realm state: Add enableGuestUserIndicator
chrisbobbe Dec 20, 2023
a4a26f1
AvatarItem: Stop carelessly amputating parts of people's names
chrisbobbe Dec 20, 2023
d33f134
AccountDetailsScreen: Remove unneeded fallback for empty-string full_…
chrisbobbe Dec 21, 2023
f35cc29
compose: In content-input placeholder, remove @ for 1:1 DMs, to match…
chrisbobbe Dec 21, 2023
2273d56
CustomProfileFields: Use styled UserItem for (unknown user)
chrisbobbe Dec 21, 2023
9b46739
test [nfc]: Pull out mock_, to share with another caller in different…
chrisbobbe Dec 21, 2023
17eb634
test: Have our mock GetText `mock_` handle our special "{_}" message
chrisbobbe Dec 21, 2023
7cc4d9f
msglist: Respect muted users in DM recipient headers by saying "Muted…
chrisbobbe Dec 22, 2023
47c2d94
LightboxHeader [nfc]: Use our usual ZulipText wrapper instead of plai…
chrisbobbe Dec 21, 2023
889827b
generateInboundEvents tests: Fix type of our `_` GetText function in …
chrisbobbe Jan 4, 2024
1c0266d
ui [nfc]: Centralize how we choose display text for a user's name
chrisbobbe Dec 21, 2023
8b5c507
GroupPmConversationItem [nfc]: Prepare to support italics in names
chrisbobbe Dec 21, 2023
9650e88
AvatarItem: Remove animation
chrisbobbe Dec 21, 2023
ee39d54
AvatarItem [nfc]: Convert to a function component
chrisbobbe Dec 21, 2023
9171d7d
ui: Show "(guest)" indicator on guest users' names, when realm settin…
chrisbobbe Dec 21, 2023
0c29df4
i18n [nfc]: Factor out noTranslation helper
gnprice Dec 29, 2023
eb3fb95
user [nfc]: Handle name fallback to message within getFullNameText an…
gnprice Dec 29, 2023
e729dbc
content [nfc]: Add TODO for one place we don't yet have "(guest)" mar…
chrisbobbe Jan 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions src/__tests__/lib/exampleData.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { randString, randInt } from '../../utils/misc';
import { makeUserId } from '../../api/idTypes';
import type { InitialData } from '../../api/apiTypes';
import { EventTypes, type UpdateMessageEvent } from '../../api/eventTypes';
import { CreateWebPublicStreamPolicy } from '../../api/permissionsTypes';
import { CreateWebPublicStreamPolicy, Role } from '../../api/permissionsTypes';
import type {
AccountSwitchAction,
LoginSuccessAction,
Expand Down Expand Up @@ -131,6 +131,7 @@ type UserOrBotPropertiesArgs = {|
email?: string,
full_name?: string,
avatar_url?: AvatarURL,
role?: Role,
|};

/**
Expand Down Expand Up @@ -161,7 +162,7 @@ const userOrBotProperties = (args: UserOrBotPropertiesArgs) => {

email: args.email ?? `${randName}@example.org`,
full_name: args.full_name ?? `${randName} User`,
is_admin: false,
role: args.role ?? Role.Member,
timezone: 'UTC',
user_id,
});
Expand All @@ -175,8 +176,6 @@ export const makeUser = (args: UserOrBotPropertiesArgs = Object.freeze({})): Use
is_bot: false,
bot_type: null,
// bot_owner omitted

is_guest: false,
profile_data: {},
});

Expand Down Expand Up @@ -860,10 +859,7 @@ export const action = Object.freeze({
can_create_web_public_streams: false,
can_subscribe_other_users: false,
can_invite_others_to_realm: false,

// $FlowIgnore[cannot-read]: Faithfully representing what servers send
is_admin: selfUser.is_admin,

is_admin: false,
is_owner: false,
is_billing_admin: selfUser.is_billing_admin,
is_moderator: false,
Expand Down
19 changes: 19 additions & 0 deletions src/__tests__/lib/intl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* @flow strict-local */

import type { GetText } from '../../types';

// Our translation function, usually given the name _.
// eslint-disable-next-line no-underscore-dangle
export const mock_: GetText = m => {
if (typeof m === 'object') {
if (m.text === '{_}') {
// $FlowIgnore[incompatible-indexer]
/* $FlowIgnore[incompatible-cast]
We expect an `m.values` that corresponds to `m.text`. */
const values = (m.values: {| +_: string |});
return values._;
}
return m.text;
}
return m;
};
29 changes: 7 additions & 22 deletions src/account-info/AccountDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import PresenceStatusIndicator from '../common/PresenceStatusIndicator';
import { getDisplayEmailForUser } from '../selectors';
import { Role } from '../api/permissionsTypes';
import ZulipTextIntl from '../common/ZulipTextIntl';
import { getOwnUserId } from '../users/userSelectors';
import { getOwnUserRole } from '../permissionSelectors';
import { getFullNameReactText } from '../users/userSelectors';

const componentStyles = createStyleSheet({
componentListItem: {
Expand Down Expand Up @@ -70,17 +69,8 @@ export default function AccountDetails(props: Props): Node {
state => getUserStatus(state, props.user.user_id).status_emoji,
);
const realm = useSelector(state => state.realm);
const { enableGuestUserIndicator } = realm;
const displayEmail = getDisplayEmailForUser(realm, user);
const ownUserId = useSelector(getOwnUserId);
const ownUserRole = useSelector(getOwnUserRole);

// user.role will be missing when the server has feature level <59. For
// those old servers, we can use getOwnUserRole for the "own" (or "self")
// user's role, but nothing will give us the role of a non-"own" user, so
// we just won't show any role in that case.
// TODO(server-4.0): user.role will never be missing; use that for "own"
// and non-"own" users.
const role = user.user_id === ownUserId ? ownUserRole : user.role;

return (
<ComponentList outerSpacing itemStyle={componentStyles.componentListItem}>
Expand All @@ -94,25 +84,20 @@ export default function AccountDetails(props: Props): Node {
hideIfOffline={false}
useOpaqueBackground={false}
/>
<ZulipText
<ZulipTextIntl
selectable
style={[styles.largerText, componentStyles.boldText]}
text={user.full_name}
text={getFullNameReactText({ user, enableGuestUserIndicator })}
/>
</View>
{displayEmail !== null && showEmail && (
<View>
<ZulipText selectable style={styles.largerText} text={displayEmail} />
</View>
)}
{
// TODO(server-4.0): Remove conditional; we'll always know the role.
role != null && (
<View>
<ZulipTextIntl selectable style={styles.largerText} text={getRoleText(role)} />
</View>
)
}
<View>
<ZulipTextIntl selectable style={styles.largerText} text={getRoleText(user.role)} />
</View>
{showStatus && (
<View style={componentStyles.statusWrapper}>
{userStatusEmoji && (
Expand Down
14 changes: 4 additions & 10 deletions src/account-info/AccountDetailsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ import AccountDetails from './AccountDetails';
import ZulipText from '../common/ZulipText';
import ActivityText from '../title/ActivityText';
import { doNarrow } from '../actions';
import { getUserIsActive, tryGetUserForId } from '../users/userSelectors';
import { getFullNameReactText, getUserIsActive, tryGetUserForId } from '../users/userSelectors';
import { nowInTimeZone } from '../utils/date';
import CustomProfileFields from './CustomProfileFields';
import { getRealm } from '../directSelectors';

const styles = createStyleSheet({
errorText: {
Expand Down Expand Up @@ -54,6 +55,7 @@ export default function AccountDetailsScreen(props: Props): Node {
const dispatch = useDispatch();
const user = useSelector(state => tryGetUserForId(state, props.route.params.userId));
const isActive = useSelector(state => getUserIsActive(state, props.route.params.userId));
const enableGuestUserIndicator = useSelector(state => getRealm(state).enableGuestUserIndicator);

const handleChatPress = useCallback(() => {
invariant(user, 'Callback handleChatPress is used only if user is known');
Expand All @@ -79,16 +81,8 @@ export default function AccountDetailsScreen(props: Props): Node {
}
}

const title = {
text: '{_}',
values: {
// This causes the name not to get translated.
_: user.full_name || ' ',
},
};

return (
<Screen title={title}>
<Screen title={getFullNameReactText({ user, enableGuestUserIndicator })}>
<AccountDetails user={user} showEmail showStatus />
<View style={styles.itemWrapper}>
<ActivityText style={globalStyles.largerText} user={user} />
Expand Down
9 changes: 0 additions & 9 deletions src/account-info/CustomProfileFields.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,21 @@ import { View } from 'react-native';
import { type UserOrBot, type UserId } from '../api/modelTypes';
import WebLink from '../common/WebLink';
import ZulipText from '../common/ZulipText';
import ZulipTextIntl from '../common/ZulipTextIntl';
import { ensureUnreachable } from '../generics';
import { useSelector } from '../react-redux';
import { tryGetUserForId } from '../selectors';
import {
type CustomProfileFieldValue,
getCustomProfileFieldsForUser,
} from '../users/userSelectors';
import UserItem from '../users/UserItem';
import { useNavigation } from '../react-navigation';

/* eslint-disable no-shadow */

type Props = {|
+user: UserOrBot,
|};

function CustomProfileFieldUser(props: {| +userId: UserId |}): React.Node {
const { userId } = props;
const user = useSelector(state => tryGetUserForId(state, userId));

const navigation = useNavigation();
const onPress = React.useCallback(
Expand All @@ -34,10 +29,6 @@ function CustomProfileFieldUser(props: {| +userId: UserId |}): React.Node {
[navigation],
);

if (!user) {
return <ZulipTextIntl text="(unknown user)" />;
}

return <UserItem userId={userId} onPress={onPress} size="medium" />;
}

Expand Down
3 changes: 2 additions & 1 deletion src/account-info/ProfileScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';

import { type UserId } from '../api/idTypes';
import { TranslationContext } from '../boot/TranslationProvider';
import { noTranslation } from '../i18n/i18n';
import type { RouteProp } from '../react-navigation';
import type { MainTabsNavigationProp } from '../main/MainTabsScreen';
import { createStyleSheet } from '../styles';
Expand Down Expand Up @@ -146,7 +147,7 @@ export default function ProfileScreen(props: Props): Node {
: undefined
}
title="Set your status"
subtitle={status_text != null ? { text: '{_}', values: { _: status_text } } : undefined}
subtitle={status_text != null ? noTranslation(status_text) : undefined}
onPress={() => {
navigation.push('user-status');
}}
Expand Down
11 changes: 9 additions & 2 deletions src/action-sheets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { roleIsAtLeast } from '../permissionSelectors';
import { kNotificationBotEmail } from '../api/constants';
import type { AppNavigationMethods } from '../nav/AppNavigator';
import { type ImperativeHandle as ComposeBoxImperativeHandle } from '../compose/ComposeBox';
import { getFullNameText } from '../users/userSelectors';

// TODO really this belongs in a libdef.
export type ShowActionSheetWithOptions = (
Expand Down Expand Up @@ -998,10 +999,16 @@ export const showPmConversationActionSheet = (args: {|
navigation: AppNavigationMethods,
_: GetText,
|},
backgroundData: $ReadOnly<{ ownUser: User, allUsersById: Map<UserId, UserOrBot>, ... }>,
backgroundData: $ReadOnly<{
ownUser: User,
allUsersById: Map<UserId, UserOrBot>,
enableGuestUserIndicator: boolean,
...
}>,
pmKeyRecipients: PmKeyRecipients,
|}): void => {
const { showActionSheetWithOptions, callbacks, backgroundData, pmKeyRecipients } = args;
const { enableGuestUserIndicator } = backgroundData;
showActionSheet({
showActionSheetWithOptions,
// TODO(ios-14.5): Check for Intl.ListFormat support in all environments
Expand All @@ -1011,7 +1018,7 @@ export const showPmConversationActionSheet = (args: {|
.map(userId => {
const user = backgroundData.allUsersById.get(userId);
invariant(user, 'allUsersById incomplete; could not show PM action sheet');
return user.full_name;
return callbacks._(getFullNameText({ user, enableGuestUserIndicator }));
})
.sort()
.join(', '),
Expand Down
21 changes: 2 additions & 19 deletions src/api/modelTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,6 @@ export type User = {|
// FL 121. The doc wrongly says it always appears. See
// https://chat.zulip.org/#narrow/stream/412-api-documentation/topic/.60is_active.60.20in.20.60.2Fregister.60.20response/near/1371606

// Deprecated (by us); these don't have events to update them. Use the
// `role` property when available. For the self user, use getOwnUserRole,
// which has a fallback for when `role` is absent.
// TODO(server-4.0): Remove these and rely on `role`.
-is_owner?: boolean, // TODO(server-3.0): New in FL 8
-is_admin: boolean,
-is_guest?: boolean, // TODO(server-1.9): New; if absent, treat as false.

// TODO(server-5.0): New in FL 73
+is_billing_admin?: boolean,

Expand All @@ -311,8 +303,7 @@ export type User = {|
// TODO(server-3.0): Replaced in FL 1 by bot_owner_id
+bot_owner?: string,

// TODO(server-4.0): New in FL 59
+role?: Role,
+role: Role,
Comment on lines -314 to +306
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

52f253a api [nfc]: Make UserOrBot.role required, since we require Server 4.0

20 files changed, 94 insertions(+), 180 deletions(-)

I think my ideal version of this change would have been several commits:

  • make role always present, propagate that as far as the types require;
  • switch various code to rely on that instead of the legacy properties, in one or several commits;
  • remove the legacy properties from the Redux state and the API, as either one or two commits.

But given that we hope to be done with this codebase soon, this is OK; no need to go back and split it now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah OK, makes sense. Thanks for the feedback!


// The ? is for future-proofing. Greg explains in 2020-02, at
// https://github.com/zulip/zulip-mobile/pull/3789#discussion_r378554698 ,
Expand Down Expand Up @@ -373,13 +364,6 @@ export type CrossRealmBot = {|
// FL 121. The doc wrongly says it always appears. See
// https://chat.zulip.org/#narrow/stream/412-api-documentation/topic/.60is_active.60.20in.20.60.2Fregister.60.20response/near/1371606

// Deprecated (by us); these don't have events to update them. Use the
// `role` property when available.
// TODO(server-4.0): Remove these and rely on `role`.
-is_owner?: boolean, // TODO(server-3.0): New in FL 8
-is_admin: boolean,
-is_guest?: boolean, // TODO(server-1.9): New; if absent, treat as false.

// TODO(server-5.0): New in FL 73
+is_billing_admin?: boolean,

Expand All @@ -399,8 +383,7 @@ export type CrossRealmBot = {|
// TODO(server-3.0): Replaced in FL 1 by bot_owner_id
// +bot_owner?: string,

// TODO(server-4.0): New in FL 59
+role?: Role,
+role: Role,

// The ? is for future-proofing. For bots it's always '':
// https://github.com/zulip/zulip-mobile/pull/3789#issuecomment-581218576
Expand Down
5 changes: 3 additions & 2 deletions src/common/SelectableOptionsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SelectableOptionRow from './SelectableOptionRow';
import ZulipTextIntl from './ZulipTextIntl';
import { createStyleSheet } from '../styles';
import { TranslationContext } from '../boot/TranslationProvider';
import { noTranslation } from '../i18n/i18n';

type Item<TKey> = $ReadOnly<{|
key: TKey,
Expand Down Expand Up @@ -100,9 +101,9 @@ export default function SelectableOptionsScreen<TItemKey: string | number>(

/* eslint-disable prefer-template */
const itemData =
_(item.subtitle ?? { text: '{_}', values: { _: '' } }).toUpperCase()
_(item.subtitle ?? noTranslation('')).toUpperCase()
+ ' '
+ _(item.title ?? { text: '{_}', values: { _: '' } }).toUpperCase();
+ _(item.title ?? noTranslation('')).toUpperCase();
/* eslint-enable prefer-template */

const filterData = filter.toUpperCase();
Expand Down
5 changes: 3 additions & 2 deletions src/common/ServerCompatBanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import type { Node } from 'react';

import ZulipBanner from './ZulipBanner';
import { useSelector, useGlobalSelector, useDispatch } from '../react-redux';
import { getOwnUser } from '../selectors';
import { getIdentity, getServerVersion } from '../account/accountsSelectors';
import { getSession, getGlobalSettings } from '../directSelectors';
import { dismissCompatNotice } from '../session/sessionActions';
import { openLinkWithUserPreference } from '../utils/openLink';
import { ZulipVersion } from '../utils/zulipVersion';
import { getOwnUserRole, roleIsAtLeast } from '../permissionSelectors';
import { roleIsAtLeast } from '../permissionSelectors';
import { Role } from '../api/permissionsTypes';

/**
Expand Down Expand Up @@ -52,7 +53,7 @@ export default function ServerCompatBanner(props: Props): Node {
);
const zulipVersion = useSelector(getServerVersion);
const realm = useSelector(state => getIdentity(state).realm);
const isAtLeastAdmin = useSelector(state => roleIsAtLeast(getOwnUserRole(state), Role.Admin));
const isAtLeastAdmin = useSelector(state => roleIsAtLeast(getOwnUser(state).role, Role.Admin));
const settings = useGlobalSelector(getGlobalSettings);

let visible = false;
Expand Down
16 changes: 12 additions & 4 deletions src/compose/ComposeBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import AnnouncementOnly from '../message/AnnouncementOnly';
import MentionWarnings from './MentionWarnings';
import {
getAuth,
getOwnUser,
getStreamInNarrow,
getStreamsById,
getVideoChatProvider,
Expand All @@ -66,7 +67,7 @@ import AutocompleteView from '../autocomplete/AutocompleteView';
import { getAllUsersById, getOwnUserId } from '../users/userSelectors';
import * as api from '../api';
import { ensureUnreachable } from '../generics';
import { getOwnUserRole, roleIsAtLeast } from '../permissionSelectors';
import { roleIsAtLeast } from '../permissionSelectors';
import { Role } from '../api/permissionsTypes';
import useUncontrolledInput from '../useUncontrolledInput';
import { tryFetch } from '../message/fetchActions';
Expand Down Expand Up @@ -187,15 +188,15 @@ const ComposeBox: React$AbstractComponent<Props, ImperativeHandle> = forwardRef(
const zulipFeatureLevel = useSelector(getZulipFeatureLevel);
const ownUserId = useSelector(getOwnUserId);
const allUsersById = useSelector(getAllUsersById);
const isAtLeastAdmin = useSelector(state => roleIsAtLeast(getOwnUserRole(state), Role.Admin));
const isAtLeastAdmin = useSelector(state => roleIsAtLeast(getOwnUser(state).role, Role.Admin));
const isAnnouncementOnly = useSelector(state =>
getIsActiveStreamAnnouncementOnly(state, props.narrow),
);
const isSubscribed = useSelector(state => getIsActiveStreamSubscribed(state, props.narrow));
const stream = useSelector(state => getStreamInNarrow(state, props.narrow));
const streamsById = useSelector(getStreamsById);
const videoChatProvider = useSelector(getVideoChatProvider);
const mandatoryTopics = useSelector(state => getRealm(state).mandatoryTopics);
const { mandatoryTopics, enableGuestUserIndicator } = useSelector(getRealm);

const mentionWarnings = React.useRef<React$ElementRef<typeof MentionWarnings> | null>(null);

Expand Down Expand Up @@ -713,7 +714,14 @@ const ComposeBox: React$AbstractComponent<Props, ImperativeHandle> = forwardRef(
return <AnnouncementOnly />;
}

const placeholder = getComposeInputPlaceholder(narrow, ownUserId, allUsersById, streamsById);
const placeholder = getComposeInputPlaceholder(
narrow,
ownUserId,
allUsersById,
streamsById,
enableGuestUserIndicator,
_,
);
Comment on lines -716 to +724
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parameter list has gotten increasingly ridiculous and should probably become getComposeInputPlaceholder(narrow, backgroundData). But shrug probably not worth changing in this legacy codebase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah.


const SubmitButtonIcon = isEditing ? IconDone : IconSend;

Expand Down
Loading