Skip to content

feat: add "on this page" to empty table message when you're past page 1 #4886

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 9 commits into from
Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion site/src/components/Conditionals/ChooseOne.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const ChooseOne = ({
}
if (conditionedOptions.some((cond) => cond.props.condition === undefined)) {
throw new Error(
"A non-final Cond in a ChooseOne does not have a condition prop.",
"A non-final Cond in a ChooseOne does not have a condition prop or the prop is undefined.",
)
}
const chosen = conditionedOptions.find((child) => child.props.condition)
Expand Down
6 changes: 6 additions & 0 deletions site/src/components/PaginationWidget/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,9 @@ export const createPaginationRef = (
): PaginationMachineRef => {
return spawn(paginationMachine.withContext(context))
}

export const nonInitialPage = (searchParams: URLSearchParams): boolean => {
const page = searchParams.get("page")
const numberPage = page ? Number(page) : 1
return numberPage > 1
}
15 changes: 10 additions & 5 deletions site/src/components/UsersTable/UsersTable.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ComponentMeta, Story } from "@storybook/react"
import {
MockSiteRoles,
MockAssignableSiteRoles,
MockUser,
MockUser2,
} from "../../testHelpers/renderHelpers"
Expand All @@ -9,34 +9,39 @@ import { UsersTable, UsersTableProps } from "./UsersTable"
export default {
title: "components/UsersTable",
component: UsersTable,
argTypes: {
isNonInitialPage: {
defaultValue: false,
},
},
} as ComponentMeta<typeof UsersTable>

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

export const Example = Template.bind({})
Example.args = {
users: [MockUser, MockUser2],
roles: MockSiteRoles,
roles: MockAssignableSiteRoles,
canEditUsers: false,
}

export const Editable = Template.bind({})
Editable.args = {
users: [MockUser, MockUser2],
roles: MockSiteRoles,
roles: MockAssignableSiteRoles,
canEditUsers: true,
}

export const Empty = Template.bind({})
Empty.args = {
users: [],
roles: MockSiteRoles,
roles: MockAssignableSiteRoles,
}

