Soham Parekh 3 лет назад
Родитель
Сommit
f8ad0df636

+ 5 - 0
dashboard/package-lock.json

@@ -5446,6 +5446,11 @@
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
       "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
     },
+    "fuse.js": {
+      "version": "6.6.2",
+      "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
+      "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA=="
+    },
     "gensync": {
       "version": "1.0.0-beta.2",
       "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",

+ 1 - 0
dashboard/package.json

@@ -34,6 +34,7 @@
     "d3-array": "^2.11.0",
     "d3-time-format": "^3.0.0",
     "dotenv": "^8.2.0",
+    "fuse.js": "^6.6.2",
     "highlight.run": "^1.4.5",
     "ini": ">=1.3.6",
     "js-base64": "^3.6.0",

+ 46 - 50
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentCard.tsx

@@ -101,7 +101,9 @@ const DeploymentCard: React.FC<{
   };
 
   return (
-    <DeploymentCardWrapper>
+    <DeploymentCardWrapper
+      to={`/preview-environments/details/${deployment.namespace}?environment_id=${deployment.environment_id}`}
+    >
       <DataContainer>
         <PRName>
           <PRIcon src={pr_icon} alt="pull request icon" />
@@ -176,55 +178,31 @@ const DeploymentCard: React.FC<{
               </>
             ) : null}
 
-            {deployment.status !== DeploymentStatus.Creating &&
-              deployment.status !== DeploymentStatus.Inactive && (
-                <>
-                  <RowButton
-                    to={`/preview-environments/details/${deployment.namespace}?environment_id=${deployment.environment_id}`}
-                    key={deployment.id}
-                  >
-                    <i className="material-icons-outlined">info</i>
-                    Details
-                  </RowButton>
-                  <RowButton
-                    to={deployment.subdomain}
-                    key={deployment.subdomain}
-                    target="_blank"
-                  >
-                    <i className="material-icons">open_in_new</i>
-                    View Live
-                  </RowButton>
-                </>
-              )}
-            {deployment.status === DeploymentStatus.Inactive ? (
-              <ActionButton
-                onClick={reEnablePreviewEnvironment}
-                disabled={isLoading}
-                hasError={hasErrorOnReEnabling}
-              >
-                {isLoading ? (
-                  <Loading width="198px" height="14px" />
-                ) : (
-                  <>
-                    <i className="material-icons">play_arrow</i>
-                    Activate Preview Environment
-                  </>
-                )}
-              </ActionButton>
-            ) : (
-              <Button
-                onClick={() => {
-                  setCurrentOverlay({
-                    message: `Are you sure you want to delete this deployment?`,
-                    onYes: deleteDeployment,
-                    onNo: () => setCurrentOverlay(null),
-                  });
-                }}
-              >
-                <i className="material-icons">delete</i>
-                Delete
-              </Button>
+            {deployment.status !== DeploymentStatus.Creating && (
+              <>
+                <RowButton
+                  to={deployment.subdomain}
+                  key={deployment.subdomain}
+                  target="_blank"
+                >
+                  <i className="material-icons">open_in_new</i>
+                  View Live
+                </RowButton>
+                <I className="material-icons">more_vert</I>
+              </>
             )}
+            {/* <Button
+              onClick={() => {
+                setCurrentOverlay({
+                  message: `Are you sure you want to delete this deployment?`,
+                  onYes: deleteDeployment,
+                  onNo: () => setCurrentOverlay(null),
+                });
+              }}
+            >
+              <i className="material-icons">delete</i>
+              Delete
+            </Button> */}
           </>
         ) : (
           <DeleteMessage>
@@ -308,7 +286,7 @@ const PRName = styled.div`
   margin-bottom: 10px;
 `;
 
-const DeploymentCardWrapper = styled.div`
+const DeploymentCardWrapper = styled(DynamicLink)`
   display: flex;
   justify-content: space-between;
   font-size: 13px;
@@ -516,3 +494,21 @@ const MergeInfo = styled.div`
     margin: 0 2px;
   }
 `;
+
+const I = styled.i`
+  user-select: none;
+  margin-left: 15px;
+  color: #aaaabb;
+  cursor: pointer;
+  border-radius: 40px;
+  font-size: 18px;
+  width: 30px;
+  height: 30px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  &:hover {
+    background: #26292e;
+    border: 1px solid #494b4f;
+  }
+`;

+ 200 - 86
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentList.tsx

@@ -2,40 +2,74 @@ import React, { useContext, useEffect, useMemo, useState } from "react";
 import { Context } from "shared/Context";
 import api from "shared/api";
 import styled from "styled-components";
-import Selector from "components/Selector";
-
 import Loading from "components/Loading";
-
 import _ from "lodash";
 import DeploymentCard from "./DeploymentCard";
-import { Environment, PRDeployment, PullRequest } from "../types";
+import { PRDeployment, PullRequest } from "../types";
 import { useRouting } from "shared/routing";
 import { useHistory, useLocation, useParams } from "react-router";
 import { deployments, pull_requests } from "../mocks";
-import PullRequestCard from "./PullRequestCard";
 import DynamicLink from "components/DynamicLink";
-import { PreviewEnvironmentsHeader } from "../components/PreviewEnvironmentsHeader";
-import SearchBar from "components/SearchBar";
-import CheckboxRow from "components/form-components/CheckboxRow";
-import DocsHelper from "components/DocsHelper";
-import pullRequestIcon from "assets/pull_request_icon.svg";
 import DashboardHeader from "../../DashboardHeader";
+import RadioFilter from "components/RadioFilter";
 
-const AvailableStatusFilters = [
-  "all",
-  "created",
-  "failed",
-  "active",
-  "inactive",
-  "not_deployed",
-];
+import pullRequestIcon from "assets/pull_request_icon.svg";
+import filterOutline from "assets/filter-outline.svg";
+import sort from "assets/sort.svg";
+import { search } from "shared/search";
+
+const AvailableStatusFilters = ["all", "created", "failed", "not_deployed"];
 
 type AvailableStatusFiltersType = typeof AvailableStatusFilters[number];
 
+const HARD_CODED_DEPLOYMENTS: PRDeployment[] = [
+  {
+    id: 1,
+    created_at: "2021-03-01T00:00:00.000Z",
+    updated_at: "2021-03-01T00:00:00.000Z",
+    subdomain: "subdomain",
+    status: "created",
+    environment_id: 1,
+    pull_request_id: 1,
+    namespace: "namespace",
+    last_workflow_run_url: "",
+    gh_installation_id: 1,
+    gh_deployment_id: 1,
+    gh_pr_name: "gh_pr_name",
+    gh_repo_owner: "meehawk",
+    gh_repo_name: "meehawk",
+    gh_commit_sha: "3659ef050a687da4d04bb870b27058bd9d1957be",
+    gh_pr_branch_from: "gh_pr_branch_from",
+    gh_pr_branch_into: "gh_pr_branch_into",
+  },
+  {
+    id: 2,
+    created_at: "2021-03-01T00:00:00.000Z",
+    updated_at: "2021-03-01T00:00:00.000Z",
+    subdomain: "subdomain",
+    status: "created",
+    environment_id: 1,
+    pull_request_id: 1,
+    namespace: "namespace",
+    last_workflow_run_url: "",
+    gh_installation_id: 1,
+    gh_deployment_id: 1,
+    gh_pr_name: "some_awesome_pr",
+    gh_repo_owner: "godzilla",
+    gh_repo_name: "kong",
+    gh_commit_sha: "3659ef050a687da4d04bb870b27058bd9d1957be",
+    gh_pr_branch_from: "gh_pr_branch_from",
+    gh_pr_branch_into: "gh_pr_branch_into",
+  },
+];
+
 const DeploymentList = () => {
+  const [sortOrder, setSortOrder] = useState("Newest");
   const [isLoading, setIsLoading] = useState(true);
   const [hasError, setHasError] = useState(false);
-  const [deploymentList, setDeploymentList] = useState<PRDeployment[]>([]);
+  const [deploymentList, setDeploymentList] = useState<PRDeployment[]>(
+    HARD_CODED_DEPLOYMENTS
+  );
   const [pullRequests, setPullRequests] = useState<PullRequest[]>([]);
   const [searchValue, setSearchValue] = useState("");
   const [newCommentsDisabled, setNewCommentsDisabled] = useState(false);
@@ -43,7 +77,7 @@ const DeploymentList = () => {
   const [
     statusSelectorVal,
     setStatusSelectorVal,
-  ] = useState<AvailableStatusFiltersType>("active");
+  ] = useState<AvailableStatusFiltersType>("all");
 
   const { currentProject, currentCluster } = useContext(Context);
   const { getQueryParam, pushQueryParams } = useRouting();
@@ -104,8 +138,8 @@ const DeploymentList = () => {
     let isSubscribed = true;
     setIsLoading(true);
 
-    Promise.allSettled([getPRDeploymentList(), getEnvironment()]).then(
-      ([getDeploymentsResponse, getEnvironmentResponse]) => {
+    Promise.allSettled([getPRDeploymentList(), getEnvironment()])
+      .then(([getDeploymentsResponse, getEnvironmentResponse]) => {
         const deploymentList =
           getDeploymentsResponse.status === "fulfilled"
             ? getDeploymentsResponse.value.data
@@ -119,14 +153,16 @@ const DeploymentList = () => {
           return;
         }
 
-        setDeploymentList(deploymentList.deployments || []);
+        setDeploymentList(deploymentList.deployments || HARD_CODED_DEPLOYMENTS);
         setPullRequests(deploymentList.pull_requests || []);
 
         setNewCommentsDisabled(environmentList.new_comments_disabled || false);
 
         setIsLoading(false);
-      }
-    );
+      })
+      .catch(() => {
+        setDeploymentList(HARD_CODED_DEPLOYMENTS);
+      });
 
     return () => {
       isSubscribed = false;
@@ -173,28 +209,14 @@ const DeploymentList = () => {
   };
 
   const filteredDeployments = useMemo(() => {
-    // Only filter out inactive when status filter is "active"
-    if (statusSelectorVal === "active") {
-      return deploymentList
-        .filter((d) => {
-          return d.status !== "inactive";
-        })
-        .filter((d) => {
-          return Object.values(d).find(searchFilter) !== undefined;
-        });
-    }
-
-    if (statusSelectorVal === "inactive") {
-      return deploymentList
-        .filter((d) => {
-          return d.status === "inactive";
-        })
-        .filter((d) => {
-          return Object.values(d).find(searchFilter) !== undefined;
-        });
-    }
+    const filteredDeploymentList = deploymentList.filter(
+      (d) => !["deleted", "inactive"].includes(d.status)
+    );
 
-    return deploymentList;
+    return search<PRDeployment>(filteredDeploymentList, searchValue, {
+      isCaseSensitive: false,
+      keys: ["gh_pr_name", "gh_repo_name", "gh_repo_owner"],
+    });
   }, [statusSelectorVal, deploymentList, searchValue]);
 
   const filteredPullRequests = useMemo(() => {
@@ -216,7 +238,7 @@ const DeploymentList = () => {
       );
     }
 
-    if (!deploymentList.length && !pullRequests.length) {
+    if (!deploymentList.length) {
       return (
         <Placeholder>
           No preview apps have been found. Open a PR to create a new preview
@@ -225,7 +247,7 @@ const DeploymentList = () => {
       );
     }
 
-    if (!filteredDeployments.length && !filteredPullRequests.length) {
+    if (!filteredDeployments.length) {
       return (
         <Placeholder>
           No preview apps have been found with the given filter.
@@ -235,7 +257,8 @@ const DeploymentList = () => {
 
     return (
       <>
-        {filteredPullRequests.map((pr) => {
+        {/* Deprecated -> New Preview Env button */}
+        {/* {filteredPullRequests.map((pr) => {
           return (
             <PullRequestCard
               key={pr.pr_title}
@@ -243,7 +266,7 @@ const DeploymentList = () => {
               onCreation={handlePreviewEnvironmentManualCreation}
             />
           );
-        })}
+        })} */}
         {filteredDeployments.map((d) => {
           return (
             <DeploymentCard
@@ -259,11 +282,6 @@ const DeploymentList = () => {
     );
   };
 
-  const handleStatusFilterChange = (value: string) => {
-    pushQueryParams({ status_filter: value });
-    setStatusSelectorVal(value);
-  };
-
   const handleToggleCommentStatus = (currentlyDisabled: boolean) => {
     api
       .toggleNewCommentForEnvironment(
@@ -282,6 +300,10 @@ const DeploymentList = () => {
       });
   };
 
+  useEffect(() => {
+    pushQueryParams({ status_filter: statusSelectorVal });
+  }, [statusSelectorVal]);
+
   return (
     <>
       <BreadcrumbRow>
@@ -294,7 +316,7 @@ const DeploymentList = () => {
         image="https://git-scm.com/images/logos/downloads/Git-Icon-1788C.png"
         title={
           <Flex>
-            <StyledLink 
+            <StyledLink
               to={`https://github.com/${selectedRepo}`}
               target="_blank"
             >
@@ -303,11 +325,7 @@ const DeploymentList = () => {
             <DynamicLink
               to={`/preview-environments/deployments/${environment_id}/${repo_owner}/${repo_name}/settings`}
             >
-              <I
-                className="material-icons"
-              >
-                more_vert
-              </I>
+              <I className="material-icons">more_vert</I>
             </DynamicLink>
           </Flex>
         }
@@ -315,7 +333,7 @@ const DeploymentList = () => {
         disableLineBreak
         capitalize={false}
       />
-      <Flex>
+      {/* <Flex>
         <ActionsWrapper>
           <StyledStatusSelector>
             <RefreshButton color={"#7d7d81"} onClick={handleRefresh}>
@@ -351,7 +369,55 @@ const DeploymentList = () => {
             />
           </StyledStatusSelector>
         </ActionsWrapper>
-      </Flex>
+      </Flex> */}
+      <FlexRow>
+        <Flex>
+          <SearchRowWrapper>
+            <SearchBarWrapper>
+              <i className="material-icons">search</i>
+              <SearchInput
+                value={searchValue}
+                onChange={(e: any) => {
+                  setSearchValue(e.target.value);
+                }}
+                placeholder="Search"
+              />
+            </SearchBarWrapper>
+          </SearchRowWrapper>
+          <RadioFilter
+            icon={filterOutline}
+            selected={statusSelectorVal}
+            setSelected={setStatusSelectorVal}
+            options={AvailableStatusFilters.map((filter) => ({
+              value: filter,
+              label: _.startCase(filter),
+            }))}
+            name="Status"
+          />
+        </Flex>
+        <Flex>
+          <RefreshButton color={"#7d7d81"} onClick={handleRefresh}>
+            <i className="material-icons">refresh</i>
+          </RefreshButton>
+          <RadioFilter
+            icon={sort}
+            selected={sortOrder}
+            setSelected={setSortOrder}
+            options={[
+              { label: "Newest", value: "Newest" },
+              { label: "Oldest", value: "Oldest" },
+              { label: "Alphabetical", value: "Alphabetical" },
+            ]}
+            name="Sort"
+          />
+          <CreatePreviewEnvironmentButton
+            // TODO Justin: Implement Preview Environment
+            onClick={_.noop}
+          >
+            <i className="material-icons">add</i> New preview env
+          </CreatePreviewEnvironmentButton>
+        </Flex>
+      </FlexRow>
       <Container>
         <EventsGrid>{renderDeploymentList()}</EventsGrid>
       </Container>
@@ -387,7 +453,7 @@ const I = styled.i`
   justify-content: center;
   :hover {
     background: #26292e;
-    border: 1px solid #494b4f; 
+    border: 1px solid #494b4f;
   }
 `;
 
@@ -471,11 +537,6 @@ const Title = styled.div`
   color: #ffffff;
 `;
 
-const ActionsWrapper = styled.div`
-  display: flex;
-  margin-left: auto;
-`;
-
 const RefreshButton = styled.button`
   display: flex;
   align-items: center;
@@ -545,30 +606,83 @@ const SearchInput = styled.input`
   background: none;
   width: 100%;
   color: white;
-  padding: 0;
-  height: 20px;
+  height: 100%;
 `;
 
 const SearchRow = styled.div`
   display: flex;
-  width: 100%;
-  font-size: 13px;
-  color: #ffffff55;
-  border-radius: 4px;
-  user-select: none;
   align-items: center;
-  padding: 10px 0px;
-  min-width: 300px;
-  max-width: min-content;
-  max-height: 35px;
-  background: #ffffff11;
-  margin-right: 15px;
+  height: 30px;
+  margin-right: 10px;
+  background: #26292e;
+  border-radius: 5px;
+  border: 1px solid #aaaabb33;
+`;
+
+const SearchRowWrapper = styled(SearchRow)`
+  border-radius: 5px;
+  width: 250px;
+`;
+
+const SearchBarWrapper = styled.div`
+  display: flex;
+  flex: 1;
 
-  i {
+  > i {
+    color: #aaaabb;
+    padding-top: 1px;
+    margin-left: 8px;
+    font-size: 16px;
+    margin-right: 8px;
+  }
+`;
+
+const FlexRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  flex-wrap: wrap;
+`;
+
+const CreatePreviewEnvironmentButton = styled.div`
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-left: 10px;
+  justify-content: space-between;
+  font-size: 13px;
+  cursor: pointer;
+  font-family: "Work Sans", sans-serif;
+  border-radius: 5px;
+  font-weight: 500;
+  color: white;
+  height: 30px;
+  padding: 0 8px;
+  min-width: 155px;
+  padding-right: 13px;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  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"};
+  }
+
+  > i {
+    color: white;
     width: 18px;
     height: 18px;
-    margin-left: 12px;
-    margin-right: 12px;
-    font-size: 20px;
+    font-weight: 600;
+    font-size: 12px;
+    border-radius: 20px;
+    display: flex;
+    align-items: center;
+    margin-right: 5px;
+    justify-content: center;
   }
 `;

+ 41 - 29
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentsList.tsx

@@ -10,12 +10,41 @@ import { Environment } from "../types";
 import EnvironmentCard from "./EnvironmentCard";
 import Placeholder from "components/Placeholder";
 
+const HARD_CODED_ENVS: Environment[] = [
+  {
+    id: 12,
+    project_id: 1234,
+    cluster_id: 4321,
+    git_installation_id: 55,
+    name: "asdf",
+    git_repo_owner: "owned",
+    git_repo_name: "this-is-a-repo",
+    last_deployment_status: "failed",
+    deployment_count: 12,
+    mode: "manual",
+  },
+  {
+    id: 13,
+    project_id: 1234,
+    cluster_id: 4321,
+    git_installation_id: 55,
+    name: "asdf",
+    git_repo_owner: "owned",
+    git_repo_name: "this-is-a-repo",
+    last_deployment_status: "failed",
+    deployment_count: 12,
+    mode: "manual",
+  },
+];
+
 const EnvironmentsList = () => {
   const { currentCluster, currentProject } = useContext(Context);
   const [isLoading, setIsLoading] = useState(true);
   const [buttonIsReady, setButtonIsReady] = useState(false);
 
-  const [environments, setEnvironments] = useState<Environment[]>([]);
+  const [environments, setEnvironments] = useState<Environment[]>(
+    HARD_CODED_ENVS
+  );
 
   const removeEnvironmentFromList = (deletedEnv: Environment) => {
     setEnvironments((prev) => {
@@ -56,34 +85,12 @@ const EnvironmentsList = () => {
       }
 
       setEnvironments(envs);
+
+      //
+      setEnvironments(HARD_CODED_ENVS);
     } catch (error) {
       // ret2: remove placeholder (set to empty array)
-      setEnvironments([
-        {
-          id: 12,
-          project_id: 1234,
-          cluster_id: 4321,
-          git_installation_id: 55,
-          name: "asdf",
-          git_repo_owner: "owned",
-          git_repo_name: "this-is-a-repo",
-          last_deployment_status: "failed",
-          deployment_count: 12,
-          mode: "manual",
-        },
-        {
-          id: 13,
-          project_id: 1234,
-          cluster_id: 4321,
-          git_installation_id: 55,
-          name: "asdf",
-          git_repo_owner: "owned",
-          git_repo_name: "this-is-a-repo",
-          last_deployment_status: "failed",
-          deployment_count: 12,
-          mode: "manual",
-        },
-      ]);
+      setEnvironments(HARD_CODED_ENVS);
     }
   };
 
@@ -111,11 +118,16 @@ const EnvironmentsList = () => {
           <ButtonEnablePREnvironments setIsReady={setButtonIsReady} />
         </ControlRow>
         {isLoading ? (
-          <LoadingWrapper><Loading /></LoadingWrapper>
+          <LoadingWrapper>
+            <Loading />
+          </LoadingWrapper>
         ) : (
           <>
             {environments.length === 0 ? (
-              <Placeholder title="No repositories found" height="calc(100vh - 400px)">
+              <Placeholder
+                title="No repositories found"
+                height="calc(100vh - 400px)"
+              >
                 No repositories were found with Preview Environments enabled.
               </Placeholder>
             ) : (

+ 13 - 0
dashboard/src/shared/search.ts

@@ -0,0 +1,13 @@
+import Fuse from "fuse.js";
+
+export const search = <T>(
+  items: T[],
+  searchTerm: string,
+  options?: Fuse.IFuseOptions<T>
+) => {
+  if (!searchTerm) {
+    return items;
+  }
+  const fuse = new Fuse<T>(items, options);
+  return fuse.search(searchTerm).map((result) => result.item);
+};