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

feat: New users page pending tab #31987

Merged
merged 88 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
35a3b76
refactor: :recycle: Create isSMTPConfigured helper
rique223 Mar 5, 2024
c7ea10e
feat: :sparkles: Create users.listByStatys and users.sendWelcomeEmail…
rique223 Mar 5, 2024
ed5cfec
test: :white_check_mark: Create tests for the users.listByStatus and …
rique223 Mar 5, 2024
4b15497
Typecheck
rique223 Mar 5, 2024
ab3f578
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Mar 6, 2024
cde16a1
feat: :sparkles: Add all tab to users page and implement useFilteredU…
rique223 Mar 6, 2024
4dd02cf
feat: :sparkles: Add Registration Status column to users table
rique223 Mar 6, 2024
f5d3068
Reviews first part
rique223 Mar 8, 2024
f3e1731
Reviews second part
rique223 Mar 8, 2024
1208a88
Reviews third part
rique223 Mar 8, 2024
522c71f
feat: :sparkles: Add new pending tab to layout and pending users count
rique223 Mar 13, 2024
0f5f857
feat: :sparkles: Add pending action to pending tab users table
rique223 Mar 13, 2024
91fcd85
feat: :sparkles: Users page table menu and pending tab buttons
rique223 Mar 14, 2024
60876b8
Review
rique223 Mar 14, 2024
3b91f00
Create fifty-cups-sort.md
rique223 Mar 14, 2024
ebc30fe
Create chilly-poems-explode.md
rique223 Mar 14, 2024
5ae9b78
refactor: :recycle: Sort pending tab users table by "Pending action" …
rique223 Mar 14, 2024
1408ae6
feat: :sparkles: Introduce reason for joining field in users page con…
rique223 Mar 14, 2024
15d73b3
feat: :sparkles: Implement exceeded seats cap callout
rique223 Mar 14, 2024
fe4b650
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Mar 15, 2024
27ba076
Merge branch 'feat/new-user-panel-backend' into feat/new-user-panel-all
rique223 Mar 15, 2024
fd988e7
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Mar 15, 2024
f6efd64
Add disabled to all user creation buttons
rique223 Mar 15, 2024
73f0371
Re-add boolean verification to seats cap
rique223 Mar 15, 2024
2dc8e3f
Create metal-candles-float.md
rique223 Mar 15, 2024
6bda963
refactor: :recycle: Make pending users count only count non-activated…
rique223 Mar 15, 2024
18c0e4b
Merge branch 'feat/new-user-panel-pending' of github.com:RocketChat/R…
rique223 Mar 15, 2024
90627ff
refactor: :recycle: Refactor listUsersByStatus endpoint to be more fl…
rique223 Mar 15, 2024
8dafa9e
Remove console.log
rique223 Mar 15, 2024
0f2614b
test: :white_check_mark: Update api tests to follow new listByStatus …
rique223 Mar 18, 2024
6b2ab3e
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Mar 18, 2024
fe7d42d
Merge branch 'feat/new-user-panel-backend' into feat/new-user-panel-all
rique223 Mar 18, 2024
9c75ae9
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Mar 18, 2024
ae7e828
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Mar 19, 2024
d8696c4
Merge branch 'feat/new-user-panel-backend' into feat/new-user-panel-all
rique223 Mar 19, 2024
57893f4
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Mar 19, 2024
a6d49b2
Review
rique223 Mar 19, 2024
eea8f9c
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Mar 20, 2024
de4a14d
Merge branch 'feat/new-user-panel-backend' into feat/new-user-panel-all
rique223 Mar 20, 2024
4d0658e
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Mar 20, 2024
bf1a5a0
Change PickedUser to DefaultUserInfo
rique223 Mar 20, 2024
ddf20d9
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Mar 20, 2024
c2ecbdd
Change PickedUser to DefaultUserInfo
rique223 Mar 20, 2024
0b84562
Typecheck
rique223 Mar 20, 2024
1010637
Create pink-parrots-end.md
rique223 Mar 21, 2024
bd078ab
Remove unnecessary type
rique223 Mar 22, 2024
19a12b4
refactor: ♻️ Refactor listUsersByStatus endpoint to be more flexible …
rique223 Mar 22, 2024
e9649e2
Merge branch 'feat/new-user-panel-backend' into feat/new-user-panel-all
rique223 Mar 22, 2024
f5b61ba
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Mar 22, 2024
8923dca
Update listByStatus typing
rique223 Mar 22, 2024
ed29479
Merge branch 'feat/new-user-panel-backend' into feat/new-user-panel-all
rique223 Mar 22, 2024
3a5004e
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Mar 22, 2024
5b74149
Typecheck
rique223 Mar 22, 2024
05544d4
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Mar 22, 2024
e2db32c
Typecheck
rique223 Mar 22, 2024
579b977
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Mar 22, 2024
6964c0b
Add active to sorting options
rique223 Mar 22, 2024
fbb307b
Update fifty-cups-sort.md
rique223 Apr 1, 2024
afd5108
Update pink-parrots-end.md
rique223 Apr 1, 2024
fe43f5b
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Apr 3, 2024
ccae8e3
Merge branch 'develop' into feat/new-user-panel-backend
rique223 Apr 8, 2024
c19face
Merge branch 'feat/new-user-panel-backend' into feat/new-user-panel-all
rique223 Apr 8, 2024
a142a57
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Apr 8, 2024
ac0e48c
Merge
rique223 Apr 23, 2024
23d074e
Merge branch 'feat/new-user-panel-all' into feat/new-user-panel-pending
rique223 Apr 23, 2024
33bf9fc
Merge branch 'develop' into feat/new-user-panel-pending
rique223 May 7, 2024
6e20ad7
refactor: :recycle: Improve callout modal copy
rique223 May 9, 2024
c78399a
refactor: :recycle: Improve pendingUsersCount
rique223 May 9, 2024
b811a57
refactor: :recycle: Add loading to pendingUsersCount
rique223 May 9, 2024
4feae75
Review 1
rique223 May 10, 2024
2f22c53
feat: :sparkles: Introduce new user-defined format options to AJV
rique223 May 15, 2024
90c3cb0
Merge branch 'develop' into feat/new-user-panel-pending
rique223 May 20, 2024
237dbc5
Merge branch 'develop' into feat/new-user-panel-pending
rique223 May 20, 2024
aea900a
Rollback UserInfo
rique223 May 22, 2024
22ccf68
Review part 1
rique223 Jun 3, 2024
2f5fd4d
refactor: :recycle: Improve SubscriptionCallout find max algorithm
rique223 Jun 3, 2024
09fdbe0
refactor: :recycle: Implement better logic for user panel callout
rique223 Jun 6, 2024
80514f0
Merge branch 'develop' into feat/new-user-panel-pending
rique223 Jun 10, 2024
bcd0dcc
Merge branch 'develop' into feat/new-user-panel-pending
rique223 Jun 13, 2024
fc2a926
Rename hook name for explicitness
tassoevan Jun 13, 2024
4417576
Remove margin
tassoevan Jun 14, 2024
d660a99
Merge branch 'develop' into feat/new-user-panel-pending
rique223 Jun 18, 2024
07f920b
Merge branch 'develop' into feat/new-user-panel-pending
rique223 Jun 19, 2024
8bdfc4e
Merge branch 'develop' into feat/new-user-panel-pending
rique223 Jun 19, 2024
c7a28d8
Merge branch 'develop' into feat/new-user-panel-pending
rique223 Jun 19, 2024
b21b0a0
Merge branch 'develop' into feat/new-user-panel-pending
rique223 Jun 20, 2024
e6c128e
Merge branch 'develop' into feat/new-user-panel-pending
rique223 Jun 20, 2024
7be9720
Merge branch 'develop' into feat/new-user-panel-pending
rique223 Jun 20, 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
13 changes: 13 additions & 0 deletions .changeset/metal-candles-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/core-typings": patch
"@rocket.chat/i18n": patch
---

