Feroze Mohideen 2 ani în urmă
părinte
comite
79d9683c5e

+ 1 - 1
api/server/handlers/porter_app/create_and_update_events.go

@@ -619,7 +619,7 @@ func (p *CreateUpdatePorterAppEventHandler) handleNotification(ctx context.Conte
 	projectId, clusterId uint,
 	agent *kubernetes.Agent,
 ) error {
-	ctx, span := telemetry.NewSpan(ctx, "handle-notification")
+	ctx, span := telemetry.NewSpan(ctx, "serve-handle-notification")
 	defer span.End()
 
 	// get the namespace associated with the deployment target id

+ 62 - 0
dashboard/src/lib/porter-apps/notification.ts

@@ -0,0 +1,62 @@
+import _ from "lodash";
+import { PorterAppNotification } from "main/home/app-dashboard/app-view/tabs/activity-feed/events/types";
+
+export type ClientNotification = {
+    isDeployRelated: boolean;
+    serviceName: string;
+    messages: PorterAppNotification[];
+    timestamp: string;
+    id: string;
+    appRevisionId: string;
+}
+
+export function deserializeNotifications (
+    notifications: PorterAppNotification[] 
+): ClientNotification[] {
+    const deployRelatedNotificationMap = _.groupBy(notifications.filter(
+        (notification) => notification.deployment.status === "PENDING"
+    ), (notification) => notification.service_name);
+    const deployRelatedNotifications = Object.keys(deployRelatedNotificationMap).map(
+        (serviceName) => {
+            const notifications = orderNotificationsByTimestamp(deployRelatedNotificationMap[serviceName], 'asc');
+            const timestamp = notifications[0].timestamp;
+            const id = notifications[0].id;
+            return {
+                isDeployRelated: true,
+                serviceName,
+                timestamp,
+                id,
+                messages: notifications,
+                appRevisionId: notifications[0].app_revision_id
+            };
+        }
+    );
+    const nonDeployRelatedNotifications = notifications.filter(
+        (notification) => notification.deployment.status !== "PENDING"
+    ).map(
+        (notification) => {
+            return {
+                isDeployRelated: false,
+                serviceName: notification.service_name,
+                timestamp: notification.timestamp,
+                id: notification.id,
+                messages: [notification],
+                appRevisionId: notification.app_revision_id
+            };
+        }
+    );
+    
+    return orderNotificationsByTimestamp([...deployRelatedNotifications, ...nonDeployRelatedNotifications], 'asc');
+}
+
+const orderNotificationsByTimestamp = <T extends {timestamp: string}[]>(notifications: T, sortOrder: 'asc' | 'desc'): T => {
+    return notifications.sort((a, b) => {
+        const aTimestamp = new Date(a.timestamp);
+        const bTimestamp = new Date(b.timestamp);
+        if (sortOrder === 'asc') {
+            return aTimestamp.getTime() - bTimestamp.getTime();
+        } else {
+            return bTimestamp.getTime() - aTimestamp.getTime();
+        }
+    });
+}

+ 5 - 4
dashboard/src/main/home/app-dashboard/app-view/LatestRevisionContext.tsx

@@ -25,13 +25,14 @@ import {
   PopulatedEnvGroup,
   populatedEnvGroup,
 } from "../validate-apply/app-settings/types";
-import { PorterAppNotification, porterAppNotificationEventMetadataValidator } from "./tabs/activity-feed/events/types";
+import { porterAppNotificationEventMetadataValidator } from "./tabs/activity-feed/events/types";
+import { ClientNotification, deserializeNotifications } from "lib/porter-apps/notification";
 
 export const LatestRevisionContext = createContext<{
   porterApp: PorterAppRecord;
   latestRevision: AppRevision;
   latestProto: PorterApp;
-  latestNotifications: PorterAppNotification[];
+  latestNotifications: ClientNotification[];
   servicesFromYaml: DetectedServices | null;
   clusterId: number;
   projectId: number;
@@ -122,7 +123,7 @@ export const LatestRevisionProvider = ({
         }
       );
 
-      const { app_revision, notifications } = await z
+      const { app_revision, notifications: porterAppNotifications } = await z
         .object({
           app_revision: appRevisionValidator,
           notifications: z.array(porterAppNotificationEventMetadataValidator)
@@ -130,7 +131,7 @@ export const LatestRevisionProvider = ({
         .parseAsync(res.data);
       return {
         app_revision,
-        notifications,
+        notifications: deserializeNotifications(porterAppNotifications),
       };
     },
     {

+ 10 - 0
dashboard/src/main/home/app-dashboard/app-view/tabs/Notifications.tsx

@@ -5,12 +5,22 @@ import NotificationFeed from "./notifications/NotificationFeed";
 const Notifications: React.FC = () => {
   const {
     latestNotifications,
+    projectId,
+    clusterId,
+    appName,
+    porterApp: { id: appId },
+    deploymentTarget: { id: deploymentTargetId },
   } = useLatestRevision();
 
   return (
     <>
       <NotificationFeed
         notifications={latestNotifications}
+        projectId={projectId}
+        clusterId={clusterId}
+        appName={appName}
+        deploymentTargetId={deploymentTargetId}
+        appId={appId}
       />
     </>
   );

+ 11 - 0
dashboard/src/main/home/app-dashboard/app-view/tabs/activity-feed/events/types.ts

@@ -44,6 +44,17 @@ export const porterAppNotificationEventMetadataValidator = z.object({
     human_readable_detail: z.string(),
     human_readable_summary: z.string(),
     timestamp: z.string(),
+    deployment: z.discriminatedUnion("status", [
+        z.object({
+            status: z.literal("PENDING")
+        }),
+        z.object({
+            status: z.literal("SUCCESS")
+        }),
+        z.object({
+            status: z.literal("FAILURE")
+        }),
+    ])
 });
 export type PorterAppNotification = z.infer<typeof porterAppNotificationEventMetadataValidator>;
 export const porterAppEventValidator = z.discriminatedUnion("type", [

+ 144 - 21
dashboard/src/main/home/app-dashboard/app-view/tabs/notifications/NotificationExpandedView.tsx

@@ -1,30 +1,75 @@
-import React from "react";
+import React, { useEffect } from "react";
 import styled from "styled-components";
-import { PorterAppNotification } from "../activity-feed/events/types";
 import Text from "components/porter/Text";
 import Spacer from "components/porter/Spacer";
 import document from "assets/document.svg";
 import Button from "components/porter/Button";
+import { ClientNotification } from "lib/porter-apps/notification";
+import { feedDate } from "shared/string_utils";
+import Container from "components/porter/Container";
+import Logs from "main/home/app-dashboard/validate-apply/logs/Logs";
 
 type Props = {
-  notification: PorterAppNotification;
+  notification: ClientNotification;
+  projectId: number;
+  clusterId: number;
+  appName: string;
+  deploymentTargetId: string;
+  appId: number;
 }
 
 const NotificationExpandedView: React.FC<Props> = ({
     notification,
+    projectId,
+    clusterId,
+    appName,
+    deploymentTargetId,
+    appId,
 }) => {
   return (
     <StyledNotificationExpandedView>
       <ExpandedViewContent>
         <Text color="helper">Event ID: {notification.id}</Text>
         <Spacer y={0.5} />
-        <Text size={16}>{notification.human_readable_summary}</Text>
-        <Spacer y={0.5} />
-        <Message>
-          <img src={document} />
-          {notification.human_readable_detail}
-        </Message>
-        <Spacer y={0.5} />
+        <StyledActivityFeed>
+          {notification.messages.map((message, i) => {
+              return (
+                  <NotificationWrapper isLast={i === notification.messages.length - 1} key={i}>
+                      {i !== notification.messages.length - 1 && notification.messages.length > 1 && <Line />}
+                      <Dot />
+                      <Time>
+                          <Text>{feedDate(message.timestamp)}</Text>
+                      </Time>
+                      <Message key={i}>
+                        <Container row>
+                          <img src={document} style={{width: "15px", marginRight: "15px"}} />
+                          {message.human_readable_summary}
+                        </Container>
+                        <Spacer y={0.5} />
+                        <Container row>
+                          {message.human_readable_detail}
+                        </Container>
+                      </Message>
+                  </NotificationWrapper>
+              );
+          })}
+        </StyledActivityFeed>
+        <Spacer y={1} />
+        <LogsContainer>
+          <Logs 
+            projectId={projectId}
+            clusterId={clusterId}
+            appName={appName}
+            serviceNames={[notification.serviceName]}
+            deploymentTargetId={deploymentTargetId}
+            appRevisionId={notification.appRevisionId}
+            logFilterNames={["service_name"]}
+            appId={appId}
+            selectedService={notification.serviceName}
+            selectedRevisionId={notification.appRevisionId}
+            defaultScrollToBottomEnabled={false}
+          />
+        </LogsContainer>
       </ExpandedViewContent>
       <ExpandedViewFooter>
         <Button>Take recommended action</Button>
@@ -37,10 +82,12 @@ export default NotificationExpandedView;
 
 const StyledNotificationExpandedView = styled.div`
 width: 100%;
+overflow-y: scroll;
 display: flex;
 flex-direction: column;
 animation: fadeIn 0.3s 0s;
-padding: 15px 20px;
+padding: 70px;
+padding-top: 15px;
 @keyframes fadeIn {
   from {
     opacity: 0;
@@ -49,9 +96,30 @@ padding: 15px 20px;
     opacity: 1;
   }
 }
-border-bottom: 1px solid #494b4f;
-border-right: 1px solid #494b4f;
 justify-content: space-between;
+::-webkit-scrollbar {
+  width: 3px;
+  :horizontal {
+    height: 3px;
+  }
+}
+
+::-webkit-scrollbar-corner {
+  width: 3px;
+  background: #ffffff11;
+  color: white;
+}
+
+::-webkit-scrollbar-track {
+  width: 3px;
+  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+  box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+}
+
+::-webkit-scrollbar-thumb {
+  background-color: darkgrey;
+  outline: 1px solid slategrey;
+}
 `;
 
 const ExpandedViewContent = styled.div`
@@ -60,21 +128,76 @@ const ExpandedViewContent = styled.div`
 `;
 
 const Message = styled.div`
+  margin-left: 20px;
+  width: 100%;
   padding: 20px;
-  background: #26292e;
+  background: ${({ theme }) => theme.fg};
+  border: 1px solid ${({ theme }) => theme.border};
   border-radius: 5px;
   line-height: 1.5em;
-  border: 1px solid #aaaabb33;
   font-size: 13px;
   display: flex;
-  align-items: center;
-  > img {
-    width: 13px;
-    margin-right: 20px;
-  }
+  flex-direction: column;
 `;
 
 const ExpandedViewFooter = styled.div`
   display: flex;
   justify-content: flex-end;
-`;
+`;
+
+const Time = styled.div`
+  opacity: 0;
+  animation: fadeIn 0.3s 0.1s;
+  animation-fill-mode: forwards;
+  width: 150px;
+`;
+
+const Line = styled.div`
+  width: 1px;
+  height: calc(100% + 30px);
+  background: #414141;
+  position: absolute;
+  left: 3px;
+  top: 36px;
+  opacity: 0;
+  animation: fadeIn 0.3s 0.1s;
+  animation-fill-mode: forwards;
+`;
+
+const Dot = styled.div`
+  width: 7px;
+  height: 7px;
+  background: #fff;
+  border-radius: 50%;
+  margin-left: -29px;
+  margin-right: 20px;
+  z-index: 1;
+  opacity: 0;
+  animation: fadeIn 0.3s 0.1s;
+  animation-fill-mode: forwards;
+`;
+
+const NotificationWrapper = styled.div<{isLast: boolean}>`
+  padding-left: 30px;
+  display: flex;
+  align-items: center;
+  position: relative;
+  margin-bottom: ${(props) => (props.isLast ? "" : "25px")};
+`;
+
+const StyledActivityFeed = styled.div`
+  width: 100%;
+  animation: fadeIn 0.3s 0s;
+  @keyframes fadeIn {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
+`;
+
+const LogsContainer = styled.div`
+
+`;

+ 21 - 5
dashboard/src/main/home/app-dashboard/app-view/tabs/notifications/NotificationFeed.tsx

@@ -2,18 +2,28 @@ import React, { useState } from "react";
 import styled from "styled-components";
 import NotificationList from "./NotificationList";
 import NotificationExpandedView from "./NotificationExpandedView";
-import { PorterAppNotification } from "../activity-feed/events/types";
+import { ClientNotification } from "lib/porter-apps/notification";
 
 type Props = {
-    notifications: PorterAppNotification[];
+    notifications: ClientNotification[];
+    projectId: number;
+    clusterId: number;
+    appName: string;
+    deploymentTargetId: string;
+    appId: number;
 };
 
 const NotificationFeed: React.FC<Props> = ({
     notifications,
+    projectId,
+    clusterId,
+    appName,
+    deploymentTargetId,
+    appId,
 }) => {
-    const [selectedNotification, setSelectedNotification] = useState<PorterAppNotification | undefined>(notifications.length ? notifications[0] : undefined);
+    const [selectedNotification, setSelectedNotification] = useState<ClientNotification | undefined>(notifications.length ? notifications[0] : undefined);
 
-    const handleTileClick = (notification: PorterAppNotification) => {
+    const handleTileClick = (notification: ClientNotification) => {
         setSelectedNotification(notification);
     };
 
@@ -28,6 +38,11 @@ const NotificationFeed: React.FC<Props> = ({
                     />
                     <NotificationExpandedView 
                         notification={selectedNotification} 
+                        projectId={projectId}
+                        clusterId={clusterId}
+                        appName={appName}
+                        deploymentTargetId={deploymentTargetId}
+                        appId={appId}
                     />
                 </>
             )}
@@ -39,7 +54,8 @@ export default NotificationFeed;
 
 const StyledNotificationFeed = styled.div`
     display: flex;
-    height: 600px;
+    margin-bottom: -50px;
+    height: 800px;
     width: 100%;
     animation: fadeIn 0.3s 0s;
     @keyframes fadeIn {

+ 7 - 7
dashboard/src/main/home/app-dashboard/app-view/tabs/notifications/NotificationList.tsx

@@ -1,12 +1,12 @@
 import React from "react";
 import styled from "styled-components";
-import { PorterAppNotification } from "../activity-feed/events/types";
 import NotificationTile from "./NotificationTile";
+import { ClientNotification } from "lib/porter-apps/notification";
 
 type Props = {
-    onTileClick: (event: PorterAppNotification) => void;
-    notifications: PorterAppNotification[];
-    selectedNotification: PorterAppNotification;
+    onTileClick: (event: ClientNotification) => void;
+    notifications: ClientNotification[];
+    selectedNotification: ClientNotification;
 };
 
 const NotificationList: React.FC<Props> = ({
@@ -31,12 +31,12 @@ const NotificationList: React.FC<Props> = ({
 export default NotificationList;
 
 const StyledNotificationList = styled.div`
-    width: 300px;
+    width: 200px;
     display: flex;
     flex-direction: column;
-    height: 600px;
+    height: 100%;
     overflow: auto;
-    border-bottom: 1px solid #494b4f;
+    border-right: 1px solid #ffffff44;
     ::-webkit-scrollbar {
         width: 3px;
         :horizontal {

+ 4 - 4
dashboard/src/main/home/app-dashboard/app-view/tabs/notifications/NotificationTile.tsx

@@ -1,12 +1,12 @@
 import React from "react";
 import styled from "styled-components";
-import { PorterAppNotification } from "../activity-feed/events/types";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
 import { feedDate } from "shared/string_utils";
+import { ClientNotification } from "lib/porter-apps/notification";
 
 type Props = {
-  notification: PorterAppNotification;
+  notification: ClientNotification;
   selected: boolean;
   onClick: () => void;
 };
@@ -21,9 +21,9 @@ const NotificationTile: React.FC<Props> = ({
       <NotificationContent>
         <Text color="helper">{feedDate(notification.timestamp)}</Text>
         <Spacer y={0.5} />
-        <NotificationSummary>{notification.human_readable_summary}</NotificationSummary>
+        <Text color="helper">Service: <ServiceName>{notification.serviceName}</ServiceName></Text>
         <Spacer y={0.5} />
-        <Text color="helper">Service: <ServiceName>{notification.service_name}</ServiceName></Text>
+        <NotificationSummary>{notification.isDeployRelated ? "Your service failed to deploy" : "Your service is unhealthy"}</NotificationSummary>
       </NotificationContent>
     </StyledNotificationTile>
   );

+ 0 - 1
dashboard/src/main/home/app-dashboard/validate-apply/build-settings/docker/DockerfileSettings.tsx

@@ -6,7 +6,6 @@ import { PorterAppFormData } from 'lib/porter-apps';
 import Input from 'components/porter/Input';
 import FileSelector from '../FileSelector';
 import styled from 'styled-components';
-import CollapsibleContainer from 'components/porter/CollapsibleContainer';
 import Button from 'components/porter/Button';
 
 type Props = {

+ 23 - 26
dashboard/src/main/home/app-dashboard/validate-apply/logs/Logs.tsx

@@ -43,6 +43,9 @@ type Props = {
     };
     filterPredeploy?: boolean;
     appId: number;
+    selectedService?: string;
+    selectedRevisionId?: string;
+    defaultScrollToBottomEnabled?: boolean;
 };
 
 const DEFAULT_LOG_TIMEOUT_SECONDS = 60;
@@ -58,6 +61,9 @@ const Logs: React.FC<Props> = ({
     logFilterNames = ["service_name", "revision", "output_stream"], // these are the names of filters that will be displayed in the UI
     filterPredeploy = false,
     appId,
+    selectedService,
+    selectedRevisionId,
+    defaultScrollToBottomEnabled = true,
 }) => {
     const { search } = useLocation();
     const queryParams = new URLSearchParams(search);
@@ -69,7 +75,7 @@ const Logs: React.FC<Props> = ({
     }
 
     const scrollToBottomRef = useRef<HTMLDivElement | null>(null);
-    const [scrollToBottomEnabled, setScrollToBottomEnabled] = useState(true);
+    const [scrollToBottomEnabled, setScrollToBottomEnabled] = useState(defaultScrollToBottomEnabled);
     const [enteredSearchText, setEnteredSearchText] = useState("");
     const [searchText, setSearchText] = useState("");
     const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
@@ -78,15 +84,22 @@ const Logs: React.FC<Props> = ({
     const [hasPorterAgent, setHasPorterAgent] = useState(true);
     const [isPorterAgentInstalling, setIsPorterAgentInstalling] = useState(false);
     const [isLoading, setIsLoading] = useState(true);
-    const [logsError, setLogsError] = useState<string | undefined>(undefined);
 
     const [selectedFilterValues, setSelectedFilterValues] = useState<Record<FilterName, string>>({
-        service_name: logQueryParamOpts?.service ?? GenericFilter.getDefaultOption("service_name").value,
+        service_name: logQueryParamOpts.service ?? selectedService ?? GenericFilter.getDefaultOption("service_name").value,
         pod_name: "", // not supported in v2
         revision: logQueryParamOpts.revision ?? GenericFilter.getDefaultOption("revision").value, // refers to revision number
         output_stream: logQueryParamOpts.output_stream ?? GenericFilter.getDefaultOption("output_stream").value,
-        revision_id: logQueryParamOpts.revision_id ?? GenericFilter.getDefaultOption("revision_id").value,
+        revision_id: logQueryParamOpts.revision_id ?? selectedRevisionId ?? GenericFilter.getDefaultOption("revision_id").value,
     });
+    // for some reason the filters were not being updated when the service name or revision number changed in the notification feed, so this ensures it
+    useEffect(() => {
+        setSelectedFilterValues({
+            ...selectedFilterValues,
+            service_name: logQueryParamOpts.service ?? selectedService ?? GenericFilter.getDefaultOption("service_name").value,
+            revision_id: logQueryParamOpts.revision_id ?? selectedRevisionId ?? GenericFilter.getDefaultOption("revision_id").value,
+        })
+    }, [selectedService, selectedRevisionId]);
 
     const { revisionIdToNumber } = useRevisionList({ appName, deploymentTargetId, projectId, clusterId });
     const { latestRevision: { revision_number: latestRevisionNumber } } = useLatestRevision();
@@ -263,7 +276,11 @@ const Logs: React.FC<Props> = ({
             } as GenericFilter,
         ].filter((f: GenericFilter) => logFilterNames.includes(f.name)))
 
-        if (latestRevisionNumber && !logQueryParamOpts.revision && !logQueryParamOpts.revision_id) { // default to filter by latest revision number if no revision-related query params supplied
+        // default to filter by latest revision number if no revision-related filter options are selected
+        if (latestRevisionNumber && 
+            selectedFilterValues.revision === GenericFilter.getDefaultOption("revision").value && 
+            selectedFilterValues.revision_id === GenericFilter.getDefaultOption("revision_id").value
+        ) { 
             setSelectedFilterValues({
                 ...selectedFilterValues,
                 revision: latestRevisionNumber.toString(),
@@ -477,18 +494,7 @@ const Logs: React.FC<Props> = ({
                 <I className="material-icons">add</I> Install Porter agent
             </Button>
         </Fieldset>
-    ) : logsError ? (
-        <Fieldset>
-            <Container row>
-                <WarnI className="material-icons">warning</WarnI>
-                <Text color="helper">
-                    Porter encountered an error retrieving logs for this application.
-                </Text>
-            </Container>
-        </Fieldset>
-    ) : (
-        renderContents()
-    );
+    ) : renderContents();
 };
 
 export default Logs;
@@ -501,15 +507,6 @@ const I = styled.i`
   justify-content: center;
 `;
 
-const WarnI = styled.i`
-  font-size: 18px;
-  display: flex;
-  align-items: center;
-  margin-right: 10px;
-  justify-content: center;
-  opacity: 0.6;
-`;
-
 const Spinner = styled.img`
   width: 15px;
   height: 15px;

+ 1 - 0
dashboard/src/shared/themes/midnight.ts

@@ -6,6 +6,7 @@ const theme = {
   button: "#3A48CA",
   clickable: {
     bg: "linear-gradient(180deg, #171B21, #121212)",
+    clickedBg: "#202126",
   },
   modalBg: "#171B2111",
   text: {

+ 10 - 1
internal/porter_app/notifications/app_event.go

@@ -85,7 +85,7 @@ func isNotificationDuplicate(
 	eventRepo repository.PorterAppEventRepository,
 	deploymentTargetID string,
 ) (bool, error) {
-	ctx, span := telemetry.NewSpan(ctx, "is-app-event-duplicate")
+	ctx, span := telemetry.NewSpan(ctx, "is-notification-duplicate")
 	defer span.End()
 
 	deploymentTargetUUID, err := uuid.Parse(deploymentTargetID)
@@ -100,6 +100,15 @@ func isNotificationDuplicate(
 	if err != nil {
 		return false, telemetry.Error(ctx, span, err, "error converting app id to int")
 	}
+
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "app-id", Value: notification.AppID},
+		telemetry.AttributeKV{Key: "app-name", Value: notification.AppName},
+		telemetry.AttributeKV{Key: "app-revision-id", Value: notification.AppRevisionID},
+		telemetry.AttributeKV{Key: "agent-event-id", Value: notification.AgentEventID},
+		telemetry.AttributeKV{Key: "service-name", Value: notification.ServiceName},
+	)
+
 	existingEvents, _, err := eventRepo.ListEventsByPorterAppIDAndDeploymentTargetID(ctx, uint(appIdInt), deploymentTargetUUID)
 	if err != nil {
 		return false, telemetry.Error(ctx, span, err, "error listing porter app events for event type with deployment target id")

+ 19 - 1
internal/porter_app/notifications/notification.go

@@ -21,7 +21,7 @@ type HandleNotificationInput struct {
 
 // HandleNotification handles the logic for processing agent events
 func HandleNotification(ctx context.Context, inp HandleNotificationInput) error {
-	ctx, span := telemetry.NewSpan(ctx, "handle-notification")
+	ctx, span := telemetry.NewSpan(ctx, "internal-handle-notification")
 	defer span.End()
 
 	// 1. parse agent event
@@ -36,6 +36,15 @@ func HandleNotification(ctx context.Context, inp HandleNotificationInput) error
 	// 2. convert agent event to baseNotification
 	baseNotification := agentEventToNotification(*agentEventMetadata)
 
+	// telemetry.WithAttributes(span,
+	// 	telemetry.AttributeKV{Key: "app-id", Value: baseNotification.AppID},
+	// 	telemetry.AttributeKV{Key: "app-name", Value: baseNotification.AppName},
+	// 	telemetry.AttributeKV{Key: "service-name", Value: baseNotification.ServiceName},
+	// 	telemetry.AttributeKV{Key: "app-revision-id", Value: baseNotification.AppRevisionID},
+	// 	telemetry.AttributeKV{Key: "agent-event-id", Value: baseNotification.AgentEventID},
+	// 	telemetry.AttributeKV{Key: "agent-detail", Value: baseNotification.AgentDetail},
+	// )
+
 	// 3. dedupe notification
 	isDuplicate, err := isNotificationDuplicate(ctx, baseNotification, inp.EventRepo, inp.DeploymentTargetID)
 	if err != nil {
@@ -46,6 +55,15 @@ func HandleNotification(ctx context.Context, inp HandleNotificationInput) error
 		return nil
 	}
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "app-id", Value: baseNotification.AppID},
+		telemetry.AttributeKV{Key: "app-name", Value: baseNotification.AppName},
+		telemetry.AttributeKV{Key: "service-name", Value: baseNotification.ServiceName},
+		telemetry.AttributeKV{Key: "app-revision-id", Value: baseNotification.AppRevisionID},
+		telemetry.AttributeKV{Key: "agent-event-id", Value: baseNotification.AgentEventID},
+		telemetry.AttributeKV{Key: "agent-detail", Value: baseNotification.AgentDetail},
+	)
+
 	// 4. hydrate notification with k8s deployment info
 	hydratedNotification, err := hydrateNotification(ctx, hydrateNotificationInput{
 		Notification:       baseNotification,