Skip to content

Commit f4925f9

Browse files
presleyppull[bot]
authored andcommitted
feat: add "on this page" to empty table message when you're past page 1 (#4886)
* Use empty page message on workspaces page * Add prop for story * AuditPage * UsersPage * Lint and format * Fix tests * Remove log * Try to fix story * Fix the right story
1 parent 28f54a5 commit f4925f9

23 files changed

+354
-221
lines changed

site/src/components/Conditionals/ChooseOne.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const ChooseOne = ({
4040
}
4141
if (conditionedOptions.some((cond) => cond.props.condition === undefined)) {
4242
throw new Error(
43-
"A non-final Cond in a ChooseOne does not have a condition prop.",
43+
"A non-final Cond in a ChooseOne does not have a condition prop or the prop is undefined.",
4444
)
4545
}
4646
const chosen = conditionedOptions.find((child) => child.props.condition)

site/src/components/PaginationWidget/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,9 @@ export const createPaginationRef = (
102102
): PaginationMachineRef => {
103103
return spawn(paginationMachine.withContext(context))
104104
}
105+
106+
export const nonInitialPage = (searchParams: URLSearchParams): boolean => {
107+
const page = searchParams.get("page")
108+
const numberPage = page ? Number(page) : 1
109+
return numberPage > 1
110+
}

site/src/components/UsersTable/UsersTable.stories.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ComponentMeta, Story } from "@storybook/react"
22
import {
3-
MockSiteRoles,
3+
MockAssignableSiteRoles,
44
MockUser,
55
MockUser2,
66
} from "../../testHelpers/renderHelpers"
@@ -9,34 +9,39 @@ import { UsersTable, UsersTableProps } from "./UsersTable"
99
export default {
1010
title: "components/UsersTable",
1111
component: UsersTable,
12+
argTypes: {
13+
isNonInitialPage: {
14+
defaultValue: false,
15+
},
16+
},
1217
} as ComponentMeta<typeof UsersTable>
1318

1419
const Template: Story<UsersTableProps> = (args) => <UsersTable {...args} />
1520

1621
export const Example = Template.bind({})
1722
Example.args = {
1823
users: [MockUser, MockUser2],
19-
roles: MockSiteRoles,
24+
roles: MockAssignableSiteRoles,
2025
canEditUsers: false,
2126
}
2227

2328
export const Editable = Template.bind({})
2429
Editable.args = {
2530
users: [MockUser, MockUser2],
26-
roles: MockSiteRoles,
31+
roles: MockAssignableSiteRoles,
2732
canEditUsers: true,
2833
}
2934

3035
export const Empty = Template.bind({})
3136
Empty.args = {
3237
users: [],
33-
roles: MockSiteRoles,
38+
roles: MockAssignableSiteRoles,
3439
}
3540

