Explorar o código

ExpandedChartWrapper as single source of truth PoC

jnfrati %!s(int64=4) %!d(string=hai) anos
pai
achega
6546a06ebe

+ 136 - 119
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChartWrapper.tsx

@@ -1,143 +1,160 @@
-import React, { Component } from "react";
-import styled from "styled-components";
+import React, { useContext, useEffect, useState } from "react";
 import { Context } from "shared/Context";
-import { RouteComponentProps, withRouter } from "react-router";
+import { useHistory, useLocation, useRouteMatch } from "react-router";
 
 import {
-  ResourceType,
   ChartType,
   StorageType,
-  ClusterType,
 } from "shared/types";
 import api from "shared/api";
-import { PorterUrl, pushQueryParams, pushFiltered } from "shared/routing";
+import { pushFiltered } from "shared/routing";
 import ExpandedJobChart from "./ExpandedJobChart";
 import ExpandedChart from "./ExpandedChart";
 import Loading from "components/Loading";
 import PageNotFound from "components/PageNotFound";
+import { useWebsockets } from "shared/hooks/useWebsockets";
 
-type PropsType = RouteComponentProps & {
+
+type Props = {
   setSidebar: (x: boolean) => void;
   isMetricsInstalled: boolean;
-};
-
-type StateType = {
-  loading: boolean;
-  currentChart: ChartType;
-};
-
-class ExpandedChartWrapper extends Component<PropsType, StateType> {
-  state = {
-    loading: true,
-    currentChart: null as ChartType,
-  };
-
-  // Retrieve full chart data (includes form and values)
-  getChartData = () => {
-    let { match } = this.props;
-    let { namespace, chartName } = match.params as any;
-    let { currentProject, currentCluster } = this.context;
-    if (currentProject && currentCluster) {
-      // TODO: add query for retrieving max revision #
-      api
-        .getRevisions(
+}
+
+const ExpandedChartWrapper: React.FunctionComponent<Props> = ({ setSidebar, isMetricsInstalled }) => {
+  // Router based state
+  const location = useLocation();
+  const history = useHistory();
+  const { params: {baseRoute, namespace, chartName}} = useRouteMatch<{baseRoute: string, namespace: string, chartName: string}>();
+
+  // Context hooks
+  const { currentCluster, currentProject} = useContext(Context)
+
+  // Component state  
+  const [revisions, setRevisions] = useState<ChartType[]>([]);
+  const [currentChart, setCurrentChart] = useState<ChartType | null>(null);
+  const [isLoading, setIsLoading] = useState<boolean>(true);
+  
+  // Websocket hook
+  const { newWebsocket, openWebsocket, closeAllWebsockets, closeWebsocket } = useWebsockets();
+  
+  useEffect(() => {
+    getRevisions()
+    .then(() => {
+      connectToChartLiveUpdates();
+      setIsLoading(false);
+    })
+
+    return () => closeAllWebsockets();
+  }, [currentCluster.id, currentProject.id])
+
+
+  useEffect(() => {
+    setCurrentChart(revisions[0])
+  }, [revisions])
+
+  const getRevisions = async () => {
+    try {
+      if (currentProject && currentCluster) {
+        const res = await api.getRevisions(
           "<token>",
-          {
-            namespace: namespace,
-            cluster_id: currentCluster.id,
-            storage: StorageType.Secret,
-          },
-          { id: currentProject.id, name: chartName }
-        )
-        .then((res) => {
-          res.data.sort((a: ChartType, b: ChartType) => {
-            return -(a.version - b.version);
-          });
-          let maxVersion = res.data[0].version;
-          api
-            .getChart(
-              "<token>",
-              {
-                namespace: namespace,
-                cluster_id: currentCluster.id,
-                storage: StorageType.Secret,
-              },
-              {
-                name: chartName,
-                revision: maxVersion,
-                id: currentProject.id,
-              }
-            )
-            .then((res) => {
-              this.setState({ currentChart: res.data, loading: false });
-            })
-            .catch((err) => {
-              console.log("err", err.response.data);
-              this.setState({ loading: false });
-            });
-        })
-        .catch((err) => {
-          console.log("err", err.response.data);
-          this.setState({ loading: false });
+            {
+              namespace: namespace,
+              cluster_id: currentCluster.id,
+              storage: StorageType.Secret,
+            },
+            { id: currentProject.id, name: chartName }
+        );
+
+        res.data.sort((a: ChartType, b: ChartType) => {
+          return -(a.version - b.version);
         });
-    }
-  };
 
-  componentDidMount() {
-    this.setState({ loading: true });
-    this.getChartData();
+        setRevisions(res.data);
+      }
+    } catch (error) {
+      console.log("err", error.response.data);      
+    }
   }
 
-  render() {
-    let { setSidebar, location, match } = this.props;
-    let { baseRoute, namespace } = match.params as any;
-    let { loading, currentChart } = this.state;
-    if (loading) {
-      return <Loading />;
-    } else if (currentChart && baseRoute === "jobs") {
-      return (
-        <ExpandedJobChart
-          namespace={namespace}
-          currentChart={currentChart}
-          currentCluster={this.context.currentCluster}
-          closeChart={() =>
-            pushFiltered(this.props, "/jobs", ["project_id"], {
-              cluster: this.context.currentCluster.name,
-              namespace: namespace,
-            })
-          }
-          setSidebar={setSidebar}
-        />
-      );
-    } else if (currentChart && baseRoute === "applications") {
-      return (
-        <ExpandedChart
-          namespace={namespace}
-          isMetricsInstalled={this.props.isMetricsInstalled}
-          currentChart={currentChart}
-          currentCluster={this.context.currentCluster}
-          closeChart={() =>
-            pushFiltered(this.props, "/applications", ["project_id"], {
-              cluster: this.context.currentCluster.name,
-              namespace: namespace,
-            })
-          }
-          setSidebar={setSidebar}
-        />
-      );
+  const connectToChartLiveUpdates = () => {
+    const apiPath = `/api/projects/${currentProject.id}/k8s/helm_releases?cluster_id=${currentCluster.id}&charts=${chartName}`;
+
+    const wsConfig = {
+      onopen: () => console.log("connected to live chart updates websocket"),
+      onmessage: (evt: MessageEvent) => {
+        const event = JSON.parse(evt.data);
+        // We ignore ADD events as the initial fetch of revisions will get all of those
+        if (event.event_type === "UPDATE") {
+          const object = event.Object as ChartType;
+          setRevisions((oldRevisions) => {
+            // Copy old array to clean up references
+            const prevRevisions = [...oldRevisions];
+            
+            // Check if it's an update of a revision or if it's a new one
+            const revisionIndex = prevRevisions.findIndex((rev) => {
+              if (rev.version === object.version) {
+                return true;
+              }
+            });
+
+            // Place new one at top of the array or update the old one
+            if (revisionIndex > -1) {
+              prevRevisions.splice(revisionIndex, 1, object);
+            } else {
+              return [object, ...prevRevisions];
+            }
+
+            return prevRevisions;
+          })
+        }
+      },
+      onclose: () => console.log("closing live chart updates websocket"),
+      onerror: (err: ErrorEvent) => {
+        console.log(err);
+        closeWebsocket(chartName);
+      }
     }
-    return <PageNotFound />;
+
+    newWebsocket(chartName, apiPath, wsConfig);
+    openWebsocket(chartName);
   }
-}
 
-ExpandedChartWrapper.contextType = Context;
 
-export default withRouter(ExpandedChartWrapper);
+  if (isLoading) {
+    return <Loading />;
+  } else if (currentChart && baseRoute === "jobs") {
+    return (
+      <ExpandedJobChart
+        namespace={namespace}
+        currentChart={currentChart}
+        currentCluster={currentCluster}
+        closeChart={() =>
+          pushFiltered({location, history}, "/jobs", ["project_id"], {
+            cluster: currentCluster.name,
+            namespace: namespace,
+          })
+        }
+        setSidebar={setSidebar}
+      />
+    );
+  } else if (currentChart && baseRoute === "applications") {
+    return (
+      <ExpandedChart
+        namespace={namespace}
+        isMetricsInstalled={isMetricsInstalled}
+        currentChart={currentChart}
+        currentCluster={currentCluster}
+        closeChart={() =>
+          pushFiltered({location, history}, "/applications", ["project_id"], {
+            cluster: currentCluster.name,
+            namespace: namespace,
+          })
+        }
+        setSidebar={setSidebar}
+      />
+    );
+  }
+  return <PageNotFound />;
+}
 
-const NotFoundPlaceholder = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  width: 100%;
-  height: 100%;
-`;
+export default ExpandedChartWrapper;