Skip to content

Commit

Permalink
refactor: move tenant information to url (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
HazimAr authored Nov 17, 2024
1 parent 8efe9a3 commit fd2fc55
Show file tree
Hide file tree
Showing 27 changed files with 321 additions and 191 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ The following environment variables are required to run the UserTask UI:
- `KEYCLOAK_CLIENT_ID` Keycloack client id
- `KEYCLOAK_CLIENT_SECRET` Keycloack client secret
- `LHUT_API_URL` User-Task proxy Url
- `LHUT_TENANT_ID` LH Tenant

## Prerequisites

Expand Down Expand Up @@ -64,6 +63,5 @@ docker run --name lh-user-tasks-ui -p 3000:3000 --rm -it \
--env KEYCLOAK_CLIENT_ID='user-tasks-client' \
--env KEYCLOAK_CLIENT_SECRET=' ' \
--env LHUT_API_URL='http://localhost:8089' \
--env LHUT_TENANT_ID='default' \
littlehorse/lh-user-tasks-ui:latest
```
5 changes: 0 additions & 5 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,4 @@ if [ ! "${LHUT_API_URL+x}" ]; then
exit 1
fi

if [ ! "${LHUT_TENANT_ID+x}" ]; then
echo "Provide the LHUT_TENANT_ID env variable"
exit 1
fi

node ui/server.js
1 change: 0 additions & 1 deletion ui/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ KEYCLOAK_CLIENT_ID=""
KEYCLOAK_CLIENT_SECRET=""

LHUT_API_URL=""
LHUT_TENANT_ID=""
1 change: 0 additions & 1 deletion ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,5 @@ docker run --name lh-user-tasks-ui -p 3000:3000 --rm \
--env KEYCLOAK_CLIENT_ID='user-tasks-client' \
--env KEYCLOAK_CLIENT_SECRET=' ' \
--env LHUT_API_URL='http://localhost:8089' \
--env LHUT_TENANT_ID='default' \
littlehorse/lh-user-tasks-ui:latest
```
71 changes: 71 additions & 0 deletions ui/src/app/[tenantId]/actions/admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use server";
import { clientWithErrorHandling } from "@/lib/client";
import {
ListUserTaskDefNamesRequest,
ListUserTasksRequest,
UserTask,
UserTaskResult,
} from "@littlehorse-enterprises/user-tasks-api-client";

export async function adminCancelUserTask(
tenantId: string,
userTask: UserTask,
) {
return clientWithErrorHandling(tenantId, (client) =>
client.adminCancelUserTask(userTask),
);
}

export async function adminAssignUserTask(
tenantId: string,
userTask: UserTask,
assignment: { userId?: string; userGroupId?: string },
) {
return clientWithErrorHandling(tenantId, (client) =>
client.adminAssignUserTask(userTask, assignment),
);
}

export async function adminListUsers(tenantId: string) {
return clientWithErrorHandling(tenantId, (client) => client.adminListUsers());
}

export async function adminListUserGroups(tenantId: string) {
return clientWithErrorHandling(tenantId, (client) =>
client.adminListUserGroups(),
);
}

export async function adminListUserTaskDefNames(
tenantId: string,
search: ListUserTaskDefNamesRequest,
) {
return clientWithErrorHandling(tenantId, (client) =>
client.adminListUserTaskDefNames(search),
);
}

export async function adminListUserTasks(
tenantId: string,
search: ListUserTasksRequest,
) {
return clientWithErrorHandling(tenantId, (client) =>
client.adminListUserTasks(search),
);
}

export async function adminGetUserTask(tenantId: string, userTask: UserTask) {
return clientWithErrorHandling(tenantId, (client) =>
client.adminGetUserTask(userTask),
);
}

export async function adminCompleteUserTask(
tenantId: string,
userTask: UserTask,
values: UserTaskResult,
) {
return clientWithErrorHandling(tenantId, (client) =>
client.adminCompleteUserTask(userTask, values),
);
}
48 changes: 48 additions & 0 deletions ui/src/app/[tenantId]/actions/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use server";
import { clientWithErrorHandling } from "@/lib/client";
import {
ListUserTasksRequest,
UserTask,
UserTaskResult,
} from "@littlehorse-enterprises/user-tasks-api-client";

export async function claimUserTask(tenantId: string, userTask: UserTask) {
return clientWithErrorHandling(tenantId, (client) =>
client.claimUserTask(userTask),
);
}

export async function cancelUserTask(tenantId: string, userTask: UserTask) {
return clientWithErrorHandling(tenantId, (client) =>
client.cancelUserTask(userTask),
);
}

export async function listUserTasks(
tenantId: string,
search: Omit<ListUserTasksRequest, "type">,
) {
return clientWithErrorHandling(tenantId, (client) =>
client.listUserTasks(search),
);
}

export async function listUserGroups(tenantId: string) {
return clientWithErrorHandling(tenantId, (client) => client.listUserGroups());
}

export async function getUserTask(tenantId: string, userTask: UserTask) {
return clientWithErrorHandling(tenantId, (client) =>
client.getUserTask(userTask),
);
}

export async function completeUserTask(
tenantId: string,
userTask: UserTask,
values: UserTaskResult,
) {
return clientWithErrorHandling(tenantId, (client) =>
client.completeUserTask(userTask, values),
);
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { adminListUserGroups, adminListUserTasks } from "@/app/actions/admin";
import ListUserTasks from "@/app/components/user-task/list";
import {
adminListUserGroups,
adminListUserTasks,
} from "@/app/[tenantId]/actions/admin";
import { ArrowLeftIcon } from "@radix-ui/react-icons";
import Link from "next/link";
import ListUserTasks from "../../components/user-task/list";

export default async function TaskPage({
params,
}: {
params: { userTaskDefName: string };
params: { tenantId: string; userTaskDefName: string };
}) {
const adminListUserGroupsResponse = await adminListUserGroups();
const adminListUserGroupsResponse = await adminListUserGroups(
params.tenantId,
);
if ("message" in adminListUserGroupsResponse)
throw new Error(adminListUserGroupsResponse.message);

const adminListUserTasksResponse = await adminListUserTasks({
const adminListUserTasksResponse = await adminListUserTasks(params.tenantId, {
limit: 10,
type: params.userTaskDefName,
});
Expand All @@ -22,7 +27,7 @@ export default async function TaskPage({
return (
<div>
<Link
href="/admin"
href={`/${params.tenantId}/admin`}
className="mb-2 text-sm text-blue-500 flex items-center gap-2"
>
<ArrowLeftIcon className="size-4" />
Expand Down
37 changes: 23 additions & 14 deletions ui/src/app/admin/page.tsx → ui/src/app/[tenantId]/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import Link from "next/link";
import { adminListUserTaskDefNames } from "../actions/admin";

export default async function AdminPage() {
const adminListUserTaskDefNamesResponse = await adminListUserTaskDefNames({
// TODO: add pagination so this needs to be on client using `useInfiniteQuery`
limit: 100,
});
export default async function AdminPage({
params,
}: {
params: { tenantId: string };
}) {
const adminListUserTaskDefNamesResponse = await adminListUserTaskDefNames(
params.tenantId,
{
// TODO: add pagination so this needs to be on client using `useInfiniteQuery`
limit: 100,
},
);
if ("message" in adminListUserTaskDefNamesResponse)
throw new Error(adminListUserTaskDefNamesResponse.message);

Expand All @@ -24,15 +31,17 @@ export default async function AdminPage() {
return (
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-bold">View UserTask Definitions</h1>
{adminListUserTaskDefNamesResponse.userTaskDefNames.map((taskDefName) => (
<Link
key={taskDefName}
href={`/admin/${taskDefName}`}
className="underline"
>
{taskDefName}
</Link>
))}
{adminListUserTaskDefNamesResponse.userTaskDefNames.map(
(userTaskDefName) => (
<a
key={userTaskDefName}
href={`/${params.tenantId}/admin/${userTaskDefName}`}
className="underline"
>
{userTaskDefName}
</a>
),
)}
</div>
);
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";
import logo from "@/../public/images/logo.png";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -16,14 +15,17 @@ import { signOut, useSession } from "next-auth/react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useTenantId } from "../layout";

export default function Header() {
const session = useSession();
const pathname = usePathname();
const tenantId = useTenantId();

return (
<>
{session.data?.roles.includes("lh-user-tasks-admin") &&
pathname == "/" && (
pathname == `/${tenantId}` && (
<p className="text-sm text-destructive-foreground bg-destructive text-center py-2">
Viewing as User
</p>
Expand All @@ -33,18 +35,9 @@ export default function Header() {
<div>
<Image src={logo} alt="Logo" width={100} height={50} priority />
</div>
<h1 className="text-xl font-bold">LittleHorse UserTasks</h1>
</div>
<div className="flex-1 flex justify-end gap-2 items-center">
{session.data?.roles.includes("lh-user-tasks-admin") &&
(pathname.startsWith("/admin") ? (
<Button variant="ghost" asChild>
<Link href="/">View As User</Link>
</Button>
) : (
<Button variant="destructive" asChild>
<Link href="/admin">Back to Admin View</Link>
</Button>
))}
{session.status == "loading" && (
<Skeleton className="size-10 rounded-full" />
)}
Expand All @@ -59,8 +52,27 @@ export default function Header() {
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>{session.data.user.name}</DropdownMenuLabel>
<DropdownMenuLabel className="text-sm text-muted-foreground">
Tenant: {tenantId}
</DropdownMenuLabel>

<DropdownMenuSeparator />
{session.data?.roles.includes("lh-user-tasks-admin") && (
<>
{pathname.startsWith(`/${tenantId}/admin`) ? (
<DropdownMenuItem asChild>
<Link href={`/${tenantId}`}>View As User</Link>
</DropdownMenuItem>
) : (
<DropdownMenuItem asChild className="text-destructive">
<Link href={`/${tenantId}/admin`}>
Back to Admin View
</Link>
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItem
onClick={() => {
signOut();
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
adminAssignUserTask,
adminListUserGroups,
adminListUsers,
} from "@/app/actions/admin";
} from "@/app/[tenantId]/actions/admin";
import { useTenantId } from "@/app/[tenantId]/layout";
import { Button, buttonVariants } from "@/components/ui/button";
import {
Dialog,
Expand Down Expand Up @@ -35,6 +36,7 @@ export default function AssignUserTaskButton({
}: {
userTask: UserTask;
}) {
const tenantId = useTenantId();
const [users, setUsers] = useState<User[]>([]);
const [selectedUser, setSelectedUser] = useState<User | undefined>(
userTask.user,
Expand All @@ -47,7 +49,7 @@ export default function AssignUserTaskButton({

useEffect(() => {
// TODO: data from server
adminListUsers()
adminListUsers(tenantId)
.then((data) => {
if ("message" in data) {
toast.error(data.message);
Expand All @@ -60,7 +62,7 @@ export default function AssignUserTaskButton({
console.error(e);
});

adminListUserGroups()
adminListUserGroups(tenantId)
.then((data) => {
if ("message" in data) {
toast.error(data.message);
Expand Down Expand Up @@ -168,7 +170,7 @@ export default function AssignUserTaskButton({
);

try {
const response = await adminAssignUserTask(userTask, {
const response = await adminAssignUserTask(tenantId, userTask, {
userId: selectedUser?.id,
userGroupId: selectedUserGroup?.id,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { adminCancelUserTask } from "@/app/actions/admin";
import { cancelUserTask } from "@/app/actions/user";
import { adminCancelUserTask } from "@/app/[tenantId]/actions/admin";
import { cancelUserTask } from "@/app/[tenantId]/actions/user";
import { useTenantId } from "@/app/[tenantId]/layout";
import {
AlertDialog,
AlertDialogAction,
Expand All @@ -23,6 +24,8 @@ export default function CancelUserTaskButton({
userTask: UserTask;
admin?: boolean;
}) {
const tenantId = useTenantId();

return (
<AlertDialog>
<AlertDialogTrigger asChild>
Expand All @@ -45,8 +48,8 @@ export default function CancelUserTaskButton({
onClick={async () => {
try {
const response = admin
? await adminCancelUserTask(userTask)
: await cancelUserTask(userTask);
? await adminCancelUserTask(tenantId, userTask)
: await cancelUserTask(tenantId, userTask);

if (response && "message" in response)
return toast.error(response.message);
Expand Down
Loading

0 comments on commit fd2fc55

Please sign in to comment.