export const Loading = Template.bind({})
Loading.args = {
users: [],
roles: MockSiteRoles,
roles: MockAssignableSiteRoles,
isLoading: true,
}
Loading.parameters = {
Expand Down
1 change: 1 addition & 0 deletions site/src/components/UsersTable/UsersTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe("AuditPage", () => {
onActivateUser={() => jest.fn()}
onResetUserPassword={() => jest.fn()}
onUpdateUserRoles={() => jest.fn()}
isNonInitialPage={false}
/>,
)

Expand Down
3 changes: 3 additions & 0 deletions site/src/components/UsersTable/UsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface UsersTableProps {
user: TypesGen.User,
roles: TypesGen.Role["name"][],
) => void
isNonInitialPage: boolean
}

export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
Expand All @@ -46,6 +47,7 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
isUpdatingUserRoles,
canEditUsers,
isLoading,
isNonInitialPage,
}) => {
return (
<TableContainer>
Expand Down Expand Up @@ -78,6 +80,7 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
onResetUserPassword={onResetUserPassword}
onSuspendUser={onSuspendUser}
onUpdateUserRoles={onUpdateUserRoles}
isNonInitialPage={isNonInitialPage}
/>
</TableBody>
</Table>
Expand Down
253 changes: 135 additions & 118 deletions site/src/components/UsersTable/UsersTableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import Box from "@material-ui/core/Box"
import { makeStyles } from "@material-ui/core/styles"
import TableCell from "@material-ui/core/TableCell"
import TableRow from "@material-ui/core/TableRow"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { LastUsed } from "components/LastUsed/LastUsed"
import { FC } from "react"
import { useTranslation } from "react-i18next"
import * as TypesGen from "../../api/typesGenerated"
import { combineClasses } from "../../util/combineClasses"
import { AvatarData } from "../AvatarData/AvatarData"
Expand All @@ -12,15 +14,6 @@ import { RoleSelect } from "../RoleSelect/RoleSelect"
import { TableLoader } from "../TableLoader/TableLoader"
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"

export const Language = {
emptyMessage: "No users found",
suspendMenuItem: "Suspend",
deleteMenuItem: "Delete",
listWorkspacesMenuItem: "View workspaces",
activateMenuItem: "Activate",
resetPasswordMenuItem: "Reset password",
}

interface UsersTableBodyProps {
users?: TypesGen.User[]
roles?: TypesGen.AssignableRoles[]
Expand All @@ -36,6 +29,7 @@ interface UsersTableBodyProps {
user: TypesGen.User,
roles: TypesGen.Role["name"][],
) => void
isNonInitialPage: boolean
}

export const UsersTableBody: FC<
Expand All @@ -52,121 +46,144 @@ export const UsersTableBody: FC<
isUpdatingUserRoles,
canEditUsers,
isLoading,
isNonInitialPage,
}) => {
const styles = useStyles()

if (isLoading) {
return <TableLoader />
}

if (!users || users.length === 0) {
return (
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={Language.emptyMessage} />
</Box>
</TableCell>
</TableRow>
)
}
const { t } = useTranslation("usersPage")

return (
<>
{users.map((user) => {
// When the user has no role we want to show they are a Member
const fallbackRole: TypesGen.Role = {
name: "member",
display_name: "Member",
}
const userRoles = user.roles.length === 0 ? [fallbackRole] : user.roles
<ChooseOne>
<Cond condition={Boolean(isLoading)}>
<TableLoader />
</Cond>
<Cond condition={!users || users.length === 0}>
<ChooseOne>
<Cond condition={isNonInitialPage}>
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={t("emptyPageMessage")} />
</Box>
</TableCell>
</TableRow>
</Cond>
<Cond>
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={t("emptyMessage")} />
</Box>
</TableCell>
</TableRow>
</Cond>
</ChooseOne>
</Cond>
<Cond>
<>
{users &&
users.map((user) => {
// When the user has no role we want to show they are a Member
const fallbackRole: TypesGen.Role = {
name: "member",
display_name: "Member",
}
const userRoles =
user.roles.length === 0 ? [fallbackRole] : user.roles

return (
<TableRow key={user.id}>
<TableCell>
<AvatarData
title={user.username}
subtitle={user.email}
highlightTitle
avatar={
user.avatar_url ? (
<img
className={styles.avatar}
alt={`${user.username}'s Avatar`}
src={user.avatar_url}
return (
<TableRow key={user.id}>
<TableCell>
<AvatarData
title={user.username}
subtitle={user.email}
highlightTitle
avatar={
user.avatar_url ? (
<img
className={styles.avatar}
alt={`${user.username}'s Avatar`}
src={user.avatar_url}
/>
) : null
}
/>
) : null
}
/>
</TableCell>
<TableCell
className={combineClasses([
styles.status,
user.status === "suspended" ? styles.suspended : undefined,
])}
>
{user.status}
</TableCell>
<TableCell>
<LastUsed lastUsedAt={user.last_seen_at} />
</TableCell>
<TableCell>
{canEditUsers ? (
<RoleSelect
roles={roles ?? []}
selectedRoles={userRoles}
loading={isUpdatingUserRoles}
onChange={(roles) => {
// Remove the fallback role because it is only for the UI
roles = roles.filter((role) => role !== fallbackRole.name)
onUpdateUserRoles(user, roles)
}}
/>
) : (
<>{userRoles.map((role) => role.display_name).join(", ")}</>
)}
</TableCell>
{canEditUsers && (
<TableCell>
<TableRowMenu
data={user}
menuItems={
// Return either suspend or activate depending on status
(user.status === "active"
? [
{
label: Language.suspendMenuItem,
onClick: onSuspendUser,
},
]
: [
{
label: Language.activateMenuItem,
onClick: onActivateUser,
},
]
).concat(
{
label: Language.deleteMenuItem,
onClick: onDeleteUser,
},
{
label: Language.listWorkspacesMenuItem,
onClick: onListWorkspaces,
},
{
label: Language.resetPasswordMenuItem,
onClick: onResetUserPassword,
},
)
}
/>
</TableCell>
)}
</TableRow>
)
})}
</>
</TableCell>
<TableCell
className={combineClasses([
styles.status,
user.status === "suspended"
? styles.suspended
: undefined,
])}
>
{user.status}
</TableCell>
<TableCell>
<LastUsed lastUsedAt={user.last_seen_at} />
</TableCell>
<TableCell>
{canEditUsers ? (
<RoleSelect
roles={roles ?? []}
selectedRoles={userRoles}
loading={isUpdatingUserRoles}
onChange={(roles) => {
// Remove the fallback role because it is only for the UI
roles = roles.filter(
(role) => role !== fallbackRole.name,
)
onUpdateUserRoles(user, roles)
}}
/>
) : (
<>
{userRoles.map((role) => role.display_name).join(", ")}
</>
)}
</TableCell>
{canEditUsers && (
<TableCell>
<TableRowMenu
data={user}
menuItems={
// Return either suspend or activate depending on status
(user.status === "active"
? [
{
label: t("suspendMenuItem"),
onClick: onSuspendUser,
},
]
: [
{
label: t("activateMenuItem"),
onClick: onActivateUser,
},
]
).concat(
{
label: t("deleteMenuItem"),
onClick: onDeleteUser,
},
{
label: t("listWorkspacesMenuItem"),
onClick: onListWorkspaces,
},
{
label: t("resetPasswordMenuItem"),
onClick: onResetUserPassword,
},
)
}
/>
</TableCell>
)}
</TableRow>
)
})}
</>
</Cond>
</ChooseOne>
)
}

Expand Down
Loading