3641
export const Loading = Template.bind({})
3742
Loading.args = {
3843
users: [],
39-
roles: MockSiteRoles,
44+
roles: MockAssignableSiteRoles,
4045
isLoading: true,
4146
}
4247
Loading.parameters = {

site/src/components/UsersTable/UsersTable.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe("AuditPage", () => {
1515
onActivateUser={() => jest.fn()}
1616
onResetUserPassword={() => jest.fn()}
1717
onUpdateUserRoles={() => jest.fn()}
18+
isNonInitialPage={false}
1819
/>,
1920
)
2021

site/src/components/UsersTable/UsersTable.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface UsersTableProps {
3232
user: TypesGen.User,
3333
roles: TypesGen.Role["name"][],
3434
) => void
35+
isNonInitialPage: boolean
3536
}
3637

3738
export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
@@ -46,6 +47,7 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
4647
isUpdatingUserRoles,
4748
canEditUsers,
4849
isLoading,
50+
isNonInitialPage,
4951
}) => {
5052
return (
5153
<TableContainer>
@@ -78,6 +80,7 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
7880
onResetUserPassword={onResetUserPassword}
7981
onSuspendUser={onSuspendUser}
8082
onUpdateUserRoles={onUpdateUserRoles}
83+
isNonInitialPage={isNonInitialPage}
8184
/>
8285
</TableBody>
8386
</Table>

site/src/components/UsersTable/UsersTableBody.tsx

Lines changed: 135 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import Box from "@material-ui/core/Box"
22
import { makeStyles } from "@material-ui/core/styles"
33
import TableCell from "@material-ui/core/TableCell"
44
import TableRow from "@material-ui/core/TableRow"
5+
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
56
import { LastUsed } from "components/LastUsed/LastUsed"
67
import { FC } from "react"
8+
import { useTranslation } from "react-i18next"
79
import * as TypesGen from "../../api/typesGenerated"
810
import { combineClasses } from "../../util/combineClasses"
911
import { AvatarData } from "../AvatarData/AvatarData"
@@ -12,15 +14,6 @@ import { RoleSelect } from "../RoleSelect/RoleSelect"
1214
import { TableLoader } from "../TableLoader/TableLoader"
1315
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
1416

15-
export const Language = {
16-
emptyMessage: "No users found",
17-
suspendMenuItem: "Suspend",
18-
deleteMenuItem: "Delete",
19-
listWorkspacesMenuItem: "View workspaces",
20-
activateMenuItem: "Activate",
21-
resetPasswordMenuItem: "Reset password",
22-
}
23-
2417
interface UsersTableBodyProps {
2518
users?: TypesGen.User[]
2619
roles?: TypesGen.AssignableRoles[]
@@ -36,6 +29,7 @@ interface UsersTableBodyProps {
3629
user: TypesGen.User,
3730
roles: TypesGen.Role["name"][],
3831
) => void
32+
isNonInitialPage: boolean
3933
}
4034

4135
export const UsersTableBody: FC<
@@ -52,121 +46,144 @@ export const UsersTableBody: FC<
5246
isUpdatingUserRoles,
5347
canEditUsers,
5448
isLoading,
49+
isNonInitialPage,
5550
}) => {
5651
const styles = useStyles()
57-
58-
if (isLoading) {
59-
return <TableLoader />
60-
}
61-
62-
if (!users || users.length === 0) {
63-
return (
64-
<TableRow>
65-
<TableCell colSpan={999}>
66-
<Box p={4}>
67-
<EmptyState message={Language.emptyMessage} />
68-
</Box>
69-
</TableCell>
70-
</TableRow>
71-
)
72-
}
52+
const { t } = useTranslation("usersPage")
7353

7454
return (
75-
<>
76-
{users.map((user) => {
77-
// When the user has no role we want to show they are a Member
78-
const fallbackRole: TypesGen.Role = {
79-
name: "member",
80-
display_name: "Member",
81-
}
82-
const userRoles = user.roles.length === 0 ? [fallbackRole] : user.roles
55+
<ChooseOne>
56+
<Cond condition={Boolean(isLoading)}>
57+
<TableLoader />
58+
</Cond>
59+
<Cond condition={!users || users.length === 0}>
60+
<ChooseOne>
61+
<Cond condition={isNonInitialPage}>
62+
<TableRow>
63+
<TableCell colSpan={999}>
64+
<Box p={4}>
65+
<EmptyState message={t("emptyPageMessage")} />
66+
</Box>
67+
</TableCell>
68+
</TableRow>
69+
</Cond>
70+
<Cond>
71+
<TableRow>
72+
<TableCell colSpan={999}>
73+
<Box p={4}>
74+
<EmptyState message={t("emptyMessage")} />
75+
</Box>
76+
</TableCell>
77+
</TableRow>
78+
</Cond>
79+
</ChooseOne>
80+
</Cond>
81+
<Cond>
82+
<>
83+
{users &&
84+
users.map((user) => {
85+
// When the user has no role we want to show they are a Member
86+
const fallbackRole: TypesGen.Role = {
87+
name: "member",
88+
display_name: "Member",
89+
}
90+
const userRoles =
91+
user.roles.length === 0 ? [fallbackRole] : user.roles
8392

84-
return (
85-
<TableRow key={user.id}>
86-
<TableCell>
87-
<AvatarData
88-
title={user.username}
89-
subtitle={user.email}
90-
highlightTitle
91-
avatar={
92-
user.avatar_url ? (
93-
<img
94-
className={styles.avatar}
95-
alt={`${user.username}'s Avatar`}
96-
src={user.avatar_url}
93+
return (
94+
<TableRow key={user.id}>
95+
<TableCell>
96+
<AvatarData
97+
title={user.username}
98+
subtitle={user.email}
99+
highlightTitle
100+
avatar={
101+
user.avatar_url ? (
102+
<img
103+
className={styles.avatar}
104+
alt={`${user.username}'s Avatar`}
105+
src={user.avatar_url}
106+
/>
107+
) : null
108+
}
97109
/>
98-
) : null
99-
}
100-
/>
101-
</TableCell>
102-
<TableCell
103-
className={combineClasses([
104-
styles.status,
105-
user.status === "suspended" ? styles.suspended : undefined,
106-
])}
107-
>
108-
{user.status}
109-
</TableCell>
110-
<TableCell>
111-
<LastUsed lastUsedAt={user.last_seen_at} />
112-
</TableCell>
113-
<TableCell>
114-
{canEditUsers ? (
115-
<RoleSelect
116-
roles={roles ?? []}
117-
selectedRoles={userRoles}
118-
loading={isUpdatingUserRoles}
119-
onChange={(roles) => {
120-
// Remove the fallback role because it is only for the UI
121-
roles = roles.filter((role) => role !== fallbackRole.name)
122-
onUpdateUserRoles(user, roles)
123-
}}
124-
/>
125-
) : (
126-
<>{userRoles.map((role) => role.display_name).join(", ")}</>
127-
)}
128-
</TableCell>
129-
{canEditUsers && (
130-
<TableCell>
131-
<TableRowMenu
132-
data={user}
133-
menuItems={
134-
// Return either suspend or activate depending on status
135-
(user.status === "active"
136-
? [
137-
{
138-
label: Language.suspendMenuItem,
139-
onClick: onSuspendUser,
140-
},
141-
]
142-
: [
143-
{
144-
label: Language.activateMenuItem,
145-
onClick: onActivateUser,
146-
},
147-
]
148-
).concat(
149-
{
150-
label: Language.deleteMenuItem,
151-
onClick: onDeleteUser,
152-
},
153-
{
154-
label: Language.listWorkspacesMenuItem,
155-
onClick: onListWorkspaces,
156-
},
157-
{
158-
label: Language.resetPasswordMenuItem,
159-
onClick: onResetUserPassword,
160-
},
161-
)
162-
}
163-
/>
164-
</TableCell>
165-
)}
166-
</TableRow>
167-
)
168-
})}
169-
</>
110+
</TableCell>
111+
<TableCell
112+
className={combineClasses([
113+
styles.status,
114+
user.status === "suspended"
115+
? styles.suspended
116+
: undefined,
117+
])}
118+
>
119+
{user.status}
120+
</TableCell>
121+
<TableCell>
122+
<LastUsed lastUsedAt={user.last_seen_at} />
123+
</TableCell>
124+
<TableCell>
125+
{canEditUsers ? (
126+
<RoleSelect
127+
roles={roles ?? []}
128+
selectedRoles={userRoles}
129+
loading={isUpdatingUserRoles}
130+
onChange={(roles) => {
131+
// Remove the fallback role because it is only for the UI
132+
roles = roles.filter(
133+
(role) => role !== fallbackRole.name,
134+
)
135+
onUpdateUserRoles(user, roles)
136+
}}
137+
/>
138+
) : (
139+
<>
140+
{userRoles.map((role) => role.display_name).join(", ")}
141+
</>
142+
)}
143+
</TableCell>
144+
{canEditUsers && (
145+
<TableCell>
146+
<TableRowMenu
147+
data={user}
148+
menuItems={
149+
// Return either suspend or activate depending on status
150+
(user.status === "active"
151+
? [
152+
{
153+
label: t("suspendMenuItem"),
154+
onClick: onSuspendUser,
155+
},
156+
]
157+
: [
158+
{
159+
label: t("activateMenuItem"),
160+
onClick: onActivateUser,
161+
},
162+
]
163+
).concat(
164+
{
165+
label: t("deleteMenuItem"),
166+
onClick: onDeleteUser,
167+
},
168+
{
169+
label: t("listWorkspacesMenuItem"),
170+
onClick: onListWorkspaces,
171+
},
172+
{
173+
label: t("resetPasswordMenuItem"),
174+
onClick: onResetUserPassword,
175+
},
176+
)
177+
}
178+
/>
179+
</TableCell>
180+
)}
181+
</TableRow>
182+
)
183+
})}
184+
</>
185+
</Cond>
186+
</ChooseOne>
170187
)
171188
}
172189

0 commit comments

Comments
 (0)