Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GEN-1710]: add scroll to overview sources #1978

Merged
merged 9 commits into from
Dec 12, 2024
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-${action.id}`,
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-${destination.id}`,
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-${rule.ruleId}`,
type: 'base',
position: {
x: position['x'],
y: position['y'](idx),
},
data: mapToNodeData(rule),
});
});
}

return nodes;
};
Loading
Loading