fix: show dormant and suspended users in groups (#10333)

* fix: show dormant and suspended users in groups

* added status column
This commit is contained in:
Kira Pilot
2023-10-20 11:36:00 -04:00
committed by GitHub
parent ac322724b0
commit ed5567ba28
7 changed files with 92 additions and 47 deletions
+1 -1
View File
@@ -1709,7 +1709,7 @@ func (q *FakeQuerier) GetGroupMembers(_ context.Context, id uuid.UUID) ([]databa
for _, member := range members {
for _, user := range q.users {
if user.ID == member.UserID && user.Status == database.UserStatusActive && !user.Deleted {
if user.ID == member.UserID && !user.Deleted {
users = append(users, user)
break
}
-2
View File
@@ -1276,8 +1276,6 @@ WHERE
(group_members.group_id = $1
OR
organization_members.organization_id = $1)
AND
users.status = 'active'
AND
users.deleted = 'false'
`
-2
View File
@@ -20,8 +20,6 @@ WHERE
(group_members.group_id = @group_id
OR
organization_members.organization_id = @group_id)
AND
users.status = 'active'
AND
users.deleted = 'false';
+25 -3
View File
@@ -648,7 +648,7 @@ func TestGroup(t *testing.T) {
require.NotContains(t, group.Members, user1)
})
t.Run("FilterSuspendedUsers", func(t *testing.T) {
t.Run("IncludeSuspendedAndDormantUsers", func(t *testing.T) {
t.Parallel()
client, user := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
@@ -679,8 +679,30 @@ func TestGroup(t *testing.T) {
group, err = client.Group(ctx, group.ID)
require.NoError(t, err)
require.Len(t, group.Members, 1)
require.NotContains(t, group.Members, user1)
require.Len(t, group.Members, 2)
require.Contains(t, group.Members, user1)
require.Contains(t, group.Members, user2)
// cannot explicitly set a dormant user status so must create a new user
anotherUser, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
Email: "coder@coder.com",
Username: "coder",
Password: "SomeStrongPassword!",
OrganizationID: user.OrganizationID,
})
require.NoError(t, err)
// Ensure that new user has dormant account
require.Equal(t, codersdk.UserStatusDormant, anotherUser.Status)
group, _ = client.PatchGroup(ctx, group.ID, codersdk.PatchGroupRequest{
AddUsers: []string{anotherUser.ID.String()},
})
group, err = client.Group(ctx, group.ID)
require.NoError(t, err)
require.Len(t, group.Members, 3)
require.Contains(t, group.Members, user1)
require.Contains(t, group.Members, user2)
})
+41
View File
@@ -0,0 +1,41 @@
import Box, { type BoxProps } from "@mui/material/Box";
import { useTheme } from "@emotion/react";
import dayjs from "dayjs";
export const LastSeen = ({
value,
...boxProps
}: { value: string } & BoxProps) => {
const theme = useTheme();
const t = dayjs(value);
const now = dayjs();
let message = t.fromNow();
let color = theme.palette.text.secondary;
if (t.isAfter(now.subtract(1, "hour"))) {
color = theme.palette.success.light;
// Since the agent reports on a 10m interval,
// the last_used_at can be inaccurate when recent.
message = "Now";
} else if (t.isAfter(now.subtract(3, "day"))) {
color = theme.palette.text.secondary;
} else if (t.isAfter(now.subtract(1, "month"))) {
color = theme.palette.warning.light;
} else if (t.isAfter(now.subtract(100, "year"))) {
color = theme.palette.error.light;
} else {
message = "Never";
}
return (
<Box
component="span"
data-chromatic="ignore"
{...boxProps}
sx={{ color, ...boxProps.sx }}
>
{message}
</Box>
);
};
+22 -2
View File
@@ -43,6 +43,9 @@ import {
} from "api/queries/groups";
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
import { getErrorMessage } from "api/errors";
import Box from "@mui/material/Box";
import { LastSeen } from "components/LastSeen/LastSeen";
import { type Interpolation, type Theme } from "@emotion/react";
import LoadingButton from "@mui/lab/LoadingButton";
export const GroupPage: FC = () => {
@@ -150,7 +153,8 @@ export const GroupPage: FC = () => {
<Table>
<TableHead>
<TableRow>
<TableCell width="99%">User</TableCell>
<TableCell width="59%">User</TableCell>
<TableCell width="40">Status</TableCell>
<TableCell width="1%"></TableCell>
</TableRow>
</TableHead>
@@ -259,7 +263,7 @@ const GroupMemberRow = (props: {
return (
<TableRow key={member.id}>
<TableCell width="99%">
<TableCell width="59%">
<AvatarData
avatar={
<UserAvatar
@@ -271,6 +275,13 @@ const GroupMemberRow = (props: {
subtitle={member.email}
/>
</TableCell>
<TableCell
width="40%"
css={[styles.status, member.status === "suspended" && styles.suspended]}
>
<Box>{member.status}</Box>
<LastSeen value={member.last_seen_at} sx={{ fontSize: 12 }} />
</TableCell>
<TableCell width="1%">
{canUpdate && (
<TableRowMenu
@@ -313,4 +324,13 @@ const useStyles = makeStyles((theme) => ({
},
}));
const styles = {
status: {
textTransform: "capitalize",
},
suspended: (theme) => ({
color: theme.palette.text.secondary,
}),
} satisfies Record<string, Interpolation<Theme>>;
export default GroupPage;
@@ -1,8 +1,8 @@
import Box, { type BoxProps } from "@mui/material/Box";
import Box from "@mui/material/Box";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import Skeleton from "@mui/material/Skeleton";
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import { type Interpolation, type Theme } from "@emotion/react";
import { type FC } from "react";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
@@ -22,6 +22,7 @@ import KeyOutlined from "@mui/icons-material/KeyOutlined";
import GitHub from "@mui/icons-material/GitHub";
import PasswordOutlined from "@mui/icons-material/PasswordOutlined";
import ShieldOutlined from "@mui/icons-material/ShieldOutlined";
import { LastSeen } from "components/LastSeen/LastSeen";
import { UserRoleCell } from "./UserRoleCell";
import { type GroupsByUserId } from "api/queries/groups";
import { UserGroupsCell } from "./UserGroupsCell";
@@ -273,41 +274,6 @@ const LoginType = ({
);
};
const LastSeen = ({ value, ...boxProps }: { value: string } & BoxProps) => {
const theme = useTheme();
const t = dayjs(value);
const now = dayjs();
let message = t.fromNow();
let color = theme.palette.text.secondary;
if (t.isAfter(now.subtract(1, "hour"))) {
color = theme.palette.success.light;
// Since the agent reports on a 10m interval,
// the last_used_at can be inaccurate when recent.
message = "Now";
} else if (t.isAfter(now.subtract(3, "day"))) {
color = theme.palette.text.secondary;
} else if (t.isAfter(now.subtract(1, "month"))) {
color = theme.palette.warning.light;
} else if (t.isAfter(now.subtract(100, "year"))) {
color = theme.palette.error.light;
} else {
message = "Never";
}
return (
<Box
component="span"
data-chromatic="ignore"
{...boxProps}
sx={{ color, ...boxProps.sx }}
>
{message}
</Box>
);
};
const styles = {
status: {
textTransform: "capitalize",