Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@ type FieldConfig struct {
Type string `yaml:"type" json:"type"`
Format string `yaml:"format,omitempty" json:"format,omitempty"`
Description string `yaml:"description" json:"description"`
// lokiLabel flag is for documentation only. Use loki.labels instead
Filter string `yaml:"filter,omitempty" json:"filter,omitempty"`
}

type Frontend struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/handler/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (h *Handlers) GetUDNIdss(ctx context.Context) func(w http.ResponseWriter, r
}
for _, udn := range udns {
md := udn.Object["metadata"].(map[string]interface{})
values = append(values, fmt.Sprintf("%s.%s", md["namespace"], md["name"]))
values = append(values, fmt.Sprintf("%s/%s", md["namespace"], md["name"]))
}
writeJSON(w, http.StatusOK, utils.NonEmpty(utils.Dedup(values)))
}
Expand Down
2 changes: 1 addition & 1 deletion web/locales/en/plugin__netobserv-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"The flow contains packets with various flags: ": "The flow contains packets with various flags: ",
"ICMP type provided but protocol is {{proto}}": "ICMP type provided but protocol is {{proto}}",
"ICMP code provided but protocol is {{proto}}": "ICMP code provided but protocol is {{proto}}",
"None": "None",
"Invalid data provided. Check JSON for details.": "Invalid data provided. Check JSON for details.",
"dropped": "dropped",
"dropped by": "dropped by",
Expand Down Expand Up @@ -161,6 +160,7 @@
"M": "M",
"S": "S",
"XS": "XS",
"None": "None",
"Step {{index}}/{{count}}": "Step {{index}}/{{count}}",
"Step {{index}}/{{count}}_plural": "Step {{index}}/{{count}}",
"Previous tip": "Previous tip",
Expand Down
28 changes: 26 additions & 2 deletions web/src/components/__tests-data__/columns.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable max-len */
import * as _ from 'lodash';
import { Column, ColumnsId, getDefaultColumns } from '../../utils/columns';
import { Column, ColumnConfigDef, ColumnsId, getDefaultColumns } from '../../utils/columns';
import { FieldConfig } from '../../utils/fields';

