Просмотр исходного кода

Implemented automatic state saving for sub steps

jnfrati 4 лет назад
Родитель
Сommit
186f66f53b

+ 4 - 2
dashboard/src/main/home/Home.tsx

@@ -135,7 +135,7 @@ class Home extends Component<PropsType, StateType> {
       .then((res) => {
         if (res.data) {
           if (res.data.length === 0) {
-            pushFiltered(this.props, "/new-project", ["project_id"]);
+            pushFiltered(this.props, "/onboarding/new-project", ["project_id"]);
           } else if (res.data.length > 0 && !currentProject) {
             setProjects(res.data);
 
@@ -373,7 +373,9 @@ class Home extends Component<PropsType, StateType> {
             setCurrentProject(res.data[0]);
           } else {
             setCurrentProject(null, () =>
-              pushFiltered(this.props, "/new-project", ["project_id"])
+              pushFiltered(this.props, "/onboarding/new-project", [
+                "project_id",
+              ])
             );
           }
           this.context.setCurrentModal(null, null);

+ 1 - 1
dashboard/src/main/home/onboarding/Onboarding.tsx

@@ -14,7 +14,7 @@ const Onboarding = () => {
   const snap = useSnapshot(OFState);
 
   useEffect(() => {
-    OFState.actions.restoreState(context.currentProject?.id);
+    OFState.actions.initializeState(context.currentProject?.id);
     return () => {
       OFState.actions.clearState();
     };

+ 4 - 4
dashboard/src/main/home/onboarding/Routes.tsx

@@ -1,9 +1,9 @@
-import React, { useEffect } from "react";
-import { Route, Switch, useHistory, useLocation } from "react-router";
+import React from "react";
+import { Route, Switch } from "react-router";
 import ConnectRegistry from "./steps/ConnectRegistry/ConnectRegistry";
 import ConnectSource from "./steps/ConnectSource";
 import { NewProjectFC } from "./steps/NewProject";
-import ProvisionerForms from "./ProvisionerForms";
+import ProvisionResources from "./steps/ProvisionResources/ProvisionResources";
 
 export const Routes = () => {
   return (
@@ -19,7 +19,7 @@ export const Routes = () => {
           <ConnectRegistry />
         </Route>
         <Route path={`/onboarding/provision`}>
-          <ProvisionerForms />
+          <ProvisionResources />
         </Route>
       </Switch>
     </>

+ 45 - 8
dashboard/src/main/home/onboarding/state/index.ts

@@ -1,17 +1,19 @@
-import { proxy } from "valtio";
-import { devtools } from "valtio/utils";
-import {
-  ConnectedRegistryConfig,
-  ConnectedSourceData,
-  ProjectData,
-  StateHandler,
-} from "./StateHandler";
+import { proxy, subscribe } from "valtio";
+import { devtools, subscribeKey } from "valtio/utils";
+import { StateHandler } from "./StateHandler";
 import { StepHandler } from "./StepHandler";
+import { State as ConnectRegistryState } from "../steps/ConnectRegistry/ConnectRegistryState";
+import { State as ProvisionResourcesState } from "../steps/ProvisionResources/ProvisionResourcesState";
 
 export const OFState = proxy({
   StateHandler,
   StepHandler,
+  subscriptions: [],
   actions: {
+    initializeState: (projectId: number) => {
+      OFState.actions.restoreState(projectId);
+      OFState.subscriptions = OFState.actions.subscribeToSubstates();
+    },
     nextStep: (data: any) => {
       const currentStep = StepHandler.currentStep;
       StateHandler[currentStep.state_key] = data;
@@ -21,6 +23,8 @@ export const OFState = proxy({
     clearState: () => {
       StateHandler.actions.clearState();
       StepHandler.actions.clearState();
+      ConnectRegistryState.actions.clearState();
+      ProvisionResourcesState.actions.clearState();
     },
     saveState: () => {
       const state = JSON.stringify(OFState);
@@ -44,7 +48,40 @@ export const OFState = proxy({
       if (prevState?.StepHandler) {
         StepHandler.actions.restoreState(prevState.StepHandler);
       }
+      console.log(prevState);
+      debugger;
+      if (prevState?.substates.connected_registry) {
+        console.log(prevState?.substates.connected_registry);
+        ConnectRegistryState.actions.restoreState(
+          prevState?.substates.connected_registry
+        );
+      }
+      console.log(prevState.substates.provision_resources);
+      if (prevState?.substates.provision_resources) {
+        ProvisionResourcesState.actions.restoreState(
+          prevState?.substates?.provision_resources
+        );
+      }
     },
+    // This is in charge of keeping track so the submodules doesn't have any external dependencies
+    subscribeToSubstates: () => {
+      return Object.keys(OFState.substates).map(
+        (key: "connected_registry" | "provision_resources") => {
+          return subscribe(OFState.substates[key], () => {
+            OFState.actions.saveState();
+          });
+        }
+      );
+    },
+    unsubscribeFromSubstates: () => {
+      OFState.subscriptions.forEach((unsubscribe) =>
+        typeof unsubscribe === "function" ? unsubscribe() : ""
+      );
+    },
+  },
+  substates: {
+    connected_registry: ConnectRegistryState,
+    provision_resources: ProvisionResourcesState,
   },
 });
 

+ 1 - 1
dashboard/src/main/home/onboarding/steps/ConnectRegistry/ConnectRegistry.tsx

@@ -62,7 +62,7 @@ const ConnectRegistry = () => {
             }}
           />
           <NextStep
-            text="Continue"
+            text="Skip step"
             disabled={false}
             onClick={() => nextStep(true)}
             status={""}

+ 12 - 0
dashboard/src/main/home/onboarding/steps/ConnectRegistry/ConnectRegistryState.ts

@@ -22,6 +22,18 @@ const actions = {
     State.selectedProvider = null;
     State.currentStep = "credentials";
   },
+  restoreState(prevState: any) {
+    debugger;
+    if (prevState.selectedProvider) {
+      State.selectedProvider = prevState.selectedProvider;
+    }
+    if (prevState.currentStep) {
+      State.currentStep = prevState.currentStep;
+    }
+    if (prevState.config) {
+      State.config = prevState.config;
+    }
+  },
 };
 
 const initialState: ConnectRegistryState = {

+ 85 - 0
dashboard/src/main/home/onboarding/steps/ProvisionResources/ProvisionResources.tsx

@@ -0,0 +1,85 @@
+import Helper from "components/form-components/Helper";
+import SaveButton from "components/SaveButton";
+import TitleSection from "components/TitleSection";
+import React, { useEffect, useState } from "react";
+import { useLocation } from "react-router";
+import { useRouting } from "shared/routing";
+import styled from "styled-components";
+import { useSnapshot } from "valtio";
+import ProviderSelector from "../../components/ProviderSelector";
+import { OFState } from "../../state";
+import { SupportedProviders } from "../../types";
+
+import { State } from "./ProvisionResourcesState";
+import FormFlowWrapper from "./forms/FormFlow";
+
+const ProvisionResources = () => {
+  const snap = useSnapshot(State);
+  const { getQueryParam } = useRouting();
+  const location = useLocation();
+
+  useEffect(() => {
+    const provider = getQueryParam("provider");
+    if (provider === "aws" || provider === "gcp" || provider === "do") {
+      State.selectedProvider = provider;
+    }
+  }, [location]);
+
+  const nextStep = (skipped: boolean) => {
+    if (skipped) {
+      OFState.actions.nextStep({
+        skip: true,
+      });
+      return;
+    }
+    OFState.actions.nextStep({
+      skip: false,
+      provider: snap.selectedProvider,
+      credentials: snap.config.credentials,
+      settings: snap.config.settings,
+    });
+  };
+
+  return (
+    <>
+      <TitleSection>Getting Started</TitleSection>
+      <Subtitle>Step 3 of 3</Subtitle>
+      <Helper>
+        Porter automatically creates a cluster and registry in your cloud to run
+        applications.
+      </Helper>
+      {snap.selectedProvider ? (
+        <FormFlowWrapper nextStep={() => nextStep(false)} />
+      ) : (
+        <>
+          <ProviderSelector
+            selectProvider={(provider) => {
+              State.selectedProvider = provider;
+            }}
+          />
+          <NextStep
+            text="Skip step"
+            disabled={false}
+            onClick={() => nextStep(true)}
+            status={""}
+            makeFlush={true}
+            clearPosition={true}
+            statusPosition="right"
+            saveText=""
+          />
+        </>
+      )}
+    </>
+  );
+};
+
+export default ProvisionResources;
+
+const Subtitle = styled(TitleSection)`
+  font-size: 16px;
+  margin-top: 16px;
+`;
+
+const NextStep = styled(SaveButton)`
+  margin-top: 24px;
+`;

+ 15 - 0
dashboard/src/main/home/onboarding/steps/ProvisionResources/ProvisionResourcesState.ts

@@ -11,6 +11,7 @@ interface ConnectRegistryState {
   actions: {
     selectProvider: (provider: SupportedProviders) => void;
     clearState: () => void;
+    restoreState: (prevState: any) => void;
   };
 }
 
@@ -27,6 +28,20 @@ const initialState: ConnectRegistryState = {
       State.selectedProvider = null;
       State.currentStep = "credentials";
     },
+    restoreState(prevState: any) {
+      if (!prevState) {
+        return;
+      }
+      if (prevState.selectedProvider) {
+        State.selectedProvider = prevState.selectedProvider;
+      }
+      if (prevState.currentStep) {
+        State.currentStep = prevState.currentStep;
+      }
+      if (prevState.config) {
+        State.config = prevState.config;
+      }
+    },
   },
 };
 

+ 93 - 43
dashboard/src/main/home/onboarding/steps/ProvisionResources/forms/_GCPProvisionerForm.tsx

@@ -1,20 +1,52 @@
 import Helper from "components/form-components/Helper";
 import InputRow from "components/form-components/InputRow";
+import SelectRow from "components/form-components/SelectRow";
 import UploadArea from "components/form-components/UploadArea";
 import SaveButton from "components/SaveButton";
-import { GCPRegistryConfig } from "main/home/onboarding/types";
+import {
+  GCPProvisionerConfig,
+  GCPRegistryConfig,
+} from "main/home/onboarding/types";
 import React, { useState } from "react";
 import api from "shared/api";
 import styled from "styled-components";
 import { useSnapshot } from "valtio";
 import { State } from "../ProvisionResourcesState";
 
+const regionOptions = [
+  { value: "asia-east1", label: "asia-east1" },
+  { value: "asia-east2", label: "asia-east2" },
+  { value: "asia-northeast1", label: "asia-northeast1" },
+  { value: "asia-northeast2", label: "asia-northeast2" },
+  { value: "asia-northeast3", label: "asia-northeast3" },
+  { value: "asia-south1", label: "asia-south1" },
+  { value: "asia-southeast1", label: "asia-southeast1" },
+  { value: "asia-southeast2", label: "asia-southeast2" },
+  { value: "australia-southeast1", label: "australia-southeast1" },
+  { value: "europe-north1", label: "europe-north1" },
+  { value: "europe-west1", label: "europe-west1" },
+  { value: "europe-west2", label: "europe-west2" },
+  { value: "europe-west3", label: "europe-west3" },
+  { value: "europe-west4", label: "europe-west4" },
+  { value: "europe-west6", label: "europe-west6" },
+  { value: "northamerica-northeast1", label: "northamerica-northeast1" },
+  { value: "southamerica-east1", label: "southamerica-east1" },
+  { value: "us-central1", label: "us-central1" },
+  { value: "us-east1", label: "us-east1" },
+  { value: "us-east4", label: "us-east4" },
+  { value: "us-west1", label: "us-west1" },
+  { value: "us-west2", label: "us-west2" },
+  { value: "us-west3", label: "us-west3" },
+  { value: "us-west4", label: "us-west4" },
+];
+
 export const CredentialsForm: React.FC<{
   nextFormStep: (data: Partial<GCPRegistryConfig>) => void;
   project: any;
 }> = ({ nextFormStep, project }) => {
   const [projectId, setProjectId] = useState("");
   const [serviceAccountKey, setServiceAccountKey] = useState("");
+  const [region, setRegion] = useState("us-east1");
   const [buttonStatus, setButtonStatus] = useState("");
 
   const validate = () => {
@@ -80,6 +112,17 @@ export const CredentialsForm: React.FC<{
         height="100%"
         isRequired={true}
       />
+      <SelectRow
+        options={regionOptions}
+        width="100%"
+        value={region}
+        dropdownMaxHeight="240px"
+        setActiveValue={(x: string) => {
+          setRegion(x);
+        }}
+        label="📍 GCP Region"
+      />
+
       <SaveButton
         text="Continue"
         disabled={false}
@@ -94,30 +137,28 @@ export const CredentialsForm: React.FC<{
 };
 
 export const SettingsForm: React.FC<{
-  nextFormStep: (data: Partial<GCPRegistryConfig>) => void;
+  nextFormStep: (data: Partial<GCPProvisionerConfig>) => void;
   project: any;
 }> = ({ nextFormStep, project }) => {
-  const [registryName, setRegistryName] = useState("");
-  const [registryUrl, setRegistryUrl] = useState("");
+  const [clusterName, setClusterName] = useState("");
   const [buttonStatus, setButtonStatus] = useState("");
   const snap = useSnapshot(State);
 
   const validate = () => {
-    if (!registryName) {
-      return {
-        hasError: true,
-        error: "Registry Name cannot be empty",
-      };
-    }
-    if (!registryUrl) {
+    if (!clusterName) {
       return {
         hasError: true,
-        error: "Registry Url cannot be empty",
+        error: "Cluster Name cannot be empty",
       };
     }
+
     return { hasError: false, error: "" };
   };
 
+  const catchError = (error: any) => {
+    console.error(error);
+  };
+
   const submit = async () => {
     const validation = validate();
 
@@ -125,49 +166,58 @@ export const SettingsForm: React.FC<{
       setButtonStatus(validation.error);
       return;
     }
+    const integrationId = snap.config.credentials.id;
 
     setButtonStatus("loading");
 
-    await api.connectGCRRegistry(
-      "<token>",
-      {
-        name: registryName,
-        gcp_integration_id: snap.config.credentials.id,
-        url: registryUrl,
-      },
-      {
-        id: project.id,
-      }
-    );
+    await provisionGCR(integrationId);
+    await provisionGKE(integrationId);
     nextFormStep({
       settings: {
-        gcr_url: registryUrl,
-        registry_name: registryName,
+        cluster_name: clusterName,
       },
     });
   };
+
+  const provisionGCR = (id: number) => {
+    console.log("Provisioning GCR");
+
+    return api
+      .createGCR(
+        "<token>",
+        {
+          gcp_integration_id: id,
+        },
+        { project_id: project.id }
+      )
+      .catch(catchError);
+  };
+
+  const provisionGKE = (id: number) => {
+    console.log("Provisioning GKE");
+
+    return api
+      .createGKE(
+        "<token>",
+        {
+          gke_name: clusterName,
+          gcp_integration_id: id,
+        },
+        { project_id: project.id }
+      )
+      .catch(catchError);
+  };
+
   return (
     <>
       <InputRow
         type="text"
-        value={registryName}
-        setValue={(name: string) => setRegistryName(name)}
-        isRequired={true}
-        label="🏷️ Registry Name"
-        placeholder="ex: paper-straw"
-        width="100%"
-      />
-      <Helper>
-        GCR URI, in the form{" "}
-        <CodeBlock>[gcr_domain]/[gcp_project_id]</CodeBlock>. For example,{" "}
-        <CodeBlock>gcr.io/skynet-dev-172969</CodeBlock>.
-      </Helper>
-      <InputRow
-        type="text"
-        value={registryUrl}
-        setValue={(url: string) => setRegistryUrl(url)}
-        label="🔗 GCR URL"
-        placeholder="ex: gcr.io/skynet-dev-172969"
+        value={clusterName}
+        setValue={(x: string) => {
+          setClusterName(x);
+        }}
+        label="Cluster Name"
+        placeholder="ex: porter-cluster"
         width="100%"
         isRequired={true}
       />

+ 3 - 3
dashboard/src/main/home/onboarding/types.ts

@@ -47,7 +47,7 @@ export type AWSProvisionerConfig = {
   skip: false;
   provider: "aws";
   credentials: {
-    id: string;
+    id: number;
     arn: string;
     region: string;
   };
@@ -61,10 +61,10 @@ export type GCPProvisionerConfig = {
   skip: false;
   provider: "gcp";
   credentials: {
-    id: string;
+    id: number;
+    region: string;
   };
   settings: {
-    gcp_region: string;
     cluster_name: string;
   };
 };