diff --git a/frontend/services/destination_recognition/destination_finder.go b/frontend/services/destination_recognition/destination_finder.go
index 20258eff75..6810272660 100644
--- a/frontend/services/destination_recognition/destination_finder.go
+++ b/frontend/services/destination_recognition/destination_finder.go
@@ -2,6 +2,7 @@ package destination_recognition
import (
"context"
+ "strings"
odigosv1 "github.com/odigos-io/odigos/api/odigos/v1alpha1"
"github.com/odigos-io/odigos/common"
@@ -11,8 +12,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
-var SupportedDestinationType = []common.DestinationType{common.JaegerDestinationType, common.ElasticsearchDestinationType}
-
type DestinationDetails struct {
Type common.DestinationType `json:"type"`
Fields map[string]string `json:"fields"`
@@ -25,43 +24,42 @@ type IDestinationFinder interface {
}
func GetAllPotentialDestinationDetails(ctx context.Context, namespaces []k8s.Namespace, dests *odigosv1.DestinationList) ([]DestinationDetails, error) {
- var destinationFinder IDestinationFinder
var destinationDetails []DestinationDetails
- var err error
for _, ns := range namespaces {
- err = client.ListWithPages(client.DefaultPageSize, kube.DefaultClient.CoreV1().Services(ns.Name).List,
- ctx, metav1.ListOptions{}, func(services *k8s.ServiceList) error {
- for _, service := range services.Items {
- for _, destinationType := range SupportedDestinationType {
- destinationFinder = getDestinationFinder(destinationType)
-
- if destinationFinder.isPotentialService(service) {
- potentialDestination := destinationFinder.fetchDestinationDetails(service)
-
- if !destinationExist(dests, potentialDestination, destinationFinder) {
- destinationDetails = append(destinationDetails, potentialDestination)
- }
- break
+ err := client.ListWithPages(client.DefaultPageSize, kube.DefaultClient.CoreV1().Services(ns.Name).List, ctx, metav1.ListOptions{},
+ func(svc *k8s.ServiceList) error {
+ for _, service := range svc.Items {
+ df := getDestinationFinder(service.Name)
+
+ if df != nil && df.isPotentialService(service) {
+ pd := df.fetchDestinationDetails(service)
+
+ if !destinationExist(dests, pd, df) {
+ destinationDetails = append(destinationDetails, pd)
}
+ break
}
}
+
return nil
- })
- }
+ },
+ )
- if err != nil {
- return nil, err
+ if err != nil {
+ return nil, err
+ }
}
return destinationDetails, nil
}
-func getDestinationFinder(destinationType common.DestinationType) IDestinationFinder {
- switch destinationType {
- case common.JaegerDestinationType:
+func getDestinationFinder(serviceName string) IDestinationFinder {
+ if strings.Contains(serviceName, string(common.JaegerDestinationType)) {
return &JaegerDestinationFinder{}
- case common.ElasticsearchDestinationType:
+ }
+
+ if strings.Contains(serviceName, string(common.ElasticsearchDestinationType)) {
return &ElasticSearchDestinationFinder{}
}
diff --git a/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx b/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx
index 12724ab7bc..68750cb58e 100644
--- a/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx
+++ b/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx
@@ -19,7 +19,7 @@ const Container = styled.div`
overflow-y: scroll;
`;
-const ListItem: React.FC<{ item: ConfiguredDestination; isLastItem: boolean }> = ({ item, isLastItem }) => {
+const ListItem: React.FC<{ item: ConfiguredDestination; isLastItem: boolean }> = ({ item, isLastItem, ...props }) => {
const { removeConfiguredDestination } = useAppStore((state) => state);
const [deleteWarning, setDeleteWarning] = useState(false);
@@ -37,6 +37,7 @@ const ListItem: React.FC<{ item: ConfiguredDestination; isLastItem: boolean }> =
)}
+ {...props}
/>
=
export const ConfiguredDestinationsList: React.FC<{ data: IAppState['configuredDestinations'] }> = ({ data }) => {
return (
- {data.map(({ stored }) => (
-
+ {data.map(({ stored }, idx) => (
+
))}
);
diff --git a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/index.tsx b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/index.tsx
index ae906d20da..a9bab6cd78 100644
--- a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/index.tsx
+++ b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/index.tsx
@@ -48,16 +48,16 @@ export const DestinationsList: React.FC = ({ items, setSe
return (
- {categoryItem.items.map((destinationItem) => (
+ {categoryItem.items.map((item, idx) => (
destinationItem.supportedSignals[signal].supported)}
+ monitors={Object.keys(item.supportedSignals).filter((signal) => item.supportedSignals[signal].supported)}
monitorsWithLabels
- onClick={() => setSelectedItems(destinationItem)}
+ onClick={() => setSelectedItems(item)}
/>
))}
diff --git a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/potential-destinations-list/index.tsx b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/potential-destinations-list/index.tsx
index 1712500748..98730dd928 100644
--- a/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/potential-destinations-list/index.tsx
+++ b/frontend/webapp/containers/main/destinations/destination-modal/choose-destination-body/destinations-list/potential-destinations-list/index.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { OdigosLogo } from '@/assets';
import styled from 'styled-components';
-import { DestinationTypeItem } from '@/types';
+import type { DestinationTypeItem } from '@/types';
import { usePotentialDestinations } from '@/hooks';
import { DataTab, SectionTitle, SkeletonLoader } from '@/reuseable-components';
@@ -31,10 +31,10 @@ export const PotentialDestinationsList: React.FC = ({ setSelectedItems })
{loading ? (
) : (
- data.map((item) => (
+ data.map((item, idx) => (
{
cy.visit(ROUTES.CHOOSE_DESTINATION);
cy.contains('button', BUTTONS.ADD_DESTINATION).click();
cy.wait('@gql').then(() => {
- cy.get(DATA_IDS.SELECT_DESTINATION).contains(SELECTED_ENTITIES.DESTINATION).should('exist').click();
- expect(DATA_IDS.SELECT_DESTINATION_AUTOFILL_FIELD).to.not.be.empty;
+ cy.get(DATA_IDS.SELECT_DESTINATION).contains(SELECTED_ENTITIES.DESTINATION_DISPLAY_NAME).should('exist').click();
+ cy.get(DATA_IDS.SELECT_DESTINATION_AUTOFILL_FIELD).should('have.value', SELECTED_ENTITIES.DESTINATION_AUTOFILL_VALUE);
});
});
diff --git a/frontend/webapp/cypress/e2e/04-destinations.cy.ts b/frontend/webapp/cypress/e2e/04-destinations.cy.ts
index a510d03c74..02a20544e1 100644
--- a/frontend/webapp/cypress/e2e/04-destinations.cy.ts
+++ b/frontend/webapp/cypress/e2e/04-destinations.cy.ts
@@ -18,8 +18,8 @@ describe('Destinations CRUD', () => {
cy.get(DATA_IDS.ADD_ENTITY).click();
cy.get(DATA_IDS.ADD_DESTINATION).click();
cy.get(DATA_IDS.MODAL_ADD_DESTINATION).should('exist');
- cy.get(DATA_IDS.SELECT_DESTINATION).contains(SELECTED_ENTITIES.DESTINATION).click();
- expect(DATA_IDS.SELECT_DESTINATION_AUTOFILL_FIELD).to.not.be.empty;
+ cy.get(DATA_IDS.SELECT_DESTINATION).contains(SELECTED_ENTITIES.DESTINATION_DISPLAY_NAME).should('exist').click();
+ cy.get(DATA_IDS.SELECT_DESTINATION_AUTOFILL_FIELD).should('have.value', SELECTED_ENTITIES.DESTINATION_AUTOFILL_VALUE);
cy.get('button').contains(BUTTONS.DONE).click();
cy.wait('@gql').then(() => {
@@ -35,7 +35,7 @@ describe('Destinations CRUD', () => {
updateEntity(
{
nodeId: DATA_IDS.DESTINATION_NODE,
- nodeContains: SELECTED_ENTITIES.DESTINATION,
+ nodeContains: SELECTED_ENTITIES.DESTINATION_DISPLAY_NAME,
fieldKey: DATA_IDS.TITLE,
fieldValue: TEXTS.UPDATED_NAME,
},
@@ -58,7 +58,7 @@ describe('Destinations CRUD', () => {
deleteEntity(
{
nodeId: DATA_IDS.DESTINATION_NODE,
- nodeContains: SELECTED_ENTITIES.DESTINATION,
+ nodeContains: SELECTED_ENTITIES.DESTINATION_DISPLAY_NAME,
warnModalTitle: TEXTS.DESTINATION_WARN_MODAL_TITLE,
warnModalNote: TEXTS.DESTINATION_WARN_MODAL_NOTE,
},
diff --git a/frontend/webapp/hooks/destinations/usePotentialDestinations.ts b/frontend/webapp/hooks/destinations/usePotentialDestinations.ts
index d4ab2d837a..fe2efa35c8 100644
--- a/frontend/webapp/hooks/destinations/usePotentialDestinations.ts
+++ b/frontend/webapp/hooks/destinations/usePotentialDestinations.ts
@@ -1,56 +1,74 @@
import { useMemo } from 'react';
import { safeJsonParse } from '@/utils';
import { useQuery } from '@apollo/client';
+import { IAppState, useAppStore } from '@/store';
import { GetDestinationTypesResponse } from '@/types';
import { GET_DESTINATION_TYPE, GET_POTENTIAL_DESTINATIONS } from '@/graphql';
-interface DestinationDetails {
+interface PotentialDestination {
type: string;
fields: string;
}
interface GetPotentialDestinationsData {
- potentialDestinations: DestinationDetails[];
+ potentialDestinations: PotentialDestination[];
}
+const checkIfConfigured = (configuredDest: IAppState['configuredDestinations'][0], potentialDest: PotentialDestination, autoFilledFields: Record) => {
+ const typesMatch = configuredDest.stored.type === potentialDest.type;
+ if (!typesMatch) return false;
+
+ let fieldsMatch = false;
+
+ for (const { key, value } of configuredDest.form.fields) {
+ if (Object.hasOwn(autoFilledFields, key)) {
+ if (autoFilledFields[key] === value) {
+ fieldsMatch = true;
+ } else {
+ fieldsMatch = false;
+ break;
+ }
+ }
+ }
+
+ return fieldsMatch;
+};
+
export const usePotentialDestinations = () => {
- const { data: destinationTypesData } =
- useQuery(GET_DESTINATION_TYPE);
- const { loading, error, data } = useQuery(
- GET_POTENTIAL_DESTINATIONS
- );
+ const { configuredDestinations } = useAppStore();
+ const { data: { destinationTypes } = {} } = useQuery(GET_DESTINATION_TYPE);
+ const { loading, error, data: { potentialDestinations } = {} } = useQuery(GET_POTENTIAL_DESTINATIONS);
const mappedPotentialDestinations = useMemo(() => {
- if (!destinationTypesData || !data) return [];
+ if (!destinationTypes || !potentialDestinations) return [];
// Create a deep copy of destination types to manipulate
- const destinationTypesCopy = JSON.parse(
- JSON.stringify(destinationTypesData.destinationTypes.categories)
- );
+ const categories: GetDestinationTypesResponse['destinationTypes']['categories'] = JSON.parse(JSON.stringify(destinationTypes.categories));
// Map over the potential destinations
- return data.potentialDestinations.map((destination) => {
- for (const category of destinationTypesCopy) {
- const index = category.items.findIndex(
- (item) => item.type === destination.type
- );
- if (index !== -1) {
- // Spread the matched destination type data into the potential destination
- const matchedType = category.items[index];
- category.items.splice(index, 1); // Remove the matched item from destination types
- return {
- ...destination,
- ...matchedType,
- fields: safeJsonParse<{ [key: string]: string }>(
- destination.fields,
- {}
- ),
- };
+ return potentialDestinations
+ .map((pd) => {
+ for (const category of categories) {
+ const autoFilledFields = safeJsonParse<{ [key: string]: string }>(pd.fields, {});
+ const alreadyConfigured = !!configuredDestinations.find((cd) => checkIfConfigured(cd, pd, autoFilledFields));
+
+ if (!alreadyConfigured) {
+ const idx = category.items.findIndex((item) => item.type === pd.type);
+
+ if (idx !== -1) {
+ return {
+ // Spread the matched destination type data into the potential destination
+ ...category.items[idx],
+ fields: autoFilledFields,
+ };
+ }
+ }
}
- }
- return destination;
- });
- }, [destinationTypesData, data]);
+
+ return null;
+ })
+ .filter((pd) => !!pd);
+ }, [configuredDestinations, destinationTypes, potentialDestinations]);
return {
loading,