From 851beb45fc9ed1c045964744682e610629c8b953 Mon Sep 17 00:00:00 2001
From: Ben Elferink <ben.elferink@icloud.com>
Date: Sun, 15 Dec 2024 12:51:43 +0200
Subject: [PATCH] [GEN-1730]: add "describe odigos" to UI (#1988)

This pull request introduces a new feature to describe Odigos and
includes various changes to integrate this feature into the application.
The most important changes include the addition of a new
`DescribeOdigos` component, updates to existing components to
incorporate this new feature, and the creation of a GraphQL query and
hook for fetching Odigos data.

### New Feature: Describe Odigos

*
[`frontend/webapp/components/describe-odigos/index.tsx`](diffhunk://#diff-2e0eca8180f4d2737c72f676dc6caa03ab9295ad5a2d82a90d399e344e0b580eR1-R16):
Added the `DescribeOdigos` component which includes an icon button to
trigger the display of Odigos information.
*
[`frontend/webapp/graphql/queries/describe.ts`](diffhunk://#diff-1dd956050209ccf1d679de5ca93cc29b9ccddc56c27947dbf5f9255ea4b8d0c5R3-R143):
Created a new GraphQL query `DESCRIBE_ODIGOS` to fetch Odigos data.
*
[`frontend/webapp/hooks/describe/useDescribeOdigos.ts`](diffhunk://#diff-f96e0d4e8e6b30c653c21364c7479cd8656417588a42c68aa9346c90e838d4ccR1-R15):
Added a custom hook `useDescribeOdigos` to use the `DESCRIBE_ODIGOS`
query.

### Component Integration

*
[`frontend/webapp/components/main/header/index.tsx`](diffhunk://#diff-2c96f91ec30d2116981a9c0a562820ff9fd87c8292cb5dca11a45d6fb2ac6c04L9-R9):
Integrated the `DescribeOdigos` component into the main header.
[[1]](diffhunk://#diff-2c96f91ec30d2116981a9c0a562820ff9fd87c8292cb5dca11a45d6fb2ac6c04L9-R9)
[[2]](diffhunk://#diff-2c96f91ec30d2116981a9c0a562820ff9fd87c8292cb5dca11a45d6fb2ac6c04R45)
*
[`frontend/webapp/components/overview/all-drawers/index.tsx`](diffhunk://#diff-bf25245ffa5cb1c7ea54b941a97fc9f53caf28b7154cb4eda9b88c7b6f0944d1L2-R10):
Updated the `AllDrawers` component to include a case for
`DRAWER_OTHER_TYPES.DESCRIBE_ODIGOS`, which renders the `DescribeDrawer`
component.
[[1]](diffhunk://#diff-bf25245ffa5cb1c7ea54b941a97fc9f53caf28b7154cb4eda9b88c7b6f0944d1L2-R10)
[[2]](diffhunk://#diff-bf25245ffa5cb1c7ea54b941a97fc9f53caf28b7154cb4eda9b88c7b6f0944d1R25-R27)

### Reusable Component Enhancements

*
[`frontend/webapp/reuseable-components/icon-button/index.tsx`](diffhunk://#diff-d02358b5454137fd2842c4765b1b55d50282fea6338be0617b67385956097896R1-R63):
Created a new `IconButton` component with optional ping animation to be
used in various parts of the application.

### Code Refactoring and Cleanup

*
[`frontend/webapp/components/index.ts`](diffhunk://#diff-9a255ecda06fb13562b464a919eb789a51ea8728251b2aa31c28babfd8f3405dL1-R7):
Reordered exports for better organization and added export for the new
`describe-odigos` component.
*
[`frontend/webapp/components/notification/notification-manager.tsx`](diffhunk://#diff-57b5151297ef94f210fd8223cf1e64f73a19c43cc5f57f94cc74338251942c35L9-R10):
Replaced the custom `BellIcon` with the new `IconButton` component for
consistency.
[[1]](diffhunk://#diff-57b5151297ef94f210fd8223cf1e64f73a19c43cc5f57f94cc74338251942c35L9-R10)
[[2]](diffhunk://#diff-57b5151297ef94f210fd8223cf1e64f73a19c43cc5f57f94cc74338251942c35L109-R87)
*
[`frontend/webapp/containers/main/overview/overview-drawer/index.tsx`](diffhunk://#diff-2410bbb07cf40a69a4bc6a34db093cdfcc2cb9d4b98b144a37a2c6a3e1a511ffL69-R71):
Made several properties optional and added conditional checks to handle
undefined properties gracefully.
[[1]](diffhunk://#diff-2410bbb07cf40a69a4bc6a34db093cdfcc2cb9d4b98b144a37a2c6a3e1a511ffL69-R71)
[[2]](diffhunk://#diff-2410bbb07cf40a69a4bc6a34db093cdfcc2cb9d4b98b144a37a2c6a3e1a511ffR90-R95)
[[3]](diffhunk://#diff-732721edf7c32d883ffefa52d6e43339c6ee328c88c3cb5f0d87f6760347b599L17-R22)
[[4]](diffhunk://#diff-732721edf7c32d883ffefa52d6e43339c6ee328c88c3cb5f0d87f6760347b599L37-R37)
[[5]](diffhunk://#diff-732721edf7c32d883ffefa52d6e43339c6ee328c88c3cb5f0d87f6760347b599L55-R55)
[[6]](diffhunk://#diff-732721edf7c32d883ffefa52d6e43339c6ee328c88c3cb5f0d87f6760347b599L67-R67)
[[7]](diffhunk://#diff-732721edf7c32d883ffefa52d6e43339c6ee328c88c3cb5f0d87f6760347b599L81-R81)
[[8]](diffhunk://#diff-732721edf7c32d883ffefa52d6e43339c6ee328c88c3cb5f0d87f6760347b599L90-R90)
[[9]](diffhunk://#diff-732721edf7c32d883ffefa52d6e43339c6ee328c88c3cb5f0d87f6760347b599L106-R114)
[[10]](diffhunk://#diff-732721edf7c32d883ffefa52d6e43339c6ee328c88c3cb5f0d87f6760347b599L116-R124)
---
 .../components/describe-odigos/index.tsx      |  16 ++
 frontend/webapp/components/index.ts           |   5 +-
 .../webapp/components/main/header/index.tsx   |   3 +-
 .../notification/notification-manager.tsx     |  31 +---
 .../overview/all-drawers/describe-drawer.tsx  |  34 +++++
 .../components/overview/all-drawers/index.tsx |   8 +-
 .../overview-drawer/drawer-header/index.tsx   |  18 ++-
 .../main/overview/overview-drawer/index.tsx   |  34 +++--
 frontend/webapp/graphql/queries/describe.ts   | 141 ++++++++++++++++++
 .../webapp/hooks/actions/useActionFormData.ts |   4 +-
 frontend/webapp/hooks/describe/index.ts       |   1 +
 .../hooks/describe/useDescribeOdigos.ts       |  15 ++
 .../destinations/useDestinationFormData.ts    |   4 +-
 .../useInstrumentationRuleFormData.ts         |   4 +-
 .../hooks/notification/useClickNotif.ts       |   6 +-
 frontend/webapp/hooks/overview/useMetrics.ts  |  14 +-
 .../reuseable-components/data-card/index.tsx  |  21 ++-
 .../icon-button/index.tsx                     |  63 ++++++++
 frontend/webapp/reuseable-components/index.ts |   1 +
 frontend/webapp/store/useDrawerStore.ts       |  14 +-
 frontend/webapp/types/describe.ts             |  40 +++++
 frontend/webapp/utils/constants/string.tsx    |   3 +-
 22 files changed, 393 insertions(+), 87 deletions(-)
 create mode 100644 frontend/webapp/components/describe-odigos/index.tsx
 create mode 100644 frontend/webapp/components/overview/all-drawers/describe-drawer.tsx
 create mode 100644 frontend/webapp/hooks/describe/useDescribeOdigos.ts
 create mode 100644 frontend/webapp/reuseable-components/icon-button/index.tsx

diff --git a/frontend/webapp/components/describe-odigos/index.tsx b/frontend/webapp/components/describe-odigos/index.tsx
new file mode 100644
index 000000000..28c78de36
--- /dev/null
+++ b/frontend/webapp/components/describe-odigos/index.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import Image from 'next/image';
+import theme from '@/styles/theme';
+import { IconButton } from '@/reuseable-components';
+import { DRAWER_OTHER_TYPES, useDrawerStore } from '@/store';
+
+export const DescribeOdigos = () => {
+  const { setSelectedItem } = useDrawerStore();
+  const handleClick = () => setSelectedItem({ type: DRAWER_OTHER_TYPES.DESCRIBE_ODIGOS, id: DRAWER_OTHER_TYPES.DESCRIBE_ODIGOS });
+
+  return (
+    <IconButton onClick={handleClick} withPing pingColor={theme.colors.majestic_blue}>
+      <Image src='/brand/odigos-icon.svg' alt='logo' width={16} height={16} />
+    </IconButton>
+  );
+};
diff --git a/frontend/webapp/components/index.ts b/frontend/webapp/components/index.ts
index 73363b759..c23fdeebd 100644
--- a/frontend/webapp/components/index.ts
+++ b/frontend/webapp/components/index.ts
@@ -1,6 +1,7 @@
-export * from './setup';
-export * from './overview';
 export * from './common';
+export * from './describe-odigos';
 export * from './main';
 export * from './modals';
 export * from './notification';
+export * from './overview';
+export * from './setup';
diff --git a/frontend/webapp/components/main/header/index.tsx b/frontend/webapp/components/main/header/index.tsx
index fbfb1be26..188732d39 100644
--- a/frontend/webapp/components/main/header/index.tsx
+++ b/frontend/webapp/components/main/header/index.tsx
@@ -6,7 +6,7 @@ import { PlatformTypes } from '@/types';
 import { PlatformTitle } from './cp-title';
 import { useConnectionStore } from '@/store';
 import { ConnectionStatus } from '@/reuseable-components';
-import { NotificationManager } from '@/components/notification';
+import { DescribeOdigos, NotificationManager } from '@/components';
 
 interface MainHeaderProps {}
 
@@ -42,6 +42,7 @@ export const MainHeader: React.FC<MainHeaderProps> = () => {
 
       <AlignRight>
         <NotificationManager />
+        <DescribeOdigos />
       </AlignRight>
     </HeaderContainer>
   );
diff --git a/frontend/webapp/components/notification/notification-manager.tsx b/frontend/webapp/components/notification/notification-manager.tsx
index 20cb5c66f..36586c700 100644
--- a/frontend/webapp/components/notification/notification-manager.tsx
+++ b/frontend/webapp/components/notification/notification-manager.tsx
@@ -6,32 +6,8 @@ import { useNotificationStore } from '@/store';
 import { ACTION, getStatusIcon } from '@/utils';
 import { useOnClickOutside, useTimeAgo } from '@/hooks';
 import theme, { hexPercentValues } from '@/styles/theme';
-import { NoDataFound, Text } from '@/reuseable-components';
 import type { Notification, NotificationType } from '@/types';
-
-const BellIcon = styled.div`
-  position: relative;
-  width: 36px;
-  height: 36px;
-  border-radius: 100%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  cursor: pointer;
-  &:hover {
-    background-color: ${({ theme }) => theme.colors.white_opacity['008']};
-  }
-`;
-
-const LiveBadge = styled.div`
-  position: absolute;
-  top: 8px;
-  right: 8px;
-  width: 6px;
-  height: 6px;
-  border-radius: 100%;
-  background-color: ${({ theme }) => theme.colors.orange_og};
-`;
+import { IconButton, NoDataFound, Text } from '@/reuseable-components';
 
 const RelativeContainer = styled.div`
   position: relative;
@@ -106,10 +82,9 @@ export const NotificationManager = () => {
 
   return (
     <RelativeContainer ref={containerRef}>
-      <BellIcon onClick={toggleOpen}>
-        {!!unseenCount && <LiveBadge />}
+      <IconButton onClick={toggleOpen} withPing={!!unseenCount} pingColor={theme.colors.orange_og}>
         <Image src='/icons/common/notification.svg' alt='logo' width={16} height={16} />
-      </BellIcon>
+      </IconButton>
 
       {isOpen && (
         <AbsoluteContainer>
diff --git a/frontend/webapp/components/overview/all-drawers/describe-drawer.tsx b/frontend/webapp/components/overview/all-drawers/describe-drawer.tsx
new file mode 100644
index 000000000..4b893587f
--- /dev/null
+++ b/frontend/webapp/components/overview/all-drawers/describe-drawer.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import styled from 'styled-components';
+import { useDescribeOdigos } from '@/hooks';
+import { DATA_CARDS, safeJsonStringify } from '@/utils';
+import { DataCard, DataCardFieldTypes } from '@/reuseable-components';
+import OverviewDrawer from '@/containers/main/overview/overview-drawer';
+
+interface Props {}
+
+const DataContainer = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+`;
+
+export const DescribeDrawer: React.FC<Props> = () => {
+  const { data: describe } = useDescribeOdigos();
+
+  return (
+    <OverviewDrawer title={DATA_CARDS.DESCRIBE_ODIGOS} titleTooltip='' imageUri='/brand/odigos-icon.svg'>
+      <DataContainer>
+        <DataCard
+          title=''
+          data={[
+            {
+              type: DataCardFieldTypes.CODE,
+              value: JSON.stringify({ language: 'json', code: safeJsonStringify(describe) }),
+            },
+          ]}
+        />
+      </DataContainer>
+    </OverviewDrawer>
+  );
+};
diff --git a/frontend/webapp/components/overview/all-drawers/index.tsx b/frontend/webapp/components/overview/all-drawers/index.tsx
index 65cbb3b59..8f9329437 100644
--- a/frontend/webapp/components/overview/all-drawers/index.tsx
+++ b/frontend/webapp/components/overview/all-drawers/index.tsx
@@ -1,12 +1,13 @@
 import React from 'react';
-import { useDrawerStore } from '@/store';
 import { OVERVIEW_ENTITY_TYPES } from '@/types';
+import { DescribeDrawer } from './describe-drawer';
+import { DRAWER_OTHER_TYPES, useDrawerStore } from '@/store';
 import { ActionDrawer, DestinationDrawer, RuleDrawer, SourceDrawer } from '@/containers';
 
 const AllDrawers = () => {
   const selected = useDrawerStore(({ selectedItem }) => selectedItem);
 
-  if (!selected?.item) return null;
+  if (!selected?.type) return null;
 
   switch (selected.type) {
     case OVERVIEW_ENTITY_TYPES.RULE:
@@ -21,6 +22,9 @@ const AllDrawers = () => {
     case OVERVIEW_ENTITY_TYPES.DESTINATION:
       return <DestinationDrawer />;
 
+    case DRAWER_OTHER_TYPES.DESCRIBE_ODIGOS:
+      return <DescribeDrawer />;
+
     default:
       return <></>;
   }
diff --git a/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx b/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx
index ea94656d2..c7a78f08a 100644
--- a/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx
+++ b/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx
@@ -66,9 +66,9 @@ export interface DrawerHeaderRef {
 interface DrawerHeaderProps {
   title: string;
   titleTooltip?: string;
-  imageUri: string;
-  isEdit: boolean;
-  onEdit: () => void;
+  imageUri?: string;
+  isEdit?: boolean;
+  onEdit?: () => void;
   onClose: () => void;
 }
 
@@ -87,9 +87,12 @@ const DrawerHeader = forwardRef<DrawerHeaderRef, DrawerHeaderProps>(({ title, ti
   return (
     <HeaderContainer>
       <SectionItemsWrapper>
-        <DrawerItemImageWrapper>
-          <Image src={imageUri} alt='Drawer Item' width={16} height={16} />
-        </DrawerItemImageWrapper>
+        {!!imageUri && (
+          <DrawerItemImageWrapper>
+            <Image src={imageUri} alt='Drawer Item' width={16} height={16} />
+          </DrawerItemImageWrapper>
+        )}
+
         {!isEdit && (
           <Tooltip text={titleTooltip} withIcon>
             <Title>{title}</Title>
@@ -105,12 +108,13 @@ const DrawerHeader = forwardRef<DrawerHeaderRef, DrawerHeaderProps>(({ title, ti
       )}
 
       <SectionItemsWrapper $gap={8}>
-        {!isEdit && (
+        {!isEdit && !!onEdit && (
           <EditButton data-id='drawer-edit' variant='tertiary' onClick={onEdit}>
             <Image src='/icons/common/edit.svg' alt='Edit' width={16} height={16} />
             <ButtonText>Edit</ButtonText>
           </EditButton>
         )}
+
         <CloseButton data-id='drawer-close' variant='secondary' onClick={onClose}>
           <Image src='/icons/common/x.svg' alt='Close' width={12} height={12} />
         </CloseButton>
diff --git a/frontend/webapp/containers/main/overview/overview-drawer/index.tsx b/frontend/webapp/containers/main/overview/overview-drawer/index.tsx
index bf722f7ad..650187ac4 100644
--- a/frontend/webapp/containers/main/overview/overview-drawer/index.tsx
+++ b/frontend/webapp/containers/main/overview/overview-drawer/index.tsx
@@ -14,12 +14,12 @@ interface Props {
   title: string;
   titleTooltip?: string;
   imageUri: string;
-  isEdit: boolean;
-  isFormDirty: boolean;
-  onEdit: (bool?: boolean) => void;
-  onSave: (newTitle: string) => void;
-  onDelete: () => void;
-  onCancel: () => void;
+  isEdit?: boolean;
+  isFormDirty?: boolean;
+  onEdit?: (bool?: boolean) => void;
+  onSave?: (newTitle: string) => void;
+  onDelete?: () => void;
+  onCancel?: () => void;
 }
 
 const DrawerContent = styled.div`
@@ -34,7 +34,7 @@ const ContentArea = styled.div`
   overflow-y: auto;
 `;
 
-const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({ children, title, titleTooltip, imageUri, isEdit, isFormDirty, onEdit, onSave, onDelete, onCancel }) => {
+const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({ children, title, titleTooltip, imageUri, isEdit = false, isFormDirty = false, onEdit, onSave, onDelete, onCancel }) => {
   const { selectedItem, setSelectedItem } = useDrawerStore();
 
   useKeyDown({ key: 'Enter', active: !!selectedItem }, () => (isEdit ? clickSave() : closeDrawer()));
@@ -52,7 +52,7 @@ const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({ children, title,
 
   const closeDrawer = () => {
     setSelectedItem(null);
-    onEdit(false);
+    if (onEdit) onEdit(false);
     setIsDeleteModalOpen(false);
     setIsCancelModalOpen(false);
   };
@@ -64,7 +64,7 @@ const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({ children, title,
 
   const handleCancel = () => {
     titleRef.current?.clearTitle();
-    onCancel();
+    if (onCancel) onCancel();
     closeWarningModals();
   };
 
@@ -78,7 +78,7 @@ const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({ children, title,
   };
 
   const handleDelete = () => {
-    onDelete();
+    if (onDelete) onDelete();
     closeWarningModals();
   };
 
@@ -87,7 +87,7 @@ const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({ children, title,
   };
 
   const clickSave = () => {
-    onSave(titleRef.current?.getTitle() || '');
+    if (onSave) onSave(titleRef.current?.getTitle() || '');
   };
 
   const isLastItem = () => {
@@ -103,7 +103,15 @@ const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({ children, title,
     <>
       <Drawer isOpen onClose={isEdit ? clickCancel : closeDrawer} width={DRAWER_WIDTH} closeOnEscape={!isDeleteModalOpen && !isCancelModalOpen}>
         <DrawerContent>
-          <DrawerHeader ref={titleRef} title={title} titleTooltip={titleTooltip} imageUri={imageUri} isEdit={isEdit} onEdit={() => onEdit(true)} onClose={isEdit ? clickCancel : closeDrawer} />
+          <DrawerHeader
+            ref={titleRef}
+            title={title}
+            titleTooltip={titleTooltip}
+            imageUri={imageUri}
+            isEdit={isEdit}
+            onEdit={onEdit ? () => onEdit(true) : undefined}
+            onClose={isEdit ? clickCancel : closeDrawer}
+          />
           <ContentArea>{children}</ContentArea>
           <DrawerFooter isOpen={isEdit} onSave={clickSave} onCancel={clickCancel} onDelete={clickDelete} deleteLabel={isSource ? 'Uninstrument' : undefined} />
         </DrawerContent>
@@ -113,7 +121,7 @@ const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({ children, title,
         isOpen={isDeleteModalOpen}
         noOverlay
         name={`${selectedItem?.type}${title ? ` (${title})` : ''}`}
-        type={selectedItem?.type}
+        type={selectedItem?.type as OVERVIEW_ENTITY_TYPES}
         isLastItem={isLastItem()}
         onApprove={handleDelete}
         onDeny={closeWarningModals}
diff --git a/frontend/webapp/graphql/queries/describe.ts b/frontend/webapp/graphql/queries/describe.ts
index b2207ef9c..1280a312e 100644
--- a/frontend/webapp/graphql/queries/describe.ts
+++ b/frontend/webapp/graphql/queries/describe.ts
@@ -1,5 +1,146 @@
 import { gql } from '@apollo/client';
 
+export const DESCRIBE_ODIGOS = gql`
+  query DescribeOdigos {
+    describeOdigos {
+      odigosVersion {
+        name
+        value
+        status
+        explain
+      }
+      numberOfDestinations
+      numberOfSources
+      clusterCollector {
+        enabled {
+          name
+          value
+          status
+          explain
+        }
+        collectorGroup {
+          name
+          value
+          status
+          explain
+        }
+        deployed {
+          name
+          value
+          status
+          explain
+        }
+        deployedError {
+          name
+          value
+          status
+          explain
+        }
+        collectorReady {
+          name
+          value
+          status
+          explain
+        }
+        deploymentCreated {
+          name
+          value
+          status
+          explain
+        }
+        expectedReplicas {
+          name
+          value
+          status
+          explain
+        }
+        healthyReplicas {
+          name
+          value
+          status
+          explain
+        }
+        failedReplicas {
+          name
+          value
+          status
+          explain
+        }
+        failedReplicasReason {
+          name
+          value
+          status
+          explain
+        }
+      }
+      nodeCollector {
+        enabled {
+          name
+          value
+          status
+          explain
+        }
+        collectorGroup {
+          name
+          value
+          status
+          explain
+        }
+        deployed {
+          name
+          value
+          status
+          explain
+        }
+        deployedError {
+          name
+          value
+          status
+          explain
+        }
+        collectorReady {
+          name
+          value
+          status
+          explain
+        }
+        daemonSet {
+          name
+          value
+          status
+          explain
+        }
+        desiredNodes {
+          name
+          value
+          status
+          explain
+        }
+        currentNodes {
+          name
+          value
+          status
+          explain
+        }
+        updatedNodes {
+          name
+          value
+          status
+          explain
+        }
+        availableNodes {
+          name
+          value
+          status
+          explain
+        }
+      }
+      isSettled
+      hasErrors
+    }
+  }
+`;
+
 export const DESCRIBE_SOURCE = gql`
   query DescribeSource($namespace: String!, $kind: String!, $name: String!) {
     describeSource(namespace: $namespace, kind: $kind, name: $name) {
diff --git a/frontend/webapp/hooks/actions/useActionFormData.ts b/frontend/webapp/hooks/actions/useActionFormData.ts
index e6f721462..03d0ee864 100644
--- a/frontend/webapp/hooks/actions/useActionFormData.ts
+++ b/frontend/webapp/hooks/actions/useActionFormData.ts
@@ -1,4 +1,4 @@
-import { DrawerBaseItem } from '@/store';
+import { DrawerItem } from '@/store';
 import { useGenericForm, useNotify } from '@/hooks';
 import { FORM_ALERTS, NOTIFICATION } from '@/utils';
 import type { ActionDataParsed, ActionInput } from '@/types';
@@ -50,7 +50,7 @@ export function useActionFormData() {
     return ok;
   };
 
-  const loadFormWithDrawerItem = (drawerItem: DrawerBaseItem) => {
+  const loadFormWithDrawerItem = (drawerItem: DrawerItem) => {
     const { type, spec } = drawerItem.item as ActionDataParsed;
 
     const updatedData: ActionInput = {
diff --git a/frontend/webapp/hooks/describe/index.ts b/frontend/webapp/hooks/describe/index.ts
index 0d183ae60..16e5b9b9d 100644
--- a/frontend/webapp/hooks/describe/index.ts
+++ b/frontend/webapp/hooks/describe/index.ts
@@ -1 +1,2 @@
+export * from './useDescribeOdigos';
 export * from './useDescribeSource';
diff --git a/frontend/webapp/hooks/describe/useDescribeOdigos.ts b/frontend/webapp/hooks/describe/useDescribeOdigos.ts
new file mode 100644
index 000000000..74b90199e
--- /dev/null
+++ b/frontend/webapp/hooks/describe/useDescribeOdigos.ts
@@ -0,0 +1,15 @@
+import { useQuery } from '@apollo/client';
+import { DESCRIBE_ODIGOS } from '@/graphql';
+import type { DescribeOdigos } from '@/types';
+
+export const useDescribeOdigos = () => {
+  const { data, loading, error } = useQuery<DescribeOdigos>(DESCRIBE_ODIGOS, {
+    pollInterval: 5000,
+  });
+
+  return {
+    data: data?.describeOdigos,
+    loading,
+    error,
+  };
+};
diff --git a/frontend/webapp/hooks/destinations/useDestinationFormData.ts b/frontend/webapp/hooks/destinations/useDestinationFormData.ts
index 3f4edefd3..07927d7e0 100644
--- a/frontend/webapp/hooks/destinations/useDestinationFormData.ts
+++ b/frontend/webapp/hooks/destinations/useDestinationFormData.ts
@@ -1,5 +1,5 @@
 import { useState, useEffect } from 'react';
-import { DrawerBaseItem } from '@/store';
+import { DrawerItem } from '@/store';
 import { useQuery } from '@apollo/client';
 import { GET_DESTINATION_TYPE_DETAILS } from '@/graphql';
 import { useConnectDestinationForm, useGenericForm, useNotify } from '@/hooks';
@@ -110,7 +110,7 @@ export function useDestinationFormData(params?: { destinationType?: string; supp
     return ok;
   };
 
-  const loadFormWithDrawerItem = (drawerItem: DrawerBaseItem) => {
+  const loadFormWithDrawerItem = (drawerItem: DrawerItem) => {
     const {
       destinationType: { type },
       name,
diff --git a/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleFormData.ts b/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleFormData.ts
index c1718e65f..6fe9b7232 100644
--- a/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleFormData.ts
+++ b/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleFormData.ts
@@ -1,4 +1,4 @@
-import type { DrawerBaseItem } from '@/store';
+import type { DrawerItem } from '@/store';
 import { useGenericForm, useNotify } from '@/hooks';
 import { FORM_ALERTS, NOTIFICATION } from '@/utils';
 import { PayloadCollectionType, type InstrumentationRuleInput, type InstrumentationRuleSpec } from '@/types';
@@ -53,7 +53,7 @@ export function useInstrumentationRuleFormData() {
     return ok;
   };
 
-  const loadFormWithDrawerItem = (drawerItem: DrawerBaseItem) => {
+  const loadFormWithDrawerItem = (drawerItem: DrawerItem) => {
     const { ruleName, notes, disabled, payloadCollection } = drawerItem.item as InstrumentationRuleSpec;
 
     const updatedData: InstrumentationRuleInput = {
diff --git a/frontend/webapp/hooks/notification/useClickNotif.ts b/frontend/webapp/hooks/notification/useClickNotif.ts
index 02d05a450..a9ba5a970 100644
--- a/frontend/webapp/hooks/notification/useClickNotif.ts
+++ b/frontend/webapp/hooks/notification/useClickNotif.ts
@@ -4,7 +4,7 @@ import { getIdFromSseTarget } from '@/utils';
 import { useDestinationCRUD } from '../destinations';
 import { type Notification, OVERVIEW_ENTITY_TYPES } from '@/types';
 import { useInstrumentationRuleCRUD } from '../instrumentation-rules';
-import { DrawerBaseItem, useDrawerStore, useNotificationStore } from '@/store';
+import { DrawerItem, useDrawerStore, useNotificationStore } from '@/store';
 
 export const useClickNotif = () => {
   const { sources } = useSourceCRUD();
@@ -19,7 +19,7 @@ export const useClickNotif = () => {
     const { dismissToast } = options || {};
 
     if (crdType && target) {
-      const drawerItem: Partial<DrawerBaseItem> = {};
+      const drawerItem: Partial<DrawerItem> = {};
 
       switch (crdType) {
         case OVERVIEW_ENTITY_TYPES.RULE:
@@ -55,7 +55,7 @@ export const useClickNotif = () => {
       }
 
       if (!!drawerItem.item) {
-        setSelectedItem(drawerItem as DrawerBaseItem);
+        setSelectedItem(drawerItem as DrawerItem);
       } else {
         console.warn('notif item not found for:', { crdType, target });
       }
diff --git a/frontend/webapp/hooks/overview/useMetrics.ts b/frontend/webapp/hooks/overview/useMetrics.ts
index 5fcc8f868..2f6be9934 100644
--- a/frontend/webapp/hooks/overview/useMetrics.ts
+++ b/frontend/webapp/hooks/overview/useMetrics.ts
@@ -1,15 +1,11 @@
-import { useEffect } from 'react';
 import { useQuery } from '@apollo/client';
 import { GET_METRICS } from '@/graphql/mutations/metrics';
 import type { OverviewMetricsResponse } from '@/types';
 
-export function useMetrics() {
-  const { data, refetch } = useQuery<OverviewMetricsResponse>(GET_METRICS);
-
-  useEffect(() => {
-    const interval = setInterval(async () => await refetch(), 3000);
-    return () => clearInterval(interval);
-  }, [refetch]);
+export const useMetrics = () => {
+  const { data } = useQuery<OverviewMetricsResponse>(GET_METRICS, {
+    pollInterval: 3000,
+  });
 
   return { metrics: data };
-}
+};
diff --git a/frontend/webapp/reuseable-components/data-card/index.tsx b/frontend/webapp/reuseable-components/data-card/index.tsx
index 7105834e4..6c7788944 100644
--- a/frontend/webapp/reuseable-components/data-card/index.tsx
+++ b/frontend/webapp/reuseable-components/data-card/index.tsx
@@ -43,14 +43,19 @@ const Description = styled(Text)`
 export const DataCard: React.FC<Props> = ({ title = 'Details', titleBadge, description, data }) => {
   return (
     <CardContainer>
-      <Header>
-        <Title>
-          {title}
-          {/* NOT undefined, because we should allow zero (0) values */}
-          {titleBadge !== undefined && <Badge label={titleBadge} />}
-        </Title>
-        {!!description && <Description>{description}</Description>}
-      </Header>
+      {!!title || !!description ? (
+        <Header>
+          {!!title && (
+            <Title>
+              {title}
+              {/* NOT undefined, because we should allow zero (0) values */}
+              {titleBadge !== undefined && <Badge label={titleBadge} />}
+            </Title>
+          )}
+
+          {!!description && <Description>{description}</Description>}
+        </Header>
+      ) : null}
 
       <DataCardFields data={data} />
     </CardContainer>
diff --git a/frontend/webapp/reuseable-components/icon-button/index.tsx b/frontend/webapp/reuseable-components/icon-button/index.tsx
new file mode 100644
index 000000000..07e803bc2
--- /dev/null
+++ b/frontend/webapp/reuseable-components/icon-button/index.tsx
@@ -0,0 +1,63 @@
+import React, { CSSProperties, PropsWithChildren } from 'react';
+import styled, { keyframes } from 'styled-components';
+
+interface Props extends PropsWithChildren {
+  onClick?: () => void;
+  withPing?: boolean;
+  pingColor?: CSSProperties['backgroundColor'];
+}
+
+const Button = styled.button`
+  position: relative;
+  width: 36px;
+  height: 36px;
+  border: none;
+  border-radius: 100%;
+  background-color: transparent;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  &:hover {
+    background-color: ${({ theme }) => theme.colors.white_opacity['008']};
+  }
+`;
+
+const pingAnimation = keyframes`
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+  75%, 100% {
+    transform: scale(2);
+    opacity: 0;
+  }
+`;
+
+const Ping = styled.div<{ $color: Props['pingColor'] }>`
+  position: absolute;
+  top: 8px;
+  right: 8px;
+  width: 6px;
+  height: 6px;
+  border-radius: 100%;
+  background-color: ${({ theme, $color }) => $color || theme.colors.secondary};
+
+  &::after {
+    content: '';
+    position: absolute;
+    inset: 0;
+    border-radius: 100%;
+    background-color: ${({ theme, $color }) => $color || theme.colors.secondary};
+    animation: ${pingAnimation} 1.5s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+`;
+
+export const IconButton: React.FC<Props> = ({ children, onClick, withPing, pingColor }) => {
+  return (
+    <Button onClick={onClick}>
+      {withPing && <Ping $color={pingColor} />}
+      {children}
+    </Button>
+  );
+};
diff --git a/frontend/webapp/reuseable-components/index.ts b/frontend/webapp/reuseable-components/index.ts
index 1e2240a25..b160299d0 100644
--- a/frontend/webapp/reuseable-components/index.ts
+++ b/frontend/webapp/reuseable-components/index.ts
@@ -37,3 +37,4 @@ export * from './condition-details';
 export * from './data-card';
 export * from './data-tab';
 export * from './code';
+export * from './icon-button';
diff --git a/frontend/webapp/store/useDrawerStore.ts b/frontend/webapp/store/useDrawerStore.ts
index a4efa3a3e..bef2ef993 100644
--- a/frontend/webapp/store/useDrawerStore.ts
+++ b/frontend/webapp/store/useDrawerStore.ts
@@ -1,19 +1,19 @@
-// drawerStore.ts
 import { create } from 'zustand';
 import type { ActionDataParsed, ActualDestination, InstrumentationRuleSpec, K8sActualSource, OVERVIEW_ENTITY_TYPES, WorkloadId } from '@/types';
 
-type ItemType = OVERVIEW_ENTITY_TYPES;
+export enum DRAWER_OTHER_TYPES {
+  DESCRIBE_ODIGOS = 'describe-odigos',
+}
 
-export interface DrawerBaseItem {
+export interface DrawerItem {
+  type: OVERVIEW_ENTITY_TYPES | DRAWER_OTHER_TYPES;
   id: string | WorkloadId;
   item?: InstrumentationRuleSpec | K8sActualSource | ActionDataParsed | ActualDestination;
-  type: ItemType;
-  // Add common properties here
 }
 
 interface DrawerStoreState {
-  selectedItem: DrawerBaseItem | null;
-  setSelectedItem: (item: DrawerBaseItem | null) => void;
+  selectedItem: DrawerItem | null;
+  setSelectedItem: (item: DrawerItem | null) => void;
   isDrawerOpen: boolean;
   openDrawer: () => void;
   closeDrawer: () => void;
diff --git a/frontend/webapp/types/describe.ts b/frontend/webapp/types/describe.ts
index 5aa3a8551..ead5c51e0 100644
--- a/frontend/webapp/types/describe.ts
+++ b/frontend/webapp/types/describe.ts
@@ -81,6 +81,46 @@ interface SourceAnalyze {
   pods: PodAnalyze[];
 }
 
+interface ClusterCollectorAnalyze {
+  enabled: EntityProperty;
+  collectorGroup: EntityProperty;
+  deployed?: EntityProperty;
+  deployedError?: EntityProperty;
+  collectorReady?: EntityProperty;
+  deploymentCreated: EntityProperty;
+  expectedReplicas?: EntityProperty;
+  healthyReplicas?: EntityProperty;
+  failedReplicas?: EntityProperty;
+  failedReplicasReason?: EntityProperty;
+}
+
+interface NodeCollectorAnalyze {
+  enabled: EntityProperty;
+  collectorGroup: EntityProperty;
+  deployed?: EntityProperty;
+  deployedError?: EntityProperty;
+  collectorReady?: EntityProperty;
+  daemonSet: EntityProperty;
+  desiredNodes?: EntityProperty;
+  currentNodes?: EntityProperty;
+  updatedNodes?: EntityProperty;
+  availableNodes?: EntityProperty;
+}
+
+interface OdigosAnalyze {
+  odigosVersion: EntityProperty;
+  numberOfDestinations: number;
+  numberOfSources: number;
+  clusterCollector: ClusterCollectorAnalyze;
+  nodeCollector: NodeCollectorAnalyze;
+  isSettled: boolean;
+  hasErrors: boolean;
+}
+
 export interface DescribeSource {
   describeSource: SourceAnalyze;
 }
+
+export interface DescribeOdigos {
+  describeOdigos: OdigosAnalyze;
+}
diff --git a/frontend/webapp/utils/constants/string.tsx b/frontend/webapp/utils/constants/string.tsx
index 1eb13a917..4ccde2260 100644
--- a/frontend/webapp/utils/constants/string.tsx
+++ b/frontend/webapp/utils/constants/string.tsx
@@ -64,9 +64,10 @@ export const DATA_CARDS = {
   RULE_DETAILS: 'Instrumentation Rule Details',
   DESTINATION_DETAILS: 'Destination Details',
   SOURCE_DETAILS: 'Source Details',
-  DESCRIBE_SOURCE: 'Describe Source',
   DETECTED_CONTAINERS: 'Detected Containers',
   DETECTED_CONTAINERS_DESCRIPTION: 'The system automatically instruments the containers it detects with a supported programming language.',
+  DESCRIBE_SOURCE: 'Describe Source',
+  DESCRIBE_ODIGOS: 'Describe Odigos',
 };
 
 export const DISPLAY_TITLES = {