This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git
The following commit(s) were added to refs/heads/cybershuttle-staging by this
push:
new 6288ddf04a Backend integration with portal
6288ddf04a is described below
commit 6288ddf04ad5a41cf3f830336a6ebf9ff2eeb37b
Author: Ganning Xu <[email protected]>
AuthorDate: Mon Mar 31 23:41:18 2025 -0400
Backend integration with portal
* Prep for demo (Frontend + Backend)
* Add unversioned files
---
modules/research-framework/portal/src/App.tsx | 13 +++-
.../portal/src/components/NavBar.tsx | 12 +++-
.../portal/src/components/datasets/index.tsx | 2 +-
.../portal/src/components/home/ProjectCard.tsx | 77 +++++++++++++++++++++
.../portal/src/components/home/ProjectsSection.tsx | 35 ++++++++++
.../portal/src/components/home/ResourceCard.tsx | 80 +++++++++++++---------
.../portal/src/components/home/SessionCard.tsx | 68 +++++++++---------
.../portal/src/components/home/SessionsSection.tsx | 36 ++++++++--
.../portal/src/components/home/index.tsx | 10 +--
.../src/components/models/ModelSpecificBox.tsx | 2 +-
.../portal/src/components/models/index.tsx | 2 +-
.../portal/src/components/notebooks/index.tsx | 52 +++++++++-----
.../portal/src/components/repositories/index.tsx | 58 ++++++++++------
.../src/components/resources/ResourceDetails.tsx | 18 +++--
.../portal/src/interfaces/ProjectType.tsx | 11 +--
.../portal/src/interfaces/SessionStatusEnum.ts | 7 ++
.../portal/src/interfaces/SessionType.tsx | 20 +++---
modules/research-framework/portal/src/lib/util.ts | 9 +++
.../service/config/DevDataInitializer.java | 66 +++++++++---------
.../service/controller/ResearchHubController.java | 48 +++++++++++--
.../research/service/enums/SessionStatusEnum.java | 17 +++++
.../service/handlers/ResearchHubHandler.java | 11 ++-
.../research/service/handlers/SessionHandler.java | 24 +++++++
.../research/service/model/entity/Project.java | 11 +++
.../research/service/model/entity/Session.java | 27 +++++---
.../service/model/repo/SessionRepository.java | 9 +++
.../src/main/resources/application.yml | 2 +-
27 files changed, 533 insertions(+), 194 deletions(-)
diff --git a/modules/research-framework/portal/src/App.tsx
b/modules/research-framework/portal/src/App.tsx
index 64a6ce7ef4..0cfb22af89 100644
--- a/modules/research-framework/portal/src/App.tsx
+++ b/modules/research-framework/portal/src/App.tsx
@@ -7,6 +7,8 @@ import { ModelDetails } from "./components/models/ModelDetails";
import { Datasets } from "./components/datasets";
import { DatasetDetails } from "./components/datasets/DatasetDetails";
import ResourceDetails from "./components/resources/ResourceDetails";
+import Notebooks from "./components/notebooks";
+import Repositories from "./components/repositories";
function App() {
const colorMode = useColorMode();
@@ -19,9 +21,16 @@ function App() {
<BrowserRouter>
<Routes>
<Route index element={<Home />} />
- <Route path="/resources/">
- <Route path=":id" element={<ResourceDetails />} />
+ <Route path="/resources">
+ <Route path="notebooks" element={<Notebooks />} />
+ <Route path="datasets" element={<Datasets />} />
+ <Route path="repositories" element={<Repositories />} />
+ <Route path="models" element={<Models />} />
+
+ {/* Dynamic Route for specific resource details */}
+ <Route path=":type/:id" element={<ResourceDetails />} />
</Route>
+
<Route path="/notebook">
<Route path=":slug" element={<ProjectDetails />} />
</Route>
diff --git a/modules/research-framework/portal/src/components/NavBar.tsx
b/modules/research-framework/portal/src/components/NavBar.tsx
index 90ec9713ad..03ebf74e1b 100644
--- a/modules/research-framework/portal/src/components/NavBar.tsx
+++ b/modules/research-framework/portal/src/components/NavBar.tsx
@@ -9,11 +9,19 @@ const NAV_CONTENT = [
},
{
title: "Datasets",
- url: "/datasets",
+ url: "/resources/datasets",
+ },
+ {
+ title: "Repositories",
+ url: "/resources/repositories",
+ },
+ {
+ title: "Notebooks",
+ url: "/resources/notebooks",
},
{
title: "Models",
- url: "/models",
+ url: "/resources/models",
},
];
diff --git
a/modules/research-framework/portal/src/components/datasets/index.tsx
b/modules/research-framework/portal/src/components/datasets/index.tsx
index 2abb968b6b..6d89984f6d 100644
--- a/modules/research-framework/portal/src/components/datasets/index.tsx
+++ b/modules/research-framework/portal/src/components/datasets/index.tsx
@@ -39,7 +39,7 @@ export const Datasets = () => {
<>
<NavBar />
- <Container maxW="container.lg" p={4}>
+ <Container maxW="container.lg" mt={8}>
<HStack alignItems="flex-end" justify="space-between">
<PageHeader
title="Datasets"
diff --git
a/modules/research-framework/portal/src/components/home/ProjectCard.tsx
b/modules/research-framework/portal/src/components/home/ProjectCard.tsx
new file mode 100644
index 0000000000..8096c93a4a
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/ProjectCard.tsx
@@ -0,0 +1,77 @@
+import { ProjectType } from "@/interfaces/ProjectType";
+import api from "@/lib/api";
+import { API_VERSION, BACKEND_URL } from "@/lib/constants";
+import { Button, Card, HStack, Text } from "@chakra-ui/react";
+import { useEffect, useState } from "react";
+
+async function checkIfSessionWithProjectExists(project: ProjectType) {
+ try {
+ await api.get(`/hub/project/${project.id}/exists`);
+
+ return true;
+ } catch (error: any) {
+ return false;
+ }
+}
+
+export const ProjectCard = ({ project }: { project: ProjectType }) => {
+ const [exists, setExists] = useState(false);
+ useEffect(() => {
+ let intervalId: NodeJS.Timeout;
+
+ async function fetchData() {
+ const exists = await checkIfSessionWithProjectExists(project);
+ setExists(exists);
+ }
+
+ fetchData(); // Initial fetch
+
+ intervalId = setInterval(fetchData, 2000); // Fetch every 5 seconds
+
+ return () => clearInterval(intervalId); // Cleanup on unmount
+ }, [project]); // Depend on project so it updates correctly
+
+ return (
+ <Card.Root overflow="hidden" size="md">
+ <Card.Body gap="2">
+ {/* Card Content */}
+ <HStack alignItems="center" justifyContent="space-between">
+ <Card.Title>{project.name}</Card.Title>
+ <Text color="gray.500">
+ {new Date(project.createdAt).toLocaleDateString()}
+ </Text>
+ </HStack>
+
+ <Text fontWeight="bold">
+ Repository:{" "}
+ <Text as="span" fontWeight="normal">
+ {project.repositoryResource.name}
+ </Text>
+ </Text>
+ <Text fontWeight="bold">
+ Datasets:{" "}
+ <Text as="span" fontWeight="normal">
+ {project.datasetResources.map((dataset) => dataset.name).join(",
")}
+ </Text>
+ </Text>
+
+ {exists ? (
+ <Text color="green.600">Session already started</Text>
+ ) : (
+ <Button
+ colorPalette="black"
+ size="sm"
+ onClick={() => {
+ window.open(
+
`${BACKEND_URL}/api/${API_VERSION}/rf/hub/project/${project.id}?sessionName=${project.name}`,
+ "_blank"
+ );
+ }}
+ >
+ Open Project
+ </Button>
+ )}
+ </Card.Body>
+ </Card.Root>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
b/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
new file mode 100644
index 0000000000..ad8f930465
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
@@ -0,0 +1,35 @@
+import { ProjectType } from "@/interfaces/ProjectType";
+import api from "@/lib/api";
+import { SimpleGrid } from "@chakra-ui/react";
+import { useEffect, useState } from "react";
+import { ProjectCard } from "./ProjectCard";
+
+async function getAllProjects() {
+ try {
+ const response = await api.get("/hub/projects");
+ const data = response.data;
+ return data;
+ } catch (error) {
+ console.error("Error fetching:", error);
+ }
+}
+export const ProjectsSection = () => {
+ const [projects, setProjects] = useState<ProjectType[]>([]);
+
+ useEffect(() => {
+ async function init() {
+ const projects = await getAllProjects();
+ console.log(projects);
+ setProjects(projects);
+ }
+
+ init();
+ }, []);
+ return (
+ <SimpleGrid mt={2} columns={{ base: 1, md: 2, lg: 2 }}>
+ {projects.map((project: ProjectType) => {
+ return <ProjectCard project={project} key={project.id} />;
+ })}
+ </SimpleGrid>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/home/ResourceCard.tsx
b/modules/research-framework/portal/src/components/home/ResourceCard.tsx
index 49dc2e2b54..b5bc8c26e8 100644
--- a/modules/research-framework/portal/src/components/home/ResourceCard.tsx
+++ b/modules/research-framework/portal/src/components/home/ResourceCard.tsx
@@ -1,6 +1,6 @@
import { ModelResource, Resource } from "@/interfaces/ResourceType";
import { Tag } from "@/interfaces/TagType";
-import { resourceTypeToColor } from "@/lib/util";
+import { isValidImaage, resourceTypeToColor } from "@/lib/util";
import {
Text,
Box,
@@ -18,31 +18,36 @@ import { ModelCardButton } from "../models/ModelCardButton";
export const ResourceCard = ({ resource }: { resource: Resource }) => {
console.log("Resource:", resource);
const author = resource.authors[0];
+
+ const isValidImage = isValidImaage(resource.headerImage);
+
return (
<Box>
<Card.Root overflow="hidden" size="md">
- <Link to={`/resources/${resource.id}`}>
+ <Link to={`${resource.id}`}>
{/* Image Container with Badge */}
- <Box position="relative" width="full">
- {/* Badge in the upper-left */}
- <ResourceTypeBadge
- type={resource.type}
- position="absolute"
- top="2"
- left="2"
- zIndex="1"
- boxShadow="md" // Optional: Shadow for visibility
- />
+ {isValidImage && (
+ <Box position="relative" width="full">
+ {/* Badge in the upper-left */}
+ <ResourceTypeBadge
+ type={resource.type}
+ position="absolute"
+ top="2"
+ left="2"
+ zIndex="1"
+ boxShadow="md" // Optional: Shadow for visibility
+ />
- {/* Full-width Image */}
- <Image
- src={resource.headerImage}
- alt={resource.name}
- width="100%" // Ensure full width
- height="200px"
- objectFit="cover"
- />
- </Box>
+ {/* Full-width Image */}
+ <Image
+ src={resource.headerImage}
+ alt={resource.name}
+ width="100%" // Ensure full width
+ height="200px"
+ objectFit="cover"
+ />
+ </Box>
+ )}
<Card.Body
gap="2"
@@ -50,6 +55,11 @@ export const ResourceCard = ({ resource }: { resource:
Resource }) => {
>
{/* Card Content */}
<Card.Title>{resource.name}</Card.Title>
+ {!isValidImage && (
+ <Box>
+ <ResourceTypeBadge type={resource.type} />
+ </Box>
+ )}
<HStack flexWrap="wrap">
{resource.tags.map((tag: Tag) => (
<Badge
@@ -69,20 +79,22 @@ export const ResourceCard = ({ resource }: { resource:
Resource }) => {
</Link>
<Card.Footer justifyContent="space-between" pt={4}>
- <HStack>
- <Avatar.Root shape="full" size="sm">
- <Avatar.Fallback
- name={author.firstName + " " + author.lastName}
- />
- <Avatar.Image src={author.avatar} />
- </Avatar.Root>
+ {author && (
+ <HStack>
+ <Avatar.Root shape="full" size="sm">
+ <Avatar.Fallback
+ name={author.firstName + " " + author.lastName}
+ />
+ <Avatar.Image src={author.avatar} />
+ </Avatar.Root>
- <Box>
- <Text fontWeight="bold">
- {author.firstName + " " + author.lastName}
- </Text>
- </Box>
- </HStack>
+ <Box>
+ <Text fontWeight="bold">
+ {author.firstName + " " + author.lastName}
+ </Text>
+ </Box>
+ </HStack>
+ )}
{(resource.type as ResourceTypeEnum) === ResourceTypeEnum.MODEL && (
<ModelCardButton model={resource as ModelResource} />
diff --git
a/modules/research-framework/portal/src/components/home/SessionCard.tsx
b/modules/research-framework/portal/src/components/home/SessionCard.tsx
index a6bf2ee4be..a529a0cde8 100644
--- a/modules/research-framework/portal/src/components/home/SessionCard.tsx
+++ b/modules/research-framework/portal/src/components/home/SessionCard.tsx
@@ -1,17 +1,18 @@
import { SessionType } from "@/interfaces/SessionType";
+import { API_VERSION, BACKEND_URL } from "@/lib/constants";
import {
Box,
Text,
Card,
Heading,
HStack,
- SimpleGrid,
Badge,
+ Button,
} from "@chakra-ui/react";
const getColorPalette = (status: string) => {
switch (status) {
- case "running":
+ case "CREATED":
return "green";
case "stopped":
return "red";
@@ -23,19 +24,11 @@ const getColorPalette = (status: string) => {
};
export const SessionCard = ({ session }: { session: SessionType }) => {
return (
- <Card.Root
- size="sm"
- bg={
- session.title.toLocaleLowerCase() === "jupyter"
- ? "green.50"
- : "purple.50"
- }
- >
+ <Card.Root size="sm">
<Card.Header>
<HStack justify="space-between" alignItems="flex-start">
<Box>
- <Heading size="lg">{session.title}</Heading>
- <Text color="fg.muted">{session.started}</Text>
+ <Heading size="lg">{session.sessionName}</Heading>
</Box>
<Badge size="md" colorPalette={getColorPalette(session.status)}>
{session.status}
@@ -43,29 +36,36 @@ export const SessionCard = ({ session }: { session:
SessionType }) => {
</HStack>
</Card.Header>
<Card.Body>
- <SimpleGrid columns={2}>
- <KeyValue label="Models" value={session.models.join(", ")} />
- <KeyValue label="Datasets" value={session.datasets.join(", ")} />
- <KeyValue label="Nodes" value={session.nodes.toString()} />
- <KeyValue label="RAM" value={session.ram.toString()} />
- <KeyValue label="Storage" value={session.storage.toString()} />
- </SimpleGrid>
+ <Text color="fg.muted">
+ <Text as="span" fontWeight="bold">
+ Created
+ </Text>
+ : {new Date(session.createdAt).toLocaleString()}
+ </Text>
+
+ <HStack alignItems="center" mt={2}>
+ <Button
+ size="sm"
+ colorPalette="red"
+ variant="subtle"
+ onClick={() => {}}
+ >
+ Terminate
+ </Button>
+ <Button
+ size="sm"
+ colorPalette="green"
+ onClick={() => {
+ window.open(
+
`${BACKEND_URL}/api/${API_VERSION}/rf/hub/sessions/${session.id}/resolve`,
+ "_blank"
+ );
+ }}
+ >
+ Open Session
+ </Button>
+ </HStack>
</Card.Body>
</Card.Root>
);
};
-
-export const KeyValue = ({
- label,
- value,
-}: {
- label: string;
- value: string;
-}) => {
- return (
- <HStack>
- <Text fontWeight="bold">{label}:</Text>
- <Text>{value}</Text>
- </HStack>
- );
-};
diff --git
a/modules/research-framework/portal/src/components/home/SessionsSection.tsx
b/modules/research-framework/portal/src/components/home/SessionsSection.tsx
index d3dd2eeea4..20e8e63f44 100644
--- a/modules/research-framework/portal/src/components/home/SessionsSection.tsx
+++ b/modules/research-framework/portal/src/components/home/SessionsSection.tsx
@@ -1,15 +1,43 @@
-// @ts-expect-error This is fine
-import { MOCK_SESSIONS } from "../../data/MOCK_DATA";
import { SessionType } from "@/interfaces/SessionType";
import { SessionCard } from "./SessionCard";
import { GridContainer } from "../GridContainer";
+import api from "@/lib/api";
+import { useEffect, useState } from "react";
+
+async function getSessions() {
+ try {
+ const response = await api.get("/hub/sessions");
+ const data = response.data;
+ return data;
+ } catch (error) {
+ console.error("Error fetching:", error);
+ }
+}
export const SessionsSection = () => {
+ const [sessions, setSessions] = useState<SessionType[]>([]);
+
+ useEffect(() => {
+ let intervalId: NodeJS.Timeout;
+
+ async function fetchData() {
+ const sessions = await getSessions();
+ setSessions(sessions);
+ }
+
+ fetchData(); // Initial fetch
+
+ // eslint-disable-next-line prefer-const
+ intervalId = setInterval(fetchData, 2000); // Fetch every 2 seconds
+
+ return () => clearInterval(intervalId); // Cleanup on unmount
+ }, []);
+
return (
<>
<GridContainer>
- {MOCK_SESSIONS.map((session: SessionType) => {
- return <SessionCard key={session.sessionId} session={session} />;
+ {sessions.map((session: SessionType) => {
+ return <SessionCard key={session.id} session={session} />;
})}
</GridContainer>
</>
diff --git a/modules/research-framework/portal/src/components/home/index.tsx
b/modules/research-framework/portal/src/components/home/index.tsx
index 75e679536e..1007007e67 100644
--- a/modules/research-framework/portal/src/components/home/index.tsx
+++ b/modules/research-framework/portal/src/components/home/index.tsx
@@ -4,21 +4,21 @@ import NavBar from "../NavBar";
import { PageHeader } from "../PageHeader";
import { AddRepositoryButton } from "./AddRepositoryButton";
import { AddZipButton } from "./AddZipButton";
-import { NotebooksAndRepositoriesSection } from
"./NotebooksAndRepositoriesSection";
import { ButtonWithIcon } from "./ButtonWithIcon";
import { FaPlus } from "react-icons/fa";
import { SessionsSection } from "./SessionsSection";
+import { ProjectsSection } from "./ProjectsSection";
const Home = () => {
return (
<Box>
<NavBar />
- <Container maxW="container.xl" p={4}>
+ <Container maxW="container.xl" mt={8}>
<HStack alignItems="flex-end" justify="space-between">
<PageHeader
- title="Notebook & Repositories"
- description="Community-Published Scientific Notebooks and
Repositories."
+ title="Projects"
+ description="Projects are a combination of repositories,
notebooks, models, and datasets."
/>
<HStack gap={4}>
@@ -27,7 +27,7 @@ const Home = () => {
</HStack>
</HStack>
- <NotebooksAndRepositoriesSection />
+ <ProjectsSection />
<HStack alignItems="flex-end" justify="space-between" mt={4}>
<PageHeader
diff --git
a/modules/research-framework/portal/src/components/models/ModelSpecificBox.tsx
b/modules/research-framework/portal/src/components/models/ModelSpecificBox.tsx
index ca65114c85..b5e9f1d58e 100644
---
a/modules/research-framework/portal/src/components/models/ModelSpecificBox.tsx
+++
b/modules/research-framework/portal/src/components/models/ModelSpecificBox.tsx
@@ -9,7 +9,7 @@ export const ModelSpecificBox = ({ model }: { model:
ModelResource }) => {
as="a"
w="100%"
// @ts-expect-error This is fine
- _target="_blank"
+ target="_blank"
href={`https://cybershuttle.org/workspace/applications/${model.applicationInterfaceId}/create_experiment`}
>
Create{" "}
diff --git a/modules/research-framework/portal/src/components/models/index.tsx
b/modules/research-framework/portal/src/components/models/index.tsx
index 63c4863be6..0fa41c76b5 100644
--- a/modules/research-framework/portal/src/components/models/index.tsx
+++ b/modules/research-framework/portal/src/components/models/index.tsx
@@ -36,7 +36,7 @@ export const Models = () => {
<>
<NavBar />
- <Container maxW="container.lg" p={4}>
+ <Container maxW="container.lg" mt={8}>
<HStack alignItems="flex-end" justify="space-between">
<PageHeader
title="Models"
diff --git
a/modules/research-framework/portal/src/components/notebooks/index.tsx
b/modules/research-framework/portal/src/components/notebooks/index.tsx
index 7fa14096d3..5730581c7f 100644
--- a/modules/research-framework/portal/src/components/notebooks/index.tsx
+++ b/modules/research-framework/portal/src/components/notebooks/index.tsx
@@ -1,22 +1,37 @@
-import { Box, Container, HStack, Input, SimpleGrid } from "@chakra-ui/react";
+import { Container, Input, SimpleGrid } from "@chakra-ui/react";
import NavBar from "../NavBar";
import { PageHeader } from "../PageHeader";
-import { FaCode } from "react-icons/fa";
-import { MOCK_NOTEBOOKS } from "../../data/MOCK_DATA";
-import { NotebookCard } from "./NotebookCard";
import { InputGroup } from "../ui/input-group";
import { LuSearch } from "react-icons/lu";
-import { TagsInput } from "react-tag-input-component";
-import { useState } from "react";
+import { useEffect, useState } from "react";
+import api from "@/lib/api";
+import { NotebookResource } from "@/interfaces/ResourceType";
+import { ResourceCard } from "../home/ResourceCard";
-const Notebooks = () => {
- const [tags, setTags] = useState<string[]>([]);
- let filteredNotebooks = MOCK_NOTEBOOKS;
- if (tags) {
- filteredNotebooks = MOCK_NOTEBOOKS.filter((notebook) => {
- return tags.every((tag) => notebook.tags.includes(tag));
- });
+const getNotebooks = async () => {
+ try {
+ const response = await api.get(
+ "/project-management/resources?type=NOTEBOOK"
+ );
+ const data = response.data;
+ return data;
+ } catch (error) {
+ console.error("Error fetching:", error);
}
+};
+
+const Notebooks = () => {
+ const [notebooks, setNotebooks] = useState<NotebookResource[]>([]);
+
+ useEffect(() => {
+ async function init() {
+ const notebooks = await getNotebooks();
+ setNotebooks(notebooks.content);
+ }
+
+ init();
+ }, []);
+
return (
<>
<NavBar />
@@ -24,27 +39,26 @@ const Notebooks = () => {
<Container maxW="container.lg" mt={8}>
<PageHeader
title="Notebooks"
- icon={<FaCode />}
description="Create and manage your notebooks. From here, you can
create new notebooks, view existing ones, and manage them."
/>
- <InputGroup endElement={<LuSearch />} w="100%" mt={16}>
+ <InputGroup endElement={<LuSearch />} w="100%" mt={4}>
<Input placeholder="Search" rounded="md" />
</InputGroup>
- <Box mt={4}>
+ {/* <Box mt={4}>
<TagsInput
value={tags}
onChange={setTags}
placeHolder="Filter by tags"
/>
- </Box>
+ </Box> */}
<SimpleGrid
columns={{ base: 1, md: 2, lg: 3 }}
mt={4}
gap={12}
justifyContent="space-around"
>
- {filteredNotebooks.map((notebook: any) => {
- return <NotebookCard key={notebook.slug} notebook={notebook} />;
+ {notebooks.map((notebook: NotebookResource) => {
+ return <ResourceCard resource={notebook} key={notebook.id} />;
})}
</SimpleGrid>
</Container>
diff --git
a/modules/research-framework/portal/src/components/repositories/index.tsx
b/modules/research-framework/portal/src/components/repositories/index.tsx
index 4b1e1ebf2d..1100620a9b 100644
--- a/modules/research-framework/portal/src/components/repositories/index.tsx
+++ b/modules/research-framework/portal/src/components/repositories/index.tsx
@@ -1,11 +1,37 @@
-import { Container, VStack } from "@chakra-ui/react";
+import { Container, Input, SimpleGrid } from "@chakra-ui/react";
import NavBar from "../NavBar";
-import { BsGithub } from "react-icons/bs";
import { PageHeader } from "../PageHeader";
-import { MOCK_REPOSITORIES } from "../../data/MOCK_DATA";
-import { RepositoryCard } from "./RepositoryCard";
+import api from "@/lib/api";
+import { useEffect, useState } from "react";
+import { RepositoryResource } from "@/interfaces/ResourceType";
+import { ResourceCard } from "../home/ResourceCard";
+import { LuSearch } from "react-icons/lu";
+import { InputGroup } from "../ui/input-group";
+
+const getRepositories = async () => {
+ try {
+ const response = await api.get(
+ "/project-management/resources?type=REPOSITORY"
+ );
+ const data = response.data;
+ return data;
+ } catch (error) {
+ console.error("Error fetching:", error);
+ }
+};
const Repositories = () => {
+ const [repositories, setRepositories] = useState<RepositoryResource[]>([]);
+
+ useEffect(() => {
+ async function init() {
+ const repositories = await getRepositories();
+ setRepositories(repositories.content);
+ }
+
+ init();
+ }, []);
+
return (
<>
<NavBar />
@@ -13,24 +39,16 @@ const Repositories = () => {
<Container maxW="container.lg" mt={8}>
<PageHeader
title="Repositories"
- icon={<BsGithub />}
description="View the most viewed repositories on GitHub, all
central to this page."
/>
-
- <VStack gap={4} mt={8}>
- {
- // This is the code that will be replaced by the code snippet
- // from the instructions
- MOCK_REPOSITORIES.map((repository) => {
- return (
- <RepositoryCard
- repository={repository}
- key={repository.githubUrl}
- />
- );
- })
- }
- </VStack>
+ <InputGroup mt={4} endElement={<LuSearch />} w="100%">
+ <Input placeholder="Search" rounded="md" />
+ </InputGroup>
+ <SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} gap={4} mt={4}>
+ {repositories.map((repo: RepositoryResource) => {
+ return <ResourceCard resource={repo} key={repo.id} />;
+ })}
+ </SimpleGrid>
</Container>
</>
);
diff --git
a/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
b/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
index 230e600c23..9703047ba3 100644
---
a/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
+++
b/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
@@ -23,7 +23,7 @@ import {
} from "@/interfaces/ResourceType";
import { Tag } from "@/interfaces/TagType";
import { User } from "@/interfaces/UserType";
-import { resourceTypeToColor } from "@/lib/util";
+import { isValidImaage, resourceTypeToColor } from "@/lib/util";
import { ResourceTypeBadge } from "./ResourceTypeBadge";
import { ResourceTypeEnum } from "@/interfaces/ResourceTypeEnum";
import { ModelSpecificBox } from "../models/ModelSpecificBox";
@@ -52,6 +52,8 @@ const ResourceDetails = () => {
if (!resource) return <Spinner />;
+ const validImage = isValidImaage(resource.headerImage);
+
return (
<>
<NavBar />
@@ -123,12 +125,14 @@ const ResourceDetails = () => {
</HStack>
</Box>
- <Image
- src={resource.headerImage}
- alt="Notebook Header"
- rounded="md"
- maxW="300px"
- />
+ {validImage && (
+ <Image
+ src={resource.headerImage}
+ alt="Notebook Header"
+ rounded="md"
+ maxW="300px"
+ />
+ )}
</HStack>
<Separator my={6} />
diff --git a/modules/research-framework/portal/src/interfaces/ProjectType.tsx
b/modules/research-framework/portal/src/interfaces/ProjectType.tsx
index b09bb66ced..40ecada683 100644
--- a/modules/research-framework/portal/src/interfaces/ProjectType.tsx
+++ b/modules/research-framework/portal/src/interfaces/ProjectType.tsx
@@ -1,7 +1,10 @@
-import { MetadataType } from "./MetadataType";
+import { DatasetResource, RepositoryResource } from "./ResourceType";
export interface ProjectType {
- metadata: MetadataType;
- notebookViewer?: string;
- repositoryUrl?: string;
+ id: string;
+ name: string;
+ repositoryResource: RepositoryResource;
+ datasetResources: DatasetResource[];
+ createdAt: string;
+ updatedAt: string;
}
diff --git
a/modules/research-framework/portal/src/interfaces/SessionStatusEnum.ts
b/modules/research-framework/portal/src/interfaces/SessionStatusEnum.ts
new file mode 100644
index 0000000000..48a88ea8fe
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/SessionStatusEnum.ts
@@ -0,0 +1,7 @@
+export enum SessionStatusEnum {
+ CREATED = "CREATED",
+ RUNNING = "RUNNING",
+ FINISHED = "FINISHED",
+ TERMINATED = "TERMINATED",
+ ERROR = "ERROR"
+};
\ No newline at end of file
diff --git a/modules/research-framework/portal/src/interfaces/SessionType.tsx
b/modules/research-framework/portal/src/interfaces/SessionType.tsx
index fb483cc421..7948a6d73f 100644
--- a/modules/research-framework/portal/src/interfaces/SessionType.tsx
+++ b/modules/research-framework/portal/src/interfaces/SessionType.tsx
@@ -1,11 +1,13 @@
+import { ProjectType } from "./ProjectType";
+import { SessionStatusEnum } from "./SessionStatusEnum";
+import { User } from "./UserType";
+
export interface SessionType {
- sessionId: string;
- title: string;
- started: string;
- models: string[];
- datasets: string[];
- nodes: number;
- ram: number;
- storage: number;
- status: "running" | "stopped";
+ id: string;
+ sessionName: string;
+ user: User;
+ project: ProjectType;
+ createdAt: string;
+ updatedAt: string;
+ status: SessionStatusEnum;
}
diff --git a/modules/research-framework/portal/src/lib/util.ts
b/modules/research-framework/portal/src/lib/util.ts
index 2ccc730f1f..86fdb5c4ce 100644
--- a/modules/research-framework/portal/src/lib/util.ts
+++ b/modules/research-framework/portal/src/lib/util.ts
@@ -10,4 +10,13 @@ export const resourceTypeToColor = (type: string) => {
} else {
return "gray";
}
+}
+
+export const isValidImaage = (url: string) => {
+ // should start with http or https
+
+ if (url.startsWith("http")) {
+ return true;
+ }
+ return false
}
\ No newline at end of file
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/DevDataInitializer.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/DevDataInitializer.java
index 63113523a2..10931bc7de 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/DevDataInitializer.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/DevDataInitializer.java
@@ -32,6 +32,9 @@ import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
+import java.util.HashSet;
+import java.util.Set;
+
@Component
@Profile("dev")
public class DevDataInitializer implements CommandLineRunner {
@@ -51,41 +54,40 @@ public class DevDataInitializer implements
CommandLineRunner {
@Override
public void run(String... args) {
- cleanup();
-
- User user = new User(devUserEmail, "airavata", "admin", devUserEmail);
- userRepository.save(user);
+ if (userRepository.count() == 0) {
+ User user = new User(devUserEmail, "airavata", "admin",
devUserEmail);
+ user.setAvatar("");
+ userRepository.save(user);
- RepositoryResource repositoryResource = new RepositoryResource();
- repositoryResource.setName("BMTK Repository");
- repositoryResource.setDescription("Repository for the BMTK workshop
project");
- repositoryResource.setHeaderImage("header_image.png");
-
repositoryResource.setRepositoryUrl("https://github.com/AllenInstitute/bmtk-workshop.git");
- repositoryResource.setStatus(StatusEnum.VERIFIED);
- repositoryResource.setPrivacy(PrivacyEnum.PUBLIC);
- repositoryResource = resourceRepository.save(repositoryResource);
+ RepositoryResource repositoryResource = new RepositoryResource();
+ repositoryResource.setName("BMTK Repository");
+ repositoryResource.setDescription("Repository for the BMTK
workshop project");
+ repositoryResource.setHeaderImage("header_image.png");
+
repositoryResource.setRepositoryUrl("https://github.com/AllenInstitute/bmtk-workshop.git");
+ repositoryResource.setStatus(StatusEnum.VERIFIED);
+ repositoryResource.setPrivacy(PrivacyEnum.PUBLIC);
+ repositoryResource = resourceRepository.save(repositoryResource);
- DatasetResource datasetResource = new DatasetResource();
- datasetResource.setName("BMTK Dataset");
- datasetResource.setDescription("Dataset for the BMTK workshop
project");
- datasetResource.setHeaderImage("header_image.png");
- datasetResource.setDatasetUrl("bmtk");
- datasetResource.setStatus(StatusEnum.VERIFIED);
- datasetResource.setPrivacy(PrivacyEnum.PUBLIC);
- datasetResource = resourceRepository.save(datasetResource);
+ DatasetResource datasetResource = new DatasetResource();
+ datasetResource.setName("BMTK Dataset");
+ datasetResource.setDescription("Dataset for the BMTK workshop
project");
+ datasetResource.setHeaderImage("header_image.png");
+ datasetResource.setDatasetUrl("bmtk");
+ datasetResource.setStatus(StatusEnum.VERIFIED);
+ datasetResource.setPrivacy(PrivacyEnum.PUBLIC);
+ Set<User> set = new HashSet<>();
+ set.add(user);
+ datasetResource.setAuthors(set);
+ datasetResource = resourceRepository.save(datasetResource);
- Project project = new Project();
- project.setRepositoryResource(repositoryResource);
- project.getDatasetResources().add(datasetResource);
+ Project project = new Project();
+ project.setRepositoryResource(repositoryResource);
+ project.getDatasetResources().add(datasetResource);
+ project.setName("BMTK Workshop Project");
- projectRepository.save(project);
-
- System.out.println("Initialized Project with id: " + project.getId());
- }
+ projectRepository.save(project);
- public void cleanup() {
- userRepository.deleteAll();
- projectRepository.deleteAll();
- resourceRepository.deleteAll();
+ System.out.println("Initialized Project with id: " +
project.getId());
+ }
}
-}
\ No newline at end of file
+}
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResearchHubController.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResearchHubController.java
index 38eab2f9ee..8f72290598 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResearchHubController.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResearchHubController.java
@@ -18,19 +18,24 @@
*/
package org.apache.airavata.research.service.controller;
+import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.airavata.research.service.enums.SessionStatusEnum;
import org.apache.airavata.research.service.handlers.ResearchHubHandler;
+import org.apache.airavata.research.service.handlers.SessionHandler;
+import org.apache.airavata.research.service.model.UserContext;
+import org.apache.airavata.research.service.model.entity.Project;
+import org.apache.airavata.research.service.model.entity.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.repository.query.Param;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import java.net.URI;
+import java.util.List;
@RestController
@RequestMapping("/api/v1/rf/hub")
@@ -41,11 +46,21 @@ public class ResearchHubController {
private final ResearchHubHandler rHubHandler;
+ @Autowired
+ private SessionHandler sessionHandler;
+
public ResearchHubController(ResearchHubHandler rHubHandler) {
this.rHubHandler = rHubHandler;
}
+ @GetMapping("/projects")
+ @Operation(summary = "Get all projects")
+ public ResponseEntity<List<Project>> getAllProjects() {
+ return ResponseEntity.ok(rHubHandler.getAllProjects());
+ }
+
@GetMapping("/project/{projectId}")
+ @Operation(summary = "Spawn new project session")
public ResponseEntity<?> resolveRHubUrl(@PathVariable("projectId") String
projectId, @RequestParam("sessionName") String sessionName) {
String spawnUrl = rHubHandler.spinRHubSession(projectId, sessionName);
@@ -53,7 +68,28 @@ public class ResearchHubController {
return
ResponseEntity.status(HttpStatus.FOUND).location(URI.create(spawnUrl)).build();
}
- @GetMapping("/session/{sessionId}")
+ @GetMapping("/project/{projectId}/exists")
+ @Operation(summary = "Check if session with project already exists")
+ public ResponseEntity<Boolean>
getProjectSessionExists(@PathVariable("projectId") String projectId) {
+
+ return
ResponseEntity.ok(sessionHandler.checkIfSessionExists(projectId,
UserContext.user().getId()));
+ }
+
+ @GetMapping("/sessions")
+ @Operation(summary = "Get all sessions for a given user")
+ public ResponseEntity<List<Session>> getSessions() {
+ String userId = UserContext.user().getId();
+ List<Session> sessions = sessionHandler.findAllByUserId(userId);
+ return ResponseEntity.ok(sessions);
+ }
+
+ @PatchMapping("/sessions/{sessionId}")
+ @Operation(summary = "Update a session's status")
+ public ResponseEntity<Session>
updateSessionStatus(@PathVariable(value="sessionId") String sessionId,
@Param(value="status") SessionStatusEnum status) {
+ return ResponseEntity.ok(sessionHandler.updateSessionStatus(sessionId,
status));
+ }
+
+ @GetMapping("/sessions/{sessionId}/resolve")
public ResponseEntity<?>
resolveRHubExistingSession(@PathVariable("sessionId") String sessionId) {
String spawnUrl = rHubHandler.resolveRHubExistingSession(sessionId);
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/enums/SessionStatusEnum.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/enums/SessionStatusEnum.java
new file mode 100644
index 0000000000..031e539902
--- /dev/null
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/enums/SessionStatusEnum.java
@@ -0,0 +1,17 @@
+package org.apache.airavata.research.service.enums;
+
+public enum SessionStatusEnum {
+ CREATED("CREATED"),
+ RUNNING("RUNNING"),
+ FINISHED("FINISHED"),
+ TERMINATED("TERMINATED"),
+ ERROR("ERROR");
+
+ private String str;
+ SessionStatusEnum(String str) {
+ this.str = str;
+ }
+ public String toString() {
+ return str;
+ }
+}
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResearchHubHandler.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResearchHubHandler.java
index 308edd7dbe..2aa69ef473 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResearchHubHandler.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResearchHubHandler.java
@@ -22,11 +22,14 @@ import
org.apache.airavata.research.service.model.UserContext;
import org.apache.airavata.research.service.model.entity.DatasetResource;
import org.apache.airavata.research.service.model.entity.Project;
import org.apache.airavata.research.service.model.entity.Session;
+import org.apache.airavata.research.service.model.repo.ProjectRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
+import java.util.List;
+
@Service
public class ResearchHubHandler {
@@ -36,13 +39,15 @@ public class ResearchHubHandler {
private final ProjectHandler projectHandler;
private final SessionHandler sessionHandler;
+ private final ProjectRepository projectRepository;
@Value("${cybershuttle.hub.url}")
private String csHubUrl;
- public ResearchHubHandler(ProjectHandler projectHandler, SessionHandler
sessionHandler) {
+ public ResearchHubHandler(ProjectHandler projectHandler, SessionHandler
sessionHandler, ProjectRepository projectRepository) {
this.projectHandler = projectHandler;
this.sessionHandler = sessionHandler;
+ this.projectRepository = projectRepository;
}
public String spinRHubSession(String projectId, String sessionName) {
@@ -65,4 +70,8 @@ public class ResearchHubHandler {
LOGGER.debug("Generated the session url: {} for the user: {}",
sessionUrl, UserContext.username());
return sessionUrl;
}
+
+ public List<Project> getAllProjects() {
+ return projectRepository.findAll();
+ }
}
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/SessionHandler.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/SessionHandler.java
index d9c56d6127..45360420c0 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/SessionHandler.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/SessionHandler.java
@@ -19,6 +19,7 @@
package org.apache.airavata.research.service.handlers;
import jakarta.persistence.EntityNotFoundException;
+import org.apache.airavata.research.service.enums.SessionStatusEnum;
import org.apache.airavata.research.service.model.UserContext;
import org.apache.airavata.research.service.model.entity.Project;
import org.apache.airavata.research.service.model.entity.Session;
@@ -28,6 +29,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
+import java.util.List;
import java.util.UUID;
@Service
@@ -51,8 +53,30 @@ public class SessionHandler {
public Session createSession(String sessionName, Project project) {
sessionName = StringUtils.isNotBlank(sessionName) ? sessionName :
UUID.randomUUID().toString().substring(0, 6);
Session session = new Session(sessionName, UserContext.user(),
project);
+ session.setStatus(SessionStatusEnum.CREATED);
session = sessionRepository.save(session);
LOGGER.debug("Created session with Id: {}, Name: {}", session.getId(),
sessionName);
return session;
}
+
+ public List<Session> findAllByUserId(String userId) {
+ List<Session> sessions = sessionRepository.findByUserId(userId);
+ return sessions;
+ }
+
+ public Session updateSessionStatus(String sessionId, SessionStatusEnum
status) {
+ Session session = findSession(sessionId);
+ session.setStatus(status);
+ session = sessionRepository.save(session);
+ LOGGER.debug("Updated session with Id: {}, Status: {}",
session.getId(), status);
+ return session;
+ }
+
+ public boolean checkIfSessionExists(String projectId, String userId) {
+ if (sessionRepository.findSessionByProjectIdAndUserId(projectId,
userId).isEmpty()) {
+ throw new RuntimeException("Session does not exist");
+ }
+ return true;
+ }
+
}
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/entity/Project.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/entity/Project.java
index 7431ee79fb..1c0f3deebb 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/entity/Project.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/entity/Project.java
@@ -48,6 +48,9 @@ public class Project {
@Column(nullable = false, updatable = false, length = 48)
private String id;
+ @Column(nullable = false)
+ private String name;
+
@ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "repository_resource_id")
private RepositoryResource repositoryResource;
@@ -68,6 +71,14 @@ public class Project {
@LastModifiedDate
private Instant updatedAt;
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
public String getId() {
return id;
}
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/entity/Session.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/entity/Session.java
index 826391ae11..6327a1fdb6 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/entity/Session.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/entity/Session.java
@@ -18,15 +18,8 @@
*/
package org.apache.airavata.research.service.model.entity;
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.EntityListeners;
-import jakarta.persistence.FetchType;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.Id;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.ManyToOne;
-import jakarta.persistence.Table;
+import jakarta.persistence.*;
+import org.apache.airavata.research.service.enums.SessionStatusEnum;
import org.hibernate.annotations.UuidGenerator;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
@@ -48,11 +41,11 @@ public class Session {
@Column(nullable = false)
private String sessionName;
- @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "user_id")
private User user;
- @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "project_id")
private Project project;
@@ -64,6 +57,10 @@ public class Session {
@LastModifiedDate
private Instant updatedAt;
+ @Column(nullable = false)
+ @Enumerated(EnumType.STRING)
+ private SessionStatusEnum status;
+
public Session() {
}
@@ -73,6 +70,14 @@ public class Session {
this.project = project;
}
+ public SessionStatusEnum getStatus() {
+ return status;
+ }
+
+ public void setStatus(SessionStatusEnum status) {
+ this.status = status;
+ }
+
public String getId() {
return id;
}
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/SessionRepository.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/SessionRepository.java
index 1292613150..42e9ad95e5 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/SessionRepository.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/SessionRepository.java
@@ -20,6 +20,15 @@ package org.apache.airavata.research.service.model.repo;
import org.apache.airavata.research.service.model.entity.Session;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+import java.util.Optional;
public interface SessionRepository extends JpaRepository<Session, String> {
+ List<Session> findByUserId(String userId);
+
+ @Query(value = "SELECT * FROM session WHERE project_id = :projectId AND
user_id = :userId", nativeQuery = true)
+ Optional<Session> findSessionByProjectIdAndUserId(@Param("projectId")
String projectId, @Param("userId") String userId);
}
diff --git
a/modules/research-framework/research-service/src/main/resources/application.yml
b/modules/research-framework/research-service/src/main/resources/application.yml
index 7e7f285635..9df2778bc3 100644
---
a/modules/research-framework/research-service/src/main/resources/application.yml
+++
b/modules/research-framework/research-service/src/main/resources/application.yml
@@ -9,7 +9,7 @@ server:
cybershuttle:
hub:
url: https://hub.dev.cybershuttle.org
- dev-user: "CHANGE_ME"
+ dev-user: "[email protected]"
spring:
servlet: