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

preview environment frontend integration MVP

sunguroku 4 лет назад
Родитель
Сommit
641b617ef5

+ 0 - 6
api/server/handlers/environment/list_deployments.go

@@ -34,12 +34,6 @@ func (c *ListDeploymentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
-	request := &types.GetDeploymentRequest{}
-
-	if ok := c.DecodeAndValidate(w, r, request); !ok {
-		return
-	}
-
 	// read the environment to get the environment id
 	env, err := c.Repo().Environment().ReadEnvironment(project.ID, cluster.ID, uint(ga.InstallationID))
 

+ 2 - 0
api/server/handlers/gitinstallation/oauth_callback.go

@@ -31,6 +31,7 @@ func NewGithubAppOAuthCallbackHandler(
 }
 
 func (c *GithubAppOAuthCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	fmt.Println("CALLBACK HANDLER")
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
 
 	session, err := c.Config().Store.Get(r, c.Config().ServerConf.CookieName)
@@ -43,6 +44,7 @@ func (c *GithubAppOAuthCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http
 	token, err := c.Config().GithubAppConf.Exchange(oauth2.NoContext, r.URL.Query().Get("code"))
 
 	if err != nil || !token.Valid() {
+		fmt.Println("ERROR HERE", err)
 		if redirectStr, ok := session.Values["redirect_uri"].(string); ok && redirectStr != "" {
 			// attempt to parse the redirect uri, if it fails just redirect to dashboard
 			redirectURI, err := url.Parse(redirectStr)

+ 15 - 1
dashboard/src/components/repo-selector/RepoList.tsx

@@ -30,6 +30,7 @@ const RepoList: React.FC<Props> = ({
 }) => {
   const [repos, setRepos] = useState<RepoType[]>([]);
   const [repoLoading, setRepoLoading] = useState(true);
+  const [selectedRepo, setSelectedRepo] = useState(null);
   const [repoError, setRepoError] = useState(false);
   const [accessLoading, setAccessLoading] = useState(true);
   const [accessError, setAccessError] = useState(false);
@@ -122,11 +123,24 @@ const RepoList: React.FC<Props> = ({
       });
   }, []);
 
+
+  // clear out actionConfig and SelectedRepository if new search is performed
+  useEffect(() => {
+    setActionConfig({
+      git_repo: null,
+      image_repo_uri: null,
+      git_branch: null,
+      git_repo_id: 0,
+    });
+    setSelectedRepo(null)
+  }, [searchFilter])
+
   const setRepo = (x: RepoType) => {
     let updatedConfig = actionConfig;
     updatedConfig.git_repo = x.FullName;
     updatedConfig.git_repo_id = x.GHRepoID;
     setActionConfig(updatedConfig);
+    setSelectedRepo(x.FullName)
   };
 
   const renderRepoList = () => {
@@ -180,7 +194,7 @@ const RepoList: React.FC<Props> = ({
         return (
           <RepoName
             key={i}
-            isSelected={repo.FullName === actionConfig.git_repo}
+            isSelected={repo.FullName === selectedRepo}
             lastItem={i === repos.length - 1}
             onClick={() => setRepo(repo)}
             readOnly={readOnly}

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/Dashboard.tsx

@@ -28,7 +28,7 @@ const tabOptions: {
   label: string;
   value: TabEnum;
 }[] = [
-  { label: "PR Preview", value: "preview_environments" },
+  { label: "Preview Environments", value: "preview_environments" },
   { label: "Nodes", value: "nodes" },
   { label: "Events", value: "events" },
   { label: "Metrics", value: "metrics" },

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/Routes.tsx

@@ -12,7 +12,7 @@ export const Routes = () => {
         <Route path={`${url}/node-view/:nodeId`}>
           <ExpandedNodeView />
         </Route>
-        <Route path={`${url}/pr-env-detail/:repoId`}>
+        <Route path={`${url}/pr-env-detail/:namespace`}>
           <EnvironmentDetail />
         </Route>
         <Route path={`${url}/`}>

+ 59 - 20
dashboard/src/main/home/cluster-dashboard/dashboard/preview-environments/EnvironmentDetail.tsx

@@ -5,7 +5,7 @@ import TitleSection from "components/TitleSection";
 import pr_icon from "assets/pull_request_icon.svg";
 import { useRouteMatch } from "react-router";
 import DynamicLink from "components/DynamicLink";
-import { capitalize, Environment } from "./EnvironmentList";
+import { capitalize, PRDeployment } from "./EnvironmentList";
 import Loading from "components/Loading";
 import { Context } from "shared/Context";
 import api from "shared/api";
@@ -14,15 +14,15 @@ import github from "assets/github-white.png";
 
 const mockEnvironment = {
   id: 1,
-  url: "https://porter.run",
-  pr_link: "https://githubsuperpullrequest.com",
+  environment_id: 1,
+  namespace: "pr-30",
+  pull_request_id: 30,
+  subdomain: "https://porter.run",
   status: "deployed",
-  namespace: "default",
-  actions_link: "https://githubsuperactions.com",
 };
 
 const getMockData = () =>
-  new Promise<{ data: Environment }>((resolve) => {
+  new Promise<{ data: PRDeployment }>((resolve) => {
     setTimeout(() => {
       resolve({ data: mockEnvironment });
       // resolve({ data: [] });
@@ -30,17 +30,53 @@ const getMockData = () =>
   });
 
 const EnvironmentDetail = () => {
-  const { params } = useRouteMatch<{ repoId: string }>();
+  const { params } = useRouteMatch<{ namespace: string }>();
   const context = useContext(Context);
-  const [environment, setEnvironment] = useState<Environment>(null);
+  const [environment, setEnvironment] = useState<PRDeployment>(null);
+  const [hasError, setHasError] = useState(false);
+  const [isLoading, setIsLoading] = useState(false);
 
-  useEffect(() => {
-    // TODO: FETCH REPO OR PR?
-    console.log(params.repoId);
+  const { currentProject, currentCluster, setCurrentError } = useContext(
+    Context
+  );
 
-    getMockData().then((res) => {
-      setEnvironment(res.data);
-    });
+  useEffect(() => {
+    let isSubscribed = true;
+
+    api
+    .getPRDeployment(
+      "<token>",
+      {
+        namespace: params.namespace
+      },
+      {
+        project_id: currentProject.id,
+        cluster_id: currentCluster.id,
+      }
+    ).then(({ data }) => {
+        if (!isSubscribed) {
+          return;
+        }
+
+        console.log('retrieved', data)
+        setEnvironment(data);
+      })
+      .catch((err) => {
+        console.error(err);
+        if (isSubscribed) {
+          setHasError(true);
+          setEnvironment(null);
+        }
+      })
+      .finally(() => {
+        if (isSubscribed) {
+          setIsLoading(false);
+        }
+      });
+
+      return () => {
+        isSubscribed = false;
+      };  
   }, [params]);
 
   if (!environment) {
@@ -54,16 +90,19 @@ const EnvironmentDetail = () => {
           <BackButtonImg src={backArrow} />
         </BackButton>
         <Title icon={pr_icon} iconWidth="25px">
-          {environment.url}
+          {environment.subdomain}
           <TagWrapper>
             Namespace <NamespaceTag>{environment.namespace}</NamespaceTag>
           </TagWrapper>
         </Title>
         <InfoWrapper>
-          <PRLink to={environment.pr_link} target="_blank">
+          {
+            environment.subdomain && <PRLink to={environment.subdomain} target="_blank">
             <i className="material-icons">link</i>
-            {environment.pr_link}
+            {environment.subdomain}
           </PRLink>
+
+          }
         </InfoWrapper>
         <Flex>
           <Status>
@@ -71,8 +110,8 @@ const EnvironmentDetail = () => {
             {capitalize(environment.status)}
           </Status>
           <Dot>•</Dot>
-          <GHALink to={environment.actions_link} target="_blank">
-            <img src={github} /> GitHub Action
+          <GHALink to={'https://github.com/actions'} target="_blank">
+            <img src={github} /> GitHub
             <i className="material-icons">open_in_new</i>
           </GHALink>
         </Flex>
@@ -255,7 +294,7 @@ const StatusDot = styled.div`
   width: 8px;
   height: 8px;
   background: ${(props: { status: string }) =>
-    props.status === "deployed"
+    props.status === "created"
       ? "#4797ff"
       : props.status === "failed"
       ? "#ed5f85"

+ 41 - 58
dashboard/src/main/home/cluster-dashboard/dashboard/preview-environments/EnvironmentList.tsx

@@ -1,5 +1,7 @@
 import DynamicLink from "components/DynamicLink";
-import React, { useEffect, useState } from "react";
+import React, { useContext, useEffect, useState } from "react";
+import { Context } from "shared/Context";
+import api from "shared/api";
 import { useHistory, useLocation, useRouteMatch } from "react-router";
 import { getQueryParam } from "shared/routing";
 import styled from "styled-components";
@@ -18,48 +20,14 @@ export type Environment = {
   actions_link: string;
 };
 
-const mockData: Environment[] = [
-  {
-    id: 1,
-    url: "http://some-url",
-    pr_link: "https://githubsuper",
-    status: "deployed",
-    namespace: "stuff",
-    actions_link: "https://githubsuperactions.com",
-  },
-  {
-    id: 2,
-    url: "http://some-url",
-    pr_link: "https://githubsuper",
-    status: "deployed",
-    namespace: "stuff",
-    actions_link: "https://githubsuperactions.com",
-  },
-  {
-    id: 3,
-    url: "http://some-url",
-    pr_link: "https://githubsuper",
-    status: "deployed",
-    namespace: "stuff",
-    actions_link: "https://githubsuperactions.com",
-  },
-  {
-    id: 4,
-    url: "http://some-url",
-    pr_link: "https://githubsuper",
-    status: "deployed",
-    namespace: "stuff",
-    actions_link: "https://githubsuperactions.com",
-  },
-];
-
-const getMockData = () =>
-  new Promise<{ data: Environment[] }>((resolve) => {
-    setTimeout(() => {
-      resolve({ data: mockData });
-      // resolve({ data: [] });
-    }, 2000);
-  });
+export type PRDeployment = {
+  id: number,
+  subdomain: string,
+  status: string,
+  environment_id: number,
+  pull_request_id: number,
+  namespace: string,
+}
 
 export const capitalize = (s: string) => {
   return s.charAt(0).toUpperCase() + s.substring(1).toLowerCase();
@@ -68,8 +36,12 @@ export const capitalize = (s: string) => {
 const EnvironmentList = () => {
   const [isLoading, setIsLoading] = useState(true);
   const [hasError, setHasError] = useState(false);
-  const [environmentList, setEnvironmentList] = useState<Environment[]>([]);
+  const [environmentList, setEnvironmentList] = useState<PRDeployment[]>([]);
   const [showConnectRepoFlow, setShowConnectRepoFlow] = useState(false);
+  const { currentProject, currentCluster, setCurrentError } = useContext(
+    Context
+  );
+
 
   const { url: currentUrl } = useRouteMatch();
 
@@ -78,10 +50,16 @@ const EnvironmentList = () => {
 
   useEffect(() => {
     let isSubscribed = true;
-
     // TODO: Replace get mock data by endpoint
-    getMockData()
-      .then(({ data }) => {
+    api
+    .getPRDeploymentList(
+      "<token>",
+      {},
+      {
+        project_id: currentProject.id,
+        cluster_id: currentCluster.id,
+      }
+    ).then(({ data }) => {
         if (!isSubscribed) {
           return;
         }
@@ -90,6 +68,8 @@ const EnvironmentList = () => {
           throw Error("Data is not an array");
         }
 
+        console.log('retrieved', data)
+
         setEnvironmentList(data);
       })
       .catch((err) => {
@@ -105,14 +85,13 @@ const EnvironmentList = () => {
         }
       });
 
-    return () => {
-      isSubscribed = false;
-    };
+      return () => {
+        isSubscribed = false;
+      };
   }, []);
 
   useEffect(() => {
     const action = getQueryParam({ location }, "action");
-    console.log("HERE", action, location);
     if (action === "connect-repo") {
       setShowConnectRepoFlow(true);
     } else {
@@ -167,11 +146,11 @@ const EnvironmentList = () => {
       <EventsGrid>
         {environmentList.map((env) => {
           return (
-            <EnvironmentCard>
+            <EnvironmentCard key={env.id}>
               <DataContainer>
                 <PRName>
                   <PRIcon src={pr_icon} alt="pull request icon" />
-                  {env.url}
+                  {env.subdomain}
                 </PRName>
                 <StatusContainer>
                   <Status>
@@ -182,13 +161,17 @@ const EnvironmentList = () => {
               </DataContainer>
               <Flex>
                 <RowButton
-                  to={`${currentUrl}/pr-env-detail/${env.id}`}
-                  key={env.pr_link}
+                  to={`${currentUrl}/pr-env-detail/${env.namespace}`}
+                  key={env.id}
                 >
                   <i className="material-icons-outlined">info</i>
                   Details
                 </RowButton>
-                <RowButton to={env.pr_link} target="_blank">
+                <RowButton 
+                  to={env.subdomain} 
+                  key={env.subdomain}
+                  target="_blank"
+                >
                   <i className="material-icons">open_in_new</i>
                   View Live
                 </RowButton>
@@ -359,7 +342,7 @@ const RowButton = styled(DynamicLink)`
   display: flex;
   align-items: center;
   background: #ffffff08;
-
+  cursor: pointer;
   :hover {
     background: #ffffff22;
   }
@@ -383,7 +366,7 @@ const StatusDot = styled.div`
   height: 8px;
   margin-right: 15px;
   background: ${(props: { status: string }) =>
-    props.status === "deployed"
+    props.status === "created"
       ? "#4797ff"
       : props.status === "failed"
       ? "#ed5f85"

+ 4 - 2
dashboard/src/main/home/cluster-dashboard/dashboard/preview-environments/components/ButtonEnablePREnvironments.tsx

@@ -9,9 +9,10 @@ import Loading from "components/Loading";
 
 // 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 { hasBillingEnabled } = useContext(Context);
+  // const { hasBillingEnabled } = useContext(Context);
   const [isLoading, setIsLoading] = useState(true);
   const [hasGHAccountConnected, setHasGHAccountConnected] = useState(false);
+  let hasBillingEnabled = true;
 
   const getAccounts = async () => {
     setIsLoading(true);
@@ -20,6 +21,7 @@ const ButtonEnablePREnvironments = () => {
       if (res.status !== 200) {
         throw new Error("Not authorized");
       }
+
       return res.data;
     } catch (error) {
       console.log(error);
@@ -33,7 +35,7 @@ const ButtonEnablePREnvironments = () => {
     getAccounts().then((accountsData) => {
       if (isSubscribed) {
         if (!accountsData) {
-          setHasGHAccountConnected(true);
+          setHasGHAccountConnected(false);
         } else {
           setHasGHAccountConnected(true);
         }

+ 66 - 13
dashboard/src/main/home/cluster-dashboard/dashboard/preview-environments/components/ConnectNewRepo.tsx

@@ -1,34 +1,87 @@
 import DynamicLink from "components/DynamicLink";
 import Heading from "components/form-components/Heading";
 import Helper from "components/form-components/Helper";
+import RepoList from "components/repo-selector/RepoList";
 import SelectRow from "components/form-components/SelectRow";
 import SaveButton from "components/SaveButton";
+import { ActionConfigType } from "shared/types";
 import Selector from "components/Selector";
 import TitleSection from "components/TitleSection";
-import React from "react";
 import { useRouteMatch } from "react-router";
+import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
+import api from "shared/api";
+import { Context } from "shared/Context";
 
 const porterYamlDocsLink = "https://docs.porter.run";
 
-const ConnectNewRepo = () => {
+const ConnectNewRepo: React.FC = () => {
+  const context = useContext(Context);
+  const { currentProject, currentCluster, setCurrentError } = useContext(
+    Context
+  );
+  const [repo, setRepo] = useState(null);
+  const [status, setStatus] = useState(null);
+
+  // NOTE: git_repo_id is a misnomer as this actually refers to the github app's installation id.
+  const [actionConfig, setActionConfig] = useState<ActionConfigType>({
+    git_repo: null,
+    image_repo_uri: null,
+    git_branch: null,
+    git_repo_id: 0,
+  });
+
+  useEffect(() => {
+  }, [repo])
+
   const { url } = useRouteMatch();
+
+  const addRepo = () => {
+    let [owner, repoName] = repo.split('/')
+    setStatus('loading')
+    api
+    .createEnvironment(
+      "<token>",
+      {
+        name: 'Preview',
+        git_repo_name: repoName,
+        git_repo_owner: owner,
+      },
+      {
+        project_id: currentProject.id,
+        cluster_id: currentCluster.id,
+        git_installation_id: actionConfig.git_repo_id,
+      }
+    )
+    .then(() => {
+      setStatus('successful');
+      window.location.href = `${url}?selected_tab=preview_environments`
+    })
+    .catch((err) => {
+      err = JSON.stringify(err);
+      setStatus('error')
+      setCurrentError(err)
+    })
+  }
+
   return (
     <div>
       <ControlRow>
         <BackButton to={`${url}?selected_tab=preview_environments`}>
           <i className="material-icons">close</i>
         </BackButton>
-        <Title>Connect a new repo</Title>
+        <Title>Enable Preview Environments</Title>
       </ControlRow>
 
-      <Heading>Select repo</Heading>
+      <Heading>Select a Repository</Heading>
       <br />
-      <Selector
-        width="100%"
-        options={[]}
-        setActiveValue={console.log}
-        activeValue=""
+      <RepoList
+        actionConfig={actionConfig}
+        setActionConfig={(a: ActionConfigType) => {
+          setActionConfig(a)
+          setRepo(a.git_repo)
+        }}
+        readOnly={false}
       />
 
       <Heading>Disclaimer</Heading>
@@ -41,12 +94,12 @@ const ConnectNewRepo = () => {
       </PorterYamlLink>
       <ActionContainer>
         <SaveButton
-          text="Connect repo"
-          disabled={false}
-          onClick={() => console.log()}
+          text="Add Repository"
+          disabled={actionConfig.git_repo_id ? false : true}
+          onClick={addRepo}
           makeFlush={true}
           clearPosition={true}
-          status={""}
+          status={status}
           statusPosition={"left"}
         ></SaveButton>
       </ActionContainer>

+ 1 - 0
dashboard/src/main/home/modals/AccountSettingsModal.tsx

@@ -30,6 +30,7 @@ const AccountSettingsModal = () => {
     api
       .getGithubAccounts("<token>", {}, {})
       .then(({ data }) => {
+        console.log('github account', data)
         setAccessData(data);
         setAccessLoading(false);
       })

+ 1 - 1
dashboard/src/shared/Context.tsx

@@ -154,7 +154,7 @@ class ContextProvider extends Component<PropsType, StateType> {
         this.setState({ edition });
       }
     },
-    hasBillingEnabled: null,
+    hasBillingEnabled: false,
     setHasBillingEnabled: (isBillingEnabled: boolean) => {
       this.setState({ hasBillingEnabled: isBillingEnabled });
     },

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

@@ -111,6 +111,22 @@ const createEmailVerification = baseApi<{}, {}>("POST", (pathParams) => {
   return `/api/email/verify/initiate`;
 });
 
+const createEnvironment = baseApi<
+{
+  name: string;
+  git_repo_owner: string;
+  git_repo_name: string;
+},
+{
+  project_id: number;
+  cluster_id: number;
+  git_installation_id: number;
+}
+>("POST", (pathParams) => {
+  let { project_id, cluster_id, git_installation_id } = pathParams;
+  return `/api/projects/${project_id}/gitrepos/${git_installation_id}/clusters/${cluster_id}/environment`;
+});
+
 const createGCPIntegration = baseApi<
   {
     gcp_key_data: string;
@@ -283,6 +299,31 @@ const updateNotificationConfig = baseApi<
   return `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${name}/notifications`;
 });
 
+const getPRDeploymentList = baseApi<
+  {},
+  {
+    cluster_id: number;
+    project_id: number;
+  }
+>("GET", (pathParams) => {
+  const { cluster_id, project_id } = pathParams;
+
+  return `/api/projects/${project_id}/gitrepos/21414420/clusters/${cluster_id}/deployments`;
+});
+
+const getPRDeployment = baseApi<
+  {
+    namespace: string,
+  },
+  {
+    cluster_id: number;
+    project_id: number;
+  }
+>("GET", (pathParams) => {
+  const { cluster_id, project_id } = pathParams;
+  return `/api/projects/${project_id}/gitrepos/21414420/clusters/${cluster_id}/deployment`;
+});
+
 const getNotificationConfig = baseApi<
   {},
   {
@@ -1217,6 +1258,7 @@ export default {
   createDOCR,
   createDOKS,
   createEmailVerification,
+  createEnvironment,
   createGCPIntegration,
   createGCR,
   createGKE,
@@ -1256,6 +1298,8 @@ export default {
   getClusterNodes,
   getClusterNode,
   getConfigMap,
+  getPRDeploymentList,
+  getPRDeployment,
   getGHAWorkflowTemplate,
   getGitRepoList,
   getGitRepos,