Implemented a new "Pending Users" tab on the users page to list users who have not yet been activated and/or have not logged in for the first time.
Additionally, added a "Pending Action" column to aid administrators in identifying necessary actions for each user. Incorporated a "Reason for Joining" field
into the user info contextual bar, along with a callout for exceeding the seats cap in the users page header. Finally, introduced a new logic to disable user creation buttons upon surpassing the seats cap.




1 change: 1 addition & 0 deletions apps/meteor/app/api/server/lib/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export async function findPaginatedUsersByStatus({
lastLogin: 1,
type: 1,
reason: 1,
federated: 1,
};

const actualSort: Record<string, 1 | -1> = sort || { username: 1 };
Expand Down
12 changes: 10 additions & 2 deletions apps/meteor/client/components/Page/PageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,20 @@ const PageHeader: FC<PageHeaderProps> = ({ children = undefined, title, onClickB
<Box
is='header'
borderBlockEndWidth='default'
minHeight='x64'
pb={8}
borderBlockEndColor={borderBlockEndColor ?? border ? 'extra-light' : 'transparent'}
{...props}
>
<Box height='100%' marginInline={24} display='flex' flexDirection='row' flexWrap='wrap' alignItems='center' color='default'>
<Box
height='100%'
marginInline={24}
minHeight='x64'
display='flex'
flexDirection='row'
flexWrap='wrap'
alignItems='center'
color='default'
>
{isMobile && (
<HeaderToolbar>
<SidebarToggler />
Expand Down
49 changes: 26 additions & 23 deletions apps/meteor/client/components/UserInfo/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type UserInfoProps = UserInfoDataProps & {
verified?: boolean;
actions: ReactElement;
roles: ReactElement[];
reason?: string;
};

const UserInfo = ({
Expand All @@ -59,6 +60,7 @@ const UserInfo = ({
customFields,
canViewAllInfo,
actions,
reason,
...props
}: UserInfoProps): ReactElement => {
const t = useTranslation();
Expand All @@ -79,53 +81,47 @@ const UserInfo = ({

<InfoPanel.Section>
{userDisplayName && <InfoPanel.Title icon={status} title={userDisplayName} />}

{statusText && (
<InfoPanel.Text>
<MarkdownText content={statusText} parseEmoji={true} />
<MarkdownText content={statusText} parseEmoji={true} variant='inline' />
</InfoPanel.Text>
)}
</InfoPanel.Section>

<InfoPanel.Section>
{roles.length !== 0 && (
{reason && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Roles')}</InfoPanel.Label>
<UserCardRoles>{roles}</UserCardRoles>
<InfoPanel.Label>{t('Reason_for_joining')}</InfoPanel.Label>
<InfoPanel.Text>{reason}</InfoPanel.Text>
</InfoPanel.Field>
)}

{Number.isInteger(utcOffset) && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Local_Time')}</InfoPanel.Label>
<InfoPanel.Text>{utcOffset && <UTCClock utcOffset={utcOffset} />}</InfoPanel.Text>
</InfoPanel.Field>
)}

{username && username !== name && (
{nickname && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Username')}</InfoPanel.Label>
<InfoPanel.Text data-qa='UserInfoUserName'>{username}</InfoPanel.Text>
<InfoPanel.Label>{t('Nickname')}</InfoPanel.Label>
<InfoPanel.Text>{nickname}</InfoPanel.Text>
</InfoPanel.Field>
)}

{canViewAllInfo && (
{roles.length !== 0 && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Last_login')}</InfoPanel.Label>
<InfoPanel.Text>{lastLogin ? timeAgo(lastLogin) : t('Never')}</InfoPanel.Text>
<InfoPanel.Label>{t('Roles')}</InfoPanel.Label>
<UserCardRoles>{roles}</UserCardRoles>
</InfoPanel.Field>
)}

{name && (
{username && username !== name && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Full_Name')}</InfoPanel.Label>
<InfoPanel.Text>{name}</InfoPanel.Text>
<InfoPanel.Label>{t('Username')}</InfoPanel.Label>
<InfoPanel.Text data-qa='UserInfoUserName'>{username}</InfoPanel.Text>
</InfoPanel.Field>
)}

{nickname && (
{Number.isInteger(utcOffset) && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Nickname')}</InfoPanel.Label>
<InfoPanel.Text>{nickname}</InfoPanel.Text>
<InfoPanel.Label>{t('Local_Time')}</InfoPanel.Label>
<InfoPanel.Text>{utcOffset && <UTCClock utcOffset={utcOffset} />}</InfoPanel.Text>
</InfoPanel.Field>
)}

Expand All @@ -138,6 +134,13 @@ const UserInfo = ({
</InfoPanel.Field>
)}

{Number.isInteger(utcOffset) && canViewAllInfo && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Last_login')}</InfoPanel.Label>
<InfoPanel.Text>{lastLogin ? timeAgo(lastLogin) : t('Never')}</InfoPanel.Text>
</InfoPanel.Field>
)}

{phone && (
<InfoPanel.Field>
<InfoPanel.Label>{t('Phone')}</InfoPanel.Label>
Expand Down
63 changes: 63 additions & 0 deletions apps/meteor/client/hooks/useLicenseLimitsByBehavior.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { LicenseBehavior, LicenseLimitKind } from '@rocket.chat/core-typings';
import { validateWarnLimit } from '@rocket.chat/license/src/validation/validateLimit';

import { useLicense } from './useLicense';

type LicenseLimitsByBehavior = Record<LicenseBehavior, LicenseLimitKind[]>;

export const useLicenseLimitsByBehavior = () => {
const result = useLicense({ loadValues: true });

if (result.isLoading || result.isError) {
return null;
}

const { license, limits } = result.data;

if (!license || !limits) {
return null;
}

const keyLimits = Object.keys(limits) as Array<keyof typeof limits>;

// Get the rule with the highest limit that applies to this key
const rules = keyLimits
.map((key) => {
const rule = license.limits[key]
?.filter((limit) => validateWarnLimit(limit.max, limits[key].value ?? 0, limit.behavior))
.reduce<{ max: number; behavior: LicenseBehavior } | null>(
(maxLimit, currentLimit) => (!maxLimit || currentLimit.max > maxLimit.max ? currentLimit : maxLimit),
null,
);

if (!rule) {
return undefined;
}

if (rule.max === 0) {
return undefined;
}

if (rule.max === -1) {
return undefined;
}

return [key, rule.behavior];
})
.filter(Boolean) as Array<[keyof typeof limits, LicenseBehavior]>;

if (!rules.length) {
return null;
}

// Group by behavior
return rules.reduce((acc, [key, behavior]) => {
if (!acc[behavior]) {
acc[behavior] = [];
}

acc[behavior].push(key);

return acc;
}, {} as LicenseLimitsByBehavior);
};
Original file line number Diff line number Diff line change
@@ -1,80 +1,33 @@
import type { LicenseBehavior } from '@rocket.chat/core-typings';
import type { LicenseInfo } from '@rocket.chat/core-typings';
import { Callout } from '@rocket.chat/fuselage';
import { validateWarnLimit } from '@rocket.chat/license/src/validation/validateLimit';
import { ExternalLink } from '@rocket.chat/ui-client';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';

import { useLicense } from '../../../hooks/useLicense';
import { useLicenseLimitsByBehavior } from '../../../hooks/useLicenseLimitsByBehavior';
import { useCheckoutUrl } from './hooks/useCheckoutUrl';

export const SubscriptionCalloutLimits = () => {
const manageSubscriptionUrl = useCheckoutUrl();

const { t } = useTranslation();
const result = useLicense({ loadValues: true });

if (result.isLoading || result.isError) {
return null;
}

const { license, limits } = result.data;
const licenseLimits = useLicenseLimitsByBehavior();

if (!license || !limits) {
if (!licenseLimits) {
return null;
}

const keyLimits = Object.keys(limits) as Array<keyof typeof limits>;

// Get the rule with the highest limit that applies to this key

const rules = keyLimits
.map((key) => {
const rule = license.limits[key]
?.filter((limit) => validateWarnLimit(limit.max, limits[key].value ?? 0, limit.behavior))
.sort((a, b) => b.max - a.max)[0];
const { prevent_action, disable_modules, invalidate_license, start_fair_policy } = licenseLimits;

if (!rule) {
return undefined;
}

if (rule.max === 0) {
return undefined;
}

if (rule.max === -1) {
return undefined;
}

return [key, rule.behavior];
})
.filter(Boolean) as Array<[keyof typeof limits, LicenseBehavior]>;

if (!rules.length) {
return null;
}

// Group by behavior
const groupedRules = rules.reduce((acc, [key, behavior]) => {
if (!acc[behavior]) {
acc[behavior] = [];
}

acc[behavior].push(key);

return acc;
}, {} as Record<LicenseBehavior, (keyof typeof limits)[]>);

const { prevent_action, disable_modules, invalidate_license, start_fair_policy } = groupedRules;

const map = (key: keyof typeof limits) => t(`subscription.callout.${key}`);
const toTranslationKey = (key: keyof LicenseInfo['limits']) => t(`subscription.callout.${key}`);

return (
<>
{start_fair_policy && (
<Callout type='warning' title={t('subscription.callout.servicesDisruptionsMayOccur')} m={8}>
<Trans i18nKey='subscription.callout.description.limitsReached' count={start_fair_policy.length}>
Your workspace reached the <>{{ val: start_fair_policy.map(map) }}</> limit.
Your workspace reached the <>{{ val: start_fair_policy.map(toTranslationKey) }}</> limit.
<ExternalLink
to={manageSubscriptionUrl({
target: 'callout',
Expand All @@ -88,10 +41,11 @@ export const SubscriptionCalloutLimits = () => {
</Trans>
</Callout>
)}

{prevent_action && (
<Callout type='danger' title={t('subscription.callout.servicesDisruptionsOccurring')} m={8}>
<Trans i18nKey='subscription.callout.description.limitsExceeded' count={prevent_action.length}>
Your workspace exceeded the <>{{ val: prevent_action.map(map) }}</> license limit.
Your workspace exceeded the <>{{ val: prevent_action.map(toTranslationKey) }}</> license limit.
<ExternalLink
to={manageSubscriptionUrl({
target: 'callout',
Expand All @@ -109,7 +63,7 @@ export const SubscriptionCalloutLimits = () => {
{disable_modules && (
<Callout type='danger' title={t('subscription.callout.capabilitiesDisabled')} m={8}>
<Trans i18nKey='subscription.callout.description.limitsExceeded' count={disable_modules.length}>
Your workspace exceeded the <>{{ val: disable_modules.map(map) }}</> license limit.
Your workspace exceeded the <>{{ val: disable_modules.map(toTranslationKey) }}</> license limit.
<ExternalLink
to={manageSubscriptionUrl({
target: 'callout',
Expand All @@ -127,7 +81,7 @@ export const SubscriptionCalloutLimits = () => {
{invalidate_license && (
<Callout type='danger' title={t('subscription.callout.allPremiumCapabilitiesDisabled')} m={8}>
<Trans i18nKey='subscription.callout.description.limitsExceeded' count={disable_modules.length}>
Your workspace exceeded the <>{{ val: invalidate_license.map(map) }}</> license limit.
Your workspace exceeded the <>{{ val: invalidate_license.map(toTranslationKey) }}</> license limit.
<ExternalLink
to={manageSubscriptionUrl({
target: 'callout',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { isUserFederated } from '@rocket.chat/core-typings';
import type { IUser } from '@rocket.chat/core-typings';
import { Callout } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
Expand Down Expand Up @@ -69,6 +68,7 @@ const AdminUserInfoWithData = ({ uid, onReload }: AdminUserInfoWithDataProps): R
lastLogin,
nickname,
canViewAllInfo,
reason,
} = data.user;

return {
Expand All @@ -91,6 +91,7 @@ const AdminUserInfoWithData = ({ uid, onReload }: AdminUserInfoWithDataProps): R
status: <UserStatus status={status} />,
statusText,
nickname,
reason,
};
}, [approveManuallyUsers, data, getRoles]);

Expand Down Expand Up @@ -119,7 +120,7 @@ const AdminUserInfoWithData = ({ uid, onReload }: AdminUserInfoWithDataProps): R
isAdmin={data?.user.roles?.includes('admin')}
userId={data?.user._id}
username={user.username}
isFederatedUser={isUserFederated(data?.user as unknown as IUser)}
isFederatedUser={!!data.user.federated}
onChange={onChange}
onReload={onReload}
/>
Expand Down
Loading
Loading