diff --git a/frontend/webapp/containers/main/destinations/destination-drawer/index.tsx b/frontend/webapp/containers/main/destinations/destination-drawer/index.tsx index 5688cd3a3..f9fd3b397 100644 --- a/frontend/webapp/containers/main/destinations/destination-drawer/index.tsx +++ b/frontend/webapp/containers/main/destinations/destination-drawer/index.tsx @@ -5,6 +5,7 @@ import styled from 'styled-components'; import { useDrawerStore } from '@/store'; import { CardDetails } from '@/components'; import buildDrawerItem from './build-drawer-item'; +import { ConditionDetails } from '@/reuseable-components'; import OverviewDrawer from '../../overview/overview-drawer'; import { DestinationFormBody } from '../destination-form-body'; import { OVERVIEW_ENTITY_TYPES, type ActualDestination } from '@/types'; @@ -20,6 +21,12 @@ const FormContainer = styled.div` overflow-y: auto; `; +const DataContainer = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + export const DestinationDrawer: React.FC = () => { const { selectedItem, setSelectedItem } = useDrawerStore(); const { destinations: destinationTypes } = useDestinationTypes(); @@ -128,7 +135,10 @@ export const DestinationDrawer: React.FC = () => { /> ) : ( - + + + + )} ); diff --git a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/choose-destination-filters/index.tsx b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/choose-destination-filters/index.tsx deleted file mode 100644 index cb652ffd2..000000000 --- a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/choose-destination-filters/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, { Dispatch, SetStateAction, useState } from 'react'; -import styled from 'styled-components'; -import { SignalUppercase } from '@/utils'; -import type { DropdownOption } from '@/types'; -import { Dropdown, Input, MonitoringCheckboxes } from '@/reuseable-components'; - -interface Props { - selectedTag: DropdownOption | undefined; - onTagSelect: (option: DropdownOption) => void; - onSearch: (value: string) => void; - selectedMonitors: SignalUppercase[]; - setSelectedMonitors: Dispatch>; -} - -const Container = styled.div` - display: flex; - align-items: center; - gap: 12px; -`; - -const WidthConstraint = styled.div` - width: 160px; - margin-right: 8px; -`; - -const DROPDOWN_OPTIONS = [ - { value: 'All types', id: 'all' }, - { value: 'Managed', id: 'managed' }, - { value: 'Self-hosted', id: 'self hosted' }, -]; - -export const ChooseDestinationFilters: React.FC = ({ selectedTag, onTagSelect, onSearch, selectedMonitors, setSelectedMonitors }) => { - const [searchTerm, setSearchTerm] = useState(''); - - const handleSearchChange = (e: React.ChangeEvent) => { - const value = e.target.value; - setSearchTerm(value); - onSearch(value); - }; - - return ( - - - - - - {}} /> - - - - ); -}; diff --git a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/index.tsx b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/index.tsx index d40486e9e..afd51997b 100644 --- a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/index.tsx +++ b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/index.tsx @@ -2,59 +2,83 @@ import React, { useMemo, useState } from 'react'; import styled from 'styled-components'; import { SignalUppercase } from '@/utils'; import { useDestinationTypes } from '@/hooks'; +import type { DestinationTypeItem } from '@/types'; import { DestinationsList } from './destinations-list'; -import { Divider, SectionTitle } from '@/reuseable-components'; -import type { DropdownOption, DestinationTypeItem } from '@/types'; -import { ChooseDestinationFilters } from './choose-destination-filters'; +import { Divider, Dropdown, Input, MonitoringCheckboxes, SectionTitle } from '@/reuseable-components'; interface Props { onSelect: (item: DestinationTypeItem) => void; + hidden?: boolean; } -const DEFAULT_MONITORS: SignalUppercase[] = ['LOGS', 'METRICS', 'TRACES']; -const DEFAULT_DROPDOWN_VALUE = { id: 'all', value: 'All types' }; - const Container = styled.div` display: flex; flex-direction: column; gap: 24px; `; -export const ChooseDestinationBody: React.FC = ({ onSelect }) => { - const [searchValue, setSearchValue] = useState(''); +const Filters = styled.div` + display: flex; + align-items: center; + gap: 12px; +`; + +const WidthConstraint = styled.div` + width: 160px; + margin-right: 8px; +`; + +const DROPDOWN_OPTIONS = [ + { value: 'All types', id: 'all' }, + { value: 'Managed', id: 'managed' }, + { value: 'Self-hosted', id: 'self hosted' }, +]; + +const DEFAULT_CATEGORY = DROPDOWN_OPTIONS[0]; +const DEFAULT_MONITORS: SignalUppercase[] = ['LOGS', 'METRICS', 'TRACES']; + +export const ChooseDestinationBody: React.FC = ({ onSelect, hidden }) => { + const [search, setSearch] = useState(''); + const [selectedCategory, setSelectedCategory] = useState(DEFAULT_CATEGORY); const [selectedMonitors, setSelectedMonitors] = useState(DEFAULT_MONITORS); - const [dropdownValue, setDropdownValue] = useState(DEFAULT_DROPDOWN_VALUE); - const { destinations } = useDestinationTypes(); + const { destinations: destinationTypes } = useDestinationTypes(); const filteredDestinations = useMemo(() => { - return destinations + return destinationTypes .map((category) => { const filteredItems = category.items.filter((item) => { - const matchesSearch = searchValue ? item.displayName.toLowerCase().includes(searchValue.toLowerCase()) : true; - const matchesDropdown = dropdownValue.id !== 'all' ? category.name === dropdownValue.id : true; - const matchesMonitor = selectedMonitors.length ? selectedMonitors.some((monitor) => item.supportedSignals[monitor.toLowerCase()]?.supported) : true; + const matchesSearch = !search || item.displayName.toLowerCase().includes(search.toLowerCase()); + const matchesCategory = selectedCategory.id === 'all' || selectedCategory.id === category.name; + const matchesMonitor = selectedMonitors.some((monitor) => item.supportedSignals[monitor.toLowerCase()]?.supported); - return matchesSearch && matchesDropdown && matchesMonitor; + return matchesSearch && matchesCategory && matchesMonitor; }); return { ...category, items: filteredItems }; }) - .filter((category) => category.items.length > 0); // Filter out empty categories - }, [destinations, searchValue, dropdownValue, selectedMonitors]); + .filter(({ items }) => !!items.length); // Filter out empty categories + }, [destinationTypes, search, selectedCategory, selectedMonitors]); + + if (hidden) return null; return ( - setDropdownValue(opt)} - onSearch={setSearchValue} - selectedMonitors={selectedMonitors} - setSelectedMonitors={setSelectedMonitors} - /> + + + + setSearch(value)} /> + + + setSelectedCategory(opt)} onDeselect={() => {}} /> + + + + + ); -}; +}; \ No newline at end of file diff --git a/frontend/webapp/containers/main/destinations/destination-modal/index.tsx b/frontend/webapp/containers/main/destinations/destination-modal/index.tsx index 07c54092d..723c7e964 100644 --- a/frontend/webapp/containers/main/destinations/destination-modal/index.tsx +++ b/frontend/webapp/containers/main/destinations/destination-modal/index.tsx @@ -125,17 +125,21 @@ export const DestinationModal: React.FC = ({ isOnboard - {!!selectedItem ? ( + {/* + in other modals we would render this out, but for this case we will use "hidden" instead, + this is to preserve the filters-state when going back-and-forth between selections + */} + diff --git a/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx b/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx index 69f151a2d..60151769f 100644 --- a/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx +++ b/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx @@ -1,5 +1,5 @@ 'use client'; -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import styled from 'styled-components'; import MultiSourceControl from '../multi-source-control'; import { OverviewActionMenuContainer } from '../overview-actions-menu'; @@ -17,10 +17,16 @@ const NODE_HEIGHT = 80; export default function OverviewDataFlowContainer() { const { containerRef, containerWidth, containerHeight } = useContainerSize(); + const { data, filteredData, startPolling } = useComputePlatform(); const { handleNodeClick } = useNodeDataFlowHandlers(); - const { data, filteredData } = useComputePlatform(); const { metrics } = useMetrics(); + useEffect(() => { + // this is to start polling on component mount in an attempt to fix any initial errors with sources/destinations + if (!!data?.computePlatform.k8sActualSources.length || !!data?.computePlatform.destinations.length) startPolling(); + // only on-mount, if we include "data" this might trigger on every refetch + }, []); + // Memoized node and edge builder to improve performance const { nodes, edges } = useMemo(() => { return buildNodesAndEdges({ diff --git a/frontend/webapp/hooks/compute-platform/useComputePlatform.ts b/frontend/webapp/hooks/compute-platform/useComputePlatform.ts index 588e0062b..f3835e368 100644 --- a/frontend/webapp/hooks/compute-platform/useComputePlatform.ts +++ b/frontend/webapp/hooks/compute-platform/useComputePlatform.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import { useQuery } from '@apollo/client'; import { useBooleanStore } from '@/store'; import { GET_COMPUTE_PLATFORM } from '@/graphql'; @@ -25,7 +25,7 @@ export const useComputePlatform = (): UseComputePlatformHook => { let retries = 0; const maxRetries = 5; - const retryInterval = 2 * 1000; // time in milliseconds + const retryInterval = 3 * 1000; // time in milliseconds while (retries < maxRetries) { await new Promise((resolve) => setTimeout(resolve, retryInterval)); @@ -36,11 +36,6 @@ export const useComputePlatform = (): UseComputePlatformHook => { togglePolling(false); }, [refetch, togglePolling]); - // this is to start polling on component mount in an attempt to fix any initial errors with sources/destinations - useEffect(() => { - startPolling(); - }, []); - const mappedData = useMemo(() => { if (!data) return undefined;