@odigos/ui-kit 0.0.49 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/lib/components/data-card/data-card-fields/index.d.ts +1 -5
  3. package/lib/components/data-card/data-card.stories.d.ts +4 -4
  4. package/lib/components/data-finger/data-finger.stories.d.ts +9 -0
  5. package/lib/components/data-finger/index.d.ts +12 -0
  6. package/lib/components/icon-button/index.d.ts +2 -0
  7. package/lib/components/index.d.ts +5 -0
  8. package/lib/components/popup/index.d.ts +17 -0
  9. package/lib/components/popup/popup.stories.d.ts +9 -0
  10. package/lib/components/popup-form/index.d.ts +18 -0
  11. package/lib/components/popup-form/popup-form.stories.d.ts +10 -0
  12. package/lib/components/tag/index.d.ts +8 -0
  13. package/lib/components/tag/tag.stories.d.ts +13 -0
  14. package/lib/components.js +8 -8
  15. package/lib/constants.js +1 -1
  16. package/lib/containers/service-map/helpers/generate-dag-positions.d.ts +5 -0
  17. package/lib/containers/service-map/helpers/generate-spiral-grid-position.d.ts +2 -0
  18. package/lib/containers/service-map/index.d.ts +1 -1
  19. package/lib/containers/system-overview/describe/index.d.ts +1 -1
  20. package/lib/containers.js +277 -368
  21. package/lib/functions/has-unhealthy-instances/index.d.ts +2 -0
  22. package/lib/functions/index.d.ts +1 -0
  23. package/lib/functions.js +5 -5
  24. package/lib/hooks/index.d.ts +1 -0
  25. package/lib/hooks/usePopup.d.ts +17 -0
  26. package/lib/hooks.js +3 -3
  27. package/lib/icons/common/avatar-icon/avatar-icon.stories.d.ts +8 -0
  28. package/lib/icons/common/avatar-icon/index.d.ts +2 -0
  29. package/lib/icons/common/index.d.ts +2 -0
  30. package/lib/icons/common/user-group-icon/index.d.ts +2 -0
  31. package/lib/icons/common/user-group-icon/user-group-icon.stories.d.ts +8 -0
  32. package/lib/icons/math/index.d.ts +2 -0
  33. package/lib/icons/math/minus-circled-icon/index.d.ts +2 -0
  34. package/lib/icons/math/minus-circled-icon/minus-circled-icon.stories.d.ts +8 -0
  35. package/lib/icons/math/plus-circled-icon/index.d.ts +2 -0
  36. package/lib/icons/math/plus-circled-icon/plus-circled-icon.stories.d.ts +8 -0
  37. package/lib/icons.js +35 -7
  38. package/lib/{index-C1V7D2ey.js → index-1-F2f04H.js} +2270 -2205
  39. package/lib/{index-CeBxw8J4.js → index-4BmryHdH.js} +4 -4
  40. package/lib/{index-Bw7RE2T2.js → index-BC03UmY5.js} +8 -1
  41. package/lib/{index-ZTzxu5fz.js → index-CMsBAVAn.js} +2 -2
  42. package/lib/{index-w9lkC6fb.js → index-CPMIZB66.js} +12 -4
  43. package/lib/{index-DOU0EdZP.js → index-Coq_Nro0.js} +2 -2
  44. package/lib/{index-CkTdd3MS.js → index-CyHOJpMl.js} +1 -1
  45. package/lib/{index-DsEoqSQn.js → index-Dbs7YARA.js} +2 -2
  46. package/lib/{index-DxR7e2Cq.js → index-H8TwBQHm.js} +1 -1
  47. package/lib/{index-CeDmxXUE.js → index-LgzkJ05H.js} +2 -2
  48. package/lib/snippets/copy-text/index.d.ts +6 -0
  49. package/lib/snippets/index.d.ts +5 -2
  50. package/lib/snippets/pod-container/index.d.ts +5 -0
  51. package/lib/snippets/source-container/index.d.ts +7 -0
  52. package/lib/snippets.js +7 -7
  53. package/lib/store/index.d.ts +1 -0
  54. package/lib/store/useActiveNodeStore.d.ts +11 -0
  55. package/lib/store.js +1 -1
  56. package/lib/theme.js +1 -1
  57. package/lib/types/describe/index.d.ts +2 -2
  58. package/lib/types/sources/index.d.ts +10 -9
  59. package/lib/{useSourceSelectionFormData-C5VMfWEf.js → useSourceSelectionFormData-CwggurLH.js} +2 -2
  60. package/lib/{useTransition-mupXsbop.js → useTransition-DmHfJSEP.js} +38 -6
  61. package/package.json +1 -1
  62. package/lib/containers/data-flow-actions-menu/search/recent-searches/index.d.ts +0 -2
  63. /package/lib/{components/data-card/data-card-fields → snippets/source-container}/override-runtime.d.ts +0 -0
package/lib/containers.js CHANGED
@@ -1,21 +1,21 @@
1
- import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle, useMemo, Fragment, useCallback } from 'react';
1
+ import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle, useMemo, Fragment, useCallback, Children } from 'react';
2
2
  import styled, { css } from 'styled-components';
3
- import { l as DISPLAY_TITLES, T as Theme, h as usePendingStore, g as useNotificationStore, b as useDrawerStore, B as BUTTON_TEXTS, c as useEntityStore, A as ACTION_OPTIONS, m as getActionIcon, f as useModalStore, F as FORM_ALERTS, d as useFilterStore, M as MONITORS_OPTIONS, o as getInstrumentationRuleIcon, a as useDataStreamStore, e as useInstrumentStore, n as getEntityId, S as STORAGE_KEYS, k as DEFAULT_DATA_STREAM_NAME, j as useSetupStore, I as INSTRUMENTATION_RULE_OPTIONS, i as useSelectedStore, u as useDarkMode } from './index-Bw7RE2T2.js';
3
+ import { m as DISPLAY_TITLES, T as Theme, i as usePendingStore, h as useNotificationStore, c as useDrawerStore, B as BUTTON_TEXTS, d as useEntityStore, A as ACTION_OPTIONS, n as getActionIcon, g as useModalStore, F as FORM_ALERTS, e as useFilterStore, M as MONITORS_OPTIONS, p as getInstrumentationRuleIcon, b as useDataStreamStore, f as useInstrumentStore, o as getEntityId, S as STORAGE_KEYS, l as DEFAULT_DATA_STREAM_NAME, k as useSetupStore, I as INSTRUMENTATION_RULE_OPTIONS, j as useSelectedStore, v as ImageErrorIcon, a as useDarkMode } from './index-BC03UmY5.js';
4
4
  import { ActionType, ActionKeyTypes, InputTypes, FieldTypes, EntityTypes, StatusType, Crud, OtherStatus, NodeTypes, EdgeTypes, AddNodeTypes, SignalType, HeadersCollectionKeyTypes, CustomInstrumentationsKeyTypes, CodeAttributesKeyTypes, PayloadCollectionKeyTypes, InstrumentationRuleType, K8sResourceKind } from './types.js';
5
- import { j as DataCardFieldTypes, v as FieldLabel, C as Checkbox, u as FieldError, K as Input, P as InputTable, R as KeyValueInputsList, O as InputList, a5 as Text, a0 as Segment, $ as SectionTitle, m as DocsButton, V as MonitorsCheckboxes, a6 as TextArea, o as Drawer, h as ConditionDetails, D as DataCard, ac as FlexColumn, U as Modal, X as NavigationButtons, ag as ModalBody, Z as NotificationNote, e as AutocompleteInput, l as Divider, a3 as Status, ab as FlexRow, a8 as Tooltip, z as IconWrapped, W as MonitorsIcons, ah as TableContainer, ai as TableTitleWrap, y as IconTitleBadge, aj as TableWrap, Q as InteractiveTable, ad as CenterThis, Y as NoDataFound, a9 as TraceLoader, f as Badge, s as ExtendArrow, ae as VerticalScroll, a1 as SelectionButton, d as Button, r as Dropdown, n as nodeConfig, ak as useNodesState, al as useEdgesState, F as Flow, am as applyNodeChanges, a7 as Toggle, I as IconButton, A as AddButton, t as FadeLoader, k as DataTab, a4 as Stepper, i as DataCardFields, an as MarkerType, G as IconsNav, x as IconGroup } from './index-C1V7D2ey.js';
5
+ import { l as DataCardFieldTypes, z as FieldLabel, h as Checkbox, y as FieldError, U as Input, W as InputTable, Y as KeyValueInputsList, V as InputList, ad as Text, a7 as Segment, a6 as SectionTitle, r as DocsButton, _ as MonitorsCheckboxes, ae as TextArea, s as Drawer, j as ConditionDetails, D as DataCard, ak as FlexColumn, Z as Modal, a0 as NavigationButtons, ao as ModalBody, a2 as NotificationNote, f as AutocompleteInput, q as Divider, aa as Status, aj as FlexRow, ag as Tooltip, O as IconWrapped, $ as MonitorsIcons, ap as TableContainer, aq as TableTitleWrap, K as IconTitleBadge, ar as TableWrap, X as InteractiveTable, al as CenterThis, a1 as NoDataFound, ah as TraceLoader, g as Badge, w as ExtendArrow, am as VerticalScroll, a8 as SelectionButton, e as Button, v as Dropdown, n as nodeConfig, as as useNodesState, at as useEdgesState, F as Flow, au as applyNodeChanges, a3 as Popup, af as Toggle, I as IconButton, A as AddButton, x as FadeLoader, o as DataTab, ab as Stepper, k as DataCardFields, av as MarkerType, Q as IconsNav, C as CopyText, p as DescribeRow, P as PodContainer, d as SourceContainer, J as IconGroup, a4 as PopupForm } from './index-1-F2f04H.js';
6
6
  import { i as isEmpty, s as safeJsonParse } from './index-BnvrwbRB.js';
