Rename Organization to Project
We'll probably need a concept of organizations at some point in the future, but in practice the way we're using these in the codebase right now is as a project, so this renames it to that to avoid confusion.
This commit is contained in:
@@ -72,12 +72,12 @@ const CountLabel = ({ label, count }: { label: string; count: number }) => {
|
||||
|
||||
export const NewDatasetCard = () => {
|
||||
const router = useRouter();
|
||||
const selectedOrgId = useAppStore((s) => s.selectedOrgId);
|
||||
const selectedProjectId = useAppStore((s) => s.selectedProjectId);
|
||||
const createMutation = api.datasets.create.useMutation();
|
||||
const [createDataset, isLoading] = useHandledAsyncCallback(async () => {
|
||||
const newDataset = await createMutation.mutateAsync({ organizationId: selectedOrgId ?? "" });
|
||||
const newDataset = await createMutation.mutateAsync({ projectId: selectedProjectId ?? "" });
|
||||
await router.push({ pathname: "/data/[id]", query: { id: newDataset.id } });
|
||||
}, [createMutation, router, selectedOrgId]);
|
||||
}, [createMutation, router, selectedProjectId]);
|
||||
|
||||
return (
|
||||
<AspectRatio ratio={1.2} w="full">
|
||||
|
||||
@@ -76,17 +76,17 @@ const CountLabel = ({ label, count }: { label: string; count: number }) => {
|
||||
|
||||
export const NewExperimentCard = () => {
|
||||
const router = useRouter();
|
||||
const selectedOrgId = useAppStore((s) => s.selectedOrgId);
|
||||
const selectedProjectId = useAppStore((s) => s.selectedProjectId);
|
||||
const createMutation = api.experiments.create.useMutation();
|
||||
const [createExperiment, isLoading] = useHandledAsyncCallback(async () => {
|
||||
const newExperiment = await createMutation.mutateAsync({
|
||||
organizationId: selectedOrgId ?? "",
|
||||
projectId: selectedProjectId ?? "",
|
||||
});
|
||||
await router.push({
|
||||
pathname: "/experiments/[id]",
|
||||
query: { id: newExperiment.id },
|
||||
});
|
||||
}, [createMutation, router, selectedOrgId]);
|
||||
}, [createMutation, router, selectedProjectId]);
|
||||
|
||||
return (
|
||||
<AspectRatio ratio={1.2} w="full">
|
||||
|
||||
@@ -10,15 +10,15 @@ export const useOnForkButtonPressed = () => {
|
||||
|
||||
const user = useSession().data;
|
||||
const experiment = useExperiment();
|
||||
const selectedOrgId = useAppStore((state) => state.selectedOrgId);
|
||||
const selectedProjectId = useAppStore((state) => state.selectedProjectId);
|
||||
|
||||
const forkMutation = api.experiments.fork.useMutation();
|
||||
|
||||
const [onFork, isForking] = useHandledAsyncCallback(async () => {
|
||||
if (!experiment.data?.id || !selectedOrgId) return;
|
||||
if (!experiment.data?.id || !selectedProjectId) return;
|
||||
const forkedExperimentId = await forkMutation.mutateAsync({
|
||||
id: experiment.data.id,
|
||||
organizationId: selectedOrgId,
|
||||
projectId: selectedProjectId,
|
||||
});
|
||||
await router.push({ pathname: "/experiments/[id]", query: { id: forkedExperimentId } });
|
||||
}, [forkMutation, experiment.data?.id, router]);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { HStack, Flex, Text } from "@chakra-ui/react";
|
||||
import { useSelectedOrg } from "~/utils/hooks";
|
||||
import { useSelectedProject } from "~/utils/hooks";
|
||||
|
||||
// Have to export only contents here instead of full BreadcrumbItem because Chakra doesn't
|
||||
// recognize a BreadcrumbItem exported with this component as a valid child of Breadcrumb.
|
||||
export default function ProjectBreadcrumbContents({ orgName = "" }: { orgName?: string }) {
|
||||
const { data: selectedOrg } = useSelectedOrg();
|
||||
export default function ProjectBreadcrumbContents({ projectName = "" }: { projectName?: string }) {
|
||||
const { data: selectedProject } = useSelectedProject();
|
||||
|
||||
orgName = orgName || selectedOrg?.name || "";
|
||||
projectName = projectName || selectedProject?.name || "";
|
||||
|
||||
return (
|
||||
<HStack w="full">
|
||||
@@ -18,10 +18,10 @@ export default function ProjectBreadcrumbContents({ orgName = "" }: { orgName?:
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Text>{orgName[0]?.toUpperCase()}</Text>
|
||||
<Text>{projectName[0]?.toUpperCase()}</Text>
|
||||
</Flex>
|
||||
<Text display={{ base: "none", md: "block" }} py={1}>
|
||||
{orgName}
|
||||
{projectName}
|
||||
</Text>
|
||||
</HStack>
|
||||
);
|
||||
|
||||
@@ -17,39 +17,42 @@ import React, { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { AiFillCaretDown } from "react-icons/ai";
|
||||
import { BsGear, BsPlus } from "react-icons/bs";
|
||||
import { type Organization } from "@prisma/client";
|
||||
import { type Project } from "@prisma/client";
|
||||
|
||||
import { useAppStore } from "~/state/store";
|
||||
import { api } from "~/utils/api";
|
||||
import NavSidebarOption from "./NavSidebarOption";
|
||||
import { useHandledAsyncCallback, useSelectedOrg } from "~/utils/hooks";
|
||||
import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export default function ProjectMenu() {
|
||||
const router = useRouter();
|
||||
const isActive = router.pathname.startsWith("/home");
|
||||
const utils = api.useContext();
|
||||
|
||||
const selectedOrgId = useAppStore((s) => s.selectedOrgId);
|
||||
const setSelectedOrgId = useAppStore((s) => s.setSelectedOrgId);
|
||||
const selectedProjectId = useAppStore((s) => s.selectedProjectId);
|
||||
const setselectedProjectId = useAppStore((s) => s.setselectedProjectId);
|
||||
|
||||
const { data: orgs } = api.organizations.list.useQuery();
|
||||
const { data: projects } = api.projects.list.useQuery();
|
||||
|
||||
useEffect(() => {
|
||||
if (orgs && orgs[0] && (!selectedOrgId || !orgs.find((org) => org.id === selectedOrgId))) {
|
||||
setSelectedOrgId(orgs[0].id);
|
||||
if (
|
||||
projects &&
|
||||
projects[0] &&
|
||||
(!selectedProjectId || !projects.find((proj) => proj.id === selectedProjectId))
|
||||
) {
|
||||
setselectedProjectId(projects[0].id);
|
||||
}
|
||||
}, [selectedOrgId, setSelectedOrgId, orgs]);
|
||||
}, [selectedProjectId, setselectedProjectId, projects]);
|
||||
|
||||
const { data: selectedOrg } = useSelectedOrg();
|
||||
const { data: selectedProject } = useSelectedProject();
|
||||
|
||||
const popover = useDisclosure();
|
||||
|
||||
const createMutation = api.organizations.create.useMutation();
|
||||
const createMutation = api.projects.create.useMutation();
|
||||
const [createProject, isLoading] = useHandledAsyncCallback(async () => {
|
||||
const newOrg = await createMutation.mutateAsync({ name: "New Project" });
|
||||
await utils.organizations.list.invalidate();
|
||||
setSelectedOrgId(newOrg.id);
|
||||
const newProj = await createMutation.mutateAsync({ name: "New Project" });
|
||||
await utils.projects.list.invalidate();
|
||||
setselectedProjectId(newProj.id);
|
||||
await router.push({ pathname: "/project/settings" });
|
||||
}, [createMutation, router]);
|
||||
|
||||
@@ -84,10 +87,10 @@ export default function ProjectMenu() {
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Text>{selectedOrg?.name[0]?.toUpperCase()}</Text>
|
||||
<Text>{selectedProject?.name[0]?.toUpperCase()}</Text>
|
||||
</Flex>
|
||||
<Text fontSize="sm" display={{ base: "none", md: "block" }} py={1} flex={1}>
|
||||
{selectedOrg?.name}
|
||||
{selectedProject?.name}
|
||||
</Text>
|
||||
<Icon as={AiFillCaretDown} boxSize={3} size="xs" color="gray.500" mr={2} />
|
||||
</HStack>
|
||||
@@ -104,11 +107,11 @@ export default function ProjectMenu() {
|
||||
</Text>
|
||||
<Divider />
|
||||
<VStack spacing={0} w="full">
|
||||
{orgs?.map((org) => (
|
||||
{projects?.map((proj) => (
|
||||
<ProjectOption
|
||||
key={org.id}
|
||||
org={org}
|
||||
isActive={org.id === selectedOrgId}
|
||||
key={proj.id}
|
||||
proj={proj}
|
||||
isActive={proj.id === selectedProjectId}
|
||||
onClose={popover.onClose}
|
||||
/>
|
||||
))}
|
||||
@@ -134,22 +137,22 @@ export default function ProjectMenu() {
|
||||
}
|
||||
|
||||
const ProjectOption = ({
|
||||
org,
|
||||
proj,
|
||||
isActive,
|
||||
onClose,
|
||||
}: {
|
||||
org: Organization;
|
||||
proj: Project;
|
||||
isActive: boolean;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const setSelectedOrgId = useAppStore((s) => s.setSelectedOrgId);
|
||||
const setselectedProjectId = useAppStore((s) => s.setselectedProjectId);
|
||||
const [gearHovered, setGearHovered] = useState(false);
|
||||
return (
|
||||
<HStack
|
||||
as={Link}
|
||||
href="/experiments"
|
||||
onClick={() => {
|
||||
setSelectedOrgId(org.id);
|
||||
setselectedProjectId(proj.id);
|
||||
onClose();
|
||||
}}
|
||||
w="full"
|
||||
@@ -158,11 +161,11 @@ const ProjectOption = ({
|
||||
_hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }}
|
||||
p={2}
|
||||
>
|
||||
<Text>{org.name}</Text>
|
||||
<Text>{proj.name}</Text>
|
||||
<IconButton
|
||||
as={Link}
|
||||
href="/project/settings"
|
||||
aria-label={`Open ${org.name} settings`}
|
||||
aria-label={`Open ${proj.name} settings`}
|
||||
icon={<Icon as={BsGear} boxSize={5} strokeWidth={0.5} color="gray.500" />}
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { useRouter } from "next/router";
|
||||
import { useRef, useState } from "react";
|
||||
import { api } from "~/utils/api";
|
||||
import { useHandledAsyncCallback, useSelectedOrg } from "~/utils/hooks";
|
||||
import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
|
||||
|
||||
export const DeleteProjectDialog = ({
|
||||
isOpen,
|
||||
@@ -25,20 +25,20 @@ export const DeleteProjectDialog = ({
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const selectedOrg = useSelectedOrg();
|
||||
const deleteMutation = api.organizations.delete.useMutation();
|
||||
const selectedProject = useSelectedProject();
|
||||
const deleteMutation = api.projects.delete.useMutation();
|
||||
const utils = api.useContext();
|
||||
const router = useRouter();
|
||||
|
||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const [onDeleteConfirm, isDeleting] = useHandledAsyncCallback(async () => {
|
||||
if (!selectedOrg.data?.id) return;
|
||||
await deleteMutation.mutateAsync({ id: selectedOrg.data.id });
|
||||
await utils.organizations.list.invalidate();
|
||||
if (!selectedProject.data?.id) return;
|
||||
await deleteMutation.mutateAsync({ id: selectedProject.data.id });
|
||||
await utils.projects.list.invalidate();
|
||||
await router.push({ pathname: "/experiments" });
|
||||
onClose();
|
||||
}, [deleteMutation, selectedOrg, router]);
|
||||
}, [deleteMutation, selectedProject, router]);
|
||||
|
||||
const [nameToDelete, setNameToDelete] = useState("");
|
||||
|
||||
@@ -58,10 +58,10 @@ export const DeleteProjectDialog = ({
|
||||
of the project below.
|
||||
</Text>
|
||||
<Box bgColor="orange.100" w="full" p={2} borderRadius={4}>
|
||||
<Text fontFamily="inconsolata">{selectedOrg.data?.name}</Text>
|
||||
<Text fontFamily="inconsolata">{selectedProject.data?.name}</Text>
|
||||
</Box>
|
||||
<Input
|
||||
placeholder={selectedOrg.data?.name}
|
||||
placeholder={selectedProject.data?.name}
|
||||
value={nameToDelete}
|
||||
onChange={(e) => setNameToDelete(e.target.value)}
|
||||
/>
|
||||
@@ -76,7 +76,7 @@ export const DeleteProjectDialog = ({
|
||||
colorScheme="red"
|
||||
onClick={onDeleteConfirm}
|
||||
ml={3}
|
||||
isDisabled={nameToDelete !== selectedOrg.data?.name}
|
||||
isDisabled={nameToDelete !== selectedProject.data?.name}
|
||||
w={20}
|
||||
>
|
||||
{isDeleting ? <Spinner /> : "Delete"}
|
||||
|
||||
@@ -60,7 +60,7 @@ export default function Dataset() {
|
||||
<PageHeaderContainer>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbItem>
|
||||
<ProjectBreadcrumbContents orgName={dataset.data?.organization?.name} />
|
||||
<ProjectBreadcrumbContents projectName={dataset.data?.project?.name} />
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem>
|
||||
<Link href="/data">
|
||||
|
||||
@@ -109,7 +109,7 @@ export default function Experiment() {
|
||||
<PageHeaderContainer>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbItem>
|
||||
<ProjectBreadcrumbContents orgName={experiment.data?.organization?.name} />
|
||||
<ProjectBreadcrumbContents projectName={experiment.data?.project?.name} />
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem>
|
||||
<Link href="/experiments">
|
||||
|
||||
@@ -34,17 +34,17 @@ import { useMemo } from "react";
|
||||
import AppShell from "~/components/nav/AppShell";
|
||||
import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
|
||||
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
||||
import { useSelectedOrg } from "~/utils/hooks";
|
||||
import { useSelectedProject } from "~/utils/hooks";
|
||||
import dayjs from "~/utils/dayjs";
|
||||
import { api } from "~/utils/api";
|
||||
import LoggedCallTable from "~/components/dashboard/LoggedCallTable";
|
||||
|
||||
export default function LoggedCalls() {
|
||||
const { data: selectedOrg } = useSelectedOrg();
|
||||
const { data: selectedProject } = useSelectedProject();
|
||||
|
||||
const stats = api.dashboard.stats.useQuery(
|
||||
{ organizationId: selectedOrg?.id ?? "" },
|
||||
{ enabled: !!selectedOrg },
|
||||
{ projectId: selectedProject?.id ?? "" },
|
||||
{ enabled: !!selectedProject },
|
||||
);
|
||||
|
||||
const data = useMemo(() => {
|
||||
@@ -71,7 +71,7 @@ export default function LoggedCalls() {
|
||||
</PageHeaderContainer>
|
||||
<VStack px={8} pt={4} alignItems="flex-start" spacing={4}>
|
||||
<Text fontSize="2xl" fontWeight="bold">
|
||||
{selectedOrg?.name}
|
||||
{selectedProject?.name}
|
||||
</Text>
|
||||
<Divider />
|
||||
<VStack margin="auto" spacing={4} align="stretch" w="full">
|
||||
|
||||
@@ -17,33 +17,35 @@ import { BsTrash } from "react-icons/bs";
|
||||
import AppShell from "~/components/nav/AppShell";
|
||||
import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
|
||||
import { api } from "~/utils/api";
|
||||
import { useHandledAsyncCallback, useSelectedOrg } from "~/utils/hooks";
|
||||
import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
|
||||
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
|
||||
import CopiableCode from "~/components/CopiableCode";
|
||||
import { DeleteProjectDialog } from "~/components/projectSettings/DeleteProjectDialog";
|
||||
|
||||
export default function Settings() {
|
||||
const utils = api.useContext();
|
||||
const { data: selectedOrg } = useSelectedOrg();
|
||||
const { data: selectedProject } = useSelectedProject();
|
||||
|
||||
const apiKey =
|
||||
selectedOrg?.apiKeys?.length && selectedOrg?.apiKeys[0] ? selectedOrg?.apiKeys[0].apiKey : "";
|
||||
selectedProject?.apiKeys?.length && selectedProject?.apiKeys[0]
|
||||
? selectedProject?.apiKeys[0].apiKey
|
||||
: "";
|
||||
|
||||
const updateMutation = api.organizations.update.useMutation();
|
||||
const updateMutation = api.projects.update.useMutation();
|
||||
const [onSaveName] = useHandledAsyncCallback(async () => {
|
||||
if (name && name !== selectedOrg?.name && selectedOrg?.id) {
|
||||
if (name && name !== selectedProject?.name && selectedProject?.id) {
|
||||
await updateMutation.mutateAsync({
|
||||
id: selectedOrg.id,
|
||||
id: selectedProject.id,
|
||||
updates: { name },
|
||||
});
|
||||
await Promise.all([utils.organizations.get.invalidate({ id: selectedOrg.id })]);
|
||||
await Promise.all([utils.projects.get.invalidate({ id: selectedProject.id })]);
|
||||
}
|
||||
}, [updateMutation, selectedOrg]);
|
||||
}, [updateMutation, selectedProject]);
|
||||
|
||||
const [name, setName] = useState(selectedOrg?.name);
|
||||
const [name, setName] = useState(selectedProject?.name);
|
||||
useEffect(() => {
|
||||
setName(selectedOrg?.name);
|
||||
}, [selectedOrg?.name]);
|
||||
setName(selectedProject?.name);
|
||||
}, [selectedProject?.name]);
|
||||
|
||||
const deleteProjectOpen = useDisclosure();
|
||||
|
||||
@@ -66,7 +68,7 @@ export default function Settings() {
|
||||
Project Settings
|
||||
</Text>
|
||||
<Text fontSize="sm">
|
||||
Configure your project settings. These settings only apply to {selectedOrg?.name}.
|
||||
Configure your project settings. These settings only apply to {selectedProject?.name}.
|
||||
</Text>
|
||||
</VStack>
|
||||
<VStack
|
||||
@@ -90,7 +92,7 @@ export default function Settings() {
|
||||
borderColor="gray.300"
|
||||
/>
|
||||
<Button
|
||||
isDisabled={!name || name === selectedOrg?.name}
|
||||
isDisabled={!name || name === selectedProject?.name}
|
||||
colorScheme="orange"
|
||||
borderRadius={4}
|
||||
mt={2}
|
||||
@@ -113,12 +115,12 @@ export default function Settings() {
|
||||
</VStack>
|
||||
<CopiableCode code={apiKey} />
|
||||
<Divider />
|
||||
{selectedOrg?.personalOrgUserId ? (
|
||||
{selectedProject?.personalProjectUserId ? (
|
||||
<VStack alignItems="flex-start">
|
||||
<Subtitle>Personal Project</Subtitle>
|
||||
<Text fontSize="sm">
|
||||
This project is {selectedOrg?.personalOrgUser?.name}'s personal project. It cannot
|
||||
be deleted.
|
||||
This project is {selectedProject?.personalProjectUser?.name}'s personal project.
|
||||
It cannot be deleted.
|
||||
</Text>
|
||||
</VStack>
|
||||
) : (
|
||||
@@ -129,7 +131,7 @@ export default function Settings() {
|
||||
</Text>
|
||||
<HStack
|
||||
as={Button}
|
||||
isDisabled={selectedOrg?.role !== "ADMIN"}
|
||||
isDisabled={selectedProject?.role !== "ADMIN"}
|
||||
colorScheme="red"
|
||||
variant="outline"
|
||||
borderRadius={4}
|
||||
@@ -137,7 +139,7 @@ export default function Settings() {
|
||||
onClick={deleteProjectOpen.onOpen}
|
||||
>
|
||||
<Icon as={BsTrash} />
|
||||
<Text>Delete {selectedOrg?.name}</Text>
|
||||
<Text>Delete {selectedProject?.name}</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { worldChampsRouter } from "./routers/worldChamps.router";
|
||||
import { datasetsRouter } from "./routers/datasets.router";
|
||||
import { datasetEntries } from "./routers/datasetEntries.router";
|
||||
import { externalApiRouter } from "./routers/externalApi.router";
|
||||
import { organizationsRouter } from "./routers/organizations.router";
|
||||
import { projectsRouter } from "./routers/projects.router";
|
||||
import { dashboardRouter } from "./routers/dashboard.router";
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ export const appRouter = createTRPCRouter({
|
||||
worldChamps: worldChampsRouter,
|
||||
datasets: datasetsRouter,
|
||||
datasetEntries: datasetEntries,
|
||||
organizations: organizationsRouter,
|
||||
projects: projectsRouter,
|
||||
dashboard: dashboardRouter,
|
||||
externalApi: externalApiRouter,
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ export const dashboardRouter = createTRPCRouter({
|
||||
z.object({
|
||||
// TODO: actually take startDate into account
|
||||
startDate: z.string().optional(),
|
||||
organizationId: z.string(),
|
||||
projectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
@@ -22,7 +22,7 @@ export const dashboardRouter = createTRPCRouter({
|
||||
"LoggedCall.id",
|
||||
"LoggedCallModelResponse.originalLoggedCallId",
|
||||
)
|
||||
.where("organizationId", "=", input.organizationId)
|
||||
.where("projectId", "=", input.projectId)
|
||||
.select(({ fn }) => [
|
||||
sql<Date>`date_trunc('day', "LoggedCallModelResponse"."startTime")`.as("period"),
|
||||
sql<number>`count("LoggedCall"."id")::int`.as("numQueries"),
|
||||
@@ -70,7 +70,7 @@ export const dashboardRouter = createTRPCRouter({
|
||||
"LoggedCall.id",
|
||||
"LoggedCallModelResponse.originalLoggedCallId",
|
||||
)
|
||||
.where("organizationId", "=", input.organizationId)
|
||||
.where("projectId", "=", input.projectId)
|
||||
.select(({ fn }) => [
|
||||
fn.sum(fn.coalesce("LoggedCallModelResponse.totalCost", sql<number>`0`)).as("totalCost"),
|
||||
fn.count("LoggedCall.id").as("numQueries"),
|
||||
@@ -79,7 +79,7 @@ export const dashboardRouter = createTRPCRouter({
|
||||
|
||||
const errors = await kysely
|
||||
.selectFrom("LoggedCall")
|
||||
.where("organizationId", "=", input.organizationId)
|
||||
.where("projectId", "=", input.projectId)
|
||||
.leftJoin(
|
||||
"LoggedCallModelResponse",
|
||||
"LoggedCall.id",
|
||||
|
||||
@@ -3,20 +3,20 @@ import { createTRPCRouter, protectedProcedure, publicProcedure } from "~/server/
|
||||
import { prisma } from "~/server/db";
|
||||
import {
|
||||
requireCanModifyDataset,
|
||||
requireCanModifyOrganization,
|
||||
requireCanModifyProject,
|
||||
requireCanViewDataset,
|
||||
requireCanViewOrganization,
|
||||
requireCanViewProject,
|
||||
} from "~/utils/accessControl";
|
||||
|
||||
export const datasetsRouter = createTRPCRouter({
|
||||
list: protectedProcedure
|
||||
.input(z.object({ organizationId: z.string() }))
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.query(async ({ input, ctx }) => {
|
||||
await requireCanViewOrganization(input.organizationId, ctx);
|
||||
await requireCanViewProject(input.projectId, ctx);
|
||||
|
||||
const datasets = await prisma.dataset.findMany({
|
||||
where: {
|
||||
organizationId: input.organizationId,
|
||||
projectId: input.projectId,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
@@ -36,26 +36,26 @@ export const datasetsRouter = createTRPCRouter({
|
||||
return await prisma.dataset.findFirstOrThrow({
|
||||
where: { id: input.id },
|
||||
include: {
|
||||
organization: true,
|
||||
project: true,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
create: protectedProcedure
|
||||
.input(z.object({ organizationId: z.string() }))
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await requireCanModifyOrganization(input.organizationId, ctx);
|
||||
await requireCanModifyProject(input.projectId, ctx);
|
||||
|
||||
const numDatasets = await prisma.dataset.count({
|
||||
where: {
|
||||
organizationId: input.organizationId,
|
||||
projectId: input.projectId,
|
||||
},
|
||||
});
|
||||
|
||||
return await prisma.dataset.create({
|
||||
data: {
|
||||
name: `Dataset ${numDatasets + 1}`,
|
||||
organizationId: input.organizationId,
|
||||
projectId: input.projectId,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -8,9 +8,9 @@ import { generateNewCell } from "~/server/utils/generateNewCell";
|
||||
import {
|
||||
canModifyExperiment,
|
||||
requireCanModifyExperiment,
|
||||
requireCanModifyOrganization,
|
||||
requireCanModifyProject,
|
||||
requireCanViewExperiment,
|
||||
requireCanViewOrganization,
|
||||
requireCanViewProject,
|
||||
} from "~/utils/accessControl";
|
||||
import generateTypes from "~/modelProviders/generateTypes";
|
||||
import { promptConstructorVersion } from "~/promptConstructor/version";
|
||||
@@ -44,13 +44,13 @@ export const experimentsRouter = createTRPCRouter({
|
||||
};
|
||||
}),
|
||||
list: protectedProcedure
|
||||
.input(z.object({ organizationId: z.string() }))
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.query(async ({ input, ctx }) => {
|
||||
await requireCanViewOrganization(input.organizationId, ctx);
|
||||
await requireCanViewProject(input.projectId, ctx);
|
||||
|
||||
const experiments = await prisma.experiment.findMany({
|
||||
where: {
|
||||
organizationId: input.organizationId,
|
||||
projectId: input.projectId,
|
||||
},
|
||||
orderBy: {
|
||||
sortIndex: "desc",
|
||||
@@ -90,7 +90,7 @@ export const experimentsRouter = createTRPCRouter({
|
||||
const experiment = await prisma.experiment.findFirstOrThrow({
|
||||
where: { id: input.id },
|
||||
include: {
|
||||
organization: true,
|
||||
project: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -108,10 +108,10 @@ export const experimentsRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
fork: protectedProcedure
|
||||
.input(z.object({ id: z.string(), organizationId: z.string() }))
|
||||
.input(z.object({ id: z.string(), projectId: z.string() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await requireCanViewExperiment(input.id, ctx);
|
||||
await requireCanModifyOrganization(input.organizationId, ctx);
|
||||
await requireCanModifyProject(input.projectId, ctx);
|
||||
|
||||
const [
|
||||
existingExp,
|
||||
@@ -264,7 +264,7 @@ export const experimentsRouter = createTRPCRouter({
|
||||
id: newExperimentId,
|
||||
sortIndex: maxSortIndex + 1,
|
||||
label: `${existingExp.label} (forked)`,
|
||||
organizationId: input.organizationId,
|
||||
projectId: input.projectId,
|
||||
},
|
||||
}),
|
||||
prisma.promptVariant.createMany({
|
||||
@@ -294,9 +294,9 @@ export const experimentsRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
create: protectedProcedure
|
||||
.input(z.object({ organizationId: z.string() }))
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await requireCanModifyOrganization(input.organizationId, ctx);
|
||||
await requireCanModifyProject(input.projectId, ctx);
|
||||
|
||||
const maxSortIndex =
|
||||
(
|
||||
@@ -304,7 +304,7 @@ export const experimentsRouter = createTRPCRouter({
|
||||
_max: {
|
||||
sortIndex: true,
|
||||
},
|
||||
where: { organizationId: input.organizationId },
|
||||
where: { projectId: input.projectId },
|
||||
})
|
||||
)._max?.sortIndex ?? 0;
|
||||
|
||||
@@ -312,7 +312,7 @@ export const experimentsRouter = createTRPCRouter({
|
||||
data: {
|
||||
sortIndex: maxSortIndex + 1,
|
||||
label: `Experiment ${maxSortIndex + 1}`,
|
||||
organizationId: input.organizationId,
|
||||
projectId: input.projectId,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ export const externalApiRouter = createTRPCRouter({
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
const reqPayload = await reqValidator.spa(input.reqPayload);
|
||||
const cacheKey = hashRequest(key.organizationId, reqPayload as JsonValue);
|
||||
const cacheKey = hashRequest(key.projectId, reqPayload as JsonValue);
|
||||
|
||||
const existingResponse = await prisma.loggedCallModelResponse.findFirst({
|
||||
where: {
|
||||
@@ -84,7 +84,7 @@ export const externalApiRouter = createTRPCRouter({
|
||||
|
||||
await prisma.loggedCall.create({
|
||||
data: {
|
||||
organizationId: key.organizationId,
|
||||
projectId: key.projectId,
|
||||
startTime: new Date(input.startTime),
|
||||
cacheHit: true,
|
||||
modelResponseId: existingResponse.id,
|
||||
@@ -135,7 +135,7 @@ export const externalApiRouter = createTRPCRouter({
|
||||
const reqPayload = await reqValidator.spa(input.reqPayload);
|
||||
const respPayload = await respValidator.spa(input.respPayload);
|
||||
|
||||
const requestHash = hashRequest(key.organizationId, reqPayload as JsonValue);
|
||||
const requestHash = hashRequest(key.projectId, reqPayload as JsonValue);
|
||||
|
||||
const newLoggedCallId = uuidv4();
|
||||
const newModelResponseId = uuidv4();
|
||||
@@ -146,7 +146,7 @@ export const externalApiRouter = createTRPCRouter({
|
||||
prisma.loggedCall.create({
|
||||
data: {
|
||||
id: newLoggedCallId,
|
||||
organizationId: key.organizationId,
|
||||
projectId: key.projectId,
|
||||
startTime: new Date(input.startTime),
|
||||
cacheHit: false,
|
||||
},
|
||||
|
||||
@@ -5,15 +5,15 @@ import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
||||
import { prisma } from "~/server/db";
|
||||
import { generateApiKey } from "~/server/utils/generateApiKey";
|
||||
import userOrg from "~/server/utils/userOrg";
|
||||
import userProject from "~/server/utils/userProject";
|
||||
import {
|
||||
requireCanModifyOrganization,
|
||||
requireCanViewOrganization,
|
||||
requireIsOrgAdmin,
|
||||
requireCanModifyProject,
|
||||
requireCanViewProject,
|
||||
requireIsProjectAdmin,
|
||||
requireNothing,
|
||||
} from "~/utils/accessControl";
|
||||
|
||||
export const organizationsRouter = createTRPCRouter({
|
||||
export const projectsRouter = createTRPCRouter({
|
||||
list: protectedProcedure.query(async ({ ctx }) => {
|
||||
const userId = ctx.session.user.id;
|
||||
requireNothing(ctx);
|
||||
@@ -22,9 +22,9 @@ export const organizationsRouter = createTRPCRouter({
|
||||
return null;
|
||||
}
|
||||
|
||||
const organizations = await prisma.organization.findMany({
|
||||
const projects = await prisma.project.findMany({
|
||||
where: {
|
||||
organizationUsers: {
|
||||
projectUsers: {
|
||||
some: { userId: ctx.session.user.id },
|
||||
},
|
||||
},
|
||||
@@ -33,30 +33,30 @@ export const organizationsRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
|
||||
if (!organizations.length) {
|
||||
if (!projects.length) {
|
||||
// TODO: We should move this to a separate endpoint that is called on sign up
|
||||
const personalOrg = await userOrg(userId);
|
||||
organizations.push(personalOrg);
|
||||
const personalProject = await userProject(userId);
|
||||
projects.push(personalProject);
|
||||
}
|
||||
|
||||
return organizations;
|
||||
return projects;
|
||||
}),
|
||||
get: protectedProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
|
||||
await requireCanViewOrganization(input.id, ctx);
|
||||
const [org, userRole] = await prisma.$transaction([
|
||||
prisma.organization.findUnique({
|
||||
await requireCanViewProject(input.id, ctx);
|
||||
const [proj, userRole] = await prisma.$transaction([
|
||||
prisma.project.findUnique({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
include: {
|
||||
apiKeys: true,
|
||||
personalOrgUser: true,
|
||||
personalProjectUser: true,
|
||||
},
|
||||
}),
|
||||
prisma.organizationUser.findFirst({
|
||||
prisma.projectUser.findFirst({
|
||||
where: {
|
||||
userId: ctx.session.user.id,
|
||||
organizationId: input.id,
|
||||
projectId: input.id,
|
||||
role: {
|
||||
in: ["ADMIN", "MEMBER"],
|
||||
},
|
||||
@@ -64,20 +64,20 @@ export const organizationsRouter = createTRPCRouter({
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!org) {
|
||||
if (!proj) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
|
||||
return {
|
||||
...org,
|
||||
...proj,
|
||||
role: userRole?.role ?? null,
|
||||
};
|
||||
}),
|
||||
update: protectedProcedure
|
||||
.input(z.object({ id: z.string(), updates: z.object({ name: z.string() }) }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await requireCanModifyOrganization(input.id, ctx);
|
||||
return await prisma.organization.update({
|
||||
await requireCanModifyProject(input.id, ctx);
|
||||
return await prisma.project.update({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
@@ -90,36 +90,36 @@ export const organizationsRouter = createTRPCRouter({
|
||||
.input(z.object({ name: z.string() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
requireNothing(ctx);
|
||||
const newOrgId = uuidv4();
|
||||
const [newOrg] = await prisma.$transaction([
|
||||
prisma.organization.create({
|
||||
const newProjectId = uuidv4();
|
||||
const [newProject] = await prisma.$transaction([
|
||||
prisma.project.create({
|
||||
data: {
|
||||
id: newOrgId,
|
||||
id: newProjectId,
|
||||
name: input.name,
|
||||
},
|
||||
}),
|
||||
prisma.organizationUser.create({
|
||||
prisma.projectUser.create({
|
||||
data: {
|
||||
userId: ctx.session.user.id,
|
||||
organizationId: newOrgId,
|
||||
projectId: newProjectId,
|
||||
role: "ADMIN",
|
||||
},
|
||||
}),
|
||||
prisma.apiKey.create({
|
||||
data: {
|
||||
name: "Default API Key",
|
||||
organizationId: newOrgId,
|
||||
projectId: newProjectId,
|
||||
apiKey: generateApiKey(),
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return newOrg;
|
||||
return newProject;
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await requireIsOrgAdmin(input.id, ctx);
|
||||
return await prisma.organization.delete({
|
||||
await requireIsProjectAdmin(input.id, ctx);
|
||||
return await prisma.project.delete({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
type OutputEvaluation,
|
||||
type Dataset,
|
||||
type DatasetEntry,
|
||||
type Organization,
|
||||
type OrganizationUser,
|
||||
type Project,
|
||||
type ProjectUser,
|
||||
type WorldChampEntrant,
|
||||
type LoggedCall,
|
||||
type LoggedCallModelResponse,
|
||||
@@ -43,8 +43,8 @@ interface DB {
|
||||
OutputEvaluation: OutputEvaluation;
|
||||
Dataset: Dataset;
|
||||
DatasetEntry: DatasetEntry;
|
||||
Organization: Organization;
|
||||
OrganizationUser: OrganizationUser;
|
||||
Project: Project;
|
||||
ProjectUser: ProjectUser;
|
||||
WorldChampEntrant: WorldChampEntrant;
|
||||
LoggedCall: LoggedCall;
|
||||
LoggedCallModelResponse: LoggedCallModelResponse;
|
||||
|
||||
@@ -4,21 +4,21 @@ import { generateApiKey } from "~/server/utils/generateApiKey";
|
||||
|
||||
console.log("backfilling api keys");
|
||||
|
||||
const organizations = await prisma.organization.findMany({
|
||||
const projects = await prisma.project.findMany({
|
||||
include: {
|
||||
apiKeys: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`found ${organizations.length} organizations`);
|
||||
console.log(`found ${projects.length} projects`);
|
||||
|
||||
const apiKeysToCreate: Prisma.ApiKeyCreateManyInput[] = [];
|
||||
|
||||
for (const org of organizations) {
|
||||
if (!org.apiKeys.length) {
|
||||
for (const proj of projects) {
|
||||
if (!proj.apiKeys.length) {
|
||||
apiKeysToCreate.push({
|
||||
name: "Default API Key",
|
||||
organizationId: org.id,
|
||||
projectId: proj.id,
|
||||
apiKey: generateApiKey(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ const projectId = "1234";
|
||||
// Find all calls in the last 24 hours
|
||||
const responses = await prisma.loggedCall.findMany({
|
||||
where: {
|
||||
organizationId: projectId,
|
||||
projectId: projectId,
|
||||
startTime: {
|
||||
gt: dayjs()
|
||||
.subtract(24 * 3600)
|
||||
@@ -24,7 +24,7 @@ const responses = await prisma.loggedCall.findMany({
|
||||
// Find all calls in the last 24 hours with promptId 'hello-world'
|
||||
const helloWorld = await prisma.loggedCall.findMany({
|
||||
where: {
|
||||
organizationId: projectId,
|
||||
projectId: projectId,
|
||||
startTime: {
|
||||
gt: dayjs()
|
||||
.subtract(24 * 3600)
|
||||
@@ -52,7 +52,7 @@ const totalSpent = await prisma.loggedCallModelResponse.aggregate({
|
||||
},
|
||||
where: {
|
||||
originalLoggedCall: {
|
||||
organizationId: projectId,
|
||||
projectId: projectId,
|
||||
},
|
||||
startTime: {
|
||||
gt: dayjs()
|
||||
|
||||
@@ -24,9 +24,9 @@ function sortKeys(obj: JsonValue): JsonValue {
|
||||
return sortedObj;
|
||||
}
|
||||
|
||||
export function hashRequest(organizationId: string, reqPayload: JsonValue): string {
|
||||
export function hashRequest(projectId: string, reqPayload: JsonValue): string {
|
||||
const obj = {
|
||||
organizationId,
|
||||
projectId,
|
||||
reqPayload,
|
||||
};
|
||||
return hashObject(obj);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { prisma } from "~/server/db";
|
||||
import { generateApiKey } from "./generateApiKey";
|
||||
|
||||
export default async function userOrg(userId: string) {
|
||||
return await prisma.organization.upsert({
|
||||
export default async function userProject(userId: string) {
|
||||
return await prisma.project.upsert({
|
||||
where: {
|
||||
personalOrgUserId: userId,
|
||||
personalProjectUserId: userId,
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
personalOrgUserId: userId,
|
||||
organizationUsers: {
|
||||
personalProjectUserId: userId,
|
||||
projectUsers: {
|
||||
create: {
|
||||
userId: userId,
|
||||
role: "ADMIN",
|
||||
@@ -14,8 +14,8 @@ export type State = {
|
||||
api: APIClient | null;
|
||||
setApi: (api: APIClient) => void;
|
||||
sharedVariantEditor: SharedVariantEditorSlice;
|
||||
selectedOrgId: string | null;
|
||||
setSelectedOrgId: (orgId: string) => void;
|
||||
selectedProjectId: string | null;
|
||||
setselectedProjectId: (id: string) => void;
|
||||
};
|
||||
|
||||
export type SliceCreator<T> = StateCreator<State, [["zustand/immer", never]], [], T>;
|
||||
@@ -41,10 +41,10 @@ const useBaseStore = create<State, [["zustand/immer", never]]>(
|
||||
state.drawerOpen = false;
|
||||
}),
|
||||
sharedVariantEditor: createVariantEditorSlice(set, get, ...rest),
|
||||
selectedOrgId: null,
|
||||
setSelectedOrgId: (orgId: string) =>
|
||||
selectedProjectId: null,
|
||||
setselectedProjectId: (id: string) =>
|
||||
set((state) => {
|
||||
state.selectedOrgId = orgId;
|
||||
state.selectedProjectId = id;
|
||||
}),
|
||||
})),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationUserRole } from "@prisma/client";
|
||||
import { ProjectUserRole } from "@prisma/client";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { type TRPCContext } from "~/server/api/trpc";
|
||||
import { prisma } from "~/server/db";
|
||||
@@ -16,16 +16,16 @@ export const requireNothing = (ctx: TRPCContext) => {
|
||||
ctx.markAccessControlRun();
|
||||
};
|
||||
|
||||
export const requireIsOrgAdmin = async (organizationId: string, ctx: TRPCContext) => {
|
||||
export const requireIsProjectAdmin = async (projectId: string, ctx: TRPCContext) => {
|
||||
const userId = ctx.session?.user.id;
|
||||
if (!userId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
const isAdmin = await prisma.organizationUser.findFirst({
|
||||
const isAdmin = await prisma.projectUser.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
organizationId,
|
||||
projectId,
|
||||
role: "ADMIN",
|
||||
},
|
||||
});
|
||||
@@ -37,16 +37,16 @@ export const requireIsOrgAdmin = async (organizationId: string, ctx: TRPCContext
|
||||
ctx.markAccessControlRun();
|
||||
};
|
||||
|
||||
export const requireCanViewOrganization = async (organizationId: string, ctx: TRPCContext) => {
|
||||
export const requireCanViewProject = async (projectId: string, ctx: TRPCContext) => {
|
||||
const userId = ctx.session?.user.id;
|
||||
if (!userId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
const canView = await prisma.organizationUser.findFirst({
|
||||
const canView = await prisma.projectUser.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
organizationId,
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -57,17 +57,17 @@ export const requireCanViewOrganization = async (organizationId: string, ctx: TR
|
||||
ctx.markAccessControlRun();
|
||||
};
|
||||
|
||||
export const requireCanModifyOrganization = async (organizationId: string, ctx: TRPCContext) => {
|
||||
export const requireCanModifyProject = async (projectId: string, ctx: TRPCContext) => {
|
||||
const userId = ctx.session?.user.id;
|
||||
if (!userId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
const canModify = await prisma.organizationUser.findFirst({
|
||||
const canModify = await prisma.projectUser.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
organizationId,
|
||||
role: { in: [OrganizationUserRole.ADMIN, OrganizationUserRole.MEMBER] },
|
||||
projectId,
|
||||
role: { in: [ProjectUserRole.ADMIN, ProjectUserRole.MEMBER] },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -82,10 +82,10 @@ export const requireCanViewDataset = async (datasetId: string, ctx: TRPCContext)
|
||||
const dataset = await prisma.dataset.findFirst({
|
||||
where: {
|
||||
id: datasetId,
|
||||
organization: {
|
||||
organizationUsers: {
|
||||
project: {
|
||||
projectUsers: {
|
||||
some: {
|
||||
role: { in: [OrganizationUserRole.ADMIN, OrganizationUserRole.MEMBER] },
|
||||
role: { in: [ProjectUserRole.ADMIN, ProjectUserRole.MEMBER] },
|
||||
userId: ctx.session?.user.id,
|
||||
},
|
||||
},
|
||||
@@ -120,10 +120,10 @@ export const canModifyExperiment = async (experimentId: string, userId: string)
|
||||
prisma.experiment.findFirst({
|
||||
where: {
|
||||
id: experimentId,
|
||||
organization: {
|
||||
organizationUsers: {
|
||||
project: {
|
||||
projectUsers: {
|
||||
some: {
|
||||
role: { in: [OrganizationUserRole.ADMIN, OrganizationUserRole.MEMBER] },
|
||||
role: { in: [ProjectUserRole.ADMIN, ProjectUserRole.MEMBER] },
|
||||
userId,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,10 +5,10 @@ import { NumberParam, useQueryParam, withDefault } from "use-query-params";
|
||||
import { useAppStore } from "~/state/store";
|
||||
|
||||
export const useExperiments = () => {
|
||||
const selectedOrgId = useAppStore((state) => state.selectedOrgId);
|
||||
const selectedProjectId = useAppStore((state) => state.selectedProjectId);
|
||||
return api.experiments.list.useQuery(
|
||||
{ organizationId: selectedOrgId ?? "" },
|
||||
{ enabled: !!selectedOrgId },
|
||||
{ projectId: selectedProjectId ?? "" },
|
||||
{ enabled: !!selectedProjectId },
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,10 +27,10 @@ export const useExperimentAccess = () => {
|
||||
};
|
||||
|
||||
export const useDatasets = () => {
|
||||
const selectedOrgId = useAppStore((state) => state.selectedOrgId);
|
||||
const selectedProjectId = useAppStore((state) => state.selectedProjectId);
|
||||
return api.datasets.list.useQuery(
|
||||
{ organizationId: selectedOrgId ?? "" },
|
||||
{ enabled: !!selectedOrgId },
|
||||
{ projectId: selectedProjectId ?? "" },
|
||||
{ enabled: !!selectedProjectId },
|
||||
);
|
||||
};
|
||||
|
||||
@@ -150,7 +150,10 @@ export const useScenario = (scenarioId: string) => {
|
||||
|
||||
export const useVisibleScenarioIds = () => useScenarios().data?.scenarios.map((s) => s.id) ?? [];
|
||||
|
||||
export const useSelectedOrg = () => {
|
||||
const selectedOrgId = useAppStore((state) => state.selectedOrgId);
|
||||
return api.organizations.get.useQuery({ id: selectedOrgId ?? "" }, { enabled: !!selectedOrgId });
|
||||
export const useSelectedProject = () => {
|
||||
const selectedProjectId = useAppStore((state) => state.selectedProjectId);
|
||||
return api.projects.get.useQuery(
|
||||
{ id: selectedProjectId ?? "" },
|
||||
{ enabled: !!selectedProjectId },
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user