From f37c271581d1439d76bfdcc0f15e20db801a8134 Mon Sep 17 00:00:00 2001 From: corpulent Date: Thu, 4 Aug 2022 13:04:58 +0300 Subject: [PATCH] feat: yaml importing --- services/backend/requirements.txt | 2 + services/backend/src/api/admin.py | 1 + .../api/migrations/0003_project_visibility.py | 18 +++ services/backend/src/api/models/project.py | 1 + services/backend/src/api/routing.py | 1 + services/backend/src/main/settings.py | 4 +- .../src/components/Modal/import/form-utils.ts | 47 +++++++ .../src/components/Modal/import/index.tsx | 115 ++++++++++++++++++ .../frontend/src/components/Project/index.tsx | 19 ++- .../src/components/Projects/PreviewBlock.tsx | 4 +- .../src/components/Projects/index.tsx | 59 +++++++-- .../src/components/global/SideBar.tsx | 10 +- .../global/VisibilitySwitch/index.tsx | 38 ++++++ .../frontend/src/hooks/useImportProject.ts | 18 +++ services/frontend/src/index.css | 3 + services/frontend/src/types/index.ts | 1 + 16 files changed, 318 insertions(+), 23 deletions(-) create mode 100644 services/backend/src/api/migrations/0003_project_visibility.py create mode 100644 services/frontend/src/components/Modal/import/form-utils.ts create mode 100644 services/frontend/src/components/Modal/import/index.tsx create mode 100644 services/frontend/src/components/global/VisibilitySwitch/index.tsx create mode 100644 services/frontend/src/hooks/useImportProject.ts diff --git a/services/backend/requirements.txt b/services/backend/requirements.txt index aaf9746..8a0793f 100644 --- a/services/backend/requirements.txt +++ b/services/backend/requirements.txt @@ -15,3 +15,5 @@ boto3==1.21.46 requests==2.27.1 pyaml==21.10.1 ruamel.yaml==0.17.21 +networkx==2.8.5 +numpy==1.23.1 diff --git a/services/backend/src/api/admin.py b/services/backend/src/api/admin.py index 293214f..98e09eb 100644 --- a/services/backend/src/api/admin.py +++ b/services/backend/src/api/admin.py @@ -5,6 +5,7 @@ from .models import Project class ProjectAdmin(admin.ModelAdmin): list_display = ( 'id', + 'visibility', 'name', 'uuid', 'created_at', diff --git a/services/backend/src/api/migrations/0003_project_visibility.py b/services/backend/src/api/migrations/0003_project_visibility.py new file mode 100644 index 0000000..ad7910a --- /dev/null +++ b/services/backend/src/api/migrations/0003_project_visibility.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.4 on 2022-08-04 06:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0002_project_uuid'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='visibility', + field=models.SmallIntegerField(default='1'), + ), + ] diff --git a/services/backend/src/api/models/project.py b/services/backend/src/api/models/project.py index 15efdc3..7694d8e 100644 --- a/services/backend/src/api/models/project.py +++ b/services/backend/src/api/models/project.py @@ -10,6 +10,7 @@ class Project(models.Model): related_name="projects", on_delete=models.CASCADE, ) + visibility = models.SmallIntegerField(blank=False, null=False, default="1") name = models.CharField(max_length=500, blank=False, null=False, default="Untitled") uuid = models.CharField(max_length=500, blank=True, null=True, unique=True) data = models.TextField(blank=False) diff --git a/services/backend/src/api/routing.py b/services/backend/src/api/routing.py index 1e08ee5..27f2082 100644 --- a/services/backend/src/api/routing.py +++ b/services/backend/src/api/routing.py @@ -15,6 +15,7 @@ class DefaultRouterPlusPlus(ExtendedDefaultRouter): api_urls = [ path("", view.ViewGenericAPIView.as_view()), path("projects/", project.ProjectListCreateAPIView.as_view()), + path("projects/import/", project.ProjectImportAPIView.as_view()), path("projects//", project.ProjectGenericAPIView.as_view()), path("generate/", generate.GenerateGenericAPIView.as_view()), path("auth/self/", user.UserGenericAPIView.as_view()), diff --git a/services/backend/src/main/settings.py b/services/backend/src/main/settings.py index 522bbf5..266f752 100644 --- a/services/backend/src/main/settings.py +++ b/services/backend/src/main/settings.py @@ -25,7 +25,9 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = "django-insecure--i+bd*fda@!=_0yv$((3(@nruqvv(8c1c8no^+yjl%@b859f57" # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = os.getenv( + 'DEBUG', + 'true').lower() == 'true' ALLOWED_HOSTS = ["*"] diff --git a/services/frontend/src/components/Modal/import/form-utils.ts b/services/frontend/src/components/Modal/import/form-utils.ts new file mode 100644 index 0000000..f039750 --- /dev/null +++ b/services/frontend/src/components/Modal/import/form-utils.ts @@ -0,0 +1,47 @@ +import * as yup from "yup"; + +export interface IImportForm { + url: string; + visibility: string[]; +} + +export interface IImportFinalValues { + url: string; + visibility: number; +} + +const initialValues: IImportForm = { + url: "", + visibility: [] +}; + +export const validationSchema = yup.object({ + url: yup + .string() + .max(256, "url should be 500 characters or less") + .required("url is required") +}); + +export const getInitialValues = (values?: any): IImportForm => { + if (!values) { + return { + ...initialValues + }; + } + + const { url, visibility } = values; + + return { + url: url ?? (initialValues.url as string), + visibility: visibility ?? [] + }; +}; + +export const getFinalValues = (values: IImportForm): IImportFinalValues => { + const { url, visibility } = values; + + return { + url: url ?? "", + visibility: visibility.length ? 1 : 0 + }; +}; diff --git a/services/frontend/src/components/Modal/import/index.tsx b/services/frontend/src/components/Modal/import/index.tsx new file mode 100644 index 0000000..97ecf9e --- /dev/null +++ b/services/frontend/src/components/Modal/import/index.tsx @@ -0,0 +1,115 @@ +import { useCallback, useMemo, useState } from "react"; +import { Field, Formik } from "formik"; +import { styled } from "@mui/joy"; +import { XIcon } from "@heroicons/react/outline"; +import { CallbackFunction } from "../../../types"; +import { IImportForm } from "./form-utils"; +import { + getFinalValues, + getInitialValues, + validationSchema +} from "./form-utils"; +import TextField from "../../global/FormElements/TextField"; +import { toaster } from "../../../utils"; +import { reportErrorsAndSubmit } from "../../../utils/forms"; +import { ScrollView } from "../../ScrollView"; +import lodash from "lodash"; + +interface IModalImportProps { + onHide: CallbackFunction; + onImport: CallbackFunction; + importing: boolean; +} + +const FormContainer = styled("div")` + display: flex; + flex-direction: column; + justify-content: space-between; +`; + +const ModalImport = (props: IModalImportProps) => { + const { onHide, onImport, importing } = props; + + const handleCreate = useCallback( + (values: IImportForm, formik: any) => { + const result = getFinalValues(values); + onImport(result); + toaster(`Importing...`, "success"); + }, + [onImport, onHide] + ); + + const initialValues = useMemo(() => getInitialValues(), []); + + return ( +
+
+
+
+
+
+

Import

+ +
+ + + {(formik) => ( + +
+ + + + + + + + {importing && <>importing} + +
+ +
+ +
+
+ )} +
+
+
+
+
+ ); +}; + +export default ModalImport; diff --git a/services/frontend/src/components/Project/index.tsx b/services/frontend/src/components/Project/index.tsx index 7661b4f..80f9233 100644 --- a/services/frontend/src/components/Project/index.tsx +++ b/services/frontend/src/components/Project/index.tsx @@ -40,6 +40,7 @@ import CreateVolumeModal from "../Modal/volume/CreateVolumeModal"; import EditVolumeModal from "../Modal/volume/EditVolumeModal"; import CodeEditor from "../CodeEditor"; import { useTitle } from "../../hooks"; +import VisibilitySwitch from "../global/VisibilitySwitch"; export default function Project() { const { uuid } = useParams<{ uuid: string }>(); @@ -50,6 +51,7 @@ export default function Project() { const stateConnectionsRef = useRef<[[string, string]] | []>(); const stateNetworksRef = useRef({}); + const [isVisible, setIsVisible] = useState(false); const [generatedCode, setGeneratedCode] = useState(); const [formattedCode, setFormattedCode] = useState(""); const [showModalCreateService, setShowModalCreateService] = useState(false); @@ -126,6 +128,7 @@ export default function Project() { const onSave = () => { const payload: IProjectPayload = { name: projectName, + visibility: +isVisible, data: { canvas: { position: canvasPosition, @@ -172,6 +175,7 @@ export default function Project() { ); setProjectName(data.name); + setIsVisible(Boolean(data.visibility)); setNodes(clientNodeItems); setConnections(canvasData.canvas.connections); setNetworks(canvasData.canvas.networks); @@ -463,17 +467,12 @@ export default function Project() { />
- + /> + + + Create new project + +
)} diff --git a/services/frontend/src/components/global/SideBar.tsx b/services/frontend/src/components/global/SideBar.tsx index d552509..1c34b40 100644 --- a/services/frontend/src/components/global/SideBar.tsx +++ b/services/frontend/src/components/global/SideBar.tsx @@ -1,5 +1,5 @@ import { useLocation } from "react-router-dom"; -import { BookOpenIcon } from "@heroicons/react/outline"; +import { BookOpenIcon, PlusIcon } from "@heroicons/react/outline"; import { Link } from "react-router-dom"; import UserMenu from "./UserMenu"; import Logo from "./logo"; @@ -20,6 +20,12 @@ export default function SideBar(props: ISideBarProps) { href: "/projects", icon: BookOpenIcon, current: pathname.match(projRegex) ? true : false + }, + { + name: "New project", + href: "/projects/new", + icon: PlusIcon, + current: false } ]; @@ -36,7 +42,7 @@ export default function SideBar(props: ISideBarProps) {
-