Skip to content

Commit ed5567b

Browse files
authored
fix: show dormant and suspended users in groups (#10333)
* fix: show dormant and suspended users in groups * added status column
1 parent ac32272 commit ed5567b

File tree

7 files changed

+92
-47
lines changed

7 files changed

+92
-47
lines changed

coderd/database/dbfake/dbfake.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1709,7 +1709,7 @@ func (q *FakeQuerier) GetGroupMembers(_ context.Context, id uuid.UUID) ([]databa
17091709

17101710
for _, member := range members {
17111711
for _, user := range q.users {
1712-
if user.ID == member.UserID && user.Status == database.UserStatusActive && !user.Deleted {
1712+
if user.ID == member.UserID && !user.Deleted {
17131713
users = append(users, user)
17141714
break
17151715
}

coderd/database/queries.sql.go

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/groupmembers.sql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ WHERE
2020
(group_members.group_id = @group_id
2121
OR
2222
organization_members.organization_id = @group_id)
23-
AND
24-
users.status = 'active'
2523
AND
2624
users.deleted = 'false';
2725

enterprise/coderd/groups_test.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ func TestGroup(t *testing.T) {
648648
require.NotContains(t, group.Members, user1)
649649
})
650650

651-
t.Run("FilterSuspendedUsers", func(t *testing.T) {
651+
t.Run("IncludeSuspendedAndDormantUsers", func(t *testing.T) {
652652
t.Parallel()
653653

654654
client, user := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
@@ -679,8 +679,30 @@ func TestGroup(t *testing.T) {
679679

680680
group, err = client.Group(ctx, group.ID)
681681
require.NoError(t, err)
682-
require.Len(t, group.Members, 1)
683-
require.NotContains(t, group.Members, user1)
682+
require.Len(t, group.Members, 2)
683+
require.Contains(t, group.Members, user1)
684+
require.Contains(t, group.Members, user2)
685+
686+
// cannot explicitly set a dormant user status so must create a new user
687+
anotherUser, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
688+
Email: "coder@coder.com",
689+
Username: "coder",
690+
Password: "SomeStrongPassword!",
691+
OrganizationID: user.OrganizationID,
692+
})
693+
require.NoError(t, err)
694+
695+
// Ensure that new user has dormant account
696+
require.Equal(t, codersdk.UserStatusDormant, anotherUser.Status)
697+
698+
group, _ = client.PatchGroup(ctx, group.ID, codersdk.PatchGroupRequest{
699+
AddUsers: []string{anotherUser.ID.String()},
700+
})
701+
702+
group, err = client.Group(ctx, group.ID)
703+
require.NoError(t, err)
704+
require.Len(t, group.Members, 3)
705+
require.Contains(t, group.Members, user1)
684706
require.Contains(t, group.Members, user2)
685707
})
686708

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Box, { type BoxProps } from "@mui/material/Box";
2+
import { useTheme } from "@emotion/react";
3+
import dayjs from "dayjs";
4+
5+
export const LastSeen = ({
6+
value,
7+
...boxProps
8+
}: { value: string } & BoxProps) => {
9+
const theme = useTheme();
10+
const t = dayjs(value);
11+
const now = dayjs();
12+
13+
let message = t.fromNow();
14+
let color = theme.palette.text.secondary;
15+
16+
if (t.isAfter(now.subtract(1, "hour"))) {
17+
color = theme.palette.success.light;
18+
// Since the agent reports on a 10m interval,
19+
// the last_used_at can be inaccurate when recent.
20+
message = "Now";
21+
} else if (t.isAfter(now.subtract(3, "day"))) {
22+
color = theme.palette.text.secondary;
23+
} else if (t.isAfter(now.subtract(1, "month"))) {
24+
color = theme.palette.warning.light;
25+
} else if (t.isAfter(now.subtract(100, "year"))) {
26+
color = theme.palette.error.light;
27+
} else {
28+
message = "Never";
29+
}
30+
31+
return (
32+
<Box
33+
component="span"
34+
data-chromatic="ignore"
35+
{...boxProps}
36+
sx={{ color, ...boxProps.sx }}
37+
>
38+
{message}
39+
</Box>
40+
);
41+
};

site/src/pages/GroupsPage/GroupPage.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ import {
4343
} from "api/queries/groups";
4444
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
4545
import { getErrorMessage } from "api/errors";
46+
import Box from "@mui/material/Box";
47+
import { LastSeen } from "components/LastSeen/LastSeen";
48+
import { type Interpolation, type Theme } from "@emotion/react";
4649
import LoadingButton from "@mui/lab/LoadingButton";
4750

4851
export const GroupPage: FC = () => {
@@ -150,7 +153,8 @@ export const GroupPage: FC = () => {
150153
<Table>
151154
<TableHead>
152155
<TableRow>
153-
<TableCell width="99%">User</TableCell>
156+
<TableCell width="59%">User</TableCell>
157+
<TableCell width="40">Status</TableCell>
154158
<TableCell width="1%"></TableCell>
155159
</TableRow>
156160
</TableHead>
@@ -259,7 +263,7 @@ const GroupMemberRow = (props: {
259263

260264
return (
261265
<TableRow key={member.id}>
262-
<TableCell width="99%">
266+
<TableCell width="59%">
263267
<AvatarData
264268
avatar={
265269
<UserAvatar
@@ -271,6 +275,13 @@ const GroupMemberRow = (props: {
271275
subtitle={member.email}
272276
/>
273277
</TableCell>
278+
<TableCell
279+
width="40%"
280+
css={[styles.status, member.status === "suspended" && styles.suspended]}
281+
>
282+
<Box>{member.status}</Box>
283+
<LastSeen value={member.last_seen_at} sx={{ fontSize: 12 }} />
284+
</TableCell>
274285
<TableCell width="1%">
275286
{canUpdate && (
276287
<TableRowMenu
@@ -313,4 +324,13 @@ const useStyles = makeStyles((theme) => ({
313324
},
314325
}));
315326

327+
const styles = {
328+
status: {
329+
textTransform: "capitalize",
330+
},
331+
suspended: (theme) => ({
332+
color: theme.palette.text.secondary,
333+
}),
334+
} satisfies Record<string, Interpolation<Theme>>;
335+
316336
export default GroupPage;

site/src/pages/UsersPage/UsersTable/UsersTableBody.tsx

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import Box, { type BoxProps } from "@mui/material/Box";
1+
import Box from "@mui/material/Box";
22
import TableCell from "@mui/material/TableCell";
33
import TableRow from "@mui/material/TableRow";
44
import Skeleton from "@mui/material/Skeleton";
5-
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
5+
import { type Interpolation, type Theme } from "@emotion/react";
66
import { type FC } from "react";
77
import dayjs from "dayjs";
88
import relativeTime from "dayjs/plugin/relativeTime";
@@ -22,6 +22,7 @@ import KeyOutlined from "@mui/icons-material/KeyOutlined";
2222
import GitHub from "@mui/icons-material/GitHub";
2323
import PasswordOutlined from "@mui/icons-material/PasswordOutlined";
2424
import ShieldOutlined from "@mui/icons-material/ShieldOutlined";
25+
import { LastSeen } from "components/LastSeen/LastSeen";
2526
import { UserRoleCell } from "./UserRoleCell";
2627
import { type GroupsByUserId } from "api/queries/groups";
2728
import { UserGroupsCell } from "./UserGroupsCell";
@@ -273,41 +274,6 @@ const LoginType = ({
273274
);
274275
};
275276

276-
const LastSeen = ({ value, ...boxProps }: { value: string } & BoxProps) => {
277-
const theme = useTheme();
278-
const t = dayjs(value);
279-
const now = dayjs();
280-
281-
let message = t.fromNow();
282-
let color = theme.palette.text.secondary;
283-
284-
if (t.isAfter(now.subtract(1, "hour"))) {
285-
color = theme.palette.success.light;
286-
// Since the agent reports on a 10m interval,
287-
// the last_used_at can be inaccurate when recent.
288-
message = "Now";
289-
} else if (t.isAfter(now.subtract(3, "day"))) {
290-
color = theme.palette.text.secondary;
291-
} else if (t.isAfter(now.subtract(1, "month"))) {
292-
color = theme.palette.warning.light;
293-
} else if (t.isAfter(now.subtract(100, "year"))) {
294-
color = theme.palette.error.light;
295-
} else {
296-
message = "Never";
297-
}
298-
299-
return (
300-
<Box
301-
component="span"
302-
data-chromatic="ignore"
303-
{...boxProps}
304-
sx={{ color, ...boxProps.sx }}
305-
>
306-
{message}
307-
</Box>
308-
);
309-
};
310-
311277
const styles = {
312278
status: {
313279
textTransform: "capitalize",

0 commit comments

Comments
 (0)