@@ -6,7 +6,6 @@ import cx from 'classnames';
66import sub from 'date-fns/sub' ;
77import { useQueryState } from 'nuqs' ;
88import { useForm } from 'react-hook-form' ;
9- import { StringParam , useQueryParam , withDefault } from 'use-query-params' ;
109import { SourceKind , TSource } from '@hyperdx/common-utils/dist/types' ;
1110import {
1211 Badge ,
@@ -24,6 +23,7 @@ import {
2423 Text ,
2524 Tooltip ,
2625} from '@mantine/core' ;
26+ import { notifications } from '@mantine/notifications' ;
2727
2828import { TimePicker } from '@/components/TimePicker' ;
2929
@@ -757,57 +757,97 @@ const defaultTimeRange = parseTimeQuery('Past 1h', false);
757757
758758const CHART_HEIGHT = 300 ;
759759
760+ const findSource = (
761+ sources : TSource [ ] | undefined ,
762+ filters : {
763+ kind ?: SourceKind ;
764+ connection ?: string ;
765+ id ?: string ;
766+ } ,
767+ ) => {
768+ if ( ! sources ) return undefined ;
769+
770+ const { kind, connection, id } = filters ;
771+ return sources . find (
772+ s =>
773+ ( kind === undefined || s . kind === kind ) &&
774+ ( id === undefined || s . id === id ) &&
775+ ( connection === undefined || s . connection === connection ) ,
776+ ) ;
777+ } ;
778+
760779export const resolveSourceIds = (
761780 _logSourceId : string | null | undefined ,
762781 _metricSourceId : string | null | undefined ,
763782 sources : TSource [ ] | undefined ,
764783) => {
765- if ( _logSourceId && _metricSourceId ) {
766- return [ _logSourceId , _metricSourceId ] ;
784+ if ( ( _logSourceId && _metricSourceId ) || ! sources ) {
785+ return {
786+ logSourceId : _logSourceId ?? undefined ,
787+ metricSourceId : _metricSourceId ?? undefined ,
788+ } ;
767789 }
768790
769- // Default the metric source to the first one from the same connection as the log source
770- if ( _logSourceId && ! _metricSourceId && sources ) {
771- const { connection } = sources . find ( s => s . id === _logSourceId ) ?? { } ;
772- const metricSource = sources . find (
773- s => s . connection === connection && s . kind === SourceKind . Metric ,
774- ) ;
775- return [ _logSourceId , metricSource ?. id ] ;
791+ // Find a default metric source that matches the existing log source
792+ if ( _logSourceId && ! _metricSourceId ) {
793+ const { connection, metricSourceId : correlatedMetricSourceId } =
794+ findSource ( sources , { id : _logSourceId } ) ?? { } ;
795+ const metricSourceId =
796+ ( correlatedMetricSourceId &&
797+ findSource ( sources , { id : correlatedMetricSourceId } ) ?. id ) ??
798+ ( connection &&
799+ findSource ( sources , { connection, kind : SourceKind . Metric } ) ?. id ) ;
800+ return { logSourceId : _logSourceId , metricSourceId } ;
776801 }
777802
778- // Default the log source to the first one from the same connection as the metric source
779- if ( ! _logSourceId && _metricSourceId && sources ) {
780- const { connection } = sources . find ( s => s . id === _metricSourceId ) ?? { } ;
781- const logSource = sources . find (
782- s => s . connection === connection && s . kind === SourceKind . Log ,
783- ) ;
784- return [ logSource ?. id , _metricSourceId ] ;
803+ // Find a default log source that matches the existing metric source
804+ if ( ! _logSourceId && _metricSourceId ) {
805+ const { connection, logSourceId : correlatedLogSourceId } =
806+ findSource ( sources , { id : _metricSourceId } ) ?? { } ;
807+ const logSourceId =
808+ ( correlatedLogSourceId &&
809+ findSource ( sources , { id : correlatedLogSourceId } ) ?. id ) ??
810+ ( connection &&
811+ findSource ( sources , { connection, kind : SourceKind . Log } ) ?. id ) ;
812+ return { logSourceId, metricSourceId : _metricSourceId } ;
785813 }
786814
787- // Find a Log and Metric source from the same connection
788- if ( sources ) {
789- const connections = sources . map ( s => s . connection ) ;
790- const connectionWithBothSourceKinds = connections . find (
791- conn =>
792- sources . some ( s => s . connection === conn && s . kind === SourceKind . Log ) &&
793- sources . some (
794- s => s . connection === conn && s . kind === SourceKind . Metric ,
795- ) ,
796- ) ;
797- const logSource = sources . find (
798- s =>
799- s . connection === connectionWithBothSourceKinds &&
800- s . kind === SourceKind . Log ,
801- ) ;
802- const metricSource = sources . find (
803- s =>
804- s . connection === connectionWithBothSourceKinds &&
805- s . kind === SourceKind . Metric ,
806- ) ;
807- return [ logSource ?. id , metricSource ?. id ] ;
815+ // Find any two correlated log and metric sources
816+ const logSourceWithMetricSource = sources . find (
817+ s =>
818+ s . kind === SourceKind . Log &&
819+ s . metricSourceId &&
820+ findSource ( sources , { id : s . metricSourceId } ) ,
821+ ) ;
822+
823+ if ( logSourceWithMetricSource ) {
824+ return {
825+ logSourceId : logSourceWithMetricSource . id ,
826+ metricSourceId : logSourceWithMetricSource . metricSourceId ,
827+ } ;
808828 }
809829
810- return [ _logSourceId , _metricSourceId ] ;
830+ // Find a Log and Metric source from the same connection
831+ const connections = Array . from ( new Set ( sources . map ( s => s . connection ) ) ) ;
832+ const connectionWithBothSourceKinds = connections . find (
833+ connection =>
834+ findSource ( sources , { connection, kind : SourceKind . Log } ) &&
835+ findSource ( sources , { connection, kind : SourceKind . Metric } ) ,
836+ ) ;
837+ const logSource = connectionWithBothSourceKinds
838+ ? findSource ( sources , {
839+ connection : connectionWithBothSourceKinds ,
840+ kind : SourceKind . Log ,
841+ } )
842+ : undefined ;
843+ const metricSource = connectionWithBothSourceKinds
844+ ? findSource ( sources , {
845+ connection : connectionWithBothSourceKinds ,
846+ kind : SourceKind . Metric ,
847+ } )
848+ : undefined ;
849+
850+ return { logSourceId : logSource ?. id , metricSourceId : metricSource ?. id } ;
811851} ;
812852
813853function KubernetesDashboardPage ( ) {
@@ -816,7 +856,7 @@ function KubernetesDashboardPage() {
816856 const [ _logSourceId , setLogSourceId ] = useQueryState ( 'logSource' ) ;
817857 const [ _metricSourceId , setMetricSourceId ] = useQueryState ( 'metricSource' ) ;
818858
819- const [ logSourceId , metricSourceId ] = useMemo (
859+ const { logSourceId, metricSourceId } = useMemo (
820860 ( ) => resolveSourceIds ( _logSourceId , _metricSourceId , sources ) ,
821861 [ _logSourceId , _metricSourceId , sources ] ,
822862 ) ;
@@ -846,22 +886,59 @@ function KubernetesDashboardPage() {
846886 watch ( ( data , { name, type } ) => {
847887 if ( name === 'logSourceId' && type === 'change' ) {
848888 setLogSourceId ( data . logSourceId ?? null ) ;
889+
890+ // Default to the log source's correlated metric source
891+ if ( data . logSourceId && sources ) {
892+ const logSource = findSource ( sources , { id : data . logSourceId } ) ;
893+ const correlatedMetricSource = logSource ?. metricSourceId
894+ ? findSource ( sources , { id : logSource . metricSourceId } )
895+ : undefined ;
896+ if (
897+ correlatedMetricSource &&
898+ correlatedMetricSource . id !== data . metricSourceId
899+ ) {
900+ setMetricSourceId ( correlatedMetricSource . id ) ;
901+ notifications . show ( {
902+ id : `${ correlatedMetricSource . id } -auto-correlated-metric-source` ,
903+ title : 'Updated Metrics Source' ,
904+ message : `Using correlated metrics source: ${ correlatedMetricSource . name } ` ,
905+ } ) ;
906+ } else if ( logSource && ! correlatedMetricSource ) {
907+ notifications . show ( {
908+ id : `${ logSource . id } -not-correlated` ,
909+ title : 'Warning' ,
910+ message : `The selected logs source is not correlated with a metrics source. Source correlations can be configured in Team Settings.` ,
911+ color : 'yellow' ,
912+ } ) ;
913+ }
914+ }
849915 } else if ( name === 'metricSourceId' && type === 'change' ) {
850916 setMetricSourceId ( data . metricSourceId ?? null ) ;
917+ const metricSource = data . metricSourceId
918+ ? findSource ( sources , { id : data . metricSourceId } )
919+ : undefined ;
920+ if (
921+ metricSource &&
922+ data . logSourceId &&
923+ metricSource . logSourceId !== data . logSourceId
924+ ) {
925+ notifications . show ( {
926+ id : `${ metricSource . id } -not-correlated` ,
927+ title : 'Warning' ,
928+ message : `The selected metrics source is not correlated with the selected logs source. Source correlations can be configured in Team Settings.` ,
929+ color : 'yellow' ,
930+ } ) ;
931+ }
851932 }
852933 } ) ;
853934
854- const [ activeTab , setActiveTab ] = useQueryParam (
855- 'tab' ,
856- withDefault ( StringParam , 'pods' ) ,
857- { updateType : 'replaceIn' } ,
858- ) ;
935+ const [ activeTab , setActiveTab ] = useQueryState ( 'tab' , {
936+ defaultValue : 'pods' ,
937+ } ) ;
859938
860- const [ searchQuery , setSearchQuery ] = useQueryParam (
861- 'q' ,
862- withDefault ( StringParam , '' ) ,
863- { updateType : 'replaceIn' } ,
864- ) ;
939+ const [ searchQuery , setSearchQuery ] = useQueryState ( 'q' , {
940+ defaultValue : '' ,
941+ } ) ;
865942
866943 const {
867944 searchedTimeRange : dateRange ,
0 commit comments