Kaynağa Gözat

Merge pull request #2067 from porter-dev/master

add app to env group from backend on launch + enable users with no gha to see prev envs -> staging
Nicolas Frati 4 yıl önce
ebeveyn
işleme
2aa2dc0d18

+ 35 - 0
api/server/handlers/release/create.go

@@ -23,6 +23,7 @@ import (
 	"github.com/porter-dev/porter/internal/registry"
 	"gopkg.in/yaml.v2"
 	"helm.sh/helm/v3/pkg/release"
+	v1 "k8s.io/api/core/v1"
 )
 
 type CreateReleaseHandler struct {
@@ -110,6 +111,29 @@ func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
+	k8sAgent, err := c.GetAgent(r, cluster, "")
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	configMaps := make([]*v1.ConfigMap, 0)
+
+	if request.SyncedEnvGroups != nil && len(request.SyncedEnvGroups) > 0 {
+		for _, envGroupName := range request.SyncedEnvGroups {
+			// read the attached configmap
+			cm, _, err := k8sAgent.GetLatestVersionedConfigMap(envGroupName, namespace)
+
+			if err != nil {
+				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("Couldn't find the env group"), http.StatusNotFound))
+				return
+			}
+
+			configMaps = append(configMaps, cm)
+		}
+	}
+
 	release, err := createReleaseFromHelmRelease(c.Config(), cluster.ProjectID, cluster.ID, helmRelease)
 
 	if err != nil {
@@ -117,6 +141,17 @@ func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
+	if len(configMaps) > 0 {
+		for _, cm := range configMaps {
+
+			_, err = k8sAgent.AddApplicationToVersionedConfigMap(cm, release.Name)
+
+			if err != nil {
+				c.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(fmt.Errorf("Couldn't add %s to the config map %s", release.Name, cm.Name)))
+			}
+		}
+	}
+
 	if request.Tags != nil {
 		tags, err := c.Repo().Tag().LinkTagsToRelease(request.Tags, release)
 

+ 1 - 0
api/types/release.go

@@ -49,6 +49,7 @@ type CreateReleaseRequest struct {
 	GithubActionConfig *CreateGitActionConfigRequest `json:"github_action_config,omitempty"`
 	BuildConfig        *CreateBuildConfigRequest     `json:"build_config,omitempty"`
 	Tags               []string                      `json:"tags,omitempty"`
+	SyncedEnvGroups    []string                      `json:"synced_env_groups,omitempty"`
 }
 
 type CreateAddonRequest struct {

+ 0 - 195
dashboard/src/main/home/cluster-dashboard/preview-environments/PreviewEnvironmentsHome.tsx

@@ -1,195 +0,0 @@
-import Loading from "components/Loading";
-import React, { useCallback, useContext, useEffect, useState } from "react";
-import { useHistory, useLocation } from "react-router";
-import api from "shared/api";
-import { Context } from "shared/Context";
-import { useRouting } from "shared/routing";
-import styled from "styled-components";
-import ButtonEnablePREnvironments from "./components/ButtonEnablePREnvironments";
-import DashboardHeader from "../DashboardHeader";
-import PullRequestIcon from "assets/pull_request_icon.svg";
-import DeploymentList from "./deployments/DeploymentList";
-import EnvironmentsList from "./environments/EnvironmentsList";
-import { environments } from "./mocks";
-import { PreviewEnvironmentsHeader } from "./components/PreviewEnvironmentsHeader";
-
-const PreviewEnvironmentsHome = () => {
-  const { currentCluster, currentProject } = useContext(Context);
-
-  const [hasGHAccountsLinked, setHasGHAccountsLinked] = useState(false);
-  const [hasEnvironments, setHasEnvironments] = useState(false);
-  const [isLoading, setIsLoading] = useState(true);
-  const [environments, setEnvironments] = useState([]);
-  const [selectedRepo, setSelectedRepo] = useState("");
-
-  const { getQueryParam } = useRouting();
-  const location = useLocation();
-  const history = useHistory();
-
-  const getAccounts = async () => {
-    try {
-      const res = await api.getGithubAccounts("<token>", {}, {});
-      if (res.status !== 200) {
-        throw new Error("Not authorized");
-      }
-
-      return res.data;
-    } catch (error) {
-      throw error;
-    }
-  };
-
-  const getEnvironments = async () => {
-    try {
-      const { data } = await api.listEnvironments(
-        "<token>",
-        {},
-        {
-          project_id: currentProject?.id,
-          cluster_id: currentCluster?.id,
-        }
-      );
-
-      return data;
-    } catch (error) {
-      throw error;
-    }
-  };
-
-  const checkPreviewEnvironmentsEnabling = async (subscribeStauts: {
-    subscribed: boolean;
-  }) => {
-    try {
-      await getAccounts();
-
-      const envs = await getEnvironments();
-      // const envs = await mockRequest();
-
-      if (!subscribeStauts.subscribed) {
-        return;
-      }
-
-      if (!Array.isArray(envs)) {
-        setHasGHAccountsLinked(true);
-        return;
-      }
-
-      setHasGHAccountsLinked(true);
-      setHasEnvironments(true);
-      setEnvironments(envs);
-    } catch (error) {
-      setHasGHAccountsLinked(false);
-    }
-  };
-
-  useEffect(() => {
-    let subscribedStatus = { subscribed: true };
-
-    setIsLoading(true);
-
-    checkPreviewEnvironmentsEnabling(subscribedStatus).finally(() => {
-      if (subscribedStatus.subscribed) {
-        setIsLoading(false);
-      }
-    });
-
-    return () => {
-      subscribedStatus.subscribed = false;
-    };
-  }, [currentCluster, currentProject]);
-
-  useEffect(() => {
-    const current_repo = getQueryParam("repository");
-    setSelectedRepo(current_repo);
-  }, [location.search, history]);
-
-  if (isLoading) {
-    return (
-      <>
-        <PreviewEnvironmentsHeader />
-        <Placeholder>
-          <Loading />
-        </Placeholder>
-      </>
-    );
-  }
-
-  if (!hasGHAccountsLinked) {
-    return (
-      <>
-        <PreviewEnvironmentsHeader />
-        <Placeholder>
-          <Title>There are no repositories linked</Title>
-          <Subtitle>
-            In order to use preview environments, you must install the porter
-            app in at least one repository.
-          </Subtitle>
-          <ButtonEnablePREnvironments />
-        </Placeholder>
-      </>
-    );
-  }
-
-  if (!hasEnvironments) {
-    return (
-      <>
-        <PreviewEnvironmentsHeader />
-
-        <Placeholder>
-          <Title>Preview environments are not enabled on this cluster</Title>
-          <Subtitle>
-            In order to use preview environments, you must enable preview
-            environments on this cluster.
-          </Subtitle>
-          <ButtonEnablePREnvironments />
-        </Placeholder>
-      </>
-    );
-  }
-
-  return (
-    <>
-      <PreviewEnvironmentsHeader />
-      <EnvironmentsList
-        environments={environments}
-        setEnvironments={setEnvironments}
-      />
-    </>
-  );
-};
-
-export default PreviewEnvironmentsHome;
-
-const Placeholder = styled.div`
-  padding: 30px;
-  margin-top: 35px;
-  padding-bottom: 40px;
-  font-size: 13px;
-  color: #ffffff44;
-  min-height: 400px;
-  height: 50vh;
-  background: #ffffff11;
-  border-radius: 8px;
-  width: 100%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-direction: column;
-
-  > i {
-    font-size: 18px;
-    margin-right: 8px;
-  }
-`;
-
-const Title = styled.div`
-  font-weight: 500;
-  color: #aaaabb;
-  font-size: 16px;
-  margin-bottom: 15px;
-  width: 50%;
-`;
-
-const Subtitle = styled.div`
-  width: 50%;
-`;

+ 53 - 18
dashboard/src/main/home/cluster-dashboard/preview-environments/components/ButtonEnablePREnvironments.tsx

@@ -7,8 +7,12 @@ import { Link } from "react-router-dom";
 import DynamicLink from "components/DynamicLink";
 import Loading from "components/Loading";
 
+type Props = {
+  setIsReady: (status: boolean) => void;
+};
+
 // TODO: Billing is still not capable to show if a user can use or not PR environments, add that instead of "hasBillingEnabled"
-const ButtonEnablePREnvironments = () => {
+const ButtonEnablePREnvironments = ({ setIsReady }: Props) => {
   // const { hasBillingEnabled } = useContext(Context);
   const [isLoading, setIsLoading] = useState(true);
   const [hasGHAccountConnected, setHasGHAccountConnected] = useState(false);
@@ -46,6 +50,10 @@ const ButtonEnablePREnvironments = () => {
     };
   }, []);
 
+  useEffect(() => {
+    setIsReady(!isLoading);
+  }, [isLoading]);
+
   const getButtonProps = () => {
     const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
 
@@ -87,7 +95,7 @@ const ButtonEnablePREnvironments = () => {
         <Container>
           <Button {...getButtonProps()}>
             <img src={pr_icon} alt="Pull request icon" />
-            Connect repositories
+            Connect GitHub account
           </Button>
         </Container>
       </>
@@ -98,8 +106,7 @@ const ButtonEnablePREnvironments = () => {
     <>
       <Container>
         <Button {...getButtonProps()}>
-          <img src={pr_icon} alt="Pull request icon" />
-          Enable Preview Environments
+          <i className="material-icons">add</i> Add Repository
         </Button>
       </Container>
     </>
@@ -109,29 +116,57 @@ const ButtonEnablePREnvironments = () => {
 export default ButtonEnablePREnvironments;
 
 const Button = styled(DynamicLink)`
-  background-color: #616feecc;
-  border: none;
-  border-radius: 6px;
-  color: white;
   display: flex;
+  flex-direction: row;
   align-items: center;
-  justify-content: center;
-  padding: 8px 12px;
-  font-size: 14px;
+  justify-content: space-between;
+  font-size: 13px;
   cursor: pointer;
+  font-family: "Work Sans", sans-serif;
+  border-radius: 20px;
+  color: white;
+  height: 35px;
+  padding: 0px 8px;
+  padding-bottom: 1px;
+  margin-right: 10px;
+  font-weight: 500;
+  padding-right: 15px;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  box-shadow: 0 5px 8px 0px #00000010;
+  cursor: ${(props: { disabled?: boolean }) =>
+    props.disabled ? "not-allowed" : "pointer"};
+
+  background: ${(props: { disabled?: boolean }) =>
+    props.disabled ? "#aaaabbee" : "#616FEEcc"};
+  :hover {
+    background: ${(props: { disabled?: boolean }) =>
+      props.disabled ? "" : "#505edddd"};
+  }
+
   img {
-    margin-right: 10px;
-    width: 20px;
-    height: 20px;
+    margin-left: 2px;
+    margin-right: 5px;
+    width: 18px;
+    height: 18px;
   }
-  transition: background-color 150ms ease-out;
-  :hover {
-    background-color: #616feefb;
+
+  > i {
+    color: white;
+    width: 18px;
+    height: 18px;
+    font-weight: 600;
+    font-size: 12px;
+    border-radius: 20px;
+    display: flex;
+    align-items: center;
+    margin-right: 5px;
+    justify-content: center;
   }
 `;
 
 const Container = styled.div`
   width: 50%;
   display: flex;
-  margin-top: 20px;
 `;

+ 101 - 27
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentsList.tsx

@@ -4,51 +4,117 @@ import React, { useContext, useEffect, useState } from "react";
 import api from "shared/api";
 import { Context } from "shared/Context";
 import styled from "styled-components";
-import { deployments, environments } from "../mocks";
+import ButtonEnablePREnvironments from "../components/ButtonEnablePREnvironments";
+import { PreviewEnvironmentsHeader } from "../components/PreviewEnvironmentsHeader";
 import { Environment } from "../types";
 import EnvironmentCard from "./EnvironmentCard";
 
-type Props = {
-  environments: Environment[];
-  setEnvironments: (
-    setFunction: (prev: Environment[]) => Environment[]
-  ) => void;
-};
+const EnvironmentsList = () => {
+  const { currentCluster, currentProject } = useContext(Context);
+  const [isLoading, setIsLoading] = useState(true);
+  const [buttonIsReady, setButtonIsReady] = useState(false);
+
+  const [environments, setEnvironments] = useState<Environment[]>([]);
 
-const EnvironmentsList = ({ environments, setEnvironments }: Props) => {
   const removeEnvironmentFromList = (deletedEnv: Environment) => {
     setEnvironments((prev) => {
       return prev.filter((env) => env.id !== deletedEnv.id);
     });
   };
 
+  const getEnvironments = async () => {
+    try {
+      const { data } = await api.listEnvironments(
+        "<token>",
+        {},
+        {
+          project_id: currentProject?.id,
+          cluster_id: currentCluster?.id,
+        }
+      );
+
+      return data;
+    } catch (error) {
+      throw error;
+    }
+  };
+
+  const checkPreviewEnvironmentsEnabling = async (subscribeStauts: {
+    subscribed: boolean;
+  }) => {
+    try {
+      const envs = await getEnvironments();
+      // const envs = await mockRequest();
+
+      if (!subscribeStauts.subscribed) {
+        return;
+      }
+
+      if (!Array.isArray(envs)) {
+        return;
+      }
+
+      setEnvironments(envs);
+    } catch (error) {
+      setEnvironments([]);
+    }
+  };
+
+  useEffect(() => {
+    let subscribedStatus = { subscribed: true };
+
+    setIsLoading(true);
+
+    checkPreviewEnvironmentsEnabling(subscribedStatus).finally(() => {
+      if (subscribedStatus.subscribed) {
+        setIsLoading(false);
+      }
+    });
+
+    return () => {
+      subscribedStatus.subscribed = false;
+    };
+  }, [currentCluster, currentProject]);
+
   return (
     <>
-      <ControlRow>
-        <Button to={`/preview-environments/connect-repo`}>
-          <i className="material-icons">add</i> Add Repository
-        </Button>
-      </ControlRow>
-      {environments.length === 0 && (
-        <Placeholder>
-          No repositories found with Preview Environments enabled.
-        </Placeholder>
-      )}
-      <EnvironmentsGrid>
-        {environments.map((env) => (
-          <EnvironmentCard
-            key={env.id}
-            environment={env}
-            onDelete={removeEnvironmentFromList}
-          />
-        ))}
-      </EnvironmentsGrid>
+      <PreviewEnvironmentsHeader />
+      <Relative>
+        {isLoading || !buttonIsReady ? (
+          <FloatingPlaceholder>
+            <Loading />
+          </FloatingPlaceholder>
+        ) : null}
+
+        <ControlRow>
+          <ButtonEnablePREnvironments setIsReady={setButtonIsReady} />
+        </ControlRow>
+        {environments.length === 0 ? (
+          <Placeholder>
+            No repositories found with Preview Environments enabled.
+          </Placeholder>
+        ) : (
+          <EnvironmentsGrid>
+            {environments.map((env) => (
+              <EnvironmentCard
+                key={env.id}
+                environment={env}
+                onDelete={removeEnvironmentFromList}
+              />
+            ))}
+          </EnvironmentsGrid>
+        )}
+      </Relative>
     </>
   );
 };
 
 export default EnvironmentsList;
 
+const Relative = styled.div`
+  position: relative;
+`;
+
 const Placeholder = styled.div`
   padding: 30px;
   margin-top: 35px;
@@ -71,6 +137,14 @@ const Placeholder = styled.div`
   }
 `;
 
+const FloatingPlaceholder = styled(Placeholder)`
+  position: absolute;
+  background: #3d3f42;
+  width: 100%;
+  height: 100%;
+  margin-top: 0px;
+`;
+
 const EnvironmentsGrid = styled.div`
   margin-top: 32px;
   padding-bottom: 150px;

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/preview-environments/routes.tsx

@@ -4,7 +4,7 @@ import { Context } from "shared/Context";
 import ConnectNewRepo from "./ConnectNewRepo";
 import DeploymentDetail from "./deployments/DeploymentDetail";
 import DeploymentList from "./deployments/DeploymentList";
-import PreviewEnvironmentsHome from "./PreviewEnvironmentsHome";
+import EnvironmentsList from "./environments/EnvironmentsList";
 
 export const Routes = () => {
   const { path } = useRouteMatch();
@@ -29,7 +29,7 @@ export const Routes = () => {
           <DeploymentList />
         </Route>
         <Route path={`${path}/`}>
-          <PreviewEnvironmentsHome />
+          <EnvironmentsList />
         </Route>
       </Switch>
     </>

+ 3 - 26
dashboard/src/main/home/launch/launch-flow/LaunchFlow.tsx

@@ -301,6 +301,8 @@ const LaunchFlow: React.FC<PropsType> = (props) => {
       }
     }
 
+    const synced = values?.container?.env?.synced || [];
+
     try {
       await api.deployTemplate(
         "<token>",
@@ -312,6 +314,7 @@ const LaunchFlow: React.FC<PropsType> = (props) => {
           name: release_name,
           github_action_config: githubActionConfig,
           build_config: buildConfig,
+          synced_env_groups: synced.map((s: any) => s.name),
         },
         {
           id: currentProject.id,
@@ -329,32 +332,6 @@ const LaunchFlow: React.FC<PropsType> = (props) => {
       return;
     }
 
-    // Save application into synced groups
-    const synced = values?.container?.env?.synced || [];
-
-    const addApplicationToEnvGroupPromises = synced.map((envGroup: any) => {
-      return api.addApplicationToEnvGroup(
-        "<token>",
-        {
-          name: envGroup?.name,
-          app_name: release_name,
-        },
-        {
-          project_id: currentProject.id,
-          cluster_id: currentCluster.id,
-          namespace: selectedNamespace,
-        }
-      );
-    });
-
-    try {
-      await Promise.all(addApplicationToEnvGroupPromises);
-    } catch (error) {
-      setCurrentError(
-        "We coudln't sync the env group to the application, please go to your recently deployed application and try again through the environment tab."
-      );
-    }
-
     setSaveValuesStatus("successful");
     // redirect to dashboard with namespace
     setTimeout(() => {

+ 1 - 0
dashboard/src/shared/api.tsx

@@ -430,6 +430,7 @@ const deployTemplate = baseApi<
     name: string;
     github_action_config?: FullActionConfigType;
     build_config?: any;
+    synced_env_groups?: string[];
   },
   {
     id: number;