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-1977 automatically disable filters when not available in prometheus #662

Merged
merged 3 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
23 changes: 18 additions & 5 deletions pkg/handler/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import (
"github.com/netobserv/network-observability-console-plugin/pkg/model"
)

const codePrometheusUnsupported = 901 // code to use internally to notify a Bad Request, unsupported for prometheus queries
const (
codePrometheusUnsupported = 901 // code to use internally to notify a Bad Request, unsupported for prometheus queries
codePrometheusDisabledMetrics = 902 // code to use internally to notify a Bad Request, disabled metrics for prometheus queries
codePrometheusMissingLabels = 903 // code to use internally to notify a Bad Request, missing labels for prometheus queries
)

func writeText(w http.ResponseWriter, code int, bytes []byte) {
w.Header().Set("Content-Type", "text/plain")
Expand Down Expand Up @@ -65,16 +69,25 @@ func writeCSV(w http.ResponseWriter, code int, qr *model.AggregatedQueryResponse
}

type errorResponse struct {
Message string `json:"message,omitempty"`
PromUnsupported string `json:"promUnsupported,omitempty"`
Message string `json:"message,omitempty"`
PromUnsupported string `json:"promUnsupported,omitempty"`
PromDisabledMetrics string `json:"promDisabledMetrics,omitempty"`
PromMissingLabels string `json:"promMissingLabels,omitempty"`
}

func writeError(w http.ResponseWriter, code int, message string) {
var resp errorResponse
if code == codePrometheusUnsupported {
switch code {
case codePrometheusUnsupported:
code = http.StatusBadRequest
resp = errorResponse{PromUnsupported: message}
} else {
case codePrometheusDisabledMetrics:
code = http.StatusBadRequest
resp = errorResponse{PromDisabledMetrics: message}
case codePrometheusMissingLabels:
code = http.StatusBadRequest
resp = errorResponse{PromMissingLabels: message}
default:
resp = errorResponse{Message: message}
}
response, err := json.Marshal(resp)
Expand Down
4 changes: 2 additions & 2 deletions pkg/handler/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,12 @@ func buildTopologyQuery(
if search != nil {
if len(search.Candidates) > 0 {
// Some candidate metrics exist but they are disabled; tell the user
return "", nil, codePrometheusUnsupported, fmt.Errorf(
return "", nil, codePrometheusDisabledMetrics, fmt.Errorf(
"this request requires any of the following metric(s) to be enabled: %s."+
" Metrics can be configured in the FlowCollector resource via 'spec.processor.metrics.includeList'."+
" Alternatively, you may also install and enable Loki", search.FormatCandidates())
} else if len(search.MissingLabels) > 0 {
return "", nil, codePrometheusUnsupported, fmt.Errorf(
return "", nil, codePrometheusMissingLabels, fmt.Errorf(
"this request could not be performed with Prometheus metrics, as they are missing some of the required labels."+
" Try using different filters and/or aggregations. For example, try removing these dependencies from your query: %s."+
" Alternatively, you may also install and enable Loki", search.FormatMissingLabels())
Expand Down
1 change: 1 addition & 0 deletions web/locales/en/plugin__netobserv-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@
"Show filters": "Show filters",
"Collapse": "Collapse",
"Expand": "Expand",
"Some filters have been automatically disabled": "Some filters have been automatically disabled",
"Equals": "Equals",
"Not equals": "Not equals",
"More than": "More than",
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/drawer/netflow-traffic-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { FlowScope, Match, MetricType, RecordType, StatFunction } from '../../mo
import { ScopeConfigDef } from '../../model/scope';
import { Warning } from '../../model/warnings';
import { Column, ColumnSizeMap } from '../../utils/columns';
import { isPromUnsupportedError } from '../../utils/errors';
import { isPromError } from '../../utils/errors';
import { OverviewPanel } from '../../utils/overview-panels';
import { TruncateLength } from '../dropdowns/truncate-dropdown';
import { Error, Size } from '../messages/error';
Expand Down Expand Up @@ -239,7 +239,7 @@ export const NetflowTrafficDrawer: React.FC<NetflowTrafficDrawerProps> = React.f
item: props.currentState.includes('configLoadError') ? t('config') : props.selectedViewId
})}
error={props.error}
isLokiRelated={!props.currentState.includes('configLoadError') && !isPromUnsupportedError(props.error)}
isLokiRelated={!props.currentState.includes('configLoadError') && !isPromError(props.error)}
/>
);
} else {
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/messages/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Link } from 'react-router-dom';
import { Status } from '../../api/loki';
import { getBuildInfo, getLimits, getLokiMetrics, getStatus } from '../../api/routes';
import { ContextSingleton } from '../../utils/context';
import { getHTTPErrorDetails, getPromUnsupportedError, isPromUnsupportedError } from '../../utils/errors';
import { getHTTPErrorDetails, getPromError, isPromError } from '../../utils/errors';
import './error.css';
import { SecondaryAction } from './secondary-action';
import { StatusTexts } from './status-texts';
Expand Down Expand Up @@ -102,7 +102,7 @@ export const Error: React.FC<ErrorProps> = ({ title, error, isLokiRelated }) =>
);

const getDisplayError = React.useCallback(() => {
return isPromUnsupportedError(error) ? getPromUnsupportedError(error) : error;
return isPromError(error) ? getPromError(error) : error;
}, [error]);

React.useEffect(() => {
Expand Down
4 changes: 4 additions & 0 deletions web/src/components/netflow-traffic.css
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,8 @@ span.pf-c-button__icon.pf-m-start {

.netobserv-tab-container {
margin-top: 1.5rem;
}

.chips-popover-close-button {
padding: 0 !important;
}
32 changes: 30 additions & 2 deletions web/src/components/netflow-traffic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ColumnsId, getDefaultColumns } from '../utils/columns';
import { loadConfig } from '../utils/config';
import { ContextSingleton } from '../utils/context';
import { computeStepInterval } from '../utils/datetime';
import { getHTTPErrorDetails } from '../utils/errors';
import { getHTTPErrorDetails, getPromError, isPromMissingLabelError } from '../utils/errors';
import { checkFilterAvailable, getFilterDefinitions } from '../utils/filter-definitions';
import {
defaultArraySelectionOptions,
Expand Down Expand Up @@ -55,6 +55,7 @@ import './netflow-traffic.css';
import { SearchHandle } from './search/search';
import TabsContainer from './tabs/tabs-container';
import { FiltersToolbar } from './toolbar/filters-toolbar';
import ChipsPopover from './toolbar/filters/chips-popover';
import HistogramToolbar from './toolbar/histogram-toolbar';
import ViewOptionsToolbar from './toolbar/view-options-toolbar';

Expand Down Expand Up @@ -453,9 +454,32 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({
model.setStats(stats);
})
.catch(err => {
const errStr = getHTTPErrorDetails(err, true);

// check if it's a prom missing label error
if (isPromMissingLabelError(errStr)) {
let filtersDisabled = false;
model.filters.list.forEach(filter => {
const fieldName = model.config.columns.find(col => col.filter === filter.def.id)?.field;
if (!fieldName || errStr.includes(fieldName)) {
filtersDisabled = true;
filter.values.forEach(fv => {
fv.disabled = true;
});
}
});
if (filtersDisabled) {
// update filters to retrigger query without showing the error
updateTableFilters({ ...model.filters });
model.setChipsPopoverMessage(getPromError(errStr));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

still have an issue here when a topology group is enabled. This will loop as the filters are not involved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that would make it but a bit hacky:
a1cbc9b

The positive side is that it will manage any case (not only the topology groups)

return;
}
}

// clear flows and metrics + show error
model.setFlows([]);
model.setMetrics(defaultNetflowMetrics);
model.setError(getHTTPErrorDetails(err, true));
model.setError(errStr);
model.setWarning(undefined);
})
.finally(() => {
Expand Down Expand Up @@ -913,6 +937,10 @@ export const NetflowTraffic: React.FC<NetflowTrafficProps> = ({
/>
)}
<GuidedTourPopover id="netobserv" ref={guidedTourRef} isDark={isDarkTheme} />
<ChipsPopover
chipsPopoverMessage={model.chipsPopoverMessage}
setChipsPopoverMessage={model.setChipsPopoverMessage}
/>
</PageSection>
) : null;
};
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/tabs/netflow-topology/netflow-topology.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { ScopeConfigDef } from '../../../model/scope';
import { GraphElementPeer, LayoutName, TopologyOptions } from '../../../model/topology';
import { Warning } from '../../../model/warnings';
import { TimeRange } from '../../../utils/datetime';
import { getHTTPErrorDetails, getPromUnsupportedError, isPromUnsupportedError } from '../../../utils/errors';
import { getHTTPErrorDetails, getPromError, isPromError } from '../../../utils/errors';
import { observeDOMRect } from '../../../utils/metrics-helper';
import { SearchEvent, SearchHandle } from '../../search/search';
import { ScopeSlider } from '../../slider/scope-slider';
Expand Down Expand Up @@ -162,8 +162,8 @@ export const NetflowTopology: React.FC<NetflowTopologyProps> = React.forwardRef(
// Error might occur for instance when fetching node-based topology with drop feature enabled, and Loki disabled
// We don't want to break the whole topology due to missing drops enrichement
let strErr = getHTTPErrorDetails(err, true);
if (isPromUnsupportedError(strErr)) {
strErr = getPromUnsupportedError(strErr);
if (isPromError(strErr)) {
strErr = getPromError(strErr);
}
setWarning({
type: 'cantfetchdrops',
Expand Down
41 changes: 41 additions & 0 deletions web/src/components/toolbar/filters/chips-popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Button, Flex, FlexItem, Popover, Text } from '@patternfly/react-core';
import { TimesIcon } from '@patternfly/react-icons';
import React from 'react';
import { useTranslation } from 'react-i18next';

export interface ChipsPopoverProps {
chipsPopoverMessage?: string;
setChipsPopoverMessage: (v?: string) => void;
}

export const ChipsPopover: React.FC<ChipsPopoverProps> = ({ chipsPopoverMessage, setChipsPopoverMessage }) => {
const { t } = useTranslation('plugin__netobserv-plugin');

return (
<Popover
id="chips-popover"
isVisible={chipsPopoverMessage !== undefined}
hideOnOutsideClick={false}
showClose={false}
position="bottom"
headerContent={
<Flex direction={{ default: 'row' }}>
<FlexItem flex={{ default: 'flex_1' }}>{t('Some filters have been automatically disabled')}</FlexItem>
<FlexItem>
<Button
variant="plain"
className="chips-popover-close-button"
onClick={() => setChipsPopoverMessage(undefined)}
>
<TimesIcon />
</Button>
</FlexItem>
</Flex>
}
bodyContent={<Text> {chipsPopoverMessage}</Text>}
reference={() => document.getElementsByClassName('custom-chip-group disabled-group')?.[0] as HTMLElement}
/>
);
};

export default ChipsPopover;
3 changes: 3 additions & 0 deletions web/src/model/netflow-traffic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export function netflowTrafficModel() {
const [isShowQuerySummary, setShowQuerySummary] = React.useState<boolean>(false);
const [lastRefresh, setLastRefresh] = React.useState<Date | undefined>(undefined);
const [lastDuration, setLastDuration] = React.useState<number | undefined>(undefined);
const [chipsPopoverMessage, setChipsPopoverMessage] = React.useState<string | undefined>();
const [error, setError] = React.useState<string | undefined>();
const [isTRModalOpen, setTRModalOpen] = React.useState(false);
const [isOverviewModalOpen, setOverviewModalOpen] = React.useState(false);
Expand Down Expand Up @@ -241,6 +242,8 @@ export function netflowTrafficModel() {
setLastRefresh,
lastDuration,
setLastDuration,
chipsPopoverMessage,
setChipsPopoverMessage,
error,
setError,
isTRModalOpen,
Expand Down
36 changes: 31 additions & 5 deletions web/src/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const getHTTPErrorDetails = (err: any, checkPromUnsupported = false) => {
export const getHTTPErrorDetails = (err: any, checkPromErrors = false) => {
if (err?.response?.data) {
const header = err.toString === Object.prototype.toString ? '' : `${err}\n`;
if (typeof err.response.data === 'object') {
if (checkPromUnsupported && err.response.data.promUnsupported) {
return 'promUnsupported:' + String(err.response.data.promUnsupported);
if (checkPromErrors) {
if (err.response.data.promUnsupported) {
return 'promUnsupported:' + String(err.response.data.promUnsupported);
} else if (err.response.data.promDisabledMetrics) {
return 'promDisabledMetrics:' + String(err.response.data.promDisabledMetrics);
} else if (err.response.data.promMissingLabels) {
return 'promMissingLabels:' + String(err.response.data.promMissingLabels);
}
}
return (
header +
Expand All @@ -22,6 +28,26 @@ export const isPromUnsupportedError = (err: string) => {
return err.startsWith('promUnsupported:');
};

export const getPromUnsupportedError = (err: string) => {
return err.substring('promUnsupported:'.length);
export const isPromDisabledMetricsError = (err: string) => {
return err.startsWith('promDisabledMetrics:');
};

export const isPromMissingLabelError = (err: string) => {
return err.startsWith('promMissingLabels:');
};

export const isPromError = (err: string) => {
return isPromUnsupportedError(err) || isPromDisabledMetricsError(err) || isPromMissingLabelError(err);
};

export const getPromError = (err: string) => {
if (isPromUnsupportedError(err)) {
return err.substring('promUnsupported:'.length);
} else if (isPromDisabledMetricsError(err)) {
return err.substring('promDisabledMetrics:'.length);
} else if (isPromMissingLabelError(err)) {
return err.substring('promMissingLabels:'.length);
} else {
return err;
}
};
Loading