Skip to content

Commit

Permalink
[GEN-1710]: add scroll to overview sources (#1978)
Browse files Browse the repository at this point in the history
This pull request introduces several updates to the frontend web
application, primarily focused on refactoring components and adding new
functionalities to the overview data flow. The key changes include
converting components to named exports, building nodes for various
entity types, and constructing edges for data flow visualization.

### Component Refactoring:

*
[`frontend/webapp/containers/main/overview/multi-source-control/index.tsx`](diffhunk://#diff-cdd99a8fcd0484b24586f22b1f7b1bcf8d964bead53eeb0b2c07646c7301af70L27-R27):
Converted `MultiSourceControl` to a named export.
[[1]](diffhunk://#diff-cdd99a8fcd0484b24586f22b1f7b1bcf8d964bead53eeb0b2c07646c7301af70L27-R27)
[[2]](diffhunk://#diff-cdd99a8fcd0484b24586f22b1f7b1bcf8d964bead53eeb0b2c07646c7301af70L91-L92)
*
[`frontend/webapp/containers/main/overview/overview-actions-menu/index.tsx`](diffhunk://#diff-d5bd2313f84205e47d83344e7ac50ad55a85f3bad2fba1ca4b0288484584f0dfL21-R21):
Converted `OverviewActionsMenu` to a named export.
[[1]](diffhunk://#diff-d5bd2313f84205e47d83344e7ac50ad55a85f3bad2fba1ca4b0288484584f0dfL21-R21)
[[2]](diffhunk://#diff-d5bd2313f84205e47d83344e7ac50ad55a85f3bad2fba1ca4b0288484584f0dfL35-R35)

### Node Building for Overview Data Flow:

*
[`frontend/webapp/containers/main/overview/overview-data-flow/build-action-nodes.ts`](diffhunk://#diff-2a2957e6035bd001e3c0c52f29f01f4473b6b99a8ce4381a08e9de504916c3b1R1-R97):
Added functionality to build nodes for actions in the overview data
flow.
*
[`frontend/webapp/containers/main/overview/overview-data-flow/build-destination-nodes.ts`](diffhunk://#diff-aa30a6b7893a9d1393f196b9846b1fdc19956c7c07f4099570cb25ff2f77fa43R1-R81):
Added functionality to build nodes for destinations in the overview data
flow.
*
[`frontend/webapp/containers/main/overview/overview-data-flow/build-rule-nodes.ts`](diffhunk://#diff-a37edc5ed2e31d0b06f407f3d1d5b155d499c689f95609db388f0e8246a1800bR1-R81):
Added functionality to build nodes for instrumentation rules in the
overview data flow.
*
[`frontend/webapp/containers/main/overview/overview-data-flow/build-source-nodes.ts`](diffhunk://#diff-278da68d858c88d202c10e2011bc1263fe6c0b543dc9d0fc4995a0f0675d3db1R1-R115):
Added functionality to build nodes for sources in the overview data
flow.

### Edge Building for Data Flow:

*
[`frontend/webapp/containers/main/overview/overview-data-flow/build-edges.ts`](diffhunk://#diff-39cdfd27eadcda95cac1b52b794dc8feae0d0d46bb5f29ac1a39f96473eab483R1-R80):
Added functionality to build edges for connecting nodes in the overview
data flow.

### Utility Functions:

*
[`frontend/webapp/containers/main/overview/overview-data-flow/get-entity-counts.ts`](diffhunk://#diff-9180e7735f3b0a44ca96a2b3211d3f6adf63d412713f4f6e6b8cfe4566eb0e86R1-R18):
Added a utility function to get entity counts for different types in the
overview data flow.
*
[`frontend/webapp/containers/main/overview/overview-data-flow/get-node-positions.ts`](diffhunk://#diff-76a303d4463294b366faf6aa5f23672be0bbbc7907480ec7a19868ad114a6f59R1-R48):
Added a utility function to calculate node positions based on container
width.
  • Loading branch information
BenElferink authored Dec 12, 2024
1 parent ea35774 commit ced6950
Show file tree
Hide file tree
Showing 27 changed files with 947 additions and 552 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const DestinationListItem: React.FC<DestinationListItemProps> = ({ item,
};

return (
<ListItem data-id={`destination-${item.type}`} onClick={() => onSelect(item)}>
<ListItem data-id={`destination-${item.displayName}`} onClick={() => onSelect(item)}>
<ListItemContent>
<DestinationIconWrapper>
<Image src={item.imageUrl} width={20} height={20} alt='destination' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const Container = styled.div`
background-color: ${({ theme }) => theme.colors.dropdown_bg};
`;

const MultiSourceControl = () => {
export const MultiSourceControl = () => {
const Transition = useTransition({
container: Container,
animateIn: slide.in['center'],
Expand Down Expand Up @@ -88,5 +88,3 @@ const MultiSourceControl = () => {
</>
);
};

export default MultiSourceControl;
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const PushToEnd = styled.div`
margin-left: auto;
`;

export function OverviewActionMenuContainer() {
export const OverviewActionsMenu = () => {
return (
<MenuContainer>
<TabList />
Expand All @@ -32,4 +32,4 @@ export function OverviewActionMenuContainer() {
</PushToEnd>
</MenuContainer>
);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { type Node } from '@xyflow/react';
import nodeConfig from './node-config.json';
import { type EntityCounts } from './get-entity-counts';
import { type NodePositions } from './get-node-positions';
import { getActionIcon, getEntityIcon, getEntityLabel } from '@/utils';
import { OVERVIEW_ENTITY_TYPES, OVERVIEW_NODE_TYPES, STATUSES, type ComputePlatformMapped } from '@/types';

interface Params {
entities: ComputePlatformMapped['computePlatform']['actions'];
positions: NodePositions;
unfilteredCounts: EntityCounts;
}

const { nodeWidth, nodeHeight, framePadding } = nodeConfig;

const mapToNodeData = (entity: Params['entities'][0]) => {
return {
nodeWidth,
id: entity.id,
type: OVERVIEW_ENTITY_TYPES.ACTION,
status: STATUSES.HEALTHY,
title: getEntityLabel(entity, OVERVIEW_ENTITY_TYPES.ACTION, { prioritizeDisplayName: true }),
subTitle: entity.type,
imageUri: getActionIcon(entity.type),
monitors: entity.spec.signals,
isActive: !entity.spec.disabled,
raw: entity,
};
};

export const buildActionNodes = ({ entities, positions, unfilteredCounts }: Params) => {
const nodes: Node[] = [];
const position = positions[OVERVIEW_ENTITY_TYPES.ACTION];
const unfilteredCount = unfilteredCounts[OVERVIEW_ENTITY_TYPES.ACTION];

nodes.push({
id: 'action-header',
type: 'header',
position: {
x: positions[OVERVIEW_ENTITY_TYPES.ACTION]['x'],
y: 0,
},
data: {
nodeWidth,
title: 'Actions',
icon: getEntityIcon(OVERVIEW_ENTITY_TYPES.ACTION),
tagValue: unfilteredCounts[OVERVIEW_ENTITY_TYPES.ACTION],
},
});

if (!entities.length) {
nodes.push({
id: 'action-add',
type: 'add',
position: {
x: position['x'],
y: position['y'](),
},
data: {
nodeWidth,
type: OVERVIEW_NODE_TYPES.ADD_ACTION,
status: STATUSES.HEALTHY,
title: 'ADD ACTION',
subTitle: `Add ${!!unfilteredCount ? 'a new' : 'first'} action to modify the OpenTelemetry data`,
},
});
} else {
nodes.push({
id: 'action-frame',
type: 'frame',
position: {
x: position['x'] - framePadding,
y: position['y']() - framePadding,
},
data: {
nodeWidth: nodeWidth + 2 * framePadding,
nodeHeight: nodeHeight * entities.length + framePadding,
},
});

entities.forEach((action, idx) => {
nodes.push({
id: `action-${idx}`,
type: 'base',
extent: 'parent',
parentId: 'action-frame',
position: {
x: framePadding,
y: position['y'](idx) - (nodeHeight - framePadding),
},
data: mapToNodeData(action),
});
});
}

return nodes;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { type Node } from '@xyflow/react';
import nodeConfig from './node-config.json';
import { type EntityCounts } from './get-entity-counts';
import { type NodePositions } from './get-node-positions';
import { extractMonitors, getEntityIcon, getEntityLabel, getHealthStatus } from '@/utils';
import { OVERVIEW_ENTITY_TYPES, OVERVIEW_NODE_TYPES, STATUSES, type ComputePlatformMapped } from '@/types';

interface Params {
entities: ComputePlatformMapped['computePlatform']['destinations'];
positions: NodePositions;
unfilteredCounts: EntityCounts;
}

const { nodeWidth } = nodeConfig;

const mapToNodeData = (entity: Params['entities'][0]) => {
return {
nodeWidth,
id: entity.id,
type: OVERVIEW_ENTITY_TYPES.DESTINATION,
status: getHealthStatus(entity),
title: getEntityLabel(entity, OVERVIEW_ENTITY_TYPES.DESTINATION, { prioritizeDisplayName: true }),
subTitle: entity.destinationType.displayName,
imageUri: entity.destinationType.imageUrl || '/brand/odigos-icon.svg',
monitors: extractMonitors(entity.exportedSignals),
raw: entity,
};
};

export const buildDestinationNodes = ({ entities, positions, unfilteredCounts }: Params) => {
const nodes: Node[] = [];
const position = positions[OVERVIEW_ENTITY_TYPES.DESTINATION];
const unfilteredCount = unfilteredCounts[OVERVIEW_ENTITY_TYPES.DESTINATION];

nodes.push({
id: 'destination-header',
type: 'header',
position: {
x: positions[OVERVIEW_ENTITY_TYPES.DESTINATION]['x'],
y: 0,
},
data: {
nodeWidth,
title: 'Destinations',
icon: getEntityIcon(OVERVIEW_ENTITY_TYPES.DESTINATION),
tagValue: unfilteredCounts[OVERVIEW_ENTITY_TYPES.DESTINATION],
},
});

if (!entities.length) {
nodes.push({
id: 'destination-add',
type: 'add',
position: {
x: position['x'],
y: position['y'](),
},
data: {
nodeWidth,
type: OVERVIEW_NODE_TYPES.ADD_DESTIONATION,
status: STATUSES.HEALTHY,
title: 'ADD DESTIONATION',
subTitle: `Add ${!!unfilteredCount ? 'a new' : 'first'} destination to monitor the OpenTelemetry data`,
},
});
} else {
entities.forEach((destination, idx) => {
nodes.push({
id: `destination-${idx}`,
type: 'base',
position: {
x: position['x'],
y: position['y'](idx),
},
data: mapToNodeData(destination),
});
});
}

return nodes;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import theme from '@/styles/theme';
import { formatBytes } from '@/utils';
import { type Edge, type Node } from '@xyflow/react';
import { OVERVIEW_ENTITY_TYPES, STATUSES, WorkloadId, type OverviewMetricsResponse } from '@/types';
import nodeConfig from './node-config.json';

interface Params {
nodes: Node[];
metrics?: OverviewMetricsResponse;
containerHeight: number;
}

const { nodeHeight, framePadding } = nodeConfig;

const createEdge = (edgeId: string, params?: { label?: string; isMultiTarget?: boolean; isError?: boolean; animated?: boolean }): Edge => {
const { label, isMultiTarget, isError, animated } = params || {};
const [sourceNodeId, targetNodeId] = edgeId.split('-to-');

return {
id: edgeId,
type: !!label ? 'labeled' : 'default',
source: sourceNodeId,
target: targetNodeId,
animated,
data: { label, isMultiTarget, isError },
style: { stroke: isError ? theme.colors.dark_red : theme.colors.border },
};
};

export const buildEdges = ({ nodes, metrics, containerHeight }: Params) => {
const edges: Edge[] = [];
const actionNodeId = nodes.find(({ id: nodeId }) => ['action-frame', 'action-add'].includes(nodeId))?.id;

nodes.forEach(({ type: nodeType, id: nodeId, data: { type: entityType, id: entityId, status }, position }) => {
if (nodeType === 'base') {
switch (entityType) {
case OVERVIEW_ENTITY_TYPES.SOURCE: {
const { namespace, name, kind } = entityId as WorkloadId;
const metric = metrics?.getOverviewMetrics.sources.find((m) => m.kind === kind && m.name === name && m.namespace === namespace);

const topLimit = -nodeHeight / 2 + framePadding;
const bottomLimit = containerHeight - nodeHeight + framePadding * 2 + topLimit;

if (position.y >= topLimit && position.y <= bottomLimit) {
edges.push(
createEdge(`${nodeId}-to-${actionNodeId}`, {
animated: false,
isMultiTarget: false,
label: formatBytes(metric?.throughput),
isError: status === STATUSES.UNHEALTHY,
}),
);
}

break;
}

case OVERVIEW_ENTITY_TYPES.DESTINATION: {
const metric = metrics?.getOverviewMetrics.destinations.find((m) => m.id === entityId);

edges.push(
createEdge(`${actionNodeId}-to-${nodeId}`, {
animated: false,
isMultiTarget: true,
label: formatBytes(metric?.throughput),
isError: status === STATUSES.UNHEALTHY,
}),
);

break;
}

default:
break;
}
}
});

return edges;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { type Node } from '@xyflow/react';
import nodeConfig from './node-config.json';
import { type EntityCounts } from './get-entity-counts';
import { type NodePositions } from './get-node-positions';
import { getEntityIcon, getEntityLabel, getRuleIcon } from '@/utils';
import { OVERVIEW_ENTITY_TYPES, OVERVIEW_NODE_TYPES, STATUSES, type ComputePlatformMapped } from '@/types';

interface Params {
entities: ComputePlatformMapped['computePlatform']['instrumentationRules'];
positions: NodePositions;
unfilteredCounts: EntityCounts;
}

const { nodeWidth } = nodeConfig;

const mapToNodeData = (entity: Params['entities'][0]) => {
return {
nodeWidth,
id: entity.ruleId,
type: OVERVIEW_ENTITY_TYPES.RULE,
status: STATUSES.HEALTHY,
title: getEntityLabel(entity, OVERVIEW_ENTITY_TYPES.RULE, { prioritizeDisplayName: true }),
subTitle: entity.type,
imageUri: getRuleIcon(entity.type),
isActive: !entity.disabled,
raw: entity,
};
};

export const buildRuleNodes = ({ entities, positions, unfilteredCounts }: Params) => {
const nodes: Node[] = [];
const position = positions[OVERVIEW_ENTITY_TYPES.RULE];
const unfilteredCount = unfilteredCounts[OVERVIEW_ENTITY_TYPES.RULE];

nodes.push({
id: 'rule-header',
type: 'header',
position: {
x: positions[OVERVIEW_ENTITY_TYPES.RULE]['x'],
y: 0,
},
data: {
nodeWidth,
title: 'Instrumentation Rules',
icon: getEntityIcon(OVERVIEW_ENTITY_TYPES.RULE),
tagValue: unfilteredCounts[OVERVIEW_ENTITY_TYPES.RULE],
},
});

if (!entities.length) {
nodes.push({
id: 'rule-add',
type: 'add',
position: {
x: position['x'],
y: position['y'](),
},
data: {
nodeWidth,
type: OVERVIEW_NODE_TYPES.ADD_RULE,
status: STATUSES.HEALTHY,
title: 'ADD RULE',
subTitle: `Add ${!!unfilteredCount ? 'a new' : 'first'} rule to modify the OpenTelemetry data`,
},
});
} else {
entities.forEach((rule, idx) => {
nodes.push({
id: `rule-${idx}`,
type: 'base',
position: {
x: position['x'],
y: position['y'](idx),
},
data: mapToNodeData(rule),
});
});
}

return nodes;
};
Loading

0 comments on commit ced6950

Please sign in to comment.