diff --git a/frontend/webapp/components/overview/sources/managed.sources.table/index.tsx b/frontend/webapp/components/overview/sources/managed.sources.table/index.tsx index 7367ac979..56869e7d0 100644 --- a/frontend/webapp/components/overview/sources/managed.sources.table/index.tsx +++ b/frontend/webapp/components/overview/sources/managed.sources.table/index.tsx @@ -5,7 +5,7 @@ import { SourcesTableRow } from './sources.table.row'; import { SourcesTableHeader } from './sources.table.header'; import { EmptyList } from '@/components/lists'; import { OVERVIEW } from '@/utils'; -import { ConnectionsIcons, DeleteSource } from '@/components'; + import { ModalPositionX, ModalPositionY } from '@/design.system/modal/types'; type TableProps = { @@ -16,8 +16,9 @@ type TableProps = { filterSourcesByKind?: (kinds: string[]) => void; filterSourcesByNamespace?: (namespaces: string[]) => void; filterSourcesByLanguage?: (languages: string[]) => void; - toggleActionStatus?: (ids: string[], disabled: boolean) => void; deleteSourcesHandler?: (sources: ManagedSource[]) => void; + filterByConditionStatus?: (status: 'All' | 'True' | 'False') => void; + filterByConditionMessage: (message: string[]) => void; }; const SELECT_ALL_CHECKBOX = 'select_all'; @@ -27,11 +28,12 @@ export const ManagedSourcesTable: React.FC = ({ namespaces, onRowClick, sortSources, - toggleActionStatus, filterSourcesByKind, deleteSourcesHandler, filterSourcesByNamespace, filterSourcesByLanguage, + filterByConditionStatus, + filterByConditionMessage, }) => { const [selectedCheckbox, setSelectedCheckbox] = useState([]); const [showModal, setShowModal] = useState(false); @@ -94,13 +96,14 @@ export const ManagedSourcesTable: React.FC = ({ data={data} namespaces={namespaces} sortSources={sortSources} - toggleActionStatus={toggleActionStatus} filterSourcesByKind={filterSourcesByKind} filterSourcesByNamespace={filterSourcesByNamespace} filterSourcesByLanguage={filterSourcesByLanguage} selectedCheckbox={selectedCheckbox} onSelectedCheckboxChange={onSelectedCheckboxChange} deleteSourcesHandler={() => setShowModal(true)} + filterByConditionStatus={filterByConditionStatus} + filterByConditionMessage={filterByConditionMessage} /> {showModal && ( void; filterSourcesByKind?: (kinds: string[]) => void; filterSourcesByNamespace?: (namespaces: string[]) => void; - toggleActionStatus?: (ids: string[], disabled: boolean) => void; selectedCheckbox: string[]; onSelectedCheckboxChange: (id: string) => void; deleteSourcesHandler: () => void; filterSourcesByLanguage?: (languages: string[]) => void; + filterByConditionStatus?: (status: 'All' | 'True' | 'False') => void; + filterByConditionMessage: (message: string[]) => void; } export function SourcesTableHeader({ @@ -72,9 +75,13 @@ export function SourcesTableHeader({ deleteSourcesHandler, selectedCheckbox, onSelectedCheckboxChange, + filterByConditionStatus, + filterByConditionMessage, }: ActionsTableHeaderProps) { const [currentSortId, setCurrentSortId] = useState(''); const [groupNamespaces, setGroupNamespaces] = useState([]); + const [showSourcesWithIssues, setShowSourcesWithIssues] = useState(false); + const [groupErrorMessage, setGroupErrorMessage] = useState([]); const [groupLanguages, setGroupLanguages] = useState([ 'javascript', 'python', @@ -88,6 +95,20 @@ export function SourcesTableHeader({ K8SSourceTypes.DAEMON_SET, ]); + const { groupErrorMessages } = useSources(); + + useEffect(() => { + if (!filterByConditionStatus) { + return; + } + + setGroupErrorMessage(groupErrorMessages()); + + showSourcesWithIssues + ? filterByConditionStatus('False') + : filterByConditionStatus('All'); + }, [showSourcesWithIssues, data]); + useEffect(() => { if (namespaces) { setGroupNamespaces( @@ -140,6 +161,21 @@ export function SourcesTableHeader({ filterSourcesByLanguage && filterSourcesByLanguage(newGroup); } + function onErrorClick(message: string) { + let newGroup: string[] = []; + if (groupErrorMessage.includes(message)) { + setGroupErrorMessage( + groupErrorMessage.filter((item) => item !== message) + ); + newGroup = groupErrorMessage.filter((item) => item !== message); + } else { + setGroupErrorMessage([...groupErrorMessage, message]); + newGroup = [...groupErrorMessage, message]; + } + + filterByConditionMessage(newGroup); + } + const sourcesGroups = useMemo(() => { if (!namespaces) return []; @@ -161,7 +197,7 @@ export function SourcesTableHeader({ totalNamespacesWithApps === 1, })); - return [ + const actionsGroup = [ { label: 'Language', subTitle: 'Filter', @@ -323,6 +359,24 @@ export function SourcesTableHeader({ condition: true, }, ]; + + if (showSourcesWithIssues) { + actionsGroup.unshift({ + label: 'Error', + subTitle: 'Filter by error message', + condition: true, + items: groupErrorMessages().map((item) => ({ + label: item, + onClick: () => onErrorClick(item), + id: item, + selected: groupErrorMessage.includes(item), + disabled: + groupErrorMessage.length === 1 && groupErrorMessage.includes(item), + })), + }); + } + + return actionsGroup; }, [namespaces, groupNamespaces, data]); return ( @@ -336,6 +390,16 @@ export function SourcesTableHeader({ {`${data.length} ${OVERVIEW.MENU.SOURCES}`} + + {groupErrorMessage.length > 0 && ( + + setShowSourcesWithIssues(!showSourcesWithIssues) + } + label={'Show Sources with Errors'} + /> + )} {selectedCheckbox.length > 0 && ( { @@ -119,12 +121,14 @@ export function ManagedSourcesContainer() { diff --git a/frontend/webapp/hooks/sources/useSources.ts b/frontend/webapp/hooks/sources/useSources.ts index 1aed8ca86..7264bba74 100644 --- a/frontend/webapp/hooks/sources/useSources.ts +++ b/frontend/webapp/hooks/sources/useSources.ts @@ -175,6 +175,51 @@ export function useSources() { setSortedSources(filtered); } + function filterByConditionMessage(message: string[]) { + const sourcesWithCondition = sources?.filter((deployment) => + deployment.instrumented_application_details.conditions.some( + (condition) => condition.status === 'False' + ) + ); + + const filteredSources = sourcesWithCondition?.filter((deployment) => + deployment.instrumented_application_details.conditions.some((condition) => + message.includes(condition.message) + ) + ); + + setSortedSources(filteredSources || []); + } + + const filterByConditionStatus = (status: 'All' | 'True' | 'False') => { + if (status === 'All') { + setSortedSources(sources); + return; + } + + const filteredSources = sources?.filter((deployment) => + deployment.instrumented_application_details.conditions.some( + (condition) => condition.status === status + ) + ); + + setSortedSources(filteredSources || []); + }; + + const groupErrorMessages = (): string[] => { + const errorMessagesSet: Set = new Set(); + + sources?.forEach((deployment) => { + deployment.instrumented_application_details?.conditions + .filter((condition) => condition.status === 'False') + .forEach((condition) => { + errorMessagesSet.add(condition.message); // Using Set to avoid duplicates + }); + }); + + return Array.from(errorMessagesSet); + }; + return { upsertSources, refetchSources, @@ -187,5 +232,8 @@ export function useSources() { instrumentedNamespaces, namespaces: namespaces?.namespaces || [], deleteSourcesHandler, + filterByConditionStatus, + groupErrorMessages, + filterByConditionMessage, }; }