From db0c979a163f1c23023b5c4d24a9e140d6e54c44 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 09:41:18 -0800 Subject: [PATCH 01/21] Move repro check to its own page --- .../src/components/Common/SidebarItems.tsx | 2 + frontend/src/routeTree.gen.ts | 20 ++ .../$userName/$projectName/_layout/index.tsx | 153 +--------------- .../$userName/$projectName/_layout/repro.tsx | 172 ++++++++++++++++++ 4 files changed, 195 insertions(+), 152 deletions(-) create mode 100644 frontend/src/routes/_layout/$userName/$projectName/_layout/repro.tsx diff --git a/frontend/src/components/Common/SidebarItems.tsx b/frontend/src/components/Common/SidebarItems.tsx index 809f73ed..d94b12e5 100644 --- a/frontend/src/components/Common/SidebarItems.tsx +++ b/frontend/src/components/Common/SidebarItems.tsx @@ -10,6 +10,7 @@ import { FiFolder, } from "react-icons/fi" import { FaLaptop } from "react-icons/fa" +import { FaEquals } from "react-icons/fa6" import { IoLibraryOutline } from "react-icons/io5" import axios from "axios" import { useQuery } from "@tanstack/react-query" @@ -26,6 +27,7 @@ const items = [ { icon: FiBookOpen, title: "Publications", path: "/publications" }, { icon: SiJupyter, title: "Notebooks", path: "/notebooks" }, { icon: FiHardDrive, title: "Software", path: "/software" }, + { icon: FaEquals, title: "Reproducibility", path: "/repro" }, { icon: FiUsers, title: "Collaborators", path: "/collaborators" }, { icon: IoLibraryOutline, title: "References", path: "/references" }, { icon: FiFolder, title: "All files", path: "/files" }, diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 011bdb97..4e6afa1a 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -29,6 +29,7 @@ import { Route as LayoutUserNameProjectNameLayoutImport } from './routes/_layout import { Route as LayoutUserNameProjectNameLayoutIndexImport } from './routes/_layout/$userName/$projectName/_layout/index' import { Route as LayoutUserNameProjectNameLayoutWorkflowImport } from './routes/_layout/$userName/$projectName/_layout/workflow' import { Route as LayoutUserNameProjectNameLayoutSoftwareImport } from './routes/_layout/$userName/$projectName/_layout/software' +import { Route as LayoutUserNameProjectNameLayoutReproImport } from './routes/_layout/$userName/$projectName/_layout/repro' import { Route as LayoutUserNameProjectNameLayoutReferencesImport } from './routes/_layout/$userName/$projectName/_layout/references' import { Route as LayoutUserNameProjectNameLayoutPublicationsImport } from './routes/_layout/$userName/$projectName/_layout/publications' import { Route as LayoutUserNameProjectNameLayoutNotebooksImport } from './routes/_layout/$userName/$projectName/_layout/notebooks' @@ -136,6 +137,12 @@ const LayoutUserNameProjectNameLayoutSoftwareRoute = getParentRoute: () => LayoutUserNameProjectNameLayoutRoute, } as any) +const LayoutUserNameProjectNameLayoutReproRoute = + LayoutUserNameProjectNameLayoutReproImport.update({ + path: '/repro', + getParentRoute: () => LayoutUserNameProjectNameLayoutRoute, + } as any) + const LayoutUserNameProjectNameLayoutReferencesRoute = LayoutUserNameProjectNameLayoutReferencesImport.update({ path: '/references', @@ -355,6 +362,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutUserNameProjectNameLayoutReferencesImport parentRoute: typeof LayoutUserNameProjectNameLayoutImport } + '/_layout/$userName/$projectName/_layout/repro': { + id: '/_layout/$userName/$projectName/_layout/repro' + path: '/repro' + fullPath: '/$userName/$projectName/repro' + preLoaderRoute: typeof LayoutUserNameProjectNameLayoutReproImport + parentRoute: typeof LayoutUserNameProjectNameLayoutImport + } '/_layout/$userName/$projectName/_layout/software': { id: '/_layout/$userName/$projectName/_layout/software' path: '/software' @@ -401,6 +415,7 @@ export const routeTree = rootRoute.addChildren({ LayoutUserNameProjectNameLayoutNotebooksRoute, LayoutUserNameProjectNameLayoutPublicationsRoute, LayoutUserNameProjectNameLayoutReferencesRoute, + LayoutUserNameProjectNameLayoutReproRoute, LayoutUserNameProjectNameLayoutSoftwareRoute, LayoutUserNameProjectNameLayoutWorkflowRoute, LayoutUserNameProjectNameLayoutIndexRoute, @@ -501,6 +516,7 @@ export const routeTree = rootRoute.addChildren({ "/_layout/$userName/$projectName/_layout/notebooks", "/_layout/$userName/$projectName/_layout/publications", "/_layout/$userName/$projectName/_layout/references", + "/_layout/$userName/$projectName/_layout/repro", "/_layout/$userName/$projectName/_layout/software", "/_layout/$userName/$projectName/_layout/workflow", "/_layout/$userName/$projectName/_layout/" @@ -542,6 +558,10 @@ export const routeTree = rootRoute.addChildren({ "filePath": "_layout/$userName/$projectName/_layout/references.tsx", "parent": "/_layout/$userName/$projectName/_layout" }, + "/_layout/$userName/$projectName/_layout/repro": { + "filePath": "_layout/$userName/$projectName/_layout/repro.tsx", + "parent": "/_layout/$userName/$projectName/_layout" + }, "/_layout/$userName/$projectName/_layout/software": { "filePath": "_layout/$userName/$projectName/_layout/software.tsx", "parent": "/_layout/$userName/$projectName/_layout" diff --git a/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx b/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx index c4fc0206..fc1d943c 100644 --- a/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx +++ b/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx @@ -16,11 +16,10 @@ import { IconButton, Link, Icon, - Code, } from "@chakra-ui/react" import { createFileRoute, Link as RouterLink } from "@tanstack/react-router" import { useState } from "react" -import { FaPlus, FaSync } from "react-icons/fa" +import { FaPlus } from "react-icons/fa" import { MdEdit } from "react-icons/md" import { ExternalLinkIcon } from "@chakra-ui/icons" @@ -46,11 +45,8 @@ function ProjectView() { readmeRequest, issuesRequest, questionsRequest, - reproCheckRequest, issueStateMutation, - putDevcontainerMutation, } = useProject(userName, projectName, showClosedTodos) - const reproCheck = reproCheckRequest.data const gitRepoUrl = projectRequest.data?.git_repo_url const codespacesUrl = String(gitRepoUrl).replace("://github.com/", "://codespaces.new/") + @@ -269,153 +265,6 @@ function ProjectView() { )} - {/* Reproducibility check */} - - - - Reproducibility check - - } - size={"xs"} - onClick={() => reproCheckRequest.refetch()} - /> - - {reproCheckRequest.isPending || - reproCheckRequest.isRefetching || - putDevcontainerMutation.isPending ? ( - - - - ) : ( - <> - - Has README.md: {reproCheck?.has_readme ? "✅" : "❌"} - - - README.md has instructions:{" "} - {reproCheck?.instructions_in_readme ? "✅" : "❌"} - - - DVC initialized: {reproCheck?.is_dvc_repo ? "✅" : "❌"} - - - DVC remote defined:{" "} - {reproCheck?.n_dvc_remotes ? "✅" : "❌"} - - - Has pipeline (dvc.yaml):{" "} - {reproCheck?.has_pipeline ? "✅" : "❌"} - - - Has Calkit metadata (calkit.yaml):{" "} - {reproCheck?.has_calkit_info ? "✅" : "❌"} - - - Has dev container spec:{" "} - {reproCheck?.has_dev_container ? ( - "✅" - ) : ( - <> - {"❌ "} - putDevcontainerMutation.mutate()}> - 🔧 - - - )} - - - Environments defined:{" "} - {reproCheck ? ( - <> - {reproCheck.n_environments}{" "} - {reproCheck.n_environments ? "✅" : "❌"} - - ) : ( - "" - )} - - - Pipeline stages run in an environment:{" "} - {reproCheck ? ( - <> - {reproCheck.n_stages_with_env}/{reproCheck.n_stages}{" "} - {reproCheck.n_stages_without_env ? "❌" : "✅"} - - ) : ( - "" - )} - - - Datasets imported or created by pipeline:{" "} - {reproCheck ? ( - <> - {reproCheck.n_datasets_with_import_or_stage}/ - {reproCheck.n_datasets}{" "} - {reproCheck.n_datasets_no_import_or_stage ? "❌" : "✅"} - - ) : ( - "" - )} - - - Figures imported or created by pipeline:{" "} - {reproCheck ? ( - <> - {reproCheck.n_figures_with_import_or_stage}/ - {reproCheck.n_figures}{" "} - {reproCheck.n_figures_no_import_or_stage ? "❌" : "✅"} - - ) : ( - "" - )} - - - Publications imported or created by pipeline:{" "} - {reproCheck ? ( - <> - {reproCheck.n_publications_with_import_or_stage}/ - {reproCheck.n_publications}{" "} - {reproCheck.n_publications_no_import_or_stage - ? "❌" - : "✅"} - - ) : ( - "" - )} - - - Recommendation - - {reproCheck?.recommendation ? ( - <> - {reproCheck.recommendation} - - ) : ( - - This project looks good from here! Check in depth locally - with `calkit status` and `calkit run`. - - )} - - )} - {/* Quick actions */} diff --git a/frontend/src/routes/_layout/$userName/$projectName/_layout/repro.tsx b/frontend/src/routes/_layout/$userName/$projectName/_layout/repro.tsx new file mode 100644 index 00000000..4a588242 --- /dev/null +++ b/frontend/src/routes/_layout/$userName/$projectName/_layout/repro.tsx @@ -0,0 +1,172 @@ +import { + Box, + Spinner, + Flex, + Heading, + Text, + useColorModeValue, + IconButton, + Link, + Code, +} from "@chakra-ui/react" +import { createFileRoute } from "@tanstack/react-router" +import { FaSync } from "react-icons/fa" + +import Markdown from "../../../../../components/Common/Markdown" +import useProject from "../../../../../hooks/useProject" + +export const Route = createFileRoute( + "/_layout/$userName/$projectName/_layout/repro", +)({ + component: Repro, +}) + +function Repro() { + const secBgColor = useColorModeValue("ui.secondary", "ui.darkSlate") + const { userName, projectName } = Route.useParams() + const { reproCheckRequest, putDevcontainerMutation } = useProject( + userName, + projectName, + false, + ) + const reproCheck = reproCheckRequest.data + + return ( + <> + + + + Reproducibility check + + } + size={"xs"} + onClick={() => reproCheckRequest.refetch()} + /> + + {reproCheckRequest.isPending || + reproCheckRequest.isRefetching || + putDevcontainerMutation.isPending ? ( + + + + ) : ( + <> + Has README.md: {reproCheck?.has_readme ? "✅" : "❌"} + + README.md has instructions:{" "} + {reproCheck?.instructions_in_readme ? "✅" : "❌"} + + + DVC initialized: {reproCheck?.is_dvc_repo ? "✅" : "❌"} + + + DVC remote defined: {reproCheck?.n_dvc_remotes ? "✅" : "❌"} + + + Has pipeline (dvc.yaml):{" "} + {reproCheck?.has_pipeline ? "✅" : "❌"} + + + Has Calkit metadata (calkit.yaml):{" "} + {reproCheck?.has_calkit_info ? "✅" : "❌"} + + + Has dev container spec:{" "} + {reproCheck?.has_dev_container ? ( + "✅" + ) : ( + <> + {"❌ "} + putDevcontainerMutation.mutate()}> + 🔧 + + + )} + + + Environments defined:{" "} + {reproCheck ? ( + <> + {reproCheck.n_environments}{" "} + {reproCheck.n_environments ? "✅" : "❌"} + + ) : ( + "" + )} + + + Pipeline stages run in an environment:{" "} + {reproCheck ? ( + <> + {reproCheck.n_stages_with_env}/{reproCheck.n_stages}{" "} + {reproCheck.n_stages_without_env ? "❌" : "✅"} + + ) : ( + "" + )} + + + Datasets imported or created by pipeline:{" "} + {reproCheck ? ( + <> + {reproCheck.n_datasets_with_import_or_stage}/ + {reproCheck.n_datasets}{" "} + {reproCheck.n_datasets_no_import_or_stage ? "❌" : "✅"} + + ) : ( + "" + )} + + + Figures imported or created by pipeline:{" "} + {reproCheck ? ( + <> + {reproCheck.n_figures_with_import_or_stage}/ + {reproCheck.n_figures}{" "} + {reproCheck.n_figures_no_import_or_stage ? "❌" : "✅"} + + ) : ( + "" + )} + + + Publications imported or created by pipeline:{" "} + {reproCheck ? ( + <> + {reproCheck.n_publications_with_import_or_stage}/ + {reproCheck.n_publications}{" "} + {reproCheck.n_publications_no_import_or_stage ? "❌" : "✅"} + + ) : ( + "" + )} + + + Recommendation + + {reproCheck?.recommendation ? ( + <> + {reproCheck.recommendation} + + ) : ( + + This project looks good from here! Check in depth locally with + `calkit status` and `calkit run`. + + )} + + )} + + + ) +} From 8f26808140df6d7b6b542809fa0397c7b5ad8ee1 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 09:44:27 -0800 Subject: [PATCH 02/21] Revert "Move repro check to its own page" This reverts commit db0c979a163f1c23023b5c4d24a9e140d6e54c44. --- .../src/components/Common/SidebarItems.tsx | 2 - frontend/src/routeTree.gen.ts | 20 -- .../$userName/$projectName/_layout/index.tsx | 153 +++++++++++++++- .../$userName/$projectName/_layout/repro.tsx | 172 ------------------ 4 files changed, 152 insertions(+), 195 deletions(-) delete mode 100644 frontend/src/routes/_layout/$userName/$projectName/_layout/repro.tsx diff --git a/frontend/src/components/Common/SidebarItems.tsx b/frontend/src/components/Common/SidebarItems.tsx index d94b12e5..809f73ed 100644 --- a/frontend/src/components/Common/SidebarItems.tsx +++ b/frontend/src/components/Common/SidebarItems.tsx @@ -10,7 +10,6 @@ import { FiFolder, } from "react-icons/fi" import { FaLaptop } from "react-icons/fa" -import { FaEquals } from "react-icons/fa6" import { IoLibraryOutline } from "react-icons/io5" import axios from "axios" import { useQuery } from "@tanstack/react-query" @@ -27,7 +26,6 @@ const items = [ { icon: FiBookOpen, title: "Publications", path: "/publications" }, { icon: SiJupyter, title: "Notebooks", path: "/notebooks" }, { icon: FiHardDrive, title: "Software", path: "/software" }, - { icon: FaEquals, title: "Reproducibility", path: "/repro" }, { icon: FiUsers, title: "Collaborators", path: "/collaborators" }, { icon: IoLibraryOutline, title: "References", path: "/references" }, { icon: FiFolder, title: "All files", path: "/files" }, diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 4e6afa1a..011bdb97 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -29,7 +29,6 @@ import { Route as LayoutUserNameProjectNameLayoutImport } from './routes/_layout import { Route as LayoutUserNameProjectNameLayoutIndexImport } from './routes/_layout/$userName/$projectName/_layout/index' import { Route as LayoutUserNameProjectNameLayoutWorkflowImport } from './routes/_layout/$userName/$projectName/_layout/workflow' import { Route as LayoutUserNameProjectNameLayoutSoftwareImport } from './routes/_layout/$userName/$projectName/_layout/software' -import { Route as LayoutUserNameProjectNameLayoutReproImport } from './routes/_layout/$userName/$projectName/_layout/repro' import { Route as LayoutUserNameProjectNameLayoutReferencesImport } from './routes/_layout/$userName/$projectName/_layout/references' import { Route as LayoutUserNameProjectNameLayoutPublicationsImport } from './routes/_layout/$userName/$projectName/_layout/publications' import { Route as LayoutUserNameProjectNameLayoutNotebooksImport } from './routes/_layout/$userName/$projectName/_layout/notebooks' @@ -137,12 +136,6 @@ const LayoutUserNameProjectNameLayoutSoftwareRoute = getParentRoute: () => LayoutUserNameProjectNameLayoutRoute, } as any) -const LayoutUserNameProjectNameLayoutReproRoute = - LayoutUserNameProjectNameLayoutReproImport.update({ - path: '/repro', - getParentRoute: () => LayoutUserNameProjectNameLayoutRoute, - } as any) - const LayoutUserNameProjectNameLayoutReferencesRoute = LayoutUserNameProjectNameLayoutReferencesImport.update({ path: '/references', @@ -362,13 +355,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutUserNameProjectNameLayoutReferencesImport parentRoute: typeof LayoutUserNameProjectNameLayoutImport } - '/_layout/$userName/$projectName/_layout/repro': { - id: '/_layout/$userName/$projectName/_layout/repro' - path: '/repro' - fullPath: '/$userName/$projectName/repro' - preLoaderRoute: typeof LayoutUserNameProjectNameLayoutReproImport - parentRoute: typeof LayoutUserNameProjectNameLayoutImport - } '/_layout/$userName/$projectName/_layout/software': { id: '/_layout/$userName/$projectName/_layout/software' path: '/software' @@ -415,7 +401,6 @@ export const routeTree = rootRoute.addChildren({ LayoutUserNameProjectNameLayoutNotebooksRoute, LayoutUserNameProjectNameLayoutPublicationsRoute, LayoutUserNameProjectNameLayoutReferencesRoute, - LayoutUserNameProjectNameLayoutReproRoute, LayoutUserNameProjectNameLayoutSoftwareRoute, LayoutUserNameProjectNameLayoutWorkflowRoute, LayoutUserNameProjectNameLayoutIndexRoute, @@ -516,7 +501,6 @@ export const routeTree = rootRoute.addChildren({ "/_layout/$userName/$projectName/_layout/notebooks", "/_layout/$userName/$projectName/_layout/publications", "/_layout/$userName/$projectName/_layout/references", - "/_layout/$userName/$projectName/_layout/repro", "/_layout/$userName/$projectName/_layout/software", "/_layout/$userName/$projectName/_layout/workflow", "/_layout/$userName/$projectName/_layout/" @@ -558,10 +542,6 @@ export const routeTree = rootRoute.addChildren({ "filePath": "_layout/$userName/$projectName/_layout/references.tsx", "parent": "/_layout/$userName/$projectName/_layout" }, - "/_layout/$userName/$projectName/_layout/repro": { - "filePath": "_layout/$userName/$projectName/_layout/repro.tsx", - "parent": "/_layout/$userName/$projectName/_layout" - }, "/_layout/$userName/$projectName/_layout/software": { "filePath": "_layout/$userName/$projectName/_layout/software.tsx", "parent": "/_layout/$userName/$projectName/_layout" diff --git a/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx b/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx index fc1d943c..c4fc0206 100644 --- a/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx +++ b/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx @@ -16,10 +16,11 @@ import { IconButton, Link, Icon, + Code, } from "@chakra-ui/react" import { createFileRoute, Link as RouterLink } from "@tanstack/react-router" import { useState } from "react" -import { FaPlus } from "react-icons/fa" +import { FaPlus, FaSync } from "react-icons/fa" import { MdEdit } from "react-icons/md" import { ExternalLinkIcon } from "@chakra-ui/icons" @@ -45,8 +46,11 @@ function ProjectView() { readmeRequest, issuesRequest, questionsRequest, + reproCheckRequest, issueStateMutation, + putDevcontainerMutation, } = useProject(userName, projectName, showClosedTodos) + const reproCheck = reproCheckRequest.data const gitRepoUrl = projectRequest.data?.git_repo_url const codespacesUrl = String(gitRepoUrl).replace("://github.com/", "://codespaces.new/") + @@ -265,6 +269,153 @@ function ProjectView() { )} + {/* Reproducibility check */} + + + + Reproducibility check + + } + size={"xs"} + onClick={() => reproCheckRequest.refetch()} + /> + + {reproCheckRequest.isPending || + reproCheckRequest.isRefetching || + putDevcontainerMutation.isPending ? ( + + + + ) : ( + <> + + Has README.md: {reproCheck?.has_readme ? "✅" : "❌"} + + + README.md has instructions:{" "} + {reproCheck?.instructions_in_readme ? "✅" : "❌"} + + + DVC initialized: {reproCheck?.is_dvc_repo ? "✅" : "❌"} + + + DVC remote defined:{" "} + {reproCheck?.n_dvc_remotes ? "✅" : "❌"} + + + Has pipeline (dvc.yaml):{" "} + {reproCheck?.has_pipeline ? "✅" : "❌"} + + + Has Calkit metadata (calkit.yaml):{" "} + {reproCheck?.has_calkit_info ? "✅" : "❌"} + + + Has dev container spec:{" "} + {reproCheck?.has_dev_container ? ( + "✅" + ) : ( + <> + {"❌ "} + putDevcontainerMutation.mutate()}> + 🔧 + + + )} + + + Environments defined:{" "} + {reproCheck ? ( + <> + {reproCheck.n_environments}{" "} + {reproCheck.n_environments ? "✅" : "❌"} + + ) : ( + "" + )} + + + Pipeline stages run in an environment:{" "} + {reproCheck ? ( + <> + {reproCheck.n_stages_with_env}/{reproCheck.n_stages}{" "} + {reproCheck.n_stages_without_env ? "❌" : "✅"} + + ) : ( + "" + )} + + + Datasets imported or created by pipeline:{" "} + {reproCheck ? ( + <> + {reproCheck.n_datasets_with_import_or_stage}/ + {reproCheck.n_datasets}{" "} + {reproCheck.n_datasets_no_import_or_stage ? "❌" : "✅"} + + ) : ( + "" + )} + + + Figures imported or created by pipeline:{" "} + {reproCheck ? ( + <> + {reproCheck.n_figures_with_import_or_stage}/ + {reproCheck.n_figures}{" "} + {reproCheck.n_figures_no_import_or_stage ? "❌" : "✅"} + + ) : ( + "" + )} + + + Publications imported or created by pipeline:{" "} + {reproCheck ? ( + <> + {reproCheck.n_publications_with_import_or_stage}/ + {reproCheck.n_publications}{" "} + {reproCheck.n_publications_no_import_or_stage + ? "❌" + : "✅"} + + ) : ( + "" + )} + + + Recommendation + + {reproCheck?.recommendation ? ( + <> + {reproCheck.recommendation} + + ) : ( + + This project looks good from here! Check in depth locally + with `calkit status` and `calkit run`. + + )} + + )} + {/* Quick actions */} diff --git a/frontend/src/routes/_layout/$userName/$projectName/_layout/repro.tsx b/frontend/src/routes/_layout/$userName/$projectName/_layout/repro.tsx deleted file mode 100644 index 4a588242..00000000 --- a/frontend/src/routes/_layout/$userName/$projectName/_layout/repro.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { - Box, - Spinner, - Flex, - Heading, - Text, - useColorModeValue, - IconButton, - Link, - Code, -} from "@chakra-ui/react" -import { createFileRoute } from "@tanstack/react-router" -import { FaSync } from "react-icons/fa" - -import Markdown from "../../../../../components/Common/Markdown" -import useProject from "../../../../../hooks/useProject" - -export const Route = createFileRoute( - "/_layout/$userName/$projectName/_layout/repro", -)({ - component: Repro, -}) - -function Repro() { - const secBgColor = useColorModeValue("ui.secondary", "ui.darkSlate") - const { userName, projectName } = Route.useParams() - const { reproCheckRequest, putDevcontainerMutation } = useProject( - userName, - projectName, - false, - ) - const reproCheck = reproCheckRequest.data - - return ( - <> - - - - Reproducibility check - - } - size={"xs"} - onClick={() => reproCheckRequest.refetch()} - /> - - {reproCheckRequest.isPending || - reproCheckRequest.isRefetching || - putDevcontainerMutation.isPending ? ( - - - - ) : ( - <> - Has README.md: {reproCheck?.has_readme ? "✅" : "❌"} - - README.md has instructions:{" "} - {reproCheck?.instructions_in_readme ? "✅" : "❌"} - - - DVC initialized: {reproCheck?.is_dvc_repo ? "✅" : "❌"} - - - DVC remote defined: {reproCheck?.n_dvc_remotes ? "✅" : "❌"} - - - Has pipeline (dvc.yaml):{" "} - {reproCheck?.has_pipeline ? "✅" : "❌"} - - - Has Calkit metadata (calkit.yaml):{" "} - {reproCheck?.has_calkit_info ? "✅" : "❌"} - - - Has dev container spec:{" "} - {reproCheck?.has_dev_container ? ( - "✅" - ) : ( - <> - {"❌ "} - putDevcontainerMutation.mutate()}> - 🔧 - - - )} - - - Environments defined:{" "} - {reproCheck ? ( - <> - {reproCheck.n_environments}{" "} - {reproCheck.n_environments ? "✅" : "❌"} - - ) : ( - "" - )} - - - Pipeline stages run in an environment:{" "} - {reproCheck ? ( - <> - {reproCheck.n_stages_with_env}/{reproCheck.n_stages}{" "} - {reproCheck.n_stages_without_env ? "❌" : "✅"} - - ) : ( - "" - )} - - - Datasets imported or created by pipeline:{" "} - {reproCheck ? ( - <> - {reproCheck.n_datasets_with_import_or_stage}/ - {reproCheck.n_datasets}{" "} - {reproCheck.n_datasets_no_import_or_stage ? "❌" : "✅"} - - ) : ( - "" - )} - - - Figures imported or created by pipeline:{" "} - {reproCheck ? ( - <> - {reproCheck.n_figures_with_import_or_stage}/ - {reproCheck.n_figures}{" "} - {reproCheck.n_figures_no_import_or_stage ? "❌" : "✅"} - - ) : ( - "" - )} - - - Publications imported or created by pipeline:{" "} - {reproCheck ? ( - <> - {reproCheck.n_publications_with_import_or_stage}/ - {reproCheck.n_publications}{" "} - {reproCheck.n_publications_no_import_or_stage ? "❌" : "✅"} - - ) : ( - "" - )} - - - Recommendation - - {reproCheck?.recommendation ? ( - <> - {reproCheck.recommendation} - - ) : ( - - This project looks good from here! Check in depth locally with - `calkit status` and `calkit run`. - - )} - - )} - - - ) -} From 3b0c8643373584647dd6d02a5300faceb5ee3c22 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 09:48:22 -0800 Subject: [PATCH 03/21] Move TODOs under questions --- .../$userName/$projectName/_layout/index.tsx | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx b/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx index c4fc0206..28d45f19 100644 --- a/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx +++ b/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx @@ -156,6 +156,47 @@ function ProjectView() { "" )} + + + {/* Questions */} + + + + Questions + + } + size={"xs"} + onClick={newQuestionModal.onOpen} + /> + + + {questionsRequest.isPending ? ( + + + + ) : ( + + {questionsRequest.data?.map((question) => ( + + {question.question} + + ))} + + )} + {/* To-dos (issues) */} @@ -228,47 +269,6 @@ function ProjectView() { )} - - - {/* Questions */} - - - - Questions - - } - size={"xs"} - onClick={newQuestionModal.onOpen} - /> - - - {questionsRequest.isPending ? ( - - - - ) : ( - - {questionsRequest.data?.map((question) => ( - - {question.question} - - ))} - - )} - {/* Reproducibility check */} From 5c0c369b7d29b06f3d346ec2c60d4569660a6b33 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 10:04:45 -0800 Subject: [PATCH 04/21] Use individual spinner for project README widget --- .../$userName/$projectName/_layout/index.tsx | 694 +++++++++--------- 1 file changed, 336 insertions(+), 358 deletions(-) diff --git a/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx b/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx index 28d45f19..63019ed5 100644 --- a/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx +++ b/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx @@ -76,393 +76,371 @@ function ProjectView() { return ( <> - {readmeRequest.isPending ? ( - - - - ) : ( - - - {/* Description */} - - - Description - {projectRequest.data ? ( - <> - } - size={"xs"} - onClick={editProjectModal.onOpen} - /> - - - ) : ( - "" - )} - - {projectRequest.data?.description ? ( - {projectRequest?.data?.description} - ) : ( - "" - )} - - {/* README */} - - - README - + + + {/* Showcase */} + + + Description + {projectRequest.data ? ( + <> } size={"xs"} + onClick={editProjectModal.onOpen} /> - - - {readmeRequest.data ? ( - - {removeFirstLine(atob(String(readmeRequest?.data?.content)))} - + + ) : ( "" )} - + + {projectRequest.data?.description ? ( + {projectRequest?.data?.description} + ) : ( + "" + )} - - {/* Questions */} - - - - Questions - + {/* README */} + + + README + } + icon={} size={"xs"} - onClick={newQuestionModal.onOpen} - /> - + + + {readmeRequest.isPending ? ( + + - {questionsRequest.isPending ? ( - - - - ) : ( - - {questionsRequest.data?.map((question) => ( - - {question.question} - - ))} - - )} - - {/* To-dos (issues) */} - - - - - To-do - } - size={"xs"} - onClick={newIssueModal.onOpen} - /> - - - - - - - - Show closed - - - - + ) : readmeRequest.data ? ( + + {removeFirstLine(atob(String(readmeRequest?.data?.content)))} + + ) : ( + "" + )} + + + + {/* Questions */} + + + + Questions + + } + size={"xs"} + onClick={newQuestionModal.onOpen} + /> + + + {questionsRequest.isPending ? ( + + - {issuesRequest.isPending || - issuesRequest.isRefetching || - issueStateMutation.isPending ? ( - - + ) : ( + + {questionsRequest.data?.map((question) => ( + + {question.question} + + ))} + + )} + + {/* To-dos (issues) */} + + + + + To-do + } + size={"xs"} + onClick={newIssueModal.onOpen} + /> - ) : ( - <> - {issuesRequest?.data?.map((issue) => ( - - - - {" "} - {issue.title} ( - - #{issue.number} - - ) - - - ))} - - )} - - {/* Reproducibility check */} - - - - Reproducibility check - - } - size={"xs"} - onClick={() => reproCheckRequest.refetch()} + + + + + + + Show closed + + + + + + {issuesRequest.isPending || + issuesRequest.isRefetching || + issueStateMutation.isPending ? ( + + - {reproCheckRequest.isPending || - reproCheckRequest.isRefetching || - putDevcontainerMutation.isPending ? ( - - - - ) : ( - <> - - Has README.md: {reproCheck?.has_readme ? "✅" : "❌"} - - - README.md has instructions:{" "} - {reproCheck?.instructions_in_readme ? "✅" : "❌"} - - - DVC initialized: {reproCheck?.is_dvc_repo ? "✅" : "❌"} - - - DVC remote defined:{" "} - {reproCheck?.n_dvc_remotes ? "✅" : "❌"} - - - Has pipeline (dvc.yaml):{" "} - {reproCheck?.has_pipeline ? "✅" : "❌"} - - - Has Calkit metadata (calkit.yaml):{" "} - {reproCheck?.has_calkit_info ? "✅" : "❌"} - - - Has dev container spec:{" "} - {reproCheck?.has_dev_container ? ( - "✅" - ) : ( - <> - {"❌ "} - putDevcontainerMutation.mutate()}> - 🔧 - - - )} - - - Environments defined:{" "} - {reproCheck ? ( - <> - {reproCheck.n_environments}{" "} - {reproCheck.n_environments ? "✅" : "❌"} - - ) : ( - "" - )} - - - Pipeline stages run in an environment:{" "} - {reproCheck ? ( - <> - {reproCheck.n_stages_with_env}/{reproCheck.n_stages}{" "} - {reproCheck.n_stages_without_env ? "❌" : "✅"} - - ) : ( - "" - )} - - - Datasets imported or created by pipeline:{" "} - {reproCheck ? ( - <> - {reproCheck.n_datasets_with_import_or_stage}/ - {reproCheck.n_datasets}{" "} - {reproCheck.n_datasets_no_import_or_stage ? "❌" : "✅"} - - ) : ( - "" - )} - - - Figures imported or created by pipeline:{" "} - {reproCheck ? ( - <> - {reproCheck.n_figures_with_import_or_stage}/ - {reproCheck.n_figures}{" "} - {reproCheck.n_figures_no_import_or_stage ? "❌" : "✅"} - - ) : ( - "" - )} - - - Publications imported or created by pipeline:{" "} - {reproCheck ? ( - <> - {reproCheck.n_publications_with_import_or_stage}/ - {reproCheck.n_publications}{" "} - {reproCheck.n_publications_no_import_or_stage - ? "❌" - : "✅"} - - ) : ( - "" - )} - - + {issuesRequest?.data?.map((issue) => ( + - Recommendation - - {reproCheck?.recommendation ? ( + + + {" "} + {issue.title} ( + + #{issue.number} + + ) + + + ))} + + )} + + {/* Reproducibility check */} + + + + Reproducibility check + + } + size={"xs"} + onClick={() => reproCheckRequest.refetch()} + /> + + {reproCheckRequest.isPending || + reproCheckRequest.isRefetching || + putDevcontainerMutation.isPending ? ( + + + + ) : ( + <> + + Has README.md: {reproCheck?.has_readme ? "✅" : "❌"} + + + README.md has instructions:{" "} + {reproCheck?.instructions_in_readme ? "✅" : "❌"} + + + DVC initialized: {reproCheck?.is_dvc_repo ? "✅" : "❌"} + + + DVC remote defined: {reproCheck?.n_dvc_remotes ? "✅" : "❌"} + + + Has pipeline (dvc.yaml):{" "} + {reproCheck?.has_pipeline ? "✅" : "❌"} + + + Has Calkit metadata (calkit.yaml):{" "} + {reproCheck?.has_calkit_info ? "✅" : "❌"} + + + Has dev container spec:{" "} + {reproCheck?.has_dev_container ? ( + "✅" + ) : ( + <> + {"❌ "} + putDevcontainerMutation.mutate()}> + 🔧 + + + )} + + + Environments defined:{" "} + {reproCheck ? ( <> - {reproCheck.recommendation} + {reproCheck.n_environments}{" "} + {reproCheck.n_environments ? "✅" : "❌"} ) : ( - - This project looks good from here! Check in depth locally - with `calkit status` and `calkit run`. - + "" )} - - )} - - {/* Quick actions */} - - - Quick actions - - - 📜{" "} - - Create a new publication from a template - - - - 🔒{" "} - - Manage user tokens - - - - 🚀{" "} - - Open in GitHub Codespaces{" "} - - - - - 🔑{" "} - + + Pipeline stages run in an environment:{" "} + {reproCheck ? ( + <> + {reproCheck.n_stages_with_env}/{reproCheck.n_stages}{" "} + {reproCheck.n_stages_without_env ? "❌" : "✅"} + + ) : ( + "" + )} + + + Datasets imported or created by pipeline:{" "} + {reproCheck ? ( + <> + {reproCheck.n_datasets_with_import_or_stage}/ + {reproCheck.n_datasets}{" "} + {reproCheck.n_datasets_no_import_or_stage ? "❌" : "✅"} + + ) : ( + "" + )} + + + Figures imported or created by pipeline:{" "} + {reproCheck ? ( + <> + {reproCheck.n_figures_with_import_or_stage}/ + {reproCheck.n_figures}{" "} + {reproCheck.n_figures_no_import_or_stage ? "❌" : "✅"} + + ) : ( + "" + )} + + + Publications imported or created by pipeline:{" "} + {reproCheck ? ( + <> + {reproCheck.n_publications_with_import_or_stage}/ + {reproCheck.n_publications}{" "} + {reproCheck.n_publications_no_import_or_stage + ? "❌" + : "✅"} + + ) : ( + "" + )} + + - Configure GitHub Codespaces secrets{" "} - - - - - + Recommendation + + {reproCheck?.recommendation ? ( + <> + {reproCheck.recommendation} + + ) : ( + + This project looks good from here! Check in depth locally + with `calkit status` and `calkit run`. + + )} + + )} + + {/* Quick actions */} + + + Quick actions + + + 📜{" "} + + Create a new publication from a template + + + + 🔒{" "} + + Manage user tokens + + + + 🚀{" "} + + Open in GitHub Codespaces{" "} + + + + + 🔑{" "} + + Configure GitHub Codespaces secrets{" "} + + + - - )} + + + ) } From ee69f23675cff7da5044b7a7d7496e5db1f90014 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 10:18:21 -0800 Subject: [PATCH 05/21] Add empty showcase component --- .../components/Projects/ProjectShowcase.tsx | 18 ++++++++++ .../$userName/$projectName/_layout/index.tsx | 33 +++---------------- 2 files changed, 23 insertions(+), 28 deletions(-) create mode 100644 frontend/src/components/Projects/ProjectShowcase.tsx diff --git a/frontend/src/components/Projects/ProjectShowcase.tsx b/frontend/src/components/Projects/ProjectShowcase.tsx new file mode 100644 index 00000000..16774944 --- /dev/null +++ b/frontend/src/components/Projects/ProjectShowcase.tsx @@ -0,0 +1,18 @@ +import { Text } from "@chakra-ui/react" + +interface ProjectShowcaseProps { + ownerName: string + projectName: string +} + +function ProjectShowcase({ ownerName, projectName }: ProjectShowcaseProps) { + return ( + <> + + Showcase for {ownerName}/{projectName} + + + ) +} + +export default ProjectShowcase diff --git a/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx b/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx index 63019ed5..fb0dd3b8 100644 --- a/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx +++ b/frontend/src/routes/_layout/$userName/$projectName/_layout/index.tsx @@ -29,7 +29,7 @@ import CreateIssue from "../../../../../components/Projects/CreateIssue" import CreateQuestion from "../../../../../components/Projects/CreateQuestion" import NewPublication from "../../../../../components/Publications/NewPublication" import useProject from "../../../../../hooks/useProject" -import EditProject from "../../../../../components/Projects/EditProject" +import ProjectShowcase from "../../../../../components/Projects/ProjectShowcase" export const Route = createFileRoute( "/_layout/$userName/$projectName/_layout/", @@ -72,7 +72,6 @@ function ProjectView() { const newIssueModal = useDisclosure() const newQuestionModal = useDisclosure() const newPubTemplateModal = useDisclosure() - const editProjectModal = useDisclosure() return ( <> @@ -89,33 +88,11 @@ function ProjectView() { overflow="auto" > - Description - {projectRequest.data ? ( - <> - } - size={"xs"} - onClick={editProjectModal.onOpen} - /> - - - ) : ( - "" - )} + + Showcase + - {projectRequest.data?.description ? ( - {projectRequest?.data?.description} - ) : ( - "" - )} + {/* README */} Date: Mon, 20 Jan 2025 10:32:14 -0800 Subject: [PATCH 06/21] Update Ruff config --- backend/pyproject.toml | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 4f65562c..9d4bc8e6 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -58,27 +58,9 @@ strict = true exclude = ["venv", ".venv", "alembic"] [tool.ruff] -target-version = "py310" +target-version = "py312" exclude = ["alembic"] -[tool.ruff.lint] -select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "UP", # pyupgrade - "ARG001", # unused arguments in functions -] -ignore = [ - "E501", # line too long, handled by black - "B008", # do not perform function calls in argument defaults - "W191", # indentation contains tabs - "B904", # Allow raising exceptions without from e, for HTTPException -] - [tool.ruff.lint.pyupgrade] # Preserve types, even if a file imports `from __future__ import annotations`. keep-runtime-typing = true From 7569018f0e92a0b8c1f067192709356ea8e150e6 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 10:33:23 -0800 Subject: [PATCH 07/21] Use generic Exception --- backend/app/api/routes/projects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/api/routes/projects.py b/backend/app/api/routes/projects.py index bae6baf0..699075ad 100644 --- a/backend/app/api/routes/projects.py +++ b/backend/app/api/routes/projects.py @@ -240,7 +240,7 @@ def create_project( logger.warning(f"Failed to create: {resp.json()}") try: message = resp.json()["errors"][0]["message"].capitalize() - except: + except Exception: message = "Failed to create GitHub repo" raise HTTPException(resp.status_code, message) resp_json = resp.json() From e86907ed9dc52a96d7aa70de7c48567e839249f4 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 11:16:33 -0800 Subject: [PATCH 08/21] Finish API endpoint to get figure --- backend/app/api/routes/projects.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/backend/app/api/routes/projects.py b/backend/app/api/routes/projects.py index 699075ad..a203e399 100644 --- a/backend/app/api/routes/projects.py +++ b/backend/app/api/routes/projects.py @@ -1227,7 +1227,31 @@ def get_project_figure( current_user: CurrentUser, session: SessionDep, ) -> Figure: - raise HTTPException(501) + project = app.projects.get_project( + session=session, + owner_name=owner_name, + project_name=project_name, + current_user=current_user, + min_access_level="read", + ) + ck_info = get_ck_info( + project=project, user=current_user, session=session, ttl=120 + ) + figures = ck_info.get("figures", []) + # Get the figure content and base64 encode it + for fig in figures: + if fig.get("path") == figure_path: + item = get_project_contents( + owner_name=owner_name, + project_name=project_name, + session=session, + current_user=current_user, + path=fig["path"], + ) + fig["content"] = item.content + fig["url"] = item.url + return Figure.model_validate(fig) + raise HTTPException(404, "Figure not found") @router.post("/projects/{owner_name}/{project_name}/figures") From c26839d24b30ec7c565782591763a174719d1fa1 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 11:16:54 -0800 Subject: [PATCH 09/21] Add API endpoint for getting project showcase --- backend/app/api/routes/projects.py | 73 ++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/backend/app/api/routes/projects.py b/backend/app/api/routes/projects.py index a203e399..55d159b6 100644 --- a/backend/app/api/routes/projects.py +++ b/backend/app/api/routes/projects.py @@ -2656,3 +2656,76 @@ def get_project_app( if project_app is None: return return ProjectApp.model_validate(project_app) + + +class ProjectShowcaseFigureInput(BaseModel): + figure: str + + +class ProjectShowcaseFigure(BaseModel): + figure: Figure + + +class ProjectShowcaseText(BaseModel): + text: str + + +class ProjectShowcaseInput(BaseModel): + elements: list[ProjectShowcaseFigureInput | ProjectShowcaseText] + + +class ProjectShowcase(BaseModel): + elements: list[ProjectShowcaseFigure | ProjectShowcaseText] + + +@router.get("/projects/{owner_name}/{project_name}/showcase") +def get_project_showcase( + owner_name: str, + project_name: str, + current_user: CurrentUser, + session: SessionDep, +) -> ProjectShowcase | None: + project = app.projects.get_project( + owner_name=owner_name, + project_name=project_name, + session=session, + current_user=current_user, + min_access_level="read", + ) + incorrectly_defined = ProjectShowcase( + elements=[ + ProjectShowcaseText(text="Showcase is not correctly defined.") + ] + ) + ck_info = get_ck_info( + project=project, user=current_user, session=session, ttl=120 + ) + showcase = ck_info.get("showcase") + if showcase is None: + return + try: + inputs = ProjectShowcaseInput.model_validate(dict(elements=showcase)) + except Exception: + return incorrectly_defined + # Iterate over showcase elements, fetching the contents to return + elements_out = [] + for element_in in inputs.elements: + if isinstance(element_in, ProjectShowcaseFigureInput): + try: + element_out = ProjectShowcaseFigure( + figure=get_project_figure( + owner_name=owner_name, + project_name=project_name, + session=session, + current_user=current_user, + figure_path=element_in.figure, + ) + ) + except Exception: + element_out = ProjectShowcaseText( + text=f"Figure at path '{element_in.figure}' not found" + ) + else: + element_out = element_in + elements_out.append(element_out) + return ProjectShowcase.model_validate(dict(elements=elements_out)) From 76c0fd6ddbeabe06e89d56fedd3e5ec8d52d357b Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 11:17:39 -0800 Subject: [PATCH 10/21] Add project showcase functionality to client --- frontend/src/client/models.ts | 12 +++++++++++ frontend/src/client/schemas.ts | 38 +++++++++++++++++++++++++++++++++ frontend/src/client/services.ts | 27 +++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/frontend/src/client/models.ts b/frontend/src/client/models.ts index 9ae5e9b3..224356d6 100644 --- a/frontend/src/client/models.ts +++ b/frontend/src/client/models.ts @@ -434,6 +434,18 @@ export type ProjectPublic = { current_user_access?: "read" | "write" | "admin" | "owner" | null } +export type ProjectShowcase = { + elements: Array +} + +export type ProjectShowcaseFigure = { + figure: Figure +} + +export type ProjectShowcaseText = { + text: string +} + export type ProjectsPublic = { data: Array count: number diff --git a/frontend/src/client/schemas.ts b/frontend/src/client/schemas.ts index 285d8fb0..79936f5f 100644 --- a/frontend/src/client/schemas.ts +++ b/frontend/src/client/schemas.ts @@ -2169,6 +2169,44 @@ export const $ProjectPublic = { }, } as const +export const $ProjectShowcase = { + properties: { + elements: { + type: "array", + contains: { + type: "any-of", + contains: [ + { + type: "ProjectShowcaseFigure", + }, + { + type: "ProjectShowcaseText", + }, + ], + }, + isRequired: true, + }, + }, +} as const + +export const $ProjectShowcaseFigure = { + properties: { + figure: { + type: "Figure", + isRequired: true, + }, + }, +} as const + +export const $ProjectShowcaseText = { + properties: { + text: { + type: "string", + isRequired: true, + }, + }, +} as const + export const $ProjectsPublic = { properties: { data: { diff --git a/frontend/src/client/services.ts b/frontend/src/client/services.ts index 4fbb1b32..11a02ad6 100644 --- a/frontend/src/client/services.ts +++ b/frontend/src/client/services.ts @@ -54,6 +54,7 @@ import type { ProjectCreate, ProjectPatch, ProjectPublic, + ProjectShowcase, ProjectsPublic, Publication, Question, @@ -379,6 +380,10 @@ export type ProjectsData = { ownerName: string projectName: string } + GetProjectShowcase: { + ownerName: string + projectName: string + } } export type OrgsData = { @@ -2129,6 +2134,28 @@ export class ProjectsService { }, }) } + + /** + * Get Project Showcase + * @returns unknown Successful Response + * @throws ApiError + */ + public static getProjectShowcase( + data: ProjectsData["GetProjectShowcase"], + ): CancelablePromise { + const { ownerName, projectName } = data + return __request(OpenAPI, { + method: "GET", + url: "/projects/{owner_name}/{project_name}/showcase", + path: { + owner_name: ownerName, + project_name: projectName, + }, + errors: { + 422: `Validation Error`, + }, + }) + } } export class OrgsService { From 43e8f98e9af1fc7805aa7a341b7af969ec6837e8 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 11:23:44 -0800 Subject: [PATCH 11/21] Add request for showcase --- .../src/components/Projects/ProjectShowcase.tsx | 17 +++++++++++++---- frontend/src/hooks/useProject.ts | 12 ++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Projects/ProjectShowcase.tsx b/frontend/src/components/Projects/ProjectShowcase.tsx index 16774944..8e11b6b4 100644 --- a/frontend/src/components/Projects/ProjectShowcase.tsx +++ b/frontend/src/components/Projects/ProjectShowcase.tsx @@ -1,4 +1,6 @@ -import { Text } from "@chakra-ui/react" +import { Text, Flex, Spinner } from "@chakra-ui/react" + +import useProject from "../../hooks/useProject" interface ProjectShowcaseProps { ownerName: string @@ -6,11 +8,18 @@ interface ProjectShowcaseProps { } function ProjectShowcase({ ownerName, projectName }: ProjectShowcaseProps) { + const { showcaseRequest } = useProject(ownerName, projectName, false) return ( <> - - Showcase for {ownerName}/{projectName} - + {showcaseRequest.isPending ? ( + + + + ) : showcaseRequest.data ? ( + Got some data + ) : ( + "" + )} ) } diff --git a/frontend/src/hooks/useProject.ts b/frontend/src/hooks/useProject.ts index c3bd16c3..8f57c754 100644 --- a/frontend/src/hooks/useProject.ts +++ b/frontend/src/hooks/useProject.ts @@ -66,6 +66,17 @@ const useProject = ( refetchOnMount: false, }) + const showcaseRequest = useQuery({ + queryKey: ["projects", userName, projectName, "showcase"], + queryFn: () => + ProjectsService.getProjectShowcase({ + ownerName: userName, + projectName: projectName, + }), + refetchOnWindowFocus: false, + refetchOnMount: false, + }) + interface IssueStateChange { state: "open" | "closed" issueNumber: number @@ -102,6 +113,7 @@ const useProject = ( issuesRequest, questionsRequest, reproCheckRequest, + showcaseRequest, issueStateMutation, putDevcontainerMutation, } From 58fc7c773aaffc740d98ee2fd1c960348e6128f5 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Mon, 20 Jan 2025 11:54:25 -0800 Subject: [PATCH 12/21] Add ability to define project showcase --- .../src/components/Figures/FigureView.tsx | 104 ++++++++++++++++++ .../components/Projects/ProjectShowcase.tsx | 19 +++- .../$userName/$projectName/_layout/index.tsx | 4 +- 3 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/Figures/FigureView.tsx diff --git a/frontend/src/components/Figures/FigureView.tsx b/frontend/src/components/Figures/FigureView.tsx new file mode 100644 index 00000000..84667270 --- /dev/null +++ b/frontend/src/components/Figures/FigureView.tsx @@ -0,0 +1,104 @@ +import { Box, Image, Text } from "@chakra-ui/react" +import Plot from "react-plotly.js" +import axios from "axios" +import { useQuery } from "@tanstack/react-query" +import { getRouteApi } from "@tanstack/react-router" + +import { type Figure } from "../../client" + +interface FigureViewProps { + figure: Figure +} + +function FigureView({ figure }: FigureViewProps) { + const routeApi = getRouteApi("/_layout/$userName/$projectName") + const { userName, projectName } = routeApi.useParams() + let figView = <>Not set + if (figure.path.endsWith(".pdf")) { + figView = ( + + + + ) + } else if ( + figure.path.endsWith(".png") || + figure.path.endsWith(".jpg") || + figure.path.endsWith(".jpeg") + ) { + figView = ( + + {figure.title} + + ) + } else if (figure.path.endsWith(".json")) { + const figObject = JSON.parse(atob(String(figure.content))) + const layout = figObject.layout + figView = ( + + + + ) + } else if (figure.path.endsWith(".html")) { + // Embed HTML figure in an iframe + const { data, isPending } = useQuery({ + queryFn: () => axios.get(String(figure.url)), + queryKey: [ + "projects", + userName, + projectName, + "figure-content", + figure.path, + ], + enabled: Boolean(!figure.content && figure.url), + }) + let figContent = figure.content + if (!figure.content && figure.url) { + figContent = data?.data + } else { + figContent = "No content found" + } + figView = ( + + {figContent ? ( +