7
- import { C as CheckCircledIcon, O as OdigosLogo } from './index-DxR7e2Cq.js';
8
- import { C as CrossCircledIcon, O as OdigosLogoText, a as OverviewIcon, F as FilterIcon, D as DataStreamsIcon, R as RetryIcon, N as NotificationIcon, U as UserIcon, S as SlackLogo, K as KeyIcon, T as TerminalIcon } from './index-CeDmxXUE.js';
9
- import { u as useActionFormData, d as useSessionStorage, b as useDataStreamFormData, c as useDestinationFormData, a as useClickNotification, e as useSourceFormData, f as useSourceSelectionFormData } from './useSourceSelectionFormData-C5VMfWEf.js';
10
- import { e as useKeyDown, f as useOnClickOutside, a as useContainerSize, u as useClickNode, d as useInstrumentationRuleFormData, h as useTransition, g as useTimeAgo, b as useCopy } from './useTransition-mupXsbop.js';
11
- import { E as EditIcon, T as TrashIcon, S as SearchIcon, h as CheckIcon, A as ArrowIcon, P as PlusIcon, a as CopyIcon, i as CrossIcon } from './index-CkTdd3MS.js';
12
- import { D as DeleteWarning, C as CancelWarning } from './index-DOU0EdZP.js';
13
- import { g as getConditionsBooleans, m as mapConditions, b as getStatusIcon, c as capitalizeFirstLetter } from './index-DsEoqSQn.js';
14
- import { f as filterActions, m as getEntityLabel, l as getEntityIcon, v as sleep, o as getPlatformIcon, p as getPlatformLabel, h as formatBytes, j as getContainersIcons, q as getValueForRange, k as getDestinationIcon, g as filterSourcesByStream, e as filterSources, b as filterDestinationsByStream, a as filterDestinations, u as mapDestinationFieldsForDisplay, c as compareCondition, s as getYamlFieldsForDestination, d as deepClone, n as getMetricForEntity, r as getWorkloadId, i as getContainersInstrumentedCount, t as isOverTime } from './index-w9lkC6fb.js';
7
+ import { C as CheckCircledIcon, O as OdigosLogo } from './index-H8TwBQHm.js';
8
+ import { C as CrossCircledIcon, O as OdigosLogoText, a as OverviewIcon, F as FilterIcon, D as DataStreamsIcon, R as RetryIcon, N as NotificationIcon, U as UserIcon, S as SlackLogo, K as KeyIcon, T as TerminalIcon } from './index-LgzkJ05H.js';
9
+ import { u as useActionFormData, d as useSessionStorage, b as useDataStreamFormData, c as useDestinationFormData, a as useClickNotification, e as useSourceFormData, f as useSourceSelectionFormData } from './useSourceSelectionFormData-CwggurLH.js';
10
+ import { e as useKeyDown, f as useOnClickOutside, a as useContainerSize, u as useClickNode, g as usePopup, d as useInstrumentationRuleFormData, i as useTransition, h as useTimeAgo, b as useCopy, c as useGenericForm } from './useTransition-DmHfJSEP.js';
11
+ import { E as EditIcon, T as TrashIcon, S as SearchIcon, h as CheckIcon, A as ArrowIcon, P as PlusIcon, a as CopyIcon } from './index-CyHOJpMl.js';
12
+ import { D as DeleteWarning, C as CancelWarning } from './index-Coq_Nro0.js';
13
+ import { g as getConditionsBooleans, m as mapConditions, b as getStatusIcon, c as capitalizeFirstLetter, p as parseBooleanFromString } from './index-Dbs7YARA.js';
14
+ import { f as filterActions, m as getEntityLabel, l as getEntityIcon, w as sleep, o as getPlatformIcon, p as getPlatformLabel, h as formatBytes, j as getContainersIcons, q as getValueForRange, k as getDestinationIcon, g as filterSourcesByStream, e as filterSources, b as filterDestinationsByStream, a as filterDestinations, v as mapDestinationFieldsForDisplay, c as compareCondition, s as getYamlFieldsForDestination, d as deepClone, n as getMetricForEntity, r as getWorkloadId, t as hasUnhealthyInstances, i as getContainersInstrumentedCount, u as isOverTime } from './index-CPMIZB66.js';
15
15
  import { m as mapExportedSignals } from './index-BlZKWuxe.js';
16
- import { N as NoteBackToSummary, E as EditButton } from './index-CeBxw8J4.js';
16
+ import { N as NoteBackToSummary, E as EditButton } from './index-4BmryHdH.js';
17
17
  import { D as DESTINATION_CATEGORIES } from './index-Dqief9td.js';
18
- import { a6 as RulesIcon, a7 as SourcesIcon, a3 as ActionsIcon, a4 as DestinationsIcon } from './index-ZTzxu5fz.js';
18
+ import { a6 as RulesIcon, a7 as SourcesIcon, a3 as ActionsIcon, a4 as DestinationsIcon } from './index-CMsBAVAn.js';
19
19
  import 'react-dom';
20
20
 
