Skip to content

Commit 9bf06a6

Browse files
committed
NETOBSERV-2227: UDN adjustments, and auto-detect filters
- Auto-detect available filters per feature (so we don't need to implement specific code to detect when NetworkName filters (for instance) are available) - Add tests for auto-detection - Do not display "None" as a UDN label
1 parent fcfd1da commit 9bf06a6

File tree

7 files changed

+150
-67
lines changed

7 files changed

+150
-67
lines changed

web/locales/en/plugin__netobserv-plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
"The flow contains packets with various flags: ": "The flow contains packets with various flags: ",
4545
"ICMP type provided but protocol is {{proto}}": "ICMP type provided but protocol is {{proto}}",
4646
"ICMP code provided but protocol is {{proto}}": "ICMP code provided but protocol is {{proto}}",
47-
"None": "None",
4847
"Invalid data provided. Check JSON for details.": "Invalid data provided. Check JSON for details.",
4948
"dropped": "dropped",
5049
"dropped by": "dropped by",
@@ -161,6 +160,7 @@
161160
"M": "M",
162161
"S": "S",
163162
"XS": "XS",
163+
"None": "None",
164164
"Step {{index}}/{{count}}": "Step {{index}}/{{count}}",
165165
"Step {{index}}/{{count}}_plural": "Step {{index}}/{{count}}",
166166
"Previous tip": "Previous tip",

web/src/components/__tests-data__/columns.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable max-len */
22
import * as _ from 'lodash';
3-
import { Column, ColumnsId, getDefaultColumns } from '../../utils/columns';
3+
import { Column, ColumnConfigDef, ColumnsId, getDefaultColumns } from '../../utils/columns';
44
import { FieldConfig } from '../../utils/fields';
55

66
export const ColumnConfigSampleDefs = [
@@ -170,6 +170,16 @@ export const ColumnConfigSampleDefs = [
170170
default: false,
171171
width: 15
172172
},
173+
{
174+
id: 'SrcZone',
175+
group: 'Source',
176+
name: 'Zone',
177+
field: 'SrcK8S_Zone',
178+
filter: 'src_zone',
179+
default: false,
180+
width: 15,
181+
feature: 'zones'
182+
},
173183
{
174184
id: 'DstK8S_Name',
175185
group: 'Destination',
@@ -303,6 +313,16 @@ export const ColumnConfigSampleDefs = [
303313
default: false,
304314
width: 15
305315
},
316+
{
317+
id: 'DstZone',
318+
group: 'Destination',
319+
name: 'Zone',
320+
field: 'DstK8S_Zone',
321+
filter: 'dst_zone',
322+
default: false,
323+
width: 15,
324+
feature: 'zones'
325+
},
306326
{
307327
id: 'K8S_Name',
308328
name: 'Names',
@@ -476,6 +496,7 @@ export const ColumnConfigSampleDefs = [
476496
tooltip: 'DNS request identifier.',
477497
field: 'DnsId',
478498
filter: 'dns_id',
499+
feature: 'dnsTracking',
479500
default: false,
480501
width: 5
481502
},
@@ -485,6 +506,8 @@ export const ColumnConfigSampleDefs = [
485506
name: 'DNS Latency',
486507
tooltip: 'Time elapsed between DNS request and response.',
487508
field: 'DnsLatencyMs',
509+
filter: 'dns_latency',
510+
feature: 'dnsTracking',
488511
default: false,
489512
width: 5
490513
},
@@ -495,6 +518,7 @@ export const ColumnConfigSampleDefs = [
495518
tooltip: 'DNS RCODE name from response header.',
496519
field: 'DnsFlagsResponseCode',
497520
filter: 'dns_flag_response_code',
521+
feature: 'dnsTracking',
498522
default: false,
499523
width: 5
500524
},
@@ -518,7 +542,7 @@ export const ColumnConfigSampleDefs = [
518542
default: false,
519543
width: 10
520544
}
521-
];
545+
] as ColumnConfigDef[];
522546

523547
export const FieldConfigSample = [
524548
{

web/src/components/__tests-data__/filters.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,18 @@ export const FilterConfigSampleDefs = [
182182
examples:
183183
'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-'
184184
},
185+
{
186+
id: 'src_zone',
187+
name: 'Zone',
188+
component: 'autocomplete',
189+
category: 'source'
190+
},
191+
{
192+
id: 'dst_zone',
193+
name: 'Zone',
194+
component: 'autocomplete',
195+
category: 'destination'
196+
},
185197
{
186198
id: 'protocol',
187199
name: 'Protocol',

web/src/components/drawer/record/record-field.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,10 @@ export const RecordField: React.FC<RecordFieldProps> = ({
435435
case ColumnsId.udns: {
436436
if (Array.isArray(value)) {
437437
return nthContainer(
438-
value.map(iName => simpleTextWithTooltip(iName !== '' ? String(iName) : t('None'))),
438+
value
439+
.map(iName => String(iName))
440+
.filter(iName => iName !== '')
441+
.map(iName => simpleTextWithTooltip(iName)),
439442
true,
440443
false
441444
);

web/src/components/netflow-traffic.tsx

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -124,22 +124,6 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({
124124
return model.config.features.includes('pktDrop');
125125
}, [model.config.features]);
126126

127-
const isUdn = React.useCallback(() => {
128-
return model.config.features.includes('udnMapping');
129-
}, [model.config.features]);
130-
131-
const isPktXlat = React.useCallback(() => {
132-
return model.config.features.includes('packetTranslation');
133-
}, [model.config.features]);
134-
135-
const isNetEvents = React.useCallback(() => {
136-
return model.config.features.includes('networkEvents');
137-
}, [model.config.features]);
138-
139-
const isIPSec = React.useCallback(() => {
140-
return model.config.features.includes('ipsec');
141-
}, [model.config.features]);
142-
143127
const isPromOnly = React.useCallback(() => {
144128
return !allowLoki() || model.dataSource === 'prom';
145129
}, [allowLoki, model.dataSource]);
@@ -159,14 +143,6 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({
159143
[model.config.promLabels, isPromOnly]
160144
);
161145

162-
const isMultiCluster = React.useCallback(() => {
163-
return isPromOnly() ? dataSourceHasLabels(['K8S_ClusterName']) : model.config.features.includes('multiCluster');
164-
}, [model.config.features, dataSourceHasLabels, isPromOnly]);
165-
166-
const isZones = React.useCallback(() => {
167-
return isPromOnly() ? dataSourceHasLabels(['SrcK8S_Zone', 'DstK8S_Zone']) : model.config.features.includes('zones');
168-
}, [model.config.features, dataSourceHasLabels, isPromOnly]);
169-
170146
const getAvailableScopes = React.useCallback(() => {
171147
return model.config.scopes.filter(sc => {
172148
if (sc.feature) {
@@ -219,22 +195,14 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({
219195
}, [getAvailableColumns]);
220196

221197
const getFilterDefs = React.useCallback(() => {
222-
return getFilterDefinitions(model.config.filters, model.config.columns, t).filter(
223-
fd =>
224-
(isMultiCluster() || fd.id !== 'cluster_name') &&
225-
(isZones() || !fd.id.endsWith('_zone')) &&
226-
(isConnectionTracking() || fd.id !== 'id') &&
227-
(isDNSTracking() || !fd.id.startsWith('dns_')) &&
228-
(isPktDrop() || !fd.id.startsWith('pkt_drop_')) &&
229-
(isFlowRTT() || fd.id !== 'time_flow_rtt') &&
230-
(isUdn() || fd.id !== 'udns') &&
231-
(isPktXlat() || !fd.id.startsWith('xlat_')) &&
232-
(isNetEvents() || fd.id !== 'network_events') &&
233-
(!isPromOnly() || checkFilterAvailable(fd, model.config.promLabels)) &&
234-
(isIPSec() || !fd.id.startsWith('ipsec_'))
235-
);
198+
return getFilterDefinitions(model.config.filters, model.config.columns, t).filter(fd => {
199+
if (fd.id === 'id') {
200+
return isConnectionTracking();
201+
}
202+
return checkFilterAvailable(fd, model.config, model.dataSource);
203+
});
236204
// eslint-disable-next-line react-hooks/exhaustive-deps
237-
}, [model.config.columns, model.config.filters, model.config.promLabels, isPromOnly]);
205+
}, [model.config, model.dataSource]);
238206

239207
const getQuickFilters = React.useCallback(
240208
(c: Config = model.config) => {

web/src/utils/__tests__/filter-definitions.spec.ts

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { ColumnConfigSampleDefs } from '../../components/__tests-data__/columns';
12
import { FilterDefinitionSample } from '../../components/__tests-data__/filters';
3+
import { Config, Feature } from '../../model/config';
24
import { checkFilterAvailable, findFilter } from '../filter-definitions';
35

46
describe('Resource validation', () => {
@@ -99,35 +101,92 @@ describe('Resource checkCompletion', () => {
99101
});
100102
});
101103

102-
describe('Check availability', () => {
104+
describe('Check availability for prometheus only', () => {
103105
const simpleFilter = findFilter(FilterDefinitionSample, 'src_name')!;
104106
const k8sFilter = findFilter(FilterDefinitionSample, 'src_resource')!;
107+
const getConfig = (promLabels: string[]): Config => {
108+
return { promLabels, dataSources: ['prom'] } as Config;
109+
};
105110

106111
it('should be available', () => {
107-
let available = checkFilterAvailable(simpleFilter, ['SrcK8S_Name', 'DstK8S_Name']);
112+
let available = checkFilterAvailable(simpleFilter, getConfig(['SrcK8S_Name', 'DstK8S_Name']), 'prom');
108113
expect(available).toBe(true);
109114

110-
available = checkFilterAvailable(k8sFilter, [
111-
'SrcK8S_OwnerName',
112-
'SrcK8S_OwnerType',
113-
'SrcK8S_Namespace',
114-
'DstK8S_OwnerName',
115-
'DstK8S_OwnerType',
116-
'DstK8S_Namespace'
117-
]);
115+
available = checkFilterAvailable(
116+
k8sFilter,
117+
getConfig([
118+
'SrcK8S_OwnerName',
119+
'SrcK8S_OwnerType',
120+
'SrcK8S_Namespace',
121+
'DstK8S_OwnerName',
122+
'DstK8S_OwnerType',
123+
'DstK8S_Namespace'
124+
]),
125+
'prom'
126+
);
118127
expect(available).toBe(true);
119128
});
120129

121130
it('should not be available', () => {
122-
let available = checkFilterAvailable(simpleFilter, ['SrcK8S_OwnerName', 'DstK8S_OwnerName']);
131+
let available = checkFilterAvailable(simpleFilter, getConfig(['SrcK8S_OwnerName', 'DstK8S_OwnerName']), 'prom');
123132
expect(available).toBe(false);
124133

125-
available = checkFilterAvailable(k8sFilter, [
126-
'SrcK8S_OwnerName',
127-
'SrcK8S_Namespace',
128-
'DstK8S_OwnerName',
129-
'DstK8S_Namespace'
130-
]);
134+
available = checkFilterAvailable(
135+
k8sFilter,
136+
getConfig(['SrcK8S_OwnerName', 'SrcK8S_Namespace', 'DstK8S_OwnerName', 'DstK8S_Namespace']),
137+
'prom'
138+
);
131139
expect(available).toBe(false);
132140
});
133141
});
142+
143+
describe('Check availability against features', () => {
144+
const getConfig = (feats: Feature[]): Config => {
145+
return { features: feats, dataSources: ['loki'], columns: ColumnConfigSampleDefs } as Config;
146+
};
147+
148+
it('with standard filters', () => {
149+
const simpleFilter = findFilter(FilterDefinitionSample, 'src_name')!;
150+
const k8sFilter = findFilter(FilterDefinitionSample, 'src_resource')!;
151+
152+
let available = checkFilterAvailable(simpleFilter, getConfig([]), 'auto');
153+
expect(available).toBe(true);
154+
155+
available = checkFilterAvailable(k8sFilter, getConfig([]), 'auto');
156+
expect(available).toBe(true);
157+
158+
available = checkFilterAvailable(simpleFilter, getConfig(['dnsTracking']), 'auto');
159+
expect(available).toBe(true);
160+
161+
available = checkFilterAvailable(k8sFilter, getConfig(['dnsTracking']), 'auto');
162+
expect(available).toBe(true);
163+
});
164+
165+
it('with AZ filters', () => {
166+
const azFilter = findFilter(FilterDefinitionSample, 'src_zone')!;
167+
168+
let available = checkFilterAvailable(azFilter, getConfig([]), 'auto');
169+
expect(available).toBe(false);
170+
171+
available = checkFilterAvailable(azFilter, getConfig(['dnsTracking']), 'auto');
172+
expect(available).toBe(false);
173+
174+
available = checkFilterAvailable(azFilter, getConfig(['zones']), 'auto');
175+
expect(available).toBe(true);
176+
});
177+
178+
it('with DNS filters', () => {
179+
const dnsIdFilter = findFilter(FilterDefinitionSample, 'dns_id')!;
180+
const dnsLatilter = findFilter(FilterDefinitionSample, 'dns_latency')!;
181+
182+
let available = checkFilterAvailable(dnsIdFilter, getConfig([]), 'auto');
183+
expect(available).toBe(false);
184+
available = checkFilterAvailable(dnsLatilter, getConfig([]), 'auto');
185+
expect(available).toBe(false);
186+
187+
available = checkFilterAvailable(dnsIdFilter, getConfig(['dnsTracking']), 'auto');
188+
expect(available).toBe(true);
189+
available = checkFilterAvailable(dnsLatilter, getConfig(['dnsTracking']), 'auto');
190+
expect(available).toBe(true);
191+
});
192+
});

web/src/utils/filter-definitions.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { TFunction } from 'i18next';
22
import * as _ from 'lodash';
33
import { Field } from '../api/ipfix';
4+
import { Config } from '../model/config';
45
import {
56
FilterCategory,
67
FilterComponent,
@@ -11,6 +12,7 @@ import {
1112
FiltersEncoder,
1213
FilterValue
1314
} from '../model/filters';
15+
import { DataSource } from '../model/flow-query';
1416
import { joinResource, SplitResource, splitResource, SplitStage } from '../model/resource';
1517
import { getPort } from '../utils/port';
1618
import { ColumnConfigDef } from './columns';
@@ -336,14 +338,29 @@ export const findFilter = (filterDefinitions: FilterDefinition[], id: FilterId)
336338
return filterDefinitions.find(def => def.id === id);
337339
};
338340

339-
export const checkFilterAvailable = (fd: FilterDefinition, labels: string[]) => {
340-
const q = fd.encoder([{ v: 'any' }], false, false, false);
341-
const parts = q.split('&');
342-
for (let i = 0; i < parts.length; i++) {
343-
const kv = parts[i].split('=');
344-
if (kv.length === 0 || !labels.includes(kv[0])) {
345-
return false;
341+
export const checkFilterAvailable = (fd: FilterDefinition, config: Config, dataSource: DataSource) => {
342+
const allowLoki = config.dataSources.some(ds => ds === 'loki');
343+
const isPromOnly = !allowLoki || dataSource === 'prom';
344+
345+
if (isPromOnly) {
346+
// "encode" a dummy query to check related labels, and make sure they're all part of available prom labels
347+
const q = fd.encoder([{ v: 'any' }], false, false, false);
348+
const parts = q.split('&');
349+
for (let i = 0; i < parts.length; i++) {
350+
const kv = parts[i].split('=');
351+
if (kv.length === 0 || !config.promLabels.includes(kv[0])) {
352+
return false;
353+
}
346354
}
355+
return true;
347356
}
357+
358+
// Check against enabled features
359+
const colConfig = config.columns.find(c => c.filter === fd.id);
360+
if (colConfig?.feature) {
361+
return config.features.includes(colConfig?.feature);
362+
}
363+
364+
// Allow by default
348365
return true;
349366
};

0 commit comments

Comments
 (0)