Преглед изворни кода

Integrate activity feed events (#3059)

* integrated events

* fixed activity feed date

* hide activity feed tabs

---------

Co-authored-by: Justin Rhee <jusrhee@Justins-MacBook-Air.local>
jusrhee пре 3 година
родитељ
комит
346b6615ad

+ 1 - 1
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -606,7 +606,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
       case "events":
         return <EventsTab currentChart={appData.chart} />;
       case "activity":
-        return <ActivityFeed chart={appData.chart} />;
+        return <ActivityFeed chart={appData.chart} stackName={appData?.app?.name}/>;
       case "logs":
         return <LogSection currentChart={appData.chart} />;
       case "metrics":

+ 75 - 60
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/ActivityFeed.tsx

@@ -8,86 +8,85 @@ import Text from "components/porter/Text";
 import Container from "components/porter/Container";
 
 import EventCard from "./EventCard";
+import Loading from "components/Loading";
+import Spacer from "components/porter/Spacer";
+import Fieldset from "components/porter/Fieldset";
+
+import { feedDate } from "shared/string_utils";
 
 type Props = {
   chart: any;
+  stackName: string;
 };
 
-const dummyEvents = [
-  {
-    "id": 0,
-    "status": "SUCCESS",
-    "type": "BUILD",
-    "type_external_source": "GITHUB",
-    "created_at": "",
-    "updated_at": "",
-    "porter_app_id": 0,
-    "metadata": {
-      // keys depend on "type". See below
-    }
-  },
-  {
-    "id": 0,
-    "status": "FAILED",
-    "type": "DEPLOY",
-    "type_external_source": "KUBERNETES",
-    "created_at": "",
-    "updated_at": "",
-    "porter_app_id": 0,
-    "metadata": {
-      // keys depend on "type". See below
-    }
-  },
-  {
-    "id": 0,
-    "status": "PROGRESSING",
-    "type": "PRE_DEPLOY",
-    "type_external_source": "KUBERNETES",
-    "created_at": "",
-    "updated_at": "",
-    "porter_app_id": 0,
-    "metadata": {
-      // keys depend on "type". See below
-    }
-  },
-  {
-    "id": 0,
-    "status": "FAILED",
-    "type": "APP_EVENT",
-    "type_external_source": "KUBERNETES",
-    "created_at": "",
-    "updated_at": "",
-    "porter_app_id": 0,
-    "metadata": {
-      // keys depend on "type". See below
-    }
-  },
-]
-
 const ActivityFeed: React.FC<Props> = ({
   chart,
+  stackName,
 }) => {
   const { currentProject, currentCluster } = useContext(Context);
 
+  const [events, setEvents] = useState<any[]>([]);
+  const [loading, setLoading] = useState<boolean>(true);
+  const [error, setError] = useState<any>(null);
+
+  const getEvents = async () => {
+    setLoading(true);
+    try {
+      const res = await api.getFeedEvents(
+        "<token>",
+        {},
+        {
+          cluster_id: currentCluster.id,
+          project_id: currentProject.id,
+          stack_name: stackName,
+        }
+      );
+      setEvents(res.data.events);
+      setLoading(false);
+    } catch (err) {
+      setError(err);
+      setLoading(false);
+    }
+  }
+  
   useEffect(() => {
-    // Do something
+    getEvents();
   }, []);
 
+  if (error) {
+    return (
+      <Fieldset>
+        <Text size={16}>Error retrieving events</Text>
+        <Spacer height="15px" />
+        <Text color="helper">An unexpected error occurred.</Text>
+      </Fieldset>
+    );
+  }
+
+  if (loading) {
+    return (
+      <div>
+        <Spacer y={2} />
+        <Loading />
+      </div>
+    );
+  };
+
   return (
     <StyledActivityFeed>
-      {dummyEvents.map((event, i) => {
+      {events.map((event, i) => {
         return (
           <EventWrapper 
-            isLast={i === dummyEvents.length - 1} 
+            isLast={i === events.length - 1} 
             key={i}
           >
-            {(i !== dummyEvents.length - 1) && <Line />}
+            {(i !== events.length - 1 && events.length > 1) && <Line />}
             <Dot />
             <Time>
-              <Text>Jun 16</Text>
-              <Text>12:00 PM</Text>
+              <Text>{feedDate(event.created_at).split(", ")[0]}</Text>
+              <Text>{feedDate(event.created_at).split(", ")[1]}</Text>
             </Time>
-            <EventCard event={event} />
+            <EventCard event={event} i={i} />
           </EventWrapper>
         );
       })}
@@ -99,6 +98,9 @@ export default ActivityFeed;
 
 const Time = styled.div`
   margin-right: -5px;
+  opacity: 0;
+  animation: fadeIn 0.3s 0.1s;
+  animation-fill-mode: forwards;
 `;
 
 const Line = styled.div`
@@ -108,7 +110,9 @@ const Line = styled.div`
   position: absolute;
   left: 3px;
   top: 36px;
-  opacity: 1;
+  opacity: 0;
+  animation: fadeIn 0.3s 0.1s;
+  animation-fill-mode: forwards;
 `;
 
 const Dot = styled.div`
@@ -119,7 +123,9 @@ const Dot = styled.div`
   position: absolute;
   left: 0;
   top: 36px;
-  opacity: 1;
+  opacity: 0;
+  animation: fadeIn 0.3s 0.1s;
+  animation-fill-mode: forwards;
 `;
 
 const EventWrapper = styled.div<{
@@ -134,4 +140,13 @@ const EventWrapper = styled.div<{
 
 const StyledActivityFeed = styled.div`
   width: 100%;
+  animation: fadeIn 0.3s 0s;
+  @keyframes fadeIn {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
 `;

+ 40 - 2
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/EventCard.tsx

@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useState } from "react";
 import styled from "styled-components";
 
 import app_event from "assets/app_event.png";
@@ -16,6 +16,7 @@ import Container from "components/porter/Container";
 import Spacer from "components/porter/Spacer";
 import Link from "components/porter/Link";
 import Icon from "components/porter/Icon";
+import Modal from "components/porter/Modal";
 
 type Props = {
   event: any;
@@ -23,7 +24,11 @@ type Props = {
 
 const EventCard: React.FC<Props> = ({
   event,
+  i,
 }) => {
+  const [showModal, setShowModal] = useState<boolean>(false);
+  const [modalContent, setModalContent] = useState<React.ReactNode>(null);
+
   const getIcon = (eventType: string) => {
     switch (eventType) {
       case "APP_EVENT":
@@ -106,7 +111,20 @@ const EventCard: React.FC<Props> = ({
     if (event.type === "APP_EVENT") {
       return (
         <>
-          <Link hasunderline onClick={() => alert("TODO: open details modal")}>View details</Link>
+          <Link hasunderline onClick={() => {
+            setModalContent(
+              <>
+                <Container row>
+                  <Icon height="20px" src={app_event} />
+                  <Spacer inline width="10px" />
+                  <Text size={16}>Event details</Text>
+                </Container>
+                <Spacer y={1} />
+                <Text>TODO: display event logs</Text>
+              </>
+            )
+            setShowModal(true);
+          }}>View details</Link>
           <Spacer inline x={1} />
         </>
       );
@@ -205,6 +223,11 @@ const EventCard: React.FC<Props> = ({
           <Text color="helper">user@email.com</Text>
         )}
       </Container>
+      {showModal && (
+        <Modal closeModal={() => setShowModal(false)}>
+          {modalContent}
+        </Modal>
+      )}
     </StyledEventCard>
   );
 };
@@ -221,4 +244,19 @@ const StyledEventCard = styled.div`
   border-radius: 5px;
   background: ${({ theme }) => theme.fg};
   border: 1px solid ${({ theme }) => theme.border};
+  opacity: 0;
+  animation: slideIn 0.5s 0s;
+  animation-fill-mode: forwards;
+  @keyframes slideIn {
+    from {
+      margin-left: -10px;
+      opacity: 0;
+      margin-right: 10px;
+    }
+    to {
+      margin-left: 0;
+      opacity: 1;
+      margin-right: 0;
+    }
+  }
 `;

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

@@ -211,6 +211,18 @@ const deletePorterApp = baseApi<
   return `/api/projects/${project_id}/clusters/${cluster_id}/stacks/${name}`;
 });
 
+const getFeedEvents = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+    stack_name: string;
+  }
+>("GET", (pathParams) => {
+  let { project_id, cluster_id, stack_name } = pathParams;
+  return `/api/projects/${project_id}/clusters/${cluster_id}/stacks/${stack_name}/events`;
+});
+
 const createEnvironment = baseApi<
   {
     name: string;
@@ -2770,6 +2782,7 @@ export default {
   removeStackAppResource,
   addStackEnvGroup,
   removeStackEnvGroup,
+  getFeedEvents,
 
   // STATUS
   getGithubStatus,

+ 17 - 0
dashboard/src/shared/string_utils.ts

@@ -28,6 +28,23 @@ export const relativeDate = (date: string | number) => {
   return rtf.format(-time.time, time.unitOfTime);
 };
 
+export const feedDate = (timestamp: string) => {
+  const date = new Date(timestamp);
+  const monthNames = [
+    'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
+  ];
+  const month = monthNames[date.getUTCMonth()];
+  const day = date.getUTCDate();
+  const hours = date.getUTCHours();
+  const minutes = date.getUTCMinutes();
+  const formattedHours = hours % 12 === 0 ? 12 : hours % 12; // Convert to 12-hour format
+  const period = hours < 12 ? 'AM' : 'PM';
+  const formattedDate = `${month} ${day}, ${formattedHours}:${minutes.toString().padStart(2, '0')} ${period}`;
+
+  return formattedDate;
+}
+
 export const timeFrom = (
   time: string | number,
   secondTime?: string | number