Skip to content

Commit

Permalink
update user tables
Browse files Browse the repository at this point in the history
  • Loading branch information
pablonyx committed Dec 9, 2024
1 parent c193760 commit a9e15bf
Show file tree
Hide file tree
Showing 10 changed files with 464 additions and 200 deletions.
4 changes: 3 additions & 1 deletion backend/danswer/auth/invited_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ def get_invited_users() -> list[str]:
try:
store = get_kv_store()

return cast(list, store.load(KV_USER_STORE_KEY))
invited_users = cast(list, store.load(KV_USER_STORE_KEY))
print("INVITED USERS", invited_users)
return invited_users
except KvKeyNotFoundError:
return list()

Expand Down
2 changes: 2 additions & 0 deletions backend/danswer/server/manage/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,5 +266,7 @@ class FullModelVersionResponse(BaseModel):
class AllUsersResponse(BaseModel):
accepted: list[FullUserSnapshot]
invited: list[InvitedUserSnapshot]
slack_users: list[FullUserSnapshot]
accepted_pages: int
invited_pages: int
slack_users_pages: int
42 changes: 38 additions & 4 deletions backend/danswer/server/manage/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def set_user_role(
def list_all_users(
q: str | None = None,
accepted_page: int | None = None,
slack_users_page: int | None = None,
invited_page: int | None = None,
user: User | None = Depends(current_curator_or_admin_user),
db_session: Session = Depends(get_session),
Expand All @@ -131,18 +132,24 @@ def list_all_users(
for user in list_users(db_session, email_filter_string=q)
if not is_api_key_email_address(user.email)
]
accepted_emails = {user.email for user in users}

slack_users = [user for user in users if user.role == UserRole.SLACK_USER]
accepted_users = [user for user in users if user.role != UserRole.SLACK_USER]

accepted_emails = {user.email for user in accepted_users}
slack_users_emails = {user.email for user in slack_users}
invited_emails = get_invited_users()
if q:
invited_emails = [
email for email in invited_emails if re.search(r"{}".format(q), email, re.I)
]

accepted_count = len(accepted_emails)
slack_users_count = len(slack_users_emails)
invited_count = len(invited_emails)

# If any of q, accepted_page, or invited_page is None, return all users
if accepted_page is None or invited_page is None:
if accepted_page is None or invited_page is None or slack_users_page is None:
return AllUsersResponse(
accepted=[
FullUserSnapshot(
Expand All @@ -153,11 +160,23 @@ def list_all_users(
UserStatus.LIVE if user.is_active else UserStatus.DEACTIVATED
),
)
for user in users
for user in accepted_users
],
slack_users=[
FullUserSnapshot(
id=user.id,
email=user.email,
role=user.role,
status=(
UserStatus.LIVE if user.is_active else UserStatus.DEACTIVATED
),
)
for user in slack_users
],
invited=[InvitedUserSnapshot(email=email) for email in invited_emails],
accepted_pages=1,
invited_pages=1,
slack_users_pages=1,
)

# Otherwise, return paginated results
Expand All @@ -169,13 +188,27 @@ def list_all_users(
role=user.role,
status=UserStatus.LIVE if user.is_active else UserStatus.DEACTIVATED,
)
for user in users
for user in accepted_users
][accepted_page * USERS_PAGE_SIZE : (accepted_page + 1) * USERS_PAGE_SIZE],
slack_users=[
FullUserSnapshot(
id=user.id,
email=user.email,
role=user.role,
status=UserStatus.LIVE if user.is_active else UserStatus.DEACTIVATED,
)
for user in slack_users
][
slack_users_page
* USERS_PAGE_SIZE : (slack_users_page + 1)
* USERS_PAGE_SIZE
],
invited=[InvitedUserSnapshot(email=email) for email in invited_emails][
invited_page * USERS_PAGE_SIZE : (invited_page + 1) * USERS_PAGE_SIZE
],
accepted_pages=accepted_count // USERS_PAGE_SIZE + 1,
invited_pages=invited_count // USERS_PAGE_SIZE + 1,
slack_users_pages=slack_users_count // USERS_PAGE_SIZE + 1,
)


