Prechádzať zdrojové kódy

feat/allow env group opening in new tab (#2610)

* feat: allow env group opening in new tab

* chore: minor fix

Co-authored-by: Soham Parekh <sohamparekh@soham.parekh-macOS>
meehawk 3 rokov pred
rodič
commit
074795c88a

+ 11 - 0
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -30,6 +30,7 @@ import loadable from "@loadable/component";
 import Loading from "components/Loading";
 import JobRunTable from "./chart/JobRunTable";
 import TagFilter from "./TagFilter";
+import ExpandedEnvGroupDashboard from "./env-groups/ExpandedEnvGroupDashboard";
 
 // @ts-ignore
 const LazyDatabasesRoutes = loadable(() => import("./databases/routes.tsx"), {
@@ -334,6 +335,16 @@ class ClusterDashboard extends Component<PropsType, StateType> {
 
           {this.renderBodyForApps()}
         </GuardedRoute>
+        <GuardedRoute
+          path={"/env-groups/:name"}
+          scope="env_group"
+          resource=""
+          verb={["get", "list"]}
+        >
+          <ExpandedEnvGroupDashboard
+            currentCluster={this.props.currentCluster}
+          />
+        </GuardedRoute>
         <GuardedRoute
           path={"/env-groups"}
           scope="env_group"

+ 28 - 30
dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroup.tsx

@@ -5,6 +5,8 @@ import key from "assets/key.svg";
 
 import { Context } from "shared/Context";
 import { readableDate } from "shared/string_utils";
+import { Link } from "react-router-dom";
+import _ from "lodash";
 
 export type EnvGroupData = {
   name: string;
@@ -15,54 +17,50 @@ export type EnvGroupData = {
 
 type PropsType = {
   envGroup: EnvGroupData;
-  setExpanded: () => void;
 };
 
 type StateType = {
-  expand: boolean;
   update: any[];
 };
 
 export default class EnvGroup extends Component<PropsType, StateType> {
   state = {
-    expand: false,
     update: [] as any[],
   };
 
   render() {
-    let { envGroup, setExpanded } = this.props;
+    let { envGroup } = this.props;
     let name = envGroup?.name;
     let timestamp = envGroup?.created_at;
     let namespace = envGroup?.namespace;
     let version = envGroup?.version;
 
     return (
-      <StyledEnvGroup
-        onMouseEnter={() => this.setState({ expand: true })}
-        onMouseLeave={() => this.setState({ expand: false })}
-        expand={this.state.expand}
-        onClick={() => setExpanded()}
-      >
-        <Title>
-          <IconWrapper>
-            <Icon src={key} />
-          </IconWrapper>
-          {name}
-        </Title>
-
-        <BottomWrapper>
-          <InfoWrapper>
-            <LastDeployed>Last updated {readableDate(timestamp)}</LastDeployed>
-          </InfoWrapper>
-
-          <TagWrapper>
-            Namespace
-            <NamespaceTag>{namespace}</NamespaceTag>
-          </TagWrapper>
-        </BottomWrapper>
-
-        <Version>v{version}</Version>
-      </StyledEnvGroup>
+      <Link to={`/env-groups/${name}${window.location.search}`} target="_self">
+        <StyledEnvGroup>
+          <Title>
+            <IconWrapper>
+              <Icon src={key} />
+            </IconWrapper>
+            {name}
+          </Title>
+
+          <BottomWrapper>
+            <InfoWrapper>
+              <LastDeployed>
+                Last updated {readableDate(timestamp)}
+              </LastDeployed>
+            </InfoWrapper>
+
+            <TagWrapper>
+              Namespace
+              <NamespaceTag>{namespace}</NamespaceTag>
+            </TagWrapper>
+          </BottomWrapper>
+
+          <Version>v{version}</Version>
+        </StyledEnvGroup>
+      </Link>
     );
   }
 }

+ 66 - 57
dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupDashboard.tsx

@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { Component, useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 
 import sliders from "assets/sliders.svg";
@@ -15,6 +15,10 @@ import ExpandedEnvGroup from "./ExpandedEnvGroup";
 import { RouteComponentProps, withRouter } from "react-router";
 import { getQueryParam, pushQueryParams } from "shared/routing";
 import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
+import { useQuery } from "@tanstack/react-query";
+import api from "shared/api";
+import Loading from "components/Loading";
+import Placeholder from "components/Placeholder";
 
 type PropsType = RouteComponentProps &
   WithAuthProps & {
@@ -30,8 +34,8 @@ type StateType = {
   createEnvMode: boolean;
 };
 
-class EnvGroupDashboard extends Component<PropsType, StateType> {
-  state = {
+const EnvGroupDashboard = (props: PropsType) => {
+  const [state, setState] = useState<StateType>({
     expand: false,
     update: [] as any[],
     namespace: null as string,
@@ -40,48 +44,71 @@ class EnvGroupDashboard extends Component<PropsType, StateType> {
     sortType: localStorage.getItem("SortType")
       ? localStorage.getItem("SortType")
       : "Newest",
+  });
+
+  const setNamespace = (namespace: string) => {
+    setState((state) => ({ ...state, namespace }));
+    pushQueryParams(props, {
+      namespace: namespace ?? "ALL",
+    });
+  };
+
+  const setSortType = (sortType: string) => {
+    setState((state) => ({ ...state, sortType }));
+  };
+
+  const toggleCreateEnvMode = () => {
+    setState((state) => ({
+      ...state,
+      createEnvMode: !state.createEnvMode,
+    }));
+  };
+
+  const setExpandedEnvGroup = (envGroup: any | null) => {
+    setState((state) => ({ ...state, expandedEnvGroup: envGroup }));
+  };
+
+  const closeExpanded = () => {
+    pushQueryParams(props, {}, ["selected_env_group"]);
+    const redirectUrlOnClose = getQueryParam(props, "redirect_url");
+    if (redirectUrlOnClose) {
+      props.history.push(redirectUrlOnClose);
+      return;
+    }
+    setExpandedEnvGroup(null);
   };
 
-  renderBody = () => {
-    if (this.state.createEnvMode) {
+  const renderBody = () => {
+    const goBack = () =>
+      setState((state) => ({ ...state, createEnvMode: false }));
+
+    if (state.createEnvMode) {
       return (
-        <CreateEnvGroup
-          goBack={() => this.setState({ createEnvMode: false })}
-          currentCluster={this.props.currentCluster}
-        />
+        <CreateEnvGroup goBack={goBack} currentCluster={props.currentCluster} />
       );
     } else {
-      const isAuthorizedToAdd = this.props.isAuthorized("env_group", "", [
+      const isAuthorizedToAdd = props.isAuthorized("env_group", "", [
         "get",
         "create",
       ]);
+
       return (
         <>
           <ControlRow hasMultipleChilds={isAuthorizedToAdd}>
             <SortFilterWrapper>
               <NamespaceSelector
-                setNamespace={(namespace) =>
-                  this.setState({ namespace }, () => {
-                    pushQueryParams(this.props, {
-                      namespace: this.state.namespace || "ALL",
-                    });
-                  })
-                }
-                namespace={this.state.namespace}
+                setNamespace={setNamespace}
+                namespace={state.namespace}
               />
             </SortFilterWrapper>
             <Flex>
               <SortSelector
                 currentView="env-groups"
-                setSortType={(sortType) => this.setState({ sortType })}
-                sortType={this.state.sortType}
+                setSortType={setSortType}
+                sortType={state.sortType}
               />
               {isAuthorizedToAdd && (
-                <Button
-                  onClick={() =>
-                    this.setState({ createEnvMode: !this.state.createEnvMode })
-                  }
-                >
+                <Button onClick={toggleCreateEnvMode}>
                   <i className="material-icons">add</i> Create env group
                 </Button>
               )}
@@ -89,39 +116,25 @@ class EnvGroupDashboard extends Component<PropsType, StateType> {
           </ControlRow>
 
           <EnvGroupList
-            currentCluster={this.props.currentCluster}
-            namespace={this.state.namespace}
-            sortType={this.state.sortType}
-            setExpandedEnvGroup={(envGroup: any) => {
-              this.setState({ expandedEnvGroup: envGroup });
-            }}
+            currentCluster={props.currentCluster}
+            namespace={state.namespace}
+            sortType={state.sortType}
+            setExpandedEnvGroup={setExpandedEnvGroup}
           />
         </>
       );
     }
   };
 
-  closeExpanded = () => {
-    pushQueryParams(this.props, {}, ["selected_env_group"]);
-    const redirectUrlOnClose = getQueryParam(this.props, "redirect_url");
-    if (redirectUrlOnClose) {
-      this.props.history.push(redirectUrlOnClose);
-      return;
-    }
-    this.setState({ expandedEnvGroup: null });
-  };
-
-  renderContents = () => {
-    if (this.state.expandedEnvGroup) {
+  const renderContents = () => {
+    if (state.expandedEnvGroup) {
       return (
         <ExpandedEnvGroup
-          isAuthorized={this.props.isAuthorized}
-          namespace={
-            this.state.expandedEnvGroup?.namespace || this.state.namespace
-          }
-          currentCluster={this.props.currentCluster}
-          envGroup={this.state.expandedEnvGroup}
-          closeExpanded={() => this.closeExpanded()}
+          isAuthorized={props.isAuthorized}
+          namespace={state.expandedEnvGroup?.namespace || state.namespace}
+          currentCluster={props.currentCluster}
+          envGroup={state.expandedEnvGroup}
+          closeExpanded={() => closeExpanded()}
         />
       );
     } else {
@@ -134,18 +147,14 @@ class EnvGroupDashboard extends Component<PropsType, StateType> {
             disableLineBreak
             capitalize={false}
           />
-          {this.renderBody()}
+          {renderBody()}
         </>
       );
     }
   };
 
-  render() {
-    return <>{this.renderContents()}</>;
-  }
-}
-
-EnvGroupDashboard.contextType = Context;
+  return <>{renderContents()}</>;
+};
 
 export default withRouter(withAuth(EnvGroupDashboard));
 

+ 0 - 6
dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupList.tsx

@@ -98,11 +98,6 @@ const EnvGroupList: React.FunctionComponent<Props> = (props) => {
       });
   }, [currentCluster, namespace, sortType]);
 
-  const handleExpand = (envGroup: any) => {
-    pushQueryParams(props, { selected_env_group: envGroup.name }, []);
-    props.setExpandedEnvGroup(envGroup);
-  };
-
   const renderEnvGroupList = () => {
     if (isLoading || (!namespace && namespace !== "")) {
       return (
@@ -130,7 +125,6 @@ const EnvGroupList: React.FunctionComponent<Props> = (props) => {
         <EnvGroup
           key={i}
           envGroup={envGroup}
-          setExpanded={() => handleExpand(envGroup)}
         />
       );
     });

+ 173 - 0
dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroupDashboard.tsx

@@ -0,0 +1,173 @@
+import React, { Component, useContext, useEffect, useState } from "react";
+import styled from "styled-components";
+
+import sliders from "assets/sliders.svg";
+
+import { Context } from "shared/Context";
+import { ClusterType } from "shared/types";
+
+import ExpandedEnvGroup from "./ExpandedEnvGroup";
+import { RouteComponentProps, useParams, withRouter } from "react-router";
+import { getQueryParam, pushQueryParams } from "shared/routing";
+import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
+import { useQuery } from "@tanstack/react-query";
+import api from "shared/api";
+import Loading from "components/Loading";
+import Placeholder from "components/Placeholder";
+
+type PropsType = RouteComponentProps &
+  WithAuthProps & {
+    currentCluster: ClusterType;
+  };
+
+const EnvGroupDashboard = (props: PropsType) => {
+  const namespace = getQueryParam(props, "namespace");
+  const params = useParams<{ name: string }>();
+  const { currentProject } = useContext(Context);
+  const [expandedEnvGroup, setExpandedEnvGroup] = useState<any>();
+
+  const {
+    data: envGroups,
+    isLoading: listEnvGroupsLoading,
+    isError,
+  } = useQuery<any[]>(
+    ["envGroupList", currentProject.id, namespace, props.currentCluster.id],
+    async () => {
+      try {
+        if (!namespace) {
+          return [];
+        }
+
+        const res = await api.listEnvGroups(
+          "<token>",
+          {},
+          {
+            id: currentProject.id,
+            namespace: namespace,
+            cluster_id: props.currentCluster.id,
+          }
+        );
+
+        return res.data;
+      } catch (err) {
+        throw err;
+      }
+    }
+  );
+
+  useEffect(() => {
+    const name = params.name;
+
+    if (!envGroups) {
+      return;
+    }
+
+    const envGroup = envGroups.find((envGroup) => envGroup.name === name);
+
+    setExpandedEnvGroup(envGroup);
+  }, [envGroups, params]);
+
+  if (listEnvGroupsLoading) {
+    return (
+      <Placeholder>
+        <Loading />
+      </Placeholder>
+    );
+  }
+
+  const renderContents = () => {
+    if (!expandedEnvGroup) {
+      return null;
+    }
+
+    return (
+      <ExpandedEnvGroup
+        isAuthorized={props.isAuthorized}
+        namespace={expandedEnvGroup?.namespace ?? namespace}
+        currentCluster={props.currentCluster}
+        envGroup={expandedEnvGroup}
+        closeExpanded={() => props.history.push("/env-groups")}
+      />
+    );
+  };
+
+  if (listEnvGroupsLoading) {
+    return (
+      <Placeholder>
+        <Loading />
+      </Placeholder>
+    );
+  }
+
+  return <>{renderContents()}</>;
+};
+
+export default withRouter(withAuth(EnvGroupDashboard));
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+  border-bottom: 30px solid transparent;
+`;
+
+const SortFilterWrapper = styled.div`
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 30px solid transparent;
+  > div:not(:first-child) {
+  }
+`;
+
+const ControlRow = styled.div`
+  display: flex;
+  justify-content: ${(props: { hasMultipleChilds: boolean }) => {
+    if (props.hasMultipleChilds) {
+      return "space-between";
+    }
+    return "flex-end";
+  }};
+  align-items: center;
+  flex-wrap: wrap;
+`;
+
+const Button = styled.div`
+  display: flex;
+  margin-left: 10px;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 13px;
+  cursor: pointer;
+  font-family: "Work Sans", sans-serif;
+  border-radius: 5px;
+  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;
+    font-weight: 600;
+    font-size: 12px;
+    border-radius: 20px;
+    display: flex;
+    align-items: center;
+    margin-right: 5px;
+    justify-content: center;
+  }
+`;