Skip to content

Commit

Permalink
[GEN-1596] Add extended describe to source and odigos (#1686)
Browse files Browse the repository at this point in the history
This pull request introduces new features for describing Odigos and
sources, along with various improvements to the frontend components and
services. The most important changes include the addition of new
description drawers for Odigos and sources, updates to the API services,
and integration of these features into the existing components.

### New Features:
* Added `OdigosDescriptionDrawer` and `SourceDescriptionDrawer`
components to display detailed descriptions of Odigos and sources.
[[1]](diffhunk://#diff-4c8617cf98daa70e8ff7ee9893152742cc6daede3034814f62e66d3109ab056aR1-R248)
[[2]](diffhunk://#diff-f1476b3baa85929c2d816bc33571b7453fb4bace0b7a1f6e5c83b7c2b2e3c9d7R1-R282)
* Integrated new description drawers into the `OverviewHeader` and
`EditSourceForm` components.
[[1]](diffhunk://#diff-9d02b5dae936ee5dd59a0d8d3722fee2e78eca7acd0279c6d605048303f400cdL54-R58)
[[2]](diffhunk://#diff-dd26a25a84389833622d5c5b855c3d93d0f7f6a331d652d25863a17b81dda343R110-R114)

### API and Hooks Updates:
* Created new API functions `getOdigosDescription` and
`getSourceDescription` to fetch descriptions from the backend.
* Updated the `useDescribe` hook to manage fetching and state for Odigos
and source descriptions.
* Modified the `get` function in `api.ts` to use the `fetch` API instead
of `axios`.

### Asset and Export Updates:
* Added new SVG icons `describe.svg` and `refresh.svg` to the assets.
* Updated various index files to export the new components and hooks.
[[1]](diffhunk://#diff-348d5bfe60b97e626aa656d290b7f82a1f4ccd34d08bdcf8cd43e5ae6e1d4c95R2)
[[2]](diffhunk://#diff-4acfd7087b0688dfb3cb2e8adc9cb925048e2976396258273a7459dcdc40c666R1)
[[3]](diffhunk://#diff-3869e04212089fd401e12696b69ff9edcc4d2aa7c227987e4e00d6991205d6b4R6)
[[4]](diffhunk://#diff-ccdabc5e7350447daadecb689a4e8f7f3c747ffefbbc0bb26738df9052d3068cR12)

### Code Improvements:
* Replaced `NodeJS.Timer` with `NodeJS.Timeout` in `DataFlowContainer`.
* Simplified the response code logic in `DescribeOdigos` endpoint.
  • Loading branch information
alonkeyval authored Nov 6, 2024
1 parent b80b2dd commit 4b63a3d
Show file tree
Hide file tree
Showing 18 changed files with 683 additions and 35 deletions.
5 changes: 0 additions & 5 deletions frontend/endpoints/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ func DescribeOdigos(c *gin.Context) {

// construct the http response code based on the status of the odigos
returnCode := 200
if desc.HasErrors {
returnCode = 500
} else if !desc.IsSettled {
returnCode = 202
}

// Check for the Accept header
acceptHeader := c.GetHeader("Accept")
Expand Down
1 change: 1 addition & 0 deletions frontend/webapp/assets/icons/general/describe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion frontend/webapp/assets/icons/general/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import Funnel from './funnel.svg';
import FunnelFocus from './funnel-focus.svg';
import PayloadCollectionIcon from './payload-collection.svg';
export { Funnel, FunnelFocus, PayloadCollectionIcon };
import Describe from './describe.svg';
import Refresh from './refresh.svg';

export { Funnel, FunnelFocus, PayloadCollectionIcon, Describe, Refresh };
1 change: 1 addition & 0 deletions frontend/webapp/assets/icons/general/refresh.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import styled from 'styled-components';
import { KeyvalText } from '@/design.system';
import { NotificationList } from '@/components';
import { BackIcon } from '@keyval-dev/design-system';
import { OdigosDescriptionDrawer } from '@/containers';

export interface OverviewHeaderProps {
title?: string;
Expand Down Expand Up @@ -51,8 +52,10 @@ export function OverviewHeader({ title, onBackClick }: OverviewHeaderProps) {
{title}
</KeyvalText>
</HeaderTop>

{!onBackClick && <NotificationList />}
<div style={{ display: 'flex', gap: 8 }}>
{!onBackClick && <NotificationList />}
{title === 'Overview' && <OdigosDescriptionDrawer />}
</div>
</OverviewHeaderContainer>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function DataFlowContainer() {
const { destinationList, refetchDestinations } = useDestinations();

const useSearch = useSearchParams();
const intervalId = useRef<NodeJS.Timer>();
const intervalId = useRef<NodeJS.Timeout>();

const { metrics } = useOverviewMetrics();

Expand Down
1 change: 1 addition & 0 deletions frontend/webapp/containers/main/overview/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './data.flow';
export * from './odigos-describe';
248 changes: 248 additions & 0 deletions frontend/webapp/containers/main/overview/odigos-describe/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
'use client';
import React, { useEffect, useState } from 'react';
import { Describe, Refresh } from '@/assets'; // Assume RefreshIcon is the refresh icon component
import theme from '@/styles/palette';
import { useDescribe } from '@/hooks';
import styled from 'styled-components';
import { Drawer, KeyvalText } from '@/design.system';

interface OdigosDescriptionDrawerProps {}

export const OdigosDescriptionDrawer: React.FC<
OdigosDescriptionDrawerProps
> = ({}) => {
const [isOpen, setDrawerOpen] = useState(false);

const [badgeStatus, setBadgeStatus] = useState<
'error' | 'transitioning' | 'success'
>('success');

const toggleDrawer = () => setDrawerOpen(!isOpen);

const { odigosDescription, isOdigosLoading, refetchOdigosDescription } =
useDescribe();

useEffect(() => {
if (odigosDescription) {
const statuses = extractStatuses(odigosDescription);
if (statuses.includes('error')) setBadgeStatus('error');
else if (statuses.includes('transitioning'))
setBadgeStatus('transitioning');
else setBadgeStatus('success');
}
}, [odigosDescription]);

useEffect(() => {
refetchOdigosDescription();
}, [refetchOdigosDescription]);

return (
<>
<IconWrapper onClick={toggleDrawer}>
<Describe style={{ cursor: 'pointer' }} size={10} />
{!isOdigosLoading && (
<NotificationBadge status={badgeStatus}>
<KeyvalText size={10}>
{badgeStatus === 'transitioning'
? '...'
: badgeStatus === 'error'
? '!'
: ''}
</KeyvalText>
</NotificationBadge>
)}
</IconWrapper>

{isOpen && (
<Drawer
isOpen={isOpen}
onClose={() => setDrawerOpen(false)}
position="right"
width="fit-content"
>
{isOdigosLoading ? (
<LoadingMessage>Loading description...</LoadingMessage>
) : (
<DescriptionContent>
{odigosDescription
? formatOdigosDescription(
odigosDescription,
refetchOdigosDescription
)
: 'No description available.'}
</DescriptionContent>
)}
</Drawer>
)}
</>
);
};

// Function to extract statuses from the odigosDescription response
function extractStatuses(description: any): string[] {
const statuses: string[] = [];
Object.values(description.clusterCollector).forEach((item: any) => {
if (item.status) statuses.push(item.status);
});
Object.values(description.nodeCollector).forEach((item: any) => {
if (item.status) statuses.push(item.status);
});
return statuses;
}

// Render the description with status-specific styling
function formatOdigosDescription(description: any, refetch: () => void) {
return (
<div>
{/* Display Odigos Version with Refresh Button */}
{description.odigosVersion && (
<VersionHeader>
<VersionText>
{description.odigosVersion.name}: {description.odigosVersion.value}
</VersionText>
<IconWrapper onClick={refetch}>
<Refresh size={16} />
</IconWrapper>
</VersionHeader>
)}

{/* Display Destinations and Sources Count */}
<p>Destinations: {description.numberOfDestinations}</p>
<p>Sources: {description.numberOfSources}</p>

{/* Display Cluster Collector */}
<CollectorSection
title="Cluster Collector"
collector={description.clusterCollector}
/>

{/* Display Node Collector */}
<CollectorSection
title="Node Collector"
collector={description.nodeCollector}
/>
</div>
);
}

// Component to handle collector data (cluster and node collectors)
const CollectorSection: React.FC<{ title: string; collector: any }> = ({
title,
collector,
}) => (
<section style={{ marginTop: 24 }}>
<CollectorTitle>{title}</CollectorTitle>
{Object.entries(collector).map(([key, value]: [string, any]) => (
<CollectorItem
key={key}
label={value.name}
value={value.value}
status={value.status}
explain={value.explain}
/>
))}
</section>
);

// Component to handle individual collector items with conditional styling based on status
const CollectorItem: React.FC<{
label: string;
value: any;
status?: string;
explain?: string;
}> = ({ label, value, status, explain }) => {
const [showExplanation, setShowExplanation] = useState(false);
const color = status === 'error' ? theme.colors.error : theme.text.light_grey;

return (
<div
style={{
paddingLeft: '16px',
marginBottom: '8px',
}}
>
<StatusText
color={color}
onClick={() => setShowExplanation(!showExplanation)}
>
- {label}: {String(value)}
</StatusText>
{showExplanation && <StatusBadge>{explain}</StatusBadge>}
</div>
);
};

const VersionHeader = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
`;

const VersionText = styled(KeyvalText)`
font-size: 24px;
`;

const CollectorTitle = styled(KeyvalText)`
font-size: 20px;
margin-bottom: 10px;
`;

const NotificationBadge = styled.div<{ status: string }>`
position: absolute;
top: -4px;
right: -4px;
background-color: ${({ status }) =>
status === 'error'
? theme.colors.error
: status === 'transitioning'
? theme.colors.orange_brown
: theme.colors.success};
color: white;
border-radius: 50%;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
`;

const IconWrapper = styled.div`
position: relative;
padding: 8px;
width: 16px;
border-radius: 8px;
border: 1px solid ${theme.colors.blue_grey};
display: flex;
align-items: center;
cursor: pointer;
&:hover {
background-color: ${theme.colors.dark};
}
`;

const LoadingMessage = styled.p`
font-size: 1rem;
color: #555;
`;

const DescriptionContent = styled(KeyvalText)`
white-space: pre-wrap;
line-height: 1.6;
padding: 20px;
max-width: 650px;
`;

const StatusText = styled.div<{ color: string }>`
color: ${({ color }) => color};
font-weight: bold;
cursor: pointer;
`;

const StatusBadge = styled.span`
font-size: 0.8rem;
font-weight: normal;
margin-left: 4px;
color: inherit;
`;
15 changes: 15 additions & 0 deletions frontend/webapp/containers/main/sources/edit.source/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
Conditions,
} from '@/design.system';
import { BackIcon } from '@keyval-dev/design-system';
import { SourceDescriptionDrawer } from '../source-describe';
import styled from 'styled-components';

const NAME = 'name';
const KIND = 'kind';
Expand Down Expand Up @@ -106,6 +108,13 @@ export function EditSourceForm() {
<BackIcon size={14} />
<KeyvalText size={14}>{SETUP.BACK}</KeyvalText>
</BackButtonWrapper>
<DrawerContainer>
<SourceDescriptionDrawer
namespace={currentSource.namespace}
kind={currentSource.kind}
name={currentSource.name}
/>
</DrawerContainer>
{currentSource && <ManageSourceHeader source={currentSource} />}
<div style={{ display: 'flex', gap: 60 }}>
<div>
Expand Down Expand Up @@ -146,3 +155,9 @@ export function EditSourceForm() {
</ManageSourcePageContainer>
);
}

const DrawerContainer = styled.div`
position: absolute;
right: 32px;
top: 16px;
`;
Loading

0 comments on commit 4b63a3d

Please sign in to comment.