Expand All @@ -185,6 +218,7 @@ def bulk_invite_users(
current_user: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> int:
print("INVITING USERS", emails)
"""emails are string validated. If any email fails validation, no emails are
invited and an exception is raised."""

Expand Down
2 changes: 2 additions & 0 deletions backend/tests/integration/common_utils/managers/tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ def get_all_users(
return AllUsersResponse(
accepted=[FullUserSnapshot(**user) for user in data["accepted"]],
invited=[InvitedUserSnapshot(**user) for user in data["invited"]],
slack_users=[FullUserSnapshot(**user) for user in data["slack_users"]],
accepted_pages=data["accepted_pages"],
invited_pages=data["invited_pages"],
slack_users_pages=data["slack_users_pages"],
)

@staticmethod
Expand Down
2 changes: 2 additions & 0 deletions backend/tests/integration/common_utils/managers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,10 @@ def verify(
all_users = AllUsersResponse(
accepted=[FullUserSnapshot(**user) for user in data["accepted"]],
invited=[InvitedUserSnapshot(**user) for user in data["invited"]],
slack_users=[FullUserSnapshot(**user) for user in data["slack_users"]],
accepted_pages=data["accepted_pages"],
invited_pages=data["invited_pages"],
slack_users_pages=data["slack_users_pages"],
)
for accepted_user in all_users.accepted:
if accepted_user.email == user.email and accepted_user.id == user.id:
Expand Down
189 changes: 105 additions & 84 deletions web/src/app/admin/users/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
"use client";
import { Suspense, useState } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { UserPlus } from "lucide-react";
import InvitedUserTable from "@/components/admin/users/InvitedUserTable";
import SignedUpUserTable from "@/components/admin/users/SignedUpUserTable";
import { SearchBar } from "@/components/search/SearchBar";
import { useState } from "react";
import { FiPlusSquare } from "react-icons/fi";
import { Modal } from "@/components/Modal";

import { Button } from "@/components/ui/button";
import Text from "@/components/ui/text";
import { LoadingAnimation } from "@/components/Loading";
import { AdminPageTitle } from "@/components/admin/Title";
import { usePopup, PopupSpec } from "@/components/admin/connectors/Popup";
Expand All @@ -20,39 +30,7 @@ import BulkAdd from "@/components/admin/users/BulkAdd";
import { UsersResponse } from "@/lib/users/interfaces";
import { UserRole } from "@/lib/types";
import SlackUserTable from "@/components/admin/users/SlackUserTable";

const ValidDomainsDisplay = ({ validDomains }: { validDomains: string[] }) => {
if (!validDomains.length) {
return (
<div className="text-sm">
No invited users. Anyone can sign up with a valid email address. To
restrict access you can:
<div className="flex flex-wrap ml-2 mt-1">
(1) Invite users above. Once a user has been invited, only emails that
have explicitly been invited will be able to sign-up.
</div>
<div className="mt-1 ml-2">
(2) Set the{" "}
<b className="font-mono w-fit h-fit">VALID_EMAIL_DOMAINS</b>{" "}
environment variable to a comma separated list of email domains. This
will restrict access to users with email addresses from these domains.
</div>
</div>
);
}

return (
<div className="text-sm">
No invited users. Anyone with an email address with any of the following
domains can sign up: <i>{validDomains.join(", ")}</i>.
<div className="mt-2">
To further restrict access you can invite users above. Once a user has
been invited, only emails that have explicitly been invited will be able
to sign-up.
</div>
</div>
);
};
import Text from "@/components/ui/text";

const UsersTables = ({
q,
Expand All @@ -63,23 +41,29 @@ const UsersTables = ({
}) => {
const [invitedPage, setInvitedPage] = useState(1);
const [acceptedPage, setAcceptedPage] = useState(1);
const { data, isLoading, mutate, error } = useSWR<UsersResponse>(
`/api/manage/users?q=${encodeURI(q)}&accepted_page=${
const [slackUsersPage, setSlackUsersPage] = useState(1);

const { data, error, isValidating, mutate } = useSWR<UsersResponse>(
`/api/manage/users?q=${encodeURIComponent(q)}&accepted_page=${
acceptedPage - 1
}&invited_page=${invitedPage - 1}`,
}&invited_page=${invitedPage - 1}&slack_users_page=${slackUsersPage - 1}`,
errorHandlingFetcher
);

const {
data: validDomains,
isLoading: isLoadingDomains,
error: domainsError,
isValidating: isValidatingDomains,
} = useSWR<string[]>("/api/manage/admin/valid-domains", errorHandlingFetcher);

if (isLoading || isLoadingDomains) {
// Show loading animation only during the initial data fetch
if (!data) {
// console.log(!)
return <LoadingAnimation text="Loading" />;
}

if (error || !data) {
// Handle errors
if (error) {
return (
<ErrorCallout
errorTitle="Error loading users"
Expand All @@ -88,7 +72,7 @@ const UsersTables = ({
);
}

if (domainsError || !validDomains) {
if (domainsError) {
return (
<ErrorCallout
errorTitle="Error loading valid domains"
Expand All @@ -97,59 +81,95 @@ const UsersTables = ({
);
}

const { accepted, invited, accepted_pages, invited_pages } = data;
// Extract data
const {
accepted,
invited,
accepted_pages,
invited_pages,
slack_users,
slack_users_pages,
} = data;

// remove users that are already accepted
// Remove users that are already accepted
const finalInvited = invited.filter(
(user) => !accepted.map((u) => u.email).includes(user.email)
);
const slackUsers = accepted.filter(
(user) => user.role === UserRole.SLACK_USER
(user) => !accepted.some((u) => u.email === user.email)
);

return (
<>
<HidableSection sectionTitle="Invited Users">
{invited.length > 0 ? (
finalInvited.length > 0 ? (
<Tabs defaultValue="invited">
<TabsList>
<TabsTrigger value="invited">Invited Users</TabsTrigger>
<TabsTrigger value="current">Current Users</TabsTrigger>
<TabsTrigger value="danswerbot">DanswerBot Users</TabsTrigger>
</TabsList>

<TabsContent value="invited">
<Card>
<CardHeader>
<CardTitle>
Invited Users{" "}
{isValidating && <LoadingAnimation text="Loading" />}
</CardTitle>
</CardHeader>
<CardContent>
<InvitedUserTable
users={finalInvited}
setPopup={setPopup}
currentPage={invitedPage}
onPageChange={setInvitedPage}
totalPages={invited_pages}
mutate={mutate}
isLoading={isValidating}
/>
) : (
<div className="text-sm">
To invite additional teammates, use the <b>Invite Users</b> button
above!
</div>
)
) : (
<ValidDomainsDisplay validDomains={validDomains} />
)}
</HidableSection>

<HidableSection sectionTitle="Slack Users">
{slackUsers.length > 0 ? (
<SlackUserTable users={slackUsers} />
) : (
<div className="text-sm">
To invite additional teammates, use the <b>Invite Users</b> button
above!
</div>
)}
</HidableSection>
<SignedUpUserTable
users={accepted}
setPopup={setPopup}
currentPage={acceptedPage}
onPageChange={setAcceptedPage}
totalPages={accepted_pages}
mutate={mutate}
/>
</>
</CardContent>
</Card>
</TabsContent>

<TabsContent value="current">
<Card>
<CardHeader>
<CardTitle>Current Users</CardTitle>
</CardHeader>
<CardContent>
{accepted.length > 0 ? (
<SignedUpUserTable
users={accepted}
setPopup={setPopup}
currentPage={acceptedPage}
onPageChange={setAcceptedPage}
totalPages={accepted_pages}
mutate={mutate}
/>
) : (
<p>Users that have an account will show up here</p>
)}
</CardContent>
</Card>
</TabsContent>

<TabsContent value="danswerbot">
<Card>
<CardHeader>
<CardTitle>DanswerBot Users</CardTitle>
</CardHeader>
<CardContent>
{slack_users.length > 0 ? (
<SlackUserTable
currentPage={slackUsersPage}
onPageChange={setSlackUsersPage}
totalPages={slack_users_pages}
invitedUsers={finalInvited}
slackusers={slack_users}
mutate={mutate}
/>
) : (
<p>Slack-only users will show up here</p>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
);
};

Expand Down Expand Up @@ -231,6 +251,7 @@ const Page = () => {
return (
<div className="mx-auto container">
<AdminPageTitle title="Manage Users" icon={<UsersIcon size={32} />} />

<SearchableTables />
</div>
);
Expand Down
Loading

0 comments on commit a9e15bf

Please sign in to comment.