21
21
  const buildCard$3 = (action) => {
@@ -1032,7 +1032,7 @@ const PushToEnd$1 = styled.div `
1032
1032
  const RelativeContainer$3 = styled.div `
1033
1033
  position: relative;
1034
1034
  `;
1035
- const AbsoluteContainer$3 = styled.div `
1035
+ const AbsoluteContainer = styled.div `
1036
1036
  position: absolute;
1037
1037
  top: calc(100% + 8px);
1038
1038
  left: 0;
@@ -1070,7 +1070,7 @@ const ComputePlatformSelect = ({ connections, selected, onSelect, onViewAll }) =
1070
1070
  React.createElement(Title$2, null, selected?.name || selected?.id || 'no platform'),
1071
1071
  withSelect && (React.createElement(PushToEnd$1, null,
1072
1072
  React.createElement(ExtendArrow, { extend: isOpen, align: 'right' })))),
1073
- isOpen && withSelect && (React.createElement(AbsoluteContainer$3, null,
1073
+ isOpen && withSelect && (React.createElement(AbsoluteContainer, null,
1074
1074
  React.createElement(HeadWrap, null,
1075
1075
  React.createElement(Input, { placeholder: 'Search...', icon: SearchIcon, value: searchText, onChange: (e) => setSearchText(e.target.value.toLowerCase()) })),
1076
1076
  React.createElement(VerticalScroll, { style: { maxHeight: '240px' } }, filtered.map(({ id, type, name, status }, idx) => (React.createElement(SelectionButton, { key: `platform-${id}`, icon: () => getPlatformIcon(type)({ fill: status === StatusType.Success ? theme.text.success : theme.text.error }), label: `${!!name ? name : getPlatformLabel(type)} (${id})`, isSelected: selected?.id === id, onClick: () => {
@@ -1873,7 +1873,7 @@ const RelativeContainer$2 = styled.div `
1873
1873
  position: relative;
1874
1874
  max-width: 200px;
1875
1875
  `;
1876
- const AbsoluteContainer$2 = styled.div `
1876
+ styled.div `
1877
1877
  position: absolute;
1878
1878
  top: calc(100% + 8px);
1879
1879
  left: 0;
@@ -1947,8 +1947,6 @@ const buildSearchResults = ({ instrumentationRules, sources, actions, destinatio
1947
1947
  const HorizontalScroll = styled.div `
1948
1948
  display: flex;
1949
1949
  align-items: center;
1950
- padding: 12px;
1951
- border-bottom: ${({ theme }) => `1px solid ${theme.colors.border}`};
1952
1950
  overflow-x: scroll;
1953
1951
  `;
1954
1952
  const SearchResults = ({ searchText, onClose }) => {
@@ -1956,6 +1954,13 @@ const SearchResults = ({ searchText, onClose }) => {
1956
1954
  const { onClickNode } = useClickNode();
1957
1955
  const { selectedStreamName } = useDataStreamStore();
1958
1956
  const { sources, destinations, actions, instrumentationRules } = useEntityStore();
1957
+ const { popupRef, popupOpen, setPopupOpen, popupPosition, handlePosition } = usePopup();
1958
+ useEffect(() => {
1959
+ if (!popupOpen) {
1960
+ handlePosition(0, 50);
1961
+ setPopupOpen(true);
1962
+ }
1963
+ }, [popupOpen]);
1959
1964
  const [selectedCategory, setSelectedCategory] = useState('all');
1960
1965
  const { categories, searchResults } = useMemo(() => buildSearchResults({
1961
1966
  instrumentationRules,
@@ -1966,21 +1971,19 @@ const SearchResults = ({ searchText, onClose }) => {
1966
1971
  selectedCategory,
1967
1972
  }), [instrumentationRules, sources, actions, destinations, selectedStreamName, searchText, selectedCategory]);
1968
1973
  if (!searchResults.length) {
1969
- return (React.createElement(AbsoluteContainer$2, { "$padding": '12px' },
1974
+ return (React.createElement(Popup, { ref: popupRef, isOpen: popupOpen, top: popupPosition.top, left: popupPosition.left },
1970
1975
  React.createElement(NoDataFound, null)));
1971
1976
  }
1972
- return (React.createElement(AbsoluteContainer$2, null,
1973
- React.createElement(HorizontalScroll, { style: { borderBottom: `1px solid ${!searchResults.length ? 'transparent' : theme.colors.border}` } }, categories.map(({ category, label, count }) => !!count && (React.createElement(SelectionButton, { key: `category-select-${category}`, label: label, badgeLabel: count, isSelected: selectedCategory === category, onClick: () => setSelectedCategory(category) })))),
1974
- searchResults.map(({ category, label, entities }, catIdx) => !!entities.length && (React.createElement(Fragment, { key: `category-list-${category}` },
1975
- React.createElement(VerticalScroll, { style: { maxHeight: selectedCategory !== 'all' ? '240px' : '140px' } },
1976
- React.createElement(Text, { size: 12, family: 'secondary', color: theme.text.darker_grey, style: { marginLeft: '16px' } }, label),
1977
- entities.map((item, entIdx) => (React.createElement(SelectionButton, { key: `entity-${catIdx}-${entIdx}`, icon: getEntityIcon(category), label: getEntityLabel(item, category, { extended: true }), onClick: () => {
1978
- const id = getEntityId(item);
1979
- // @ts-expect-error - expects an event, but we don't need it, so null is fine, also expects full node, we only need the given values
1980
- onClickNode(null, { data: { type: category, id } });
1981
- onClose();
1982
- }, style: { width: '100%', justifyContent: 'flex-start' }, color: 'transparent' })))),
1983
- React.createElement(Divider, { thickness: catIdx === searchResults.length - 1 ? 0 : 1, length: '90%', margin: '8px auto' }))))));
1977
+ return (React.createElement(Popup, { ref: popupRef, isOpen: popupOpen, top: popupPosition.top, left: popupPosition.left, maxWidth: '420px', header: React.createElement(HorizontalScroll, null, categories.map(({ category, label, count }) => !!count && (React.createElement(SelectionButton, { key: `category-select-${category}`, label: label, badgeLabel: count, isSelected: selectedCategory === category, onClick: () => setSelectedCategory(category) })))) }, searchResults.map(({ category, label, entities }, catIdx) => !!entities.length && (React.createElement(Fragment, { key: `category-list-${category}` },
1978
+ React.createElement(VerticalScroll, { style: { maxHeight: selectedCategory !== 'all' ? '240px' : '140px', padding: '12px 0' } },
1979
+ React.createElement(Text, { size: 12, family: 'secondary', color: theme.text.darker_grey, style: { marginLeft: '16px' } }, label),
1980
+ entities.map((item, entIdx) => (React.createElement(SelectionButton, { key: `entity-${catIdx}-${entIdx}`, icon: getEntityIcon(category), label: getEntityLabel(item, category, { extended: true }), onClick: () => {
1981
+ const id = getEntityId(item);
1982
+ // @ts-expect-error - expects an event, but we don't need it, so null is fine, also expects full node, we only need the given values
1983
+ onClickNode(null, { data: { type: category, id } });
1984
+ onClose();
1985
+ }, style: { width: '100%', justifyContent: 'flex-start' }, color: 'transparent' })))),
1986
+ React.createElement(Divider, { thickness: catIdx === searchResults.length - 1 ? 0 : 1, length: '90%', margin: '8px auto' }))))));
1984
1987
  };
1985
1988
 
1986
1989
  const Search = () => {
@@ -2007,12 +2010,6 @@ const FormWrapper = styled.div `
2007
2010
  const ToggleWrapper = styled.div `
2008
2011
  padding: 12px 6px 6px 6px;
2009
2012
  `;
2010
- const Actions = styled.div `
2011
- display: flex;
2012
- align-items: center;
2013
- padding: 12px;
2014
- border-top: ${({ theme }) => `1px solid ${theme.colors.border}`};
2015
- `;
2016
2013
  const ActionButton$1 = styled(Button) `
2017
2014
  font-size: 14px;
2018
2015
  ${({ $color }) => `color: ${$color};`}
@@ -2037,45 +2034,51 @@ const Filters$1 = () => {
2037
2034
  const { selectedStreamName } = useDataStreamStore();
2038
2035
  const { getItemSS, setItemSS, removeItemSS } = useSessionStorage();
2039
2036
  const { namespaces: namespaceFilters, kinds, monitors, languages, errors, onlyErrors, setAll, clearAll, getEmptyState } = useFilterStore();
2037
+ const { popupRef, popupOpen, setPopupOpen, popupPosition, handlePosition } = usePopup();
2040
2038
  const sourcesByStream = useMemo(() => filterSourcesByStream(sources, selectedStreamName), [sources, selectedStreamName]);
2041
2039
  // We need local state, because we want to keep the filters in the store only when the user clicks on apply
2042
2040
  const [filters, setFilters] = useState({ namespaces: namespaceFilters, kinds, monitors, languages, errors, onlyErrors });
2043
2041
  const [filterCount, setFilterCount] = useState(getFilterCount(filters));
2044
- const [focused, setFocused] = useState(false);
2045
- const toggleFocused = () => setFocused((prev) => !prev);
2046
2042
  useEffect(() => {
2047
2043
  const storedFilters = getItemSS(STORAGE_KEYS.OVERVIEW_FILTERS, getEmptyState());
2048
2044
  setAll(storedFilters);
2049
2045
  }, []);
2050
2046
  useEffect(() => {
2051
- if (!focused) {
2047
+ if (!popupOpen) {
2052
2048
  const payload = { namespaces: namespaceFilters, kinds, monitors, languages, errors, onlyErrors };
2053
2049
  setFilters(payload);
2054
2050
  setFilterCount(getFilterCount(payload));
2055
2051
  }
2056
- }, [focused, namespaceFilters, kinds, monitors, errors, onlyErrors]);
2052
+ }, [popupOpen, namespaceFilters, kinds, monitors, errors, onlyErrors]);
2057
2053
  const onApply = () => {
2058
2054
  setItemSS(STORAGE_KEYS.OVERVIEW_FILTERS, filters);
2059
2055
  setAll(filters);
2060
2056
  setFilterCount(getFilterCount(filters));
2061
- setFocused(false);
2057
+ setPopupOpen(false);
2062
2058
  };
2063
2059
  const onReset = () => {
2064
2060
  removeItemSS(STORAGE_KEYS.OVERVIEW_FILTERS);
2065
2061
  clearAll();
2066
2062
  setFilters(getEmptyState());
2067
2063
  setFilterCount(0);
2068
- setFocused(false);
2064
+ setPopupOpen(false);
2069
2065
  };
2070
2066
  const onCancel = () => {
2071
- setFocused(false);
2067
+ setPopupOpen(false);
2072
2068
  };
2073
2069
  const ref = useRef(null);
2074
2070
  useOnClickOutside(ref, onCancel);
2075
- useKeyDown({ key: 'Escape', active: focused }, onCancel);
2071
+ useKeyDown({ key: 'Escape', active: popupOpen }, onCancel);
2076
2072
  return (React.createElement(RelativeContainer$2, { ref: ref },
2077
- React.createElement(SelectionButton, { label: 'Filters', icon: FilterIcon, badgeLabel: filterCount, badgeFilled: !!filterCount, withBorder: true, color: 'transparent', onClick: toggleFocused }),
2078
- React.createElement(AbsoluteContainer$2, { "$hidden": !focused },
2073
+ React.createElement(SelectionButton, { label: 'Filters', icon: FilterIcon, badgeLabel: filterCount, badgeFilled: !!filterCount, withBorder: true, color: 'transparent', onClick: () => {
2074
+ handlePosition(0, 50);
2075
+ setPopupOpen(true);
2076
+ } }),
2077
+ React.createElement(Popup, { ref: popupRef, isOpen: popupOpen, top: popupPosition.top, left: popupPosition.left, width: '420px', footer: React.createElement(FlexRow, null,
2078
+ React.createElement(ActionButton$1, { variant: 'primary', onClick: onApply }, "Apply"),
2079
+ React.createElement(ActionButton$1, { variant: 'secondary', onClick: onCancel }, "Cancel"),
2080
+ React.createElement(PushRight, null,
2081
+ React.createElement(ActionButton$1, { variant: 'tertiary', onClick: onReset, "$color": theme.text.error }, "Reset"))) },
2079
2082
  React.createElement(FormWrapper, null,
2080
2083
  React.createElement(NamespaceDropdown, { namespaces: namespaces, value: filters['namespaces'], onSelect: (val) => setFilters((prev) => ({ ...prev, namespaces: [...(prev.namespaces || []), val] })), onDeselect: (val) => setFilters((prev) => ({ ...prev, namespaces: (prev.namespaces || []).filter((opt) => opt.id !== val.id) })), showSearch: true, required: true, isMulti: true }),
2081
2084
  React.createElement(KindDropdown, { sources: sourcesByStream, value: filters['kinds'], onSelect: (val) => setFilters((prev) => ({ ...prev, kinds: [...(prev.kinds || []), val] })), onDeselect: (val) => setFilters((prev) => ({ ...prev, kinds: (prev.kinds || []).filter((opt) => opt.id !== val.id) })), showSearch: true, required: true, isMulti: true }),
@@ -2083,12 +2086,7 @@ const Filters$1 = () => {
2083
2086
  React.createElement(MonitorDropdown, { value: filters['monitors'], onSelect: (val) => setFilters((prev) => ({ ...prev, monitors: [...(prev.monitors || []), val] })), onDeselect: (val) => setFilters((prev) => ({ ...prev, monitors: (prev.monitors || []).filter((opt) => opt.id !== val.id) })), showSearch: true, required: true, isMulti: true }),
2084
2087
  React.createElement(ToggleWrapper, null,
2085
2088
  React.createElement(Toggle, { title: 'Show only sources with errors', initialValue: filters['onlyErrors'], onChange: (bool) => setFilters((prev) => ({ ...prev, errors: [], onlyErrors: bool })) })),
2086
- React.createElement(ErrorDropdown, { sources: sourcesByStream, value: filters['errors'], onSelect: (val) => setFilters((prev) => ({ ...prev, errors: [...(prev.errors || []), val] })), onDeselect: (val) => setFilters((prev) => ({ ...prev, errors: (prev.errors || []).filter((opt) => opt.id !== val.id) })), disabled: !filters['onlyErrors'], showSearch: true, required: true, isMulti: true })),
2087
- React.createElement(Actions, null,
2088
- React.createElement(ActionButton$1, { variant: 'primary', onClick: onApply }, "Apply"),
2089
- React.createElement(ActionButton$1, { variant: 'secondary', onClick: onCancel }, "Cancel"),
2090
- React.createElement(PushRight, null,
2091
- React.createElement(ActionButton$1, { variant: 'tertiary', onClick: onReset, "$color": theme.text.error }, "Reset"))))));
2089
+ React.createElement(ErrorDropdown, { sources: sourcesByStream, value: filters['errors'], onSelect: (val) => setFilters((prev) => ({ ...prev, errors: [...(prev.errors || []), val] })), onDeselect: (val) => setFilters((prev) => ({ ...prev, errors: (prev.errors || []).filter((opt) => opt.id !== val.id) })), disabled: !filters['onlyErrors'], showSearch: true, required: true, isMulti: true })))));
2092
2090
  };
2093
2091
 
2094
2092
  const Container$e = styled.div `
@@ -2162,22 +2160,9 @@ const Container$d = styled(FlexRow) `
2162
2160
  const Title = styled(Text) `
2163
2161
  text-wrap: nowrap;
2164
2162
  `;
2165
- const AbsoluteContainer$1 = styled.div `
2166
- position: absolute;
2167
- top: calc(100% + 8px);
2168
- left: 0;
2169
- z-index: 1;
2170
- background-color: ${({ theme }) => theme.colors.dropdown_bg};
2171
- border: ${({ theme }) => `1px solid ${theme.colors.border}`};
2172
- border-radius: 24px;
2173
- width: 420px;
2174
- `;
2175
- const SelectionMenuHeader = styled.div `
2176
- border-bottom: ${({ theme }) => `1px solid ${theme.colors.border}`};
2177
- padding: 12px;
2178
- `;
2179
- const SelectionContent = styled(VerticalScroll) `
2163
+ const PopupBody$1 = styled(VerticalScroll) `
2180
2164
  max-height: 240px;
2165
+ padding: 0px !important;
2181
2166
  `;
2182
2167
  const SelectionRow = styled(FlexRow) `
2183
2168
  width: 100%;
@@ -2188,9 +2173,7 @@ const Stretch = styled.div `
2188
2173
  const DataStreamSelect = ({ onClickNewDataStream, updateDataStream, deleteDataStream }) => {
2189
2174
  const theme = Theme.useTheme();
2190
2175
  const { dataStreams, selectedStreamName, setSelectedStreamName } = useDataStreamStore();
2191
- const [popupOpen, setPopupOpen] = useState(false);
2192
- const containerRef = useRef(null);
2193
- useOnClickOutside(containerRef, () => setPopupOpen(false));
2176
+ const { popupRef, popupOpen, setPopupOpen, popupPosition, handlePosition } = usePopup();
2194
2177
  const [editOpenForDataStreamName, setEditOpenForDataStreamName] = useState('');
2195
2178
  const [deleteOpenForDataStreamName, setDeleteOpenForDataStreamName] = useState('');
2196
2179
  const [searchText, setSearchText] = useState('');
@@ -2209,7 +2192,10 @@ const DataStreamSelect = ({ onClickNewDataStream, updateDataStream, deleteDataSt
2209
2192
  return (React.createElement(React.Fragment, null,
2210
2193
  React.createElement(RelativeContainer$1, null,
2211
2194
  React.createElement(Container$d, { "$gap": 0 },
2212
- React.createElement(Button, { variant: 'tertiary', onClick: () => setPopupOpen((prev) => !prev) },
2195
+ React.createElement(Button, { variant: 'tertiary', onClick: () => {
2196
+ handlePosition(0, 50);
2197
+ setPopupOpen((prev) => !prev);
2198
+ } },
2213
2199
  React.createElement(DataStreamsIcon, { fill: theme.text.info }),
2214
2200
  React.createElement(Title, null,
2215
2201
  "Data Stream: ",
@@ -2221,10 +2207,8 @@ const DataStreamSelect = ({ onClickNewDataStream, updateDataStream, deleteDataSt
2221
2207
  setSelectedStreamName('');
2222
2208
  onClickNewDataStream();
2223
2209
  }, label: BUTTON_TEXTS.NEW })),
2224
- popupOpen && (React.createElement(AbsoluteContainer$1, { ref: containerRef },
2225
- React.createElement(SelectionMenuHeader, null,
2226
- React.createElement(Input, { placeholder: 'Search...', icon: SearchIcon, value: searchText, onChange: (e) => setSearchText(e.target.value) })),
2227
- React.createElement(SelectionContent, null, rows)))),
2210
+ React.createElement(Popup, { ref: popupRef, isOpen: popupOpen, top: popupPosition.top, left: popupPosition.left, header: React.createElement(Input, { placeholder: 'Search...', icon: SearchIcon, value: searchText, onChange: (e) => setSearchText(e.target.value) }) },
2211
+ React.createElement(PopupBody$1, null, rows))),
2228
2212
  React.createElement(DeleteWarning, { isOpen: deleteOpenForDataStreamName !== '', name: deleteOpenForDataStreamName, onApprove: () => {
2229
2213
  deleteDataStream(deleteOpenForDataStreamName);
2230
2214
  setDeleteOpenForDataStreamName('');
@@ -3612,32 +3596,13 @@ const RelativeContainer = styled.div `
3612
3596
  position: relative;
3613
3597
  width: fit-content;
3614
3598
  `;
3615
- const AbsoluteContainer = styled.div `
3616
- position: absolute;
3617
- top: 40px;
3618
- left: ${({ $left }) => $left}px;
3619
- z-index: 1;
3620
- width: 370px;
3621
- height: 400px;
3622
- background-color: ${({ theme }) => theme.colors.dropdown_bg};
3623
- border: 1px solid ${({ theme }) => theme.colors.border};
3624
- border-radius: 24px;
3625
- box-shadow: 0px 10px 15px -3px ${({ theme }) => theme.colors.primary}, 0px 4px 6px -2px ${({ theme }) => theme.colors.primary};
3626
- `;
3627
- const PopupHeader = styled.div `
3628
- display: flex;
3629
- align-items: center;
3599
+ const PopupHeader = styled(FlexRow) `
3630
3600
  gap: 12px;
3631
- padding: 12px 24px;
3632
- border-bottom: 1px solid ${({ theme }) => theme.colors.border};
3601
+ padding: 0 12px;
3633
3602
  `;
3634
- const PopupBody = styled.div `
3635
- display: flex;
3636
- flex-direction: column;
3603
+ const PopupBody = styled(FlexColumn) `
3637
3604
  gap: 12px;
3638
- padding: 12px;
3639
- height: calc(100% - 74px);
3640
- border-radius: 24px;
3605
+ max-height: 400px;
3641
3606
  overflow-y: auto;
3642
3607
  `;
3643
3608
  const PopupShadow = styled.div `
@@ -3663,36 +3628,29 @@ const NotificationManager = () => {
3663
3628
  const notifications = n.filter(({ hideFromHistory }) => !hideFromHistory);
3664
3629
  const unseen = notifications.filter(({ seen }) => !seen);
3665
3630
  const unseenCount = unseen.length;
3666
- const [isOpen, setIsOpen] = useState(false);
3667
- const [popupPosition, setPopupPosition] = useState({ left: 0 });
3668
- const containerRef = useRef(null);
3669
- useOnClickOutside(containerRef, () => {
3670
- if (isOpen) {
3671
- setIsOpen(false);
3672
- if (!!unseenCount)
3631
+ const { popupRef, popupOpen, setPopupOpen, popupPosition, handlePosition } = usePopup({
3632
+ defaultClientHeight: 420,
3633
+ defaultClientwidth: 400,
3634
+ onClickOutside: () => {
3635
+ if (unseenCount)
3673
3636
  unseen.forEach(({ id }) => markAsSeen(id));
3674
- }
3637
+ },
3675
3638
  });
3676
3639
  const toggleOpen = (e) => {
3677
- const { clientX } = e;
3678
- const { innerWidth } = window;
3679
- let left = 0;
3680
- if (clientX >= innerWidth / 2)
3681
- left = -350;
3682
- setPopupPosition({ left });
3683
- setIsOpen((prev) => !prev);
3640
+ const { clientX, clientY } = e;
3641
+ handlePosition(clientX, clientY);
3642
+ setPopupOpen((prev) => !prev);
3684
3643
  };
3685
- return (React.createElement(RelativeContainer, { ref: containerRef },
3686
- React.createElement(IconButton, { "data-id": 'notif-manager-button', onClick: toggleOpen, tooltip: 'Notifications', withPing: !!unseenCount, pingColor: theme.colors.orange_og },
3644
+ return (React.createElement(RelativeContainer, null,
3645
+ React.createElement(IconButton, { onClick: toggleOpen, tooltip: 'Notifications', withPing: !!unseenCount, pingColor: theme.colors.orange_og },
3687
3646
  React.createElement(NotificationIcon, { size: 18 })),
3688
- isOpen && (React.createElement(AbsoluteContainer, { "data-id": 'notif-manager-content', "$left": popupPosition.left },
3689
- React.createElement(PopupHeader, null,
3647
+ React.createElement(Popup, { ref: popupRef, isOpen: popupOpen, asPortal: true, left: popupPosition.left, maxWidth: '400px', header: React.createElement(PopupHeader, null,
3690
3648
  React.createElement(Text, { size: 20 }, "Notifications"),
3691
3649
  !!unseenCount && (React.createElement(NewCount, { size: 12, family: 'secondary' },
3692
3650
  unseenCount,
3693
- " new"))),
3694
- React.createElement(PopupBody, null, !notifications.length ? (React.createElement(NoDataFound, { title: 'No notifications', subTitle: '' })) : (notifications.map((notif) => React.createElement(NotificationListItem, { key: `notification-${notif.id}`, ...notif, onClick: () => setIsOpen(false) })))),
3695
- React.createElement(PopupShadow, null)))));
3651
+ " new"))) },
3652
+ React.createElement(PopupBody, null, !notifications.length ? (React.createElement(NoDataFound, { title: 'No notifications', subTitle: '' })) : (notifications.map((notif) => React.createElement(NotificationListItem, { key: `notification-${notif.id}`, ...notif, onClick: () => setPopupOpen(false) })))),
3653
+ React.createElement(PopupShadow, null))));
3696
3654
  };
3697
3655
  const NotifCard = styled.div `
3698
3656
  display: flex;
@@ -3779,117 +3737,131 @@ const buildEdges = ({ theme, nodes, serviceMap }) => {
3779
3737
  return edges;
3780
3738
  };
3781
3739
 
3782
- const mapToNodeData = (entity, serviceMapEntry, isUserSource) => {
3783
- const { priorotizedStatus } = getConditionsBooleans(entity.conditions || []);
3784
- return {
3785
- id: entity.otelServiceName || entity.name,
3786
- title: getEntityLabel(entity, EntityTypes.Source, { extended: true }),
3787
- icons: isUserSource ? [UserIcon] : getContainersIcons(entity.containers),
3788
- status: priorotizedStatus,
3789
- serviceMapEntry,
3790
- };
3791
- };
3792
- const generatePosition = (existingPositions, containerWidth, containerHeight) => {
3793
- const spacing = 150; // Spacing between nodes
3794
- const centerX = containerWidth / 2 - 100;
3795
- const centerY = containerHeight / 2 - 100;
3796
- const padding = 50; // Optional: keep nodes away from edges
3797
- // True spiral grid algorithm
3798
- function getTrueSpiralGridOffset(n) {
3799
- if (n === 0)
3800
- return { gridX: 0, gridY: 0 };
3801
- let x = 0, y = 0;
3802
- let dx = 1, dy = 0;
3803
- let segmentLength = 1;
3804
- let segmentCount = 0;
3805
- let steps = 0;
3806
- for (let i = 1; i <= n; i++) {
3807
- x += dx;
3808
- y += dy;
3809
- steps++;
3810
- if (steps === segmentLength) {
3811
- steps = 0;
3812
- // Rotate direction left: right->up->left->down->right...
3813
- const temp = dx;
3814
- dx = -dy;
3815
- dy = temp;
3816
- segmentCount++;
3817
- if (segmentCount % 2 === 0) {
3818
- segmentLength++;
3740
+ const generateDAGPositions = (serviceMap, containerWidth, containerHeight) => {
3741
+ const positions = new Map();
3742
+ const nodeSpacing = 150; // Vertical spacing between nodes
3743
+ const layerSpacing = 200; // Horizontal spacing between layers
3744
+ const padding = 100; // Padding from container edges
3745
+ // Build adjacency list and calculate in-degrees for topological sorting
3746
+ const adjacencyList = new Map();
3747
+ const inDegrees = new Map();
3748
+ const allServices = new Set();
3749
+ // Initialize all services
3750
+ serviceMap.forEach((entry) => {
3751
+ allServices.add(entry.serviceName);
3752
+ entry.services.forEach((target) => {
3753
+ allServices.add(target.serviceName);
3754
+ });
3755
+ });
3756
+ // Initialize adjacency list and in-degrees
3757
+ allServices.forEach((service) => {
3758
+ adjacencyList.set(service, []);
3759
+ inDegrees.set(service, 0);
3760
+ });
3761
+ // Build the graph
3762
+ serviceMap.forEach((entry) => {
3763
+ entry.services.forEach((target) => {
3764
+ adjacencyList.get(entry.serviceName)?.push(target.serviceName);
3765
+ const currentInDegree = inDegrees.get(target.serviceName) || 0;
3766
+ inDegrees.set(target.serviceName, currentInDegree + 1);
3767
+ });
3768
+ });
3769
+ // Topological sort to determine layers
3770
+ const layers = [];
3771
+ const queue = [];
3772
+ // Add all nodes with in-degree 0 to the queue
3773
+ allServices.forEach((service) => {
3774
+ if ((inDegrees.get(service) || 0) === 0) {
3775
+ queue.push(service);
3776
+ }
3777
+ });
3778
+ // Process nodes in topological order
3779
+ while (queue.length > 0) {
3780
+ const currentLayer = [];
3781
+ const layerSize = queue.length;
3782
+ for (let i = 0; i < layerSize; i++) {
3783
+ const current = queue.shift();
3784
+ currentLayer.push(current);
3785
+ // Process neighbors
3786
+ adjacencyList.get(current)?.forEach((neighbor) => {
3787
+ const neighborInDegree = inDegrees.get(neighbor) || 0;
3788
+ inDegrees.set(neighbor, neighborInDegree - 1);
3789
+ if (neighborInDegree - 1 === 0) {
3790
+ queue.push(neighbor);
3819
3791
  }
3820
- }
3792
+ });
3821
3793
  }
3822
- return { gridX: x, gridY: y };
3823
- }
3824
- // Build a set of all possible grid positions within the container
3825
- const maxGridX = Math.floor((containerWidth / 2 - padding) / spacing);
3826
- const maxGridY = Math.floor((containerHeight / 2 - padding) / spacing);
3827
- const inBoundsPositions = new Set();
3828
- for (let gx = -maxGridX; gx <= maxGridX; gx++) {
3829
- for (let gy = -maxGridY; gy <= maxGridY; gy++) {
3830
- const x = centerX + gx * spacing;
3831
- const y = centerY + gy * spacing;
3832
- if (x >= padding && x <= containerWidth - padding && y >= padding && y <= containerHeight - padding) {
3833
- inBoundsPositions.add(`${gx},${gy}`);
3834
- }
3794
+ if (currentLayer.length > 0) {
3795
+ layers.push(currentLayer);
3835
3796
  }
3836
3797
  }
3837
- // Track used grid positions to avoid overlap
3838
- const usedPositions = new Set(existingPositions.map((pos) => `${Math.round((pos.x - centerX) / spacing)},${Math.round((pos.y - centerY) / spacing)}`));
3839
- // Find the first available spiral grid position that is in bounds and not already used
3840
- let n = 0;
3841
- while (true) {
3842
- const { gridX, gridY } = getTrueSpiralGridOffset(n);
3843
- const key = `${gridX},${gridY}`;
3844
- const x = centerX + gridX * spacing;
3845
- const y = centerY + gridY * spacing;
3846
- if (inBoundsPositions.has(key) && !usedPositions.has(key)) {
3847
- return { x, y };
3848
- }
3849
- n++;
3850
- // If we've exhausted all in-bounds positions, allow out-of-bounds placement
3851
- if (n > inBoundsPositions.size + 100) {
3852
- // Find the next unused spiral position, even if out of bounds
3853
- let m = n;
3854
- while (true) {
3855
- const { gridX: outGridX, gridY: outGridY } = getTrueSpiralGridOffset(m);
3856
- const outKey = `${outGridX},${outGridY}`;
3857
- const outX = centerX + outGridX * spacing;
3858
- const outY = centerY + outGridY * spacing;
3859
- if (!usedPositions.has(outKey)) {
3860
- return { x: outX, y: outY };
3861
- }
3862
- m++;
3798
+ // Handle any remaining nodes (cycles or isolated nodes)
3799
+ allServices.forEach((service) => {
3800
+ if (!layers.flat().includes(service)) {
3801
+ if (layers.length === 0) {
3802
+ layers.push([service]);
3803
+ }
3804
+ else {
3805
+ layers[layers.length - 1].push(service);
3863
3806
  }
3864
3807
  }
3865
- }
3808
+ });
3809
+ // Calculate positions for each layer
3810
+ layers.forEach((layer, layerIndex) => {
3811
+ const layerX = padding + layerIndex * layerSpacing;
3812
+ const layerHeight = (layer.length - 1) * nodeSpacing;
3813
+ const layerStartY = (containerHeight - layerHeight) / 2;
3814
+ layer.forEach((service, serviceIndex) => {
3815
+ const x = layerX;
3816
+ const y = layerStartY + serviceIndex * nodeSpacing;
3817
+ positions.set(service, { x, y });
3818
+ });
3819
+ });
3820
+ return positions;
3866
3821
  };
3867
- const userSource = {
3868
- namespace: '',
3869
- name: '',
3870
- kind: '',
3871
- otelServiceName: 'user',
3872
- selected: true,
3873
- numberOfInstances: 0,
3874
- dataStreamNames: [],
3875
- containers: [],
3876
- conditions: [],
3822
+
3823
+ const mapToNodeData = (serviceMapEntry, source, isUserSource) => {
3824
+ const serviceName = serviceMapEntry?.serviceName || source?.otelServiceName || source?.name;
3825
+ const icons = isUserSource ? [UserIcon] : getContainersIcons(source?.containers || []);
3826
+ const { priorotizedStatus } = getConditionsBooleans(source?.conditions || []);
3827
+ return {
3828
+ id: serviceName,
3829
+ title: serviceName,
3830
+ icons: icons.length ? icons : [ImageErrorIcon],
3831
+ status: priorotizedStatus,
3832
+ serviceMapEntry,
3833
+ };
3877
3834
  };
3878
3835
  const buildMapNodes = ({ sources, serviceMap, containerHeight, containerWidth }) => {
3879
3836
  const nodes = [];
3880
- const existingPositions = [];
3881
- if (sources.length) {
3882
- [userSource].concat(sources).forEach((source) => {
3883
- const serviceMapEntry = serviceMap.find((sm) => sm.serviceName === source.otelServiceName || sm.serviceName === source.name);
3837
+ const dagPositions = generateDAGPositions(serviceMap, containerWidth, containerHeight);
3838
+ if (serviceMap.length && sources.length) {
3839
+ // first we map the service map nodes (to include services traced, but not instrumented by Odigos)
3840
+ serviceMap.forEach((serviceMapEntry) => {
3841
+ const source = sources.find((src) => serviceMapEntry.serviceName === src.otelServiceName || serviceMapEntry.serviceName === src.name);
3842
+ const isUserSource = source?.otelServiceName === 'user' && !source?.namespace && !source?.name && !source?.kind;
3843
+ nodes.push({
3844
+ id: `${EntityTypes.Source}-${serviceMapEntry.serviceName}-${NodeTypes.MapItem}`,
3845
+ type: NodeTypes.MapItem,
3846
+ position: dagPositions.get(serviceMapEntry.serviceName),
3847
+ data: mapToNodeData(serviceMapEntry, source, isUserSource),
3848
+ });
3849
+ });
3850
+ // then we look for any sources that are not in the service map (to include services not traced)
3851
+ let currentX = 10;
3852
+ sources.forEach((source) => {
3853
+ const serviceName = source.otelServiceName || source.name;
3884
3854
  const isUserSource = source.otelServiceName === 'user' && !source.namespace && !source.name && !source.kind;
3885
- const position = generatePosition(existingPositions, containerWidth, containerHeight);
3886
- existingPositions.push(position);
3855
+ const foundNode = nodes.find((n) => n.id === `${EntityTypes.Source}-${serviceName}-${NodeTypes.MapItem}`);
3856
+ if (foundNode)
3857
+ return;
3887
3858
  nodes.push({
3888
- id: `${EntityTypes.Source}-${source.otelServiceName || source.name}-${NodeTypes.MapItem}`,
3859
+ id: `${EntityTypes.Source}-${serviceName}-${NodeTypes.MapItem}`,
3889
3860
  type: NodeTypes.MapItem,
3890
- position,
3891
- data: mapToNodeData(source, serviceMapEntry, isUserSource),
3861
+ position: { x: currentX, y: 10 },
3862
+ data: mapToNodeData(undefined, source, isUserSource),
3892
3863
  });
3864
+ currentX += 120;
3893
3865
  });
3894
3866
  }
3895
3867
  // else if (sourcesLoading) {
@@ -3904,7 +3876,7 @@ const buildMapNodes = ({ sources, serviceMap, containerHeight, containerWidth })
3904
3876
  y: containerHeight / 2 - 50,
3905
3877
  },
3906
3878
  data: {
3907
- subTitle: 'Are your services instrumented?',
3879
+ subTitle: 'Are your services instrumented & producing traces?',
3908
3880
  },
3909
3881
  });
3910
3882
  }
@@ -3916,18 +3888,57 @@ const Container$5 = styled.div `
3916
3888
  height: ${({ $heightToRemove }) => `calc(100vh - ${$heightToRemove})`};
3917
3889
  position: relative;
3918
3890
  `;
3891
+ const userSource = {
3892
+ namespace: '',
3893
+ name: '',
3894
+ kind: '',
3895
+ otelServiceName: 'user',
3896
+ selected: true,
3897
+ numberOfInstances: 0,
3898
+ dataStreamNames: [],
3899
+ containers: [],
3900
+ conditions: [],
3901
+ };
3919
3902
  const ServiceMap = ({ heightToRemove, serviceMap }) => {
3920
3903
  const theme = Theme.useTheme();
3921
3904
  const { sources, sourcesLoading } = useEntityStore();
3922
3905
  const { containerRef, containerHeight, containerWidth } = useContainerSize();
3923
3906
  const [nodes, setNodes, onNodesChange] = useNodesState([]);
3924
3907
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);
3908
+ const handleNodesChanged = (currNodes) => {
3909
+ setNodes((prevNodes) => {
3910
+ const mutatedNodes = currNodes.map((cn) => {
3911
+ const pn = prevNodes.find((pn) => pn.id === cn.id);
3912
+ cn.position = pn?.position || cn.position;
3913
+ return {
3914
+ id: cn.id,
3915
+ item: cn,
3916
+ type: pn ? 'replace' : 'add',
3917
+ };
3918
+ });
3919
+ const noDataNode = prevNodes.find((pn) => pn.id === `${EntityTypes.Source}-${NodeTypes.NoData}`);
3920
+ if (noDataNode) {
3921
+ mutatedNodes.push({
3922
+ id: `${EntityTypes.Source}-${NodeTypes.NoData}`,
3923
+ type: 'remove',
3924
+ });
3925
+ }
3926
+ return applyNodeChanges(mutatedNodes, prevNodes);
3927
+ });
3928
+ };
3929
+ useEffect(() => {
3930
+ const newNodes = buildMapNodes({ serviceMap, sources: [userSource].concat(sources), containerHeight, containerWidth });
3931
+ // greater than 1, because at the very least we would have a 'no data' node
3932
+ if (nodes.length > 1) {
3933
+ handleNodesChanged(newNodes);
3934
+ }
3935
+ else {
3936
+ setNodes(newNodes);
3937
+ }
3938
+ }, [serviceMap, sources, sourcesLoading, containerHeight, containerWidth]);
3925
3939
  useEffect(() => {
3926
3940
  setEdges(buildEdges({ theme, nodes, serviceMap }));
3927
3941
  }, [theme, nodes, serviceMap]);
3928
- useEffect(() => {
3929
- setNodes(buildMapNodes({ sources, serviceMap, containerHeight, containerWidth }));
3930
- }, [sources, sourcesLoading, serviceMap, containerHeight, containerWidth]);
3931
3942
  return (React.createElement(Container$5, { ref: containerRef, "$heightToRemove": heightToRemove },
3932
3943
  React.createElement(Flow, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, zoomOnScroll: true })));
3933
3944
  };
@@ -4164,95 +4175,37 @@ const Describe$1 = ({ source, fetchDescribeSource }) => {
4164
4175
  }
4165
4176
  return (React.createElement(FlexColumn, { "$gap": 12 }, !describe.pods?.length ? (React.createElement(CenterThis, null,
4166
4177
  React.createElement(NoDataFound, { subTitle: 'Check if you have any running pods and try again' }))) : (describe.pods.map(({ podName, nodeName, phase, agentInjected, runningLatestWorkloadRevision, containers }) => {
4167
- const podHasErrors = phase.status !== StatusType.Success ||
4168
- containers.findIndex(({ instrumentationInstances }) => instrumentationInstances.findIndex(({ healthy }) => healthy.status !== StatusType.Success) !== -1) !== -1;
4178
+ const podHasErrors = phase.status !== StatusType.Success || hasUnhealthyInstances(containers);
4169
4179
  const podStatus = podHasErrors ? StatusType.Error : StatusType.Success;
4170
- const divider = { type: DataCardFieldTypes.Divider };
4171
- const data = [];
4172
- data.push({
4173
- type: DataCardFieldTypes.CopyText,
4174
- value: `kubectl get pod ${podName.value} -n ${describe?.namespace?.value || source.namespace}`.toLowerCase(),
4175
- });
4176
- data.push(divider);
4177
- data.push({
4178
- type: DataCardFieldTypes.DescribeRow,
4179
- value: JSON.stringify({
4180
- title: nodeName.name,
4181
- tooltip: nodeName.explain,
4182
- value: {
4183
- text: nodeName.value,
4184
- status: undefined,
4185
- },
4186
- }),
4187
- });
4188
- data.push(divider);
4189
- data.push({
4190
- type: DataCardFieldTypes.DescribeRow,
4191
- value: JSON.stringify({
4192
- title: phase.name,
4193
- tooltip: phase.explain,
4194
- value: {
4195
- text: phase.value,
4196
- status: phase.status,
4197
- },
4198
- }),
4199
- });
4200
- data.push(divider);
4201
- data.push({
4202
- type: DataCardFieldTypes.DescribeRow,
4203
- value: JSON.stringify({
4204
- title: agentInjected.name,
4205
- tooltip: agentInjected.explain,
4206
- value: {
4207
- text: agentInjected.value,
4208
- status: agentInjected.status,
4209
- },
4210
- }),
4211
- });
4212
- data.push(divider);
4180
+ const cardChildren = [];
4181
+ cardChildren.push(React.createElement(CopyText, { value: `kubectl get pod ${podName.value} -n ${describe?.namespace?.value || source.namespace}`.toLowerCase() }));
4182
+ cardChildren.push(React.createElement(Divider, { length: '100%', margin: '0' }));
4183
+ cardChildren.push(React.createElement(DescribeRow, { title: nodeName.name, tooltip: nodeName.explain || '', value: {
4184
+ status: nodeName.status || undefined,
4185
+ text: nodeName.value,
4186
+ } }));
4187
+ cardChildren.push(React.createElement(Divider, { length: '100%', margin: '0' }));
4188
+ cardChildren.push(React.createElement(DescribeRow, { title: phase.name, tooltip: phase.explain || '', value: {
4189
+ status: phase.status || undefined,
4190
+ text: phase.value,
4191
+ } }));
4192
+ cardChildren.push(React.createElement(Divider, { length: '100%', margin: '0' }));
4193
+ cardChildren.push(React.createElement(DescribeRow, { title: agentInjected.name, tooltip: agentInjected.explain || '', value: {
4194
+ status: agentInjected.status || undefined,
4195
+ text: agentInjected.value,
4196
+ } }));
4197
+ cardChildren.push(React.createElement(Divider, { length: '100%', margin: '0' }));
4213
4198
  if (runningLatestWorkloadRevision?.name) {
4214
- data.push({
4215
- type: DataCardFieldTypes.DescribeRow,
4216
- value: JSON.stringify({
4217
- title: runningLatestWorkloadRevision?.name || '',
4218
- tooltip: runningLatestWorkloadRevision?.explain || '',
4219
- value: {
4220
- text: runningLatestWorkloadRevision?.value || '',
4221
- status: runningLatestWorkloadRevision?.status || '',
4222
- },
4223
- }),
4224
- });
4225
- data.push(divider);
4199
+ cardChildren.push(React.createElement(DescribeRow, { title: runningLatestWorkloadRevision.name, tooltip: runningLatestWorkloadRevision.explain || undefined, value: {
4200
+ status: runningLatestWorkloadRevision.status || undefined,
4201
+ text: runningLatestWorkloadRevision.value,
4202
+ } }));
4203
+ cardChildren.push(React.createElement(Divider, { length: '100%', margin: '0' }));
4226
4204
  }
4227
- data.push(...containers.map((container) => {
4228
- return {
4229
- type: DataCardFieldTypes.PodContainer,
4230
- value: JSON.stringify({
4231
- containerName: container.containerName.value,
4232
- actualDevice: {
4233
- title: container.actualDevices.name,
4234
- subTitle: container.actualDevices.value,
4235
- tooltip: container.actualDevices.explain,
4236
- },
4237
- started: {
4238
- title: container.started?.name || '',
4239
- subTitle: container.started?.value || '',
4240
- tooltip: container.started?.explain || '',
4241
- },
4242
- ready: {
4243
- title: container.ready?.name || '',
4244
- subTitle: container.ready?.value || '',
4245
- tooltip: container.ready?.explain || '',
4246
- },
4247
- processes: container.instrumentationInstances.map((instance) => ({
4248
- health: instance.healthy.status,
4249
- message: instance.message?.value || '',
4250
- identifyingAttributes: instance.identifyingAttributes || [],
4251
- })),
4252
- }),
4253
- };
4254
- }));
4255
- return React.createElement(DataCard, { key: `pod-${podName.value}`, title: `Pod: ${podName.value}`, withExtend: true, action: () => React.createElement(Status, { status: podStatus, title: podStatus, withIcon: true, withBorder: true }), data: data });
4205
+ containers.forEach((container) => {
4206
+ cardChildren.push(React.createElement(PodContainer, { ...container }));
4207
+ });
4208
+ return (React.createElement(DataCard, { key: `pod-${podName.value}`, title: `Pod: ${podName.value}`, withExtend: true, action: () => React.createElement(Status, { status: podStatus, title: podStatus, withIcon: true, withBorder: true }) }, Children.toArray(cardChildren)));
4256
4209
  }))));
4257
4210
  };
4258
4211
 
@@ -4319,18 +4272,13 @@ const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, resta
4319
4272
  return found;
4320
4273
  }, [isOpen, drawerEntityId, sourcesByStream]);
4321
4274
  const containersData = useMemo(() => {
4322
- const mappedContainers = thisItem?.containers?.map((container) => ({
4323
- type: DataCardFieldTypes.SourceContainer,
4324
- value: JSON.stringify(container),
4325
- callback: async (payload) => await updateSource(drawerEntityId, payload),
4326
- })) || [];
4327
4275
  const runtimeCondition = thisItem?.conditions?.find(({ type }) => type === 'RuntimeDetection');
4328
4276
  return {
4329
4277
  description: runtimeCondition?.message,
4330
4278
  isLoading: runtimeCondition?.status === OtherStatus.Loading,
4331
- items: mappedContainers,
4279
+ containers: thisItem?.containers || [],
4332
4280
  };
4333
- }, [thisItem, drawerEntityId]);
4281
+ }, [thisItem]);
4334
4282
  const tabs = useMemo(() => {
4335
4283
  const arr = [
4336
4284
  {
@@ -4391,7 +4339,7 @@ const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, resta
4391
4339
  } }))) : (React.createElement(DataContainer$1, null,
4392
4340
  React.createElement(ConditionDetails, { conditions: thisItem.conditions || [] }),
4393
4341
  React.createElement(DataCard, { title: DISPLAY_TITLES.SOURCE_DETAILS, data: !!thisItem ? buildCard(thisItem) : [] }),
4394
- React.createElement(DataCard, { title: DISPLAY_TITLES.DETECTED_CONTAINERS, titleBadge: containersData.isLoading ? OtherStatus.Loading : containersData.items.length, description: containersData.description || DISPLAY_TITLES.DETECTED_CONTAINERS_DESCRIPTION, data: containersData.items })))) : (React.createElement(Describe$1, { source: thisItem, fetchDescribeSource: fetchDescribeSource }))));
4342
+ React.createElement(DataCard, { title: DISPLAY_TITLES.DETECTED_CONTAINERS, titleBadge: containersData.isLoading ? OtherStatus.Loading : containersData.containers.length, description: containersData.description || DISPLAY_TITLES.DETECTED_CONTAINERS_DESCRIPTION }, containersData.containers.map((container) => (React.createElement(SourceContainer, { key: `source-container-${container.containerName}`, ...container, callbackRuntimeOverride: async (payload) => await updateSource(drawerEntityId, payload) }))))))) : (React.createElement(Describe$1, { source: thisItem, fetchDescribeSource: fetchDescribeSource }))));
4395
4343
  };
4396
4344
 
4397
4345
  const ActionsRow = styled(FlexRow) `
@@ -4621,59 +4569,30 @@ const ExpiresAt = ({ expiresAt }) => {
4621
4569
  const Relative = styled.div `
4622
4570
  position: relative;
4623
4571
  `;
4624
- const TokenPopover = styled(FlexColumn) `
4625
- position: absolute;
4626
- bottom: 32px;
4627
- right: 0;
4628
- z-index: 1;
4629
- gap: 8px;
4630
- padding: 24px;
4631
- background-color: ${({ theme }) => theme.colors.info};
4632
- border: 1px solid ${({ theme }) => theme.colors.border};
4633
- border-radius: 24px;
4634
- `;
4635
- const PopoverFormWrapper = styled(FlexRow) `
4636
- width: 100%;
4637
- gap: 12px;
4638
- `;
4639
- const PopoverFormButton = styled(Button) `
4640
- width: 36px;
4641
- padding: 0;
4642
- `;
4643
4572
  const TokenActions = ({ token, saveToken }) => {
4644
4573
  const theme = Theme.useTheme();
4645
4574
  const { isCopied, clickCopy } = useCopy();
4575
+ const { formData, handleFormChange, resetFormData } = useGenericForm({ token });
4646
4576
  const [isEdit, setIsEdit] = useState(false);
4647
- const [inputValue, setInputValue] = useState(token);
4648
- const popupRef = useRef(null);
4649
4577
  const onSave = () => {
4650
- saveToken(inputValue).then(onCloseEdit);
4578
+ saveToken(formData.token).then(onCloseEdit);
4651
4579
  };
4652
4580
  const onOpenEdit = () => {
4653
4581
  setIsEdit(true);
4654
4582
  };
4655
4583
  const onCloseEdit = () => {
4656
4584
  setIsEdit(false);
4657
- setInputValue(token);
4585
+ resetFormData();
4658
4586
  };
4659
4587
  const SuccessIcon = getStatusIcon(StatusType.Success, theme);
4660
- useOnClickOutside(popupRef, onCloseEdit);
4661
4588
  return (React.createElement(FlexRow, { "$gap": 0 },
4662
4589
  React.createElement(IconButton, { size: 32, onClick: () => clickCopy(token) }, isCopied ? React.createElement(SuccessIcon, null) : React.createElement(CopyIcon, null)),
4663
4590
  React.createElement(Divider, { orientation: 'vertical', length: '12px' }),
4664
4591
  React.createElement(Relative, null,
4665
4592
  React.createElement(IconButton, { size: 32, onClick: onOpenEdit },
4666
4593
  React.createElement(EditIcon, null)),
4667
- isEdit && (React.createElement(TokenPopover, { ref: popupRef },
4668
- React.createElement(Tooltip, { text: 'Contact us to generate a new one', withIcon: true },
4669
- React.createElement(Text, { size: 14, style: { lineHeight: '20px', display: 'flex' } }, "Enter a new API Token:")),
4670
- React.createElement(PopoverFormWrapper, null,
4671
- React.createElement(Input, { placeholder: 'API Token', type: 'password', value: inputValue, onChange: (e) => setInputValue(e.target.value) }),
4672
- React.createElement(FlexRow, null,
4673
- React.createElement(PopoverFormButton, { variant: 'primary', onClick: onSave },
4674
- React.createElement(CheckIcon, { fill: theme.text.primary })),
4675
- React.createElement(PopoverFormButton, { variant: 'secondary', onClick: onCloseEdit },
4676
- React.createElement(CrossIcon, null)))))))));
4594
+ isEdit && (React.createElement(PopupForm, { flipX: true, clientX: 36, isOpen: isEdit, onClose: onCloseEdit, onSave: onSave, title: 'Enter a new API Token', titleTooltip: 'Contact us to generate a new one' },
4595
+ React.createElement(Input, { placeholder: 'API Token', type: 'password', value: formData.token, onChange: (e) => handleFormChange('token', e.target.value) }))))));
4677
4596
  };
4678
4597
 
4679
4598
  const Tokens = ({ tokens, saveToken }) => {
@@ -4719,19 +4638,13 @@ const Describe = ({ fetchDescribeOdigos }) => {
4719
4638
  return (React.createElement(CenterThis, null,
4720
4639
  React.createElement(FadeLoader, null)));
4721
4640
  }
4722
- const mapObjectToCardFields = (obj) => !!obj?.name
4641
+ const mapObjectToCardChildren = (obj) => !!obj?.name
4723
4642
  ? [
4724
- {
4725
- type: DataCardFieldTypes.Divider,
4726
- },
4727
- {
4728
- type: DataCardFieldTypes.DescribeRow,
4729
- value: JSON.stringify({
4730
- title: obj.name,
4731
- subTitle: obj.explain,
4732
- value: { text: obj.value, status: obj.status },
4733
- }),
4734
- },
4643
+ React.createElement(Divider, { key: `divider-${obj.name}` }),
4644
+ React.createElement(DescribeRow, { key: `describe-row-${obj.name}`, title: obj.name, subTitle: obj.explain || undefined, value: {
4645
+ status: obj.status || parseBooleanFromString(obj.value) ? StatusType.Success : StatusType.Error,
4646
+ text: obj.value,
4647
+ } }),
4735
4648
  ]
4736
4649
  : [];
4737
4650
  return (React.createElement(React.Fragment, null,
@@ -4743,12 +4656,8 @@ const Describe = ({ fetchDescribeOdigos }) => {
4743
4656
  { title: '# of sources', value: describe?.numberOfSources?.toString() },
4744
4657
  { title: '# of destinations', value: describe?.numberOfDestinations?.toString() },
4745
4658
  ] }),
4746
- React.createElement(DataCard, { title: 'Cluster Collector', withExtend: true, data: Object.values(describe?.clusterCollector || {})
4747
- .map(mapObjectToCardFields)
4748
- .flat() }),
4749
- React.createElement(DataCard, { title: 'Node Collector', withExtend: true, data: Object.values(describe?.nodeCollector || {})
4750
- .map(mapObjectToCardFields)
4751
- .flat() })));
4659
+ React.createElement(DataCard, { title: 'Cluster Collector', withExtend: true }, Object.values(describe?.clusterCollector || {}).map(mapObjectToCardChildren)),
4660
+ React.createElement(DataCard, { title: 'Node Collector', withExtend: true }, Object.values(describe?.nodeCollector || {}).map(mapObjectToCardChildren))));
4752
4661
  };
4753
4662
 
4754
4663
  const DataContainer = styled.div `