Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NETOBSERV-223 allow empty values, and some maintenance #96

Merged
merged 1 commit into from
Mar 17, 2022
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
29 changes: 15 additions & 14 deletions pkg/handler/csv/loki_csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ func GetCSVData(qr *model.QueryResponse, columns []string) ([][]string, error) {
func manageStreams(streams model.Streams, columns []string) ([][]string, error) {
//make csv datas containing header as first line + rows
datas := make([][]string, 1)
//prepare columns for faster lookup
columnsMap := utils.GetMapInterface(columns)
//set Timestamp as first data
if columns == nil || utils.Contains(columns, timestampCol) {
includeTimestamp := false
if _, exists := columnsMap[timestampCol]; exists || len(columns) == 0 {
datas[0] = append(datas[0], timestampCol)
includeTimestamp = true
}
//keep ordered labels / field names between each lines
//filtered by columns parameter if specified
Expand All @@ -39,7 +43,7 @@ func manageStreams(streams model.Streams, columns []string) ([][]string, error)
if labels == nil {
labels = make([]string, 0, len(stream.Labels))
for name := range stream.Labels {
if columns == nil || utils.Contains(columns, name) {
if _, exists := columnsMap[name]; exists || len(columns) == 0 {
labels = append(fields, name)
}
}
Expand All @@ -59,39 +63,36 @@ func manageStreams(streams model.Streams, columns []string) ([][]string, error)
if fields == nil {
fields = make([]string, 0, len(line))
for name := range line {
if columns == nil || utils.Contains(columns, name) {
if _, exists := columnsMap[name]; exists || len(columns) == 0 {
fields = append(fields, name)
}
}
datas[0] = append(datas[0], fields...)
}

datas = append(datas, getRowDatas(stream, entry, labels, fields, line, len(datas[0]), columns))
datas = append(datas, getRowDatas(stream, entry, labels, fields, line, len(datas[0]), includeTimestamp))
}
}
return datas, nil
}

func getRowDatas(stream model.Stream, entry model.Entry, labels []string, fields []string,
line map[string]interface{}, size int, columns []string) []string {
index := 0
rowDatas := make([]string, size)
func getRowDatas(stream model.Stream, entry model.Entry, labels, fields []string,
line map[string]interface{}, size int, includeTimestamp bool) []string {
rowDatas := make([]string, 0, size)

//set timestamp
if columns == nil || utils.Contains(columns, timestampCol) {
rowDatas[index] = entry.Timestamp.String()
if includeTimestamp {
rowDatas = append(rowDatas, entry.Timestamp.String())
}

//set labels values
for _, label := range labels {
index++
rowDatas[index] = stream.Labels[label]
rowDatas = append(rowDatas, stream.Labels[label])
}

//set field values
for _, field := range fields {
index++
rowDatas[index] = fmt.Sprintf("%v", line[field])
rowDatas = append(rowDatas, fmt.Sprint(line[field]))
}

return rowDatas
Expand Down
20 changes: 5 additions & 15 deletions pkg/handler/loki.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,13 @@ func GetFlows(cfg LokiConfig, allowExport bool) func(w http.ResponseWriter, r *h
// - manage range (check RANGE_SPLIT_CHAR on front side)
return func(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
// TODO: remove all logs
hlog.Infof("GetFlows query params : %s", params)
hlog.Debugf("GetFlows query params: %s", params)

//allow export only on specific endpoints
queryBuilder := loki.NewQuery(cfg.Labels, allowExport)
for key, param := range params {
var val string
if len(param) > 0 {
val = param[0]
}

if len(val) > 0 {
if err := queryBuilder.AddParam(key, val); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
}
if err := queryBuilder.AddParams(params); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
queryBuilder, err := queryBuilder.PrepareToSubmit()
if err != nil {
Expand All @@ -75,7 +65,7 @@ func GetFlows(cfg LokiConfig, allowExport bool) func(w http.ResponseWriter, r *h
return
}
flowsURL := strings.TrimRight(cfg.URL.String(), "/") + getFlowsURLPath + "?" + query
hlog.Infof("GetFlows URL: %s", flowsURL)
hlog.Debugf("GetFlows URL: %s", flowsURL)

resp, code, err := lokiClient.Get(flowsURL)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions pkg/loki/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package loki
import (
"errors"
"fmt"
"net/url"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -140,6 +141,21 @@ func (q *Query) WriteLabelFilter(sb *strings.Builder, lfs *[]labelFilter, lj Lab
}
}

func (q *Query) AddParams(params url.Values) error {
for key, values := range params {
if len(values) == 0 {
// Silently ignore
continue
}

// Note: empty string allowed
if err := q.AddParam(key, values[0]); err != nil {
return err
}
}
return nil
}

func (q *Query) AddParam(key, value string) error {
if !filterRegexpValidation.MatchString(value) {
return fmt.Errorf("unauthorized sign in flows request: %s", value)
Expand Down
17 changes: 9 additions & 8 deletions web/locales/en/plugin__network-observability-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@
"Compact": "Compact",
"Normal": "Normal",
"Large": "Large",
"Column must be selected": "Column must be selected",
"Value is empty": "Value is empty",
"Unknown port": "Unknown port",
"Not a valid IPv4 or IPv6, nor a CIDR, nor an IP range separated by hyphen": "Not a valid IPv4 or IPv6, nor a CIDR, nor an IP range separated by hyphen",
"Unknown protocol": "Unknown protocol",
"Not a valid kubernetes label": "Not a valid kubernetes label",
"You must select an existing kubernetes object from autocomplete": "You must select an existing kubernetes object from autocomplete",
"Filter already exists": "Filter already exists",
"Specify a single port number or name.": "Specify a single port number or name.",
"Specify a single port following one of these rules:": "Specify a single port following one of these rules:",
"A port number like 80, 21": "A port number like 80, 21",
Expand Down Expand Up @@ -44,6 +36,15 @@
"A range within the IP address like 192.168.0.1-192.189.10.12:8080": "A range within the IP address like 192.168.0.1-192.189.10.12:8080",
"A CIDR specification like 192.51.100.0/24:8080": "A CIDR specification like 192.51.100.0/24:8080",
"Learn more": "Learn more",
"Column must be selected": "Column must be selected",
"Value is empty. For an empty exact match, type \"\"": "Value is empty. For an empty exact match, type \"\"",
"Value is empty": "Value is empty",
"Unknown port": "Unknown port",
"Not a valid IPv4 or IPv6, nor a CIDR, nor an IP range separated by hyphen": "Not a valid IPv4 or IPv6, nor a CIDR, nor an IP range separated by hyphen",
"Unknown protocol": "Unknown protocol",
"Not a valid Kubernetes name": "Not a valid Kubernetes name",
"You must select an existing kubernetes object from autocomplete": "You must select an existing kubernetes object from autocomplete",
"Filter already exists": "Filter already exists",
"Common": "Common",
"Clear all filters": "Clear all filters",
"Edit filters": "Edit filters",
Expand Down
87 changes: 87 additions & 0 deletions web/src/components/filter-hints.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Popover, Text, TextVariants } from '@patternfly/react-core';
import * as _ from 'lodash';
import { FilterType } from '../utils/filters';

interface FilterHintsProps {
type: FilterType;
name: string;
}

export const FilterHints: React.FC<FilterHintsProps> = ({ type, name }) => {
const { t } = useTranslation('plugin__network-observability-plugin');
let hint = '';
let examples = '';
switch (type) {
case FilterType.PORT:
hint = t('Specify a single port number or name.');
examples = `${t('Specify a single port following one of these rules:')}
- ${t('A port number like 80, 21')}
- ${t('A IANA name like HTTP, FTP')}`;
break;
case FilterType.ADDRESS:
hint = t('Specify a single address or range.');
examples = `${t('Specify addresses following one of these rules:')}
- ${t('A single IPv4 or IPv6 address like 192.0.2.0, ::1')}
- ${t('A range within the IP address like 192.168.0.1-192.189.10.12, 2001:db8::1-2001:db8::8')}
- ${t('A CIDR specification like 192.51.100.0/24, 2001:db8::/32')}`;
break;
case FilterType.PROTOCOL:
hint = t('Specify a single protocol number or name.');
examples = `${t('Specify a single protocol following one of these rules:')}
- ${t('A protocol number like 6, 17')}
- ${t('A IANA name like TCP, UDP')}`;
break;
case FilterType.NAMESPACE:
case FilterType.K8S_OBJECT:
case FilterType.K8S_NAMES:
hint = t('Specify a single kubernetes name.');
examples = `${t('Specify a single kubernetes name following these rules:')}
- ${t('Containing any alphanumeric, hyphen, underscrore or dot character')}
- ${t('Partial text like cluster, cluster-image, image-registry')}
- ${t('Exact match using quotes like "cluster-image-registry"')}
- ${t('Case sensitive match using quotes like "Deployment"')}
- ${t('Starting text like cluster, "cluster-*"')}
- ${t('Ending text like "*-registry"')}
- ${t('Pattern like "cluster-*-registry", "c*-*-r*y", -i*e-')}`;
break;
case FilterType.KIND_NAMESPACE_NAME:
hint = t('Specify an existing object from its kind and namespace.');
examples = `${t('Specify a kind, namespace and name from existing:')}
- ${t('Select kind first from suggestions')}
- ${t('Then Select namespace from suggestions')}
- ${t('Finally select object from suggestions')}
${t('You can also directly specify a kind namespace and name like pod.openshift.apiserver')}`;
break;
case FilterType.ADDRESS_PORT:
hint = t('Specify a single address or range with port');
examples = `${t('Specify addresses and port following one of these rules:')}
- ${t('A single IPv4 address with port like 192.0.2.0:8080')}
- ${t('A range within the IP address like 192.168.0.1-192.189.10.12:8080')}
- ${t('A CIDR specification like 192.51.100.0/24:8080')}`;
break;
default:
hint = '';
examples = '';
break;
}
return (
<div id="tips">
<Text component={TextVariants.p}>{hint}</Text>
{!_.isEmpty(examples) ? (
<Popover
aria-label="Hint popover"
headerContent={name}
bodyContent={<div className="text-left-pre">{examples}</div>}
hasAutoWidth={true}
position={'bottom'}
>
<Button id="more" variant="link">
{t('Learn more')}
</Button>
</Popover>
) : undefined}
</div>
);
};
Loading