export const ColumnConfigSampleDefs = [
Expand Down Expand Up @@ -170,6 +170,16 @@ export const ColumnConfigSampleDefs = [
default: false,
width: 15
},
{
id: 'SrcZone',
group: 'Source',
name: 'Zone',
field: 'SrcK8S_Zone',
filter: 'src_zone',
default: false,
width: 15,
feature: 'zones'
},
{
id: 'DstK8S_Name',
group: 'Destination',
Expand Down Expand Up @@ -303,6 +313,16 @@ export const ColumnConfigSampleDefs = [
default: false,
width: 15
},
{
id: 'DstZone',
group: 'Destination',
name: 'Zone',
field: 'DstK8S_Zone',
filter: 'dst_zone',
default: false,
width: 15,
feature: 'zones'
},
{
id: 'K8S_Name',
name: 'Names',
Expand Down Expand Up @@ -476,6 +496,7 @@ export const ColumnConfigSampleDefs = [
tooltip: 'DNS request identifier.',
field: 'DnsId',
filter: 'dns_id',
feature: 'dnsTracking',
default: false,
width: 5
},
Expand All @@ -485,6 +506,8 @@ export const ColumnConfigSampleDefs = [
name: 'DNS Latency',
tooltip: 'Time elapsed between DNS request and response.',
field: 'DnsLatencyMs',
filter: 'dns_latency',
feature: 'dnsTracking',
default: false,
width: 5
},
Expand All @@ -495,6 +518,7 @@ export const ColumnConfigSampleDefs = [
tooltip: 'DNS RCODE name from response header.',
field: 'DnsFlagsResponseCode',
filter: 'dns_flag_response_code',
feature: 'dnsTracking',
default: false,
width: 5
},
Expand All @@ -518,7 +542,7 @@ export const ColumnConfigSampleDefs = [
default: false,
width: 10
}
];
] as ColumnConfigDef[];

export const FieldConfigSample = [
{
Expand Down
12 changes: 12 additions & 0 deletions web/src/components/__tests-data__/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ export const FilterConfigSampleDefs = [
examples:
'Specify a single kubernetes name following these rules:\n - Containing any alphanumeric, hyphen, underscrore or dot character\n - Partial text like cluster, cluster-image, image-registry\n - Exact match using quotes like "cluster-image-registry"\n - Case sensitive match using quotes like "Deployment"\n - Starting text like cluster, "cluster-*"\n - Ending text like "*-registry"\n - Pattern like "cluster-*-registry", "c*-*-r*y", -i*e-'
},
{
id: 'src_zone',
name: 'Zone',
component: 'autocomplete',
category: 'source'
},
{
id: 'dst_zone',
name: 'Zone',
component: 'autocomplete',
category: 'destination'
},
{
id: 'protocol',
name: 'Protocol',
Expand Down
25 changes: 16 additions & 9 deletions web/src/components/drawer/record/record-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,18 @@ export const RecordField: React.FC<RecordFieldProps> = ({
const nthContainer = (children: (JSX.Element | undefined)[], asChild = true, childIcon = true, forcedSize?: Size) => {
return (
<Flex className={`record-field-flex-container ${forcedSize || size}`} flex={{ default: 'flex_1' }}>
{children.map((c, i) => {
const child = c ? c : emptyText();
if (i > 0 && asChild && childIcon) {
const arrow = <span className="child-arrow">{'↪'}</span>;
return sideBySideContainer(arrow, child, 'flexNone', 'flex_1', 'nowrap');
}
return child;
})}
{children.length > 0 ? (
children.map((c, i) => {
const child = c ? c : emptyText();
if (i > 0 && asChild && childIcon) {
const arrow = <span className="child-arrow">{'↪'}</span>;
return sideBySideContainer(arrow, child, 'flexNone', 'flex_1', 'nowrap');
}
return child;
})
) : (
<Text className="text-muted record-field-value">{t('n/a')}</Text>
)}
</Flex>
);
};
Expand Down Expand Up @@ -435,7 +439,10 @@ export const RecordField: React.FC<RecordFieldProps> = ({
case ColumnsId.udns: {
if (Array.isArray(value)) {
return nthContainer(
value.map(iName => simpleTextWithTooltip(iName !== '' ? String(iName) : t('None'))),
value
.map(iName => String(iName))
.filter(iName => iName !== '')
.map(iName => simpleTextWithTooltip(iName)),
Comment on lines +442 to +445
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not showing n/a greyed out for empty ones ?

Currently it's empty. That feels strange compared to other fields:

image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 6d8cec6

true,
false
);
Expand Down
46 changes: 7 additions & 39 deletions web/src/components/netflow-traffic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,6 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({
return model.config.features.includes('pktDrop');
}, [model.config.features]);

const isUdn = React.useCallback(() => {
return model.config.features.includes('udnMapping');
}, [model.config.features]);

const isPktXlat = React.useCallback(() => {
return model.config.features.includes('packetTranslation');
}, [model.config.features]);

const isNetEvents = React.useCallback(() => {
return model.config.features.includes('networkEvents');
}, [model.config.features]);

const isIPSec = React.useCallback(() => {
return model.config.features.includes('ipsec');
}, [model.config.features]);

const isPromOnly = React.useCallback(() => {
return !allowLoki() || model.dataSource === 'prom';
}, [allowLoki, model.dataSource]);
Expand All @@ -159,14 +143,6 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({
[model.config.promLabels, isPromOnly]
);

const isMultiCluster = React.useCallback(() => {
return isPromOnly() ? dataSourceHasLabels(['K8S_ClusterName']) : model.config.features.includes('multiCluster');
}, [model.config.features, dataSourceHasLabels, isPromOnly]);

const isZones = React.useCallback(() => {
return isPromOnly() ? dataSourceHasLabels(['SrcK8S_Zone', 'DstK8S_Zone']) : model.config.features.includes('zones');
}, [model.config.features, dataSourceHasLabels, isPromOnly]);

const getAvailableScopes = React.useCallback(() => {
return model.config.scopes.filter(sc => {
if (sc.feature) {
Expand Down Expand Up @@ -219,22 +195,14 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({
}, [getAvailableColumns]);

const getFilterDefs = React.useCallback(() => {
return getFilterDefinitions(model.config.filters, model.config.columns, t).filter(
fd =>
(isMultiCluster() || fd.id !== 'cluster_name') &&
(isZones() || !fd.id.endsWith('_zone')) &&
(isConnectionTracking() || fd.id !== 'id') &&
(isDNSTracking() || !fd.id.startsWith('dns_')) &&
(isPktDrop() || !fd.id.startsWith('pkt_drop_')) &&
(isFlowRTT() || fd.id !== 'time_flow_rtt') &&
(isUdn() || fd.id !== 'udns') &&
(isPktXlat() || !fd.id.startsWith('xlat_')) &&
(isNetEvents() || fd.id !== 'network_events') &&
(!isPromOnly() || checkFilterAvailable(fd, model.config.promLabels)) &&
(isIPSec() || !fd.id.startsWith('ipsec_'))
);
return getFilterDefinitions(model.config.filters, model.config.columns, t).filter(fd => {
if (fd.id === 'id') {
return isConnectionTracking();
}
return checkFilterAvailable(fd, model.config, model.dataSource);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [model.config.columns, model.config.filters, model.config.promLabels, isPromOnly]);
}, [model.config, model.dataSource]);
Comment on lines +198 to +205
Copy link
Contributor

@jpinsonneau jpinsonneau May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there was some exceptions here:

  • You already managed the UDN one
  • I see drop state and cause filters when packet drop feature is not enabled

image
image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Others filters seems to work properly 👌

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you take the operator PR too? The issue with drops is supposed to be fixed from the operator config

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using it yes

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird, I don't have them
image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the operator generated image was prior to this commit, I guess you used that old one; just triggered a new one


const getQuickFilters = React.useCallback(
(c: Config = model.config) => {
Expand Down
93 changes: 76 additions & 17 deletions web/src/utils/__tests__/filter-definitions.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ColumnConfigSampleDefs } from '../../components/__tests-data__/columns';
import { FilterDefinitionSample } from '../../components/__tests-data__/filters';
import { Config, Feature } from '../../model/config';
import { checkFilterAvailable, findFilter } from '../filter-definitions';

describe('Resource validation', () => {
Expand Down Expand Up @@ -99,35 +101,92 @@ describe('Resource checkCompletion', () => {
});
});

describe('Check availability', () => {
describe('Check availability for prometheus only', () => {
const simpleFilter = findFilter(FilterDefinitionSample, 'src_name')!;
const k8sFilter = findFilter(FilterDefinitionSample, 'src_resource')!;
const getConfig = (promLabels: string[]): Config => {
return { promLabels, dataSources: ['prom'] } as Config;
};

it('should be available', () => {
let available = checkFilterAvailable(simpleFilter, ['SrcK8S_Name', 'DstK8S_Name']);
let available = checkFilterAvailable(simpleFilter, getConfig(['SrcK8S_Name', 'DstK8S_Name']), 'prom');
expect(available).toBe(true);

available = checkFilterAvailable(k8sFilter, [
'SrcK8S_OwnerName',
'SrcK8S_OwnerType',
'SrcK8S_Namespace',
'DstK8S_OwnerName',
'DstK8S_OwnerType',
'DstK8S_Namespace'
]);
available = checkFilterAvailable(
k8sFilter,
getConfig([
'SrcK8S_OwnerName',
'SrcK8S_OwnerType',
'SrcK8S_Namespace',
'DstK8S_OwnerName',
'DstK8S_OwnerType',
'DstK8S_Namespace'
]),
'prom'
);
expect(available).toBe(true);
});

it('should not be available', () => {
let available = checkFilterAvailable(simpleFilter, ['SrcK8S_OwnerName', 'DstK8S_OwnerName']);
let available = checkFilterAvailable(simpleFilter, getConfig(['SrcK8S_OwnerName', 'DstK8S_OwnerName']), 'prom');
expect(available).toBe(false);

available = checkFilterAvailable(k8sFilter, [
'SrcK8S_OwnerName',
'SrcK8S_Namespace',
'DstK8S_OwnerName',
'DstK8S_Namespace'
]);
available = checkFilterAvailable(
k8sFilter,
getConfig(['SrcK8S_OwnerName', 'SrcK8S_Namespace', 'DstK8S_OwnerName', 'DstK8S_Namespace']),
'prom'
);
expect(available).toBe(false);
});
});

describe('Check availability against features', () => {
const getConfig = (feats: Feature[]): Config => {
return { features: feats, dataSources: ['loki'], columns: ColumnConfigSampleDefs } as Config;
};

it('with standard filters', () => {
const simpleFilter = findFilter(FilterDefinitionSample, 'src_name')!;
const k8sFilter = findFilter(FilterDefinitionSample, 'src_resource')!;

let available = checkFilterAvailable(simpleFilter, getConfig([]), 'auto');
expect(available).toBe(true);

available = checkFilterAvailable(k8sFilter, getConfig([]), 'auto');
expect(available).toBe(true);

available = checkFilterAvailable(simpleFilter, getConfig(['dnsTracking']), 'auto');
expect(available).toBe(true);

available = checkFilterAvailable(k8sFilter, getConfig(['dnsTracking']), 'auto');
expect(available).toBe(true);
});

it('with AZ filters', () => {
const azFilter = findFilter(FilterDefinitionSample, 'src_zone')!;

let available = checkFilterAvailable(azFilter, getConfig([]), 'auto');
expect(available).toBe(false);

available = checkFilterAvailable(azFilter, getConfig(['dnsTracking']), 'auto');
expect(available).toBe(false);

available = checkFilterAvailable(azFilter, getConfig(['zones']), 'auto');
expect(available).toBe(true);
});

it('with DNS filters', () => {
const dnsIdFilter = findFilter(FilterDefinitionSample, 'dns_id')!;
const dnsLatilter = findFilter(FilterDefinitionSample, 'dns_latency')!;

let available = checkFilterAvailable(dnsIdFilter, getConfig([]), 'auto');
expect(available).toBe(false);
available = checkFilterAvailable(dnsLatilter, getConfig([]), 'auto');
expect(available).toBe(false);

available = checkFilterAvailable(dnsIdFilter, getConfig(['dnsTracking']), 'auto');
expect(available).toBe(true);
available = checkFilterAvailable(dnsLatilter, getConfig(['dnsTracking']), 'auto');
expect(available).toBe(true);
});
});
1 change: 0 additions & 1 deletion web/src/utils/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ export interface FieldConfig {
type: FieldType;
format?: FieldFormat;
description: string;
filter?: string;
}
Loading