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

Integrated gitlab in current action config editor

jnfrati 4 лет назад
Родитель
Сommit
122c1b88cb

+ 13 - 2
dashboard/src/components/SearchBar.tsx

@@ -6,13 +6,19 @@ interface Props {
   setSearchFilter: (x: string) => void;
   disabled: boolean;
   prompt?: string;
+  fullWidth?: boolean;
 }
 
-const SearchBar: React.FC<Props> = ({ setSearchFilter, disabled, prompt }) => {
+const SearchBar: React.FC<Props> = ({
+  setSearchFilter,
+  disabled,
+  prompt,
+  fullWidth,
+}) => {
   const [searchInput, setSearchInput] = useState("");
 
   return (
-    <SearchRowWrapper>
+    <SearchRowWrapper fullWidth={fullWidth}>
       <SearchBarWrapper>
         <i className="material-icons">search</i>
         <SearchInput
@@ -55,6 +61,11 @@ const SearchRowWrapper = styled(SearchRow)`
   border-bottom: 0;
   border: 1px solid #ffffff55;
   border-radius: 3px;
+  ${(props: { fullWidth: boolean }) => {
+    if (props.fullWidth) {
+      return "width: 100%;";
+    }
+  }}
 `;
 
 const ButtonWrapper = styled.div`

+ 0 - 2
dashboard/src/components/repo-selector/ActionConfEditor.tsx

@@ -160,12 +160,10 @@ const ExpandedWrapper = styled.div`
   border-radius: 3px;
   border: 1px solid #ffffff44;
   max-height: 275px;
-  overflow-y: auto;
 `;
 
 const ExpandedWrapperAlt = styled(ExpandedWrapper)`
   border: 0;
-  overflow: hidden;
 `;
 
 const BackButton = styled.div`

+ 47 - 22
dashboard/src/components/repo-selector/BranchList.tsx

@@ -24,28 +24,53 @@ const BranchList: React.FC<Props> = ({ setBranch, actionConfig }) => {
 
   useEffect(() => {
     // Get branches
-    api
-      .getBranches(
-        "<token>",
-        {},
-        {
-          project_id: currentProject.id,
-          git_repo_id: actionConfig.git_repo_id,
-          kind: "github",
-          owner: actionConfig.git_repo.split("/")[0],
-          name: actionConfig.git_repo.split("/")[1],
-        }
-      )
-      .then((res) => {
-        setBranches(res.data);
-        setLoading(false);
-        setError(false);
-      })
-      .catch((err) => {
-        console.log(err);
-        setLoading(false);
-        setError(true);
-      });
+
+    if (actionConfig.git_repo_id !== 0) {
+      api
+        .getBranches(
+          "<token>",
+          {},
+          {
+            project_id: currentProject.id,
+            git_repo_id: actionConfig.git_repo_id,
+            kind: "github",
+            owner: actionConfig.git_repo.split("/")[0],
+            name: actionConfig.git_repo.split("/")[1],
+          }
+        )
+        .then((res) => {
+          setBranches(res.data);
+          setLoading(false);
+          setError(false);
+        })
+        .catch((err) => {
+          console.log(err);
+          setLoading(false);
+          setError(true);
+        });
+    } else {
+      api
+        .getGitlabBranches(
+          "<token>",
+          {},
+          {
+            project_id: currentProject.id,
+            integration_id: actionConfig.gitlab_integration_id,
+            repo_owner: actionConfig.git_repo.split("/")[0],
+            repo_name: actionConfig.git_repo.split("/")[1],
+          }
+        )
+        .then((res) => {
+          setBranches(res.data);
+          setLoading(false);
+          setError(false);
+        })
+        .catch((err) => {
+          console.log(err);
+          setLoading(false);
+          setError(true);
+        });
+    }
   }, []);
 
   const renderBranchList = () => {

+ 26 - 7
dashboard/src/components/repo-selector/ContentsList.tsx

@@ -63,13 +63,23 @@ export default class ContentsList extends Component<PropsType, StateType> {
     this.setState({ currentDir: x }, () => this.updateContents());
   };
 
-  updateContents = () => {
+  fetchContents = () => {
     let { currentProject } = this.context;
-    let { actionConfig, branch } = this.props;
-
-    // Get branch contents
-    api
-      .getBranchContents(
+    const { actionConfig, branch } = this.props;
+    if (actionConfig.gitlab_integration_id > 0) {
+      return api.getGitlabFolderContent(
+        "<token>",
+        { dir: this.state.currentDir || "./" },
+        {
+          project_id: currentProject.id,
+          integration_id: actionConfig.gitlab_integration_id,
+          repo_owner: actionConfig.git_repo.split("/")[0],
+          repo_name: actionConfig.git_repo.split("/")[1],
+          branch: branch,
+        }
+      );
+    } else {
+      return api.getBranchContents(
         "<token>",
         { dir: this.state.currentDir || "./" },
         {
@@ -80,7 +90,16 @@ export default class ContentsList extends Component<PropsType, StateType> {
           name: actionConfig.git_repo.split("/")[1],
           branch: branch,
         }
-      )
+      );
+    }
+  };
+
+  updateContents = () => {
+    let { currentProject } = this.context;
+    let { actionConfig, branch } = this.props;
+
+    // Get branch contents
+    this.fetchContents()
       .then((res) => {
         let files = [] as FileType[];
         let folders = [] as FileType[];

+ 246 - 77
dashboard/src/components/repo-selector/RepoList.tsx

@@ -8,6 +8,10 @@ import { Context } from "shared/Context";
 
 import Loading from "../Loading";
 import SearchBar from "../SearchBar";
+import OptionsDropdown from "components/OptionsDropdown";
+import { SelectField } from "material-ui";
+import SelectRow from "components/form-components/SelectRow";
+import SearchSelector from "components/SearchSelector";
 
 interface GithubAppAccessData {
   has_access: boolean;
@@ -30,87 +34,162 @@ const RepoList: React.FC<Props> = ({
   readOnly,
   filteredRepos,
 }) => {
+  const [providers, setProviders] = useState([]);
+  const [currentProvider, setCurrentProvider] = useState(null);
   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);
-  const [accessData, setAccessData] = useState<GithubAppAccessData>({
-    has_access: false,
-  });
   const [searchFilter, setSearchFilter] = useState(null);
   const { currentProject } = useContext(Context);
 
-  const loadData = async () => {
-    try {
-      const { data } = await api.getGithubAccounts("<token>", {}, {});
+  // const loadData = async () => {
+  //   try {
+  //     const { data } = await api.getGithubAccounts("<token>", {}, {});
+
+  //     setAccessData(data);
+  //     setAccessLoading(false);
+  //   } catch (error) {
+  //     setAccessError(true);
+  //     setAccessLoading(false);
+  //   }
+
+  //   let ids: number[] = [];
+
+  //   if (!userId && userId !== 0) {
+  //     ids = await api
+  //       .getGitRepos("token", {}, { project_id: currentProject.id })
+  //       .then((res) => res.data);
+  //   } else {
+  //     setRepoLoading(false);
+  //     setRepoError(true);
+  //     return;
+  //   }
+
+  //   const repoListPromises = ids.map((id) =>
+  //     api.getGitRepoList(
+  //       "<token>",
+  //       {},
+  //       { project_id: currentProject.id, git_repo_id: id }
+  //     )
+  //   );
+
+  //   try {
+  //     const resolvedRepoList = await Promise.allSettled(repoListPromises);
+
+  //     const repos: RepoType[][] = resolvedRepoList.map((repo) =>
+  //       repo.status === "fulfilled" ? repo.value.data : []
+  //     );
+
+  //     const names = new Set();
+  //     // note: would be better to use .flat() here but you need es2019 for
+  //     setRepos(
+  //       repos
+  //         .map((arr, idx) =>
+  //           arr.map((el) => {
+  //             el.GHRepoID = ids[idx];
+  //             return el;
+  //           })
+  //         )
+  //         .reduce((acc, val) => acc.concat(val), [])
+  //         .reduce((acc, val) => {
+  //           if (!names.has(val.FullName)) {
+  //             names.add(val.FullName);
+  //             return acc.concat(val);
+  //           } else {
+  //             return acc;
+  //           }
+  //         }, [])
+  //     );
+  //     setRepoLoading(false);
+  //   } catch (err) {
+  //     setRepoLoading(false);
+  //     setRepoError(true);
+  //   }
+  // };
 
-      setAccessData(data);
-      setAccessLoading(false);
-    } catch (error) {
-      setAccessError(true);
-      setAccessLoading(false);
-    }
+  useEffect(() => {
+    let isSubscribed = true;
+    api
+      .getGitProviders("<token>", {}, { project_id: currentProject.id })
+      .then((res) => {
+        const data = res.data;
+        if (isSubscribed) {
+          setProviders(data);
+
+          setCurrentProvider(data[0]);
+        }
+      });
 
-    let ids: number[] = [];
+    return () => {
+      isSubscribed = false;
+    };
+  }, []);
 
-    if (!userId && userId !== 0) {
-      ids = await api
-        .getGitRepos("token", {}, { project_id: currentProject.id })
-        .then((res) => res.data);
-    } else {
-      setRepoLoading(false);
-      setRepoError(true);
-      return;
-    }
+  const loadGithubRepos = async (repoId: number) => {
+    try {
+      const res = await api.getGitRepoList<
+        { FullName: string; Kind: "github" }[]
+      >("<token>", {}, { project_id: currentProject.id, git_repo_id: repoId });
 
-    const repoListPromises = ids.map((id) =>
-      api.getGitRepoList(
-        "<token>",
-        {},
-        { project_id: currentProject.id, git_repo_id: id }
-      )
-    );
+      const repos = res.data.map((repo) => ({ ...repo, GHRepoID: repoId }));
+      return repos;
+    } catch (error) {}
+  };
 
+  const loadGitlabRepos = async (integrationId: number) => {
     try {
-      const resolvedRepoList = await Promise.allSettled(repoListPromises);
-
-      const repos: RepoType[][] = resolvedRepoList.map((repo) =>
-        repo.status === "fulfilled" ? repo.value.data : []
+      const res = await api.getGitlabRepos<string[]>(
+        "<token>",
+        {},
+        { project_id: currentProject.id, integration_id: integrationId }
       );
+      const repos: RepoType[] = res.data.map((repo) => ({
+        FullName: repo,
+        Kind: "gitlab",
+        GitIntegrationId: integrationId,
+      }));
+      return repos;
+    } catch (error) {}
+  };
 
-      const names = new Set();
-      // note: would be better to use .flat() here but you need es2019 for
-      setRepos(
-        repos
-          .map((arr, idx) =>
-            arr.map((el) => {
-              el.GHRepoID = ids[idx];
-              return el;
-            })
-          )
-          .reduce((acc, val) => acc.concat(val), [])
-          .reduce((acc, val) => {
-            if (!names.has(val.FullName)) {
-              names.add(val.FullName);
-              return acc.concat(val);
-            } else {
-              return acc;
-            }
-          }, [])
-      );
-      setRepoLoading(false);
-    } catch (err) {
-      setRepoLoading(false);
-      setRepoError(true);
+  const loadRepos = (provider: any) => {
+    if (provider.provider === "github") {
+      return loadGithubRepos(provider.installation_id);
+    } else {
+      return loadGitlabRepos(provider.integration_id);
     }
   };
 
-  // TODO: Try to unhook before unmount
   useEffect(() => {
-    loadData();
-  }, []);
+    let isSubscribed = true;
+    if (!currentProvider) {
+      return () => {
+        isSubscribed = false;
+      };
+    }
+
+    setRepoLoading(true);
+
+    loadRepos(currentProvider)
+      .then((repos) => {
+        if (isSubscribed) {
+          setRepos(repos);
+        }
+      })
+      .catch((err) => {
+        setRepos([]);
+        console.log(err);
+      })
+      .finally(() => {
+        setRepoLoading(false);
+      });
+  }, [currentProvider]);
+
+  // TODO: Try to unhook before unmount
+  // useEffect(() => {
+  //   loadData();
+  // }, []);
 
   // clear out actionConfig and SelectedRepository if new search is performed
   useEffect(() => {
@@ -119,6 +198,7 @@ const RepoList: React.FC<Props> = ({
       image_repo_uri: null,
       git_branch: null,
       git_repo_id: 0,
+      gitlab_integration_id: 0,
     });
     setSelectedRepo(null);
   }, [searchFilter]);
@@ -126,13 +206,17 @@ const RepoList: React.FC<Props> = ({
   const setRepo = (x: RepoType) => {
     let updatedConfig = actionConfig;
     updatedConfig.git_repo = x.FullName;
-    updatedConfig.git_repo_id = x.GHRepoID;
+    if (x.Kind === "gitlab") {
+      updatedConfig.gitlab_integration_id = x.GitIntegrationId;
+    } else {
+      updatedConfig.git_repo_id = x.GHRepoID;
+    }
     setActionConfig(updatedConfig);
     setSelectedRepo(x.FullName);
   };
 
   const renderRepoList = () => {
-    if (repoLoading || accessLoading) {
+    if (repoLoading) {
       return (
         <LoadingWrapper>
           <Loading />
@@ -140,19 +224,21 @@ const RepoList: React.FC<Props> = ({
       );
     } else if (repoError) {
       return <LoadingWrapper>Error loading repos.</LoadingWrapper>;
-    } else if (repos.length == 0) {
-      if (accessError) {
+    } else if (!Array.isArray(repos) || repos.length === 0) {
+      if (currentProvider.provider === "gitlab") {
         return (
           <LoadingWrapper>
-            No connected Github repos found.
-            <A href={"/api/integrations/github-app/oauth"}>
-              Authorize Porter to view your repositories.
+            We couldn't reach gitlab to get your repos. You can
+            <A
+              style={{ marginRight: "5px" }}
+              href={`/api/projects/${currentProject.id}/oauth/gitlab?integration_id=${currentProvider.integration_id}`}
+            >
+              Connect your gitlab account to porter
             </A>
+            or select another git provider.
           </LoadingWrapper>
         );
-      }
-
-      if (accessData.accounts?.length === 0) {
+      } else {
         return (
           <LoadingWrapper>
             No connected Github repos found. You can
@@ -206,11 +292,19 @@ const RepoList: React.FC<Props> = ({
     } else {
       return (
         <>
-          <SearchBar
-            setSearchFilter={setSearchFilter}
-            disabled={repoError || repoLoading || accessError || accessLoading}
-            prompt={"Search repos..."}
-          />
+          <div style={{ display: "flex" }}>
+            <ProviderSelector
+              values={providers}
+              currentValue={currentProvider}
+              onChange={setCurrentProvider}
+            />
+            <SearchBar
+              setSearchFilter={setSearchFilter}
+              disabled={repoError || repoLoading}
+              prompt={"Search repos..."}
+              fullWidth
+            />
+          </div>
           <RepoListWrapper>
             <ExpandedWrapper>{renderRepoList()}</ExpandedWrapper>
           </RepoListWrapper>
@@ -224,6 +318,80 @@ const RepoList: React.FC<Props> = ({
 
 export default RepoList;
 
+const ProviderSelector = (props: {
+  values: any[];
+  currentValue: any;
+  onChange: (provider: any) => void;
+}) => {
+  const { values, currentValue, onChange } = props;
+  const [isOpen, setIsOpen] = useState(false);
+  const icon = `devicon-${currentValue?.provider}-plain colored`;
+  return (
+    <ProviderSelectorStyles.Wrapper>
+      <ProviderSelectorStyles.Button onClick={() => setIsOpen((prev) => !prev)}>
+        <ProviderSelectorStyles.Icon className={icon} />
+        {currentValue?.name || currentValue?.instance_url}
+      </ProviderSelectorStyles.Button>
+      {isOpen ? (
+        <ProviderSelectorStyles.OptionWrapper>
+          {values.map((provider) => {
+            return (
+              <ProviderSelectorStyles.Option onClick={() => onChange(provider)}>
+                <ProviderSelectorStyles.Icon
+                  className={`devicon-${provider?.provider}-plain colored`}
+                />
+                {provider?.name || provider?.instance_url}
+              </ProviderSelectorStyles.Option>
+            );
+          })}
+        </ProviderSelectorStyles.OptionWrapper>
+      ) : null}
+    </ProviderSelectorStyles.Wrapper>
+  );
+};
+
+const ProviderSelectorStyles = {
+  Wrapper: styled.div`
+    position: relative;
+    margin-bottom: 10px;
+    margin-right: 5px;
+  `,
+  Button: styled.div`
+    border-radius: 5px;
+    border: 1px solid white;
+    height: 100%;
+    border-bottom: 0;
+    border: 1px solid #ffffff55;
+    border-radius: 3px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 6px 15px;
+  `,
+  OptionWrapper: styled.div`
+    position: absolute;
+    background-color: #202227;
+  `,
+  Option: styled.div`
+    white-space: nowrap;
+    padding: 8px 10px;
+    display: flex;
+    align-items: center;
+    :not(:last-child) {
+      border-bottom: 1px solid black;
+      border-bottom-width: 90%;
+    }
+    :hover {
+      background-color: #363940;
+    }
+  `,
+  Icon: styled.span`
+    font-size: 24px;
+    margin-right: 15px;
+    color: white;
+  `,
+};
+
 const RepoListWrapper = styled.div`
   border: 1px solid #ffffff55;
   border-radius: 3px;
@@ -318,5 +486,6 @@ const A = styled.a`
   color: #8590ff;
   text-decoration: underline;
   margin-left: 5px;
+
   cursor: pointer;
 `;

+ 0 - 1
dashboard/src/main/home/launch/launch-flow/SourcePage.tsx

@@ -474,6 +474,5 @@ const StyledSourceBox = styled.div`
   border-radius: 5px;
   font-size: 13px;
   margin-top: 6px;
-  overflow: auto;
   margin-bottom: 25px;
 `;

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

@@ -444,7 +444,7 @@ const deployTemplate = baseApi<
     image_url?: string;
     values?: any;
     name: string;
-    git_action_config?: FullActionConfigType;
+    github_action_config?: FullActionConfigType;
     build_config?: any;
     synced_env_groups?: string[];
   },
@@ -1814,6 +1814,51 @@ const updateReleaseTags = baseApi<
     `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${release_name}/0/update_tags`
 );
 
+const getGitProviders = baseApi<{}, { project_id: number }>(
+  "GET",
+  ({ project_id }) => `/api/projects/${project_id}/integrations/git`
+);
+
+const getGitlabRepos = baseApi<
+  {},
+  { project_id: number; integration_id: number }
+>(
+  "GET",
+  ({ project_id, integration_id }) =>
+    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos`
+);
+
+const getGitlabBranches = baseApi<
+  {},
+  {
+    project_id: number;
+    integration_id: number;
+    repo_owner: string;
+    repo_name: string;
+  }
+>(
+  "GET",
+  ({ project_id, integration_id, repo_owner, repo_name }) =>
+    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/${repo_owner}/${repo_name}/branches`
+);
+
+const getGitlabFolderContent = baseApi<
+  {
+    dir: string;
+  },
+  {
+    project_id: number;
+    integration_id: number;
+    repo_owner: string;
+    repo_name: string;
+    branch: string;
+  }
+>(
+  "GET",
+  ({ project_id, integration_id, repo_owner, repo_name, branch }) =>
+    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/${repo_owner}/${repo_name}/${branch}/contents`
+);
+
 // Bundle export to allow default api import (api.<method> is more readable)
 export default {
   checkAuth,
@@ -1985,4 +2030,8 @@ export default {
   getTagsByProjectId,
   createTag,
   updateReleaseTags,
+  getGitProviders,
+  getGitlabRepos,
+  getGitlabBranches,
+  getGitlabFolderContent,
 };

+ 17 - 9
dashboard/src/shared/types.tsx

@@ -223,11 +223,18 @@ export interface FormElement {
   };
 }
 
-export interface RepoType {
+export type RepoType = {
   FullName: string;
-  kind: string;
-  GHRepoID: number;
-}
+} & (
+  | {
+      Kind: "github";
+      GHRepoID: number;
+    }
+  | {
+      Kind: "gitlab";
+      GitIntegrationId: number;
+    }
+);
 
 export interface FileType {
   path: string;
@@ -275,19 +282,20 @@ export interface InviteType {
   id: number;
 }
 
-export interface ActionConfigType {
+export type ActionConfigType = {
   git_repo: string;
   git_branch: string;
   image_repo_uri: string;
-  git_repo_id: number;
-}
+  git_repo_id?: number;
+  gitlab_integration_id?: number;
+};
 
-export interface FullActionConfigType extends ActionConfigType {
+export type FullActionConfigType = ActionConfigType & {
   dockerfile_path: string;
   folder_path: string;
   registry_id: number;
   should_create_workflow: boolean;
-}
+};
 
 export interface CapabilityType {
   github: boolean;