feat: add documentation link for logs explorer quick filters (#6854)

* feat: add documentation link for logs explorer quick filters

* refactor: simplify QuickFilters component rendering logic

* fix: properly implement the log fast filter empty state UI

* fix: don't display empty state while loading quick filters

* refactor: extract quick filter empty state into a separate component

* refactor: render quick filters empty state based on  source param

* refactor: update QuickFilters source to use QuickFiltersSource.INFRA_MONITORING for consistency

* fix: address review comments

* refactor: fix the failing test by moving QuickFilters types to types.tsx

* refactor: properly import use QuickFiltersSource

---------

Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com>
This commit is contained in:
Shaheer Kochai 2025-01-29 10:39:42 +04:30 committed by GitHub
parent 08e1fd3ca5
commit 42a5d71d81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 279 additions and 64 deletions

View File

@ -0,0 +1,90 @@
interface EmptyQuickFilterIconProps {
width?: number;
height?: number;
className?: string;
}
function EmptyQuickFilterIcon({
width = 32,
height = 32,
className,
}: EmptyQuickFilterIconProps): JSX.Element {
return (
<svg
width={width}
height={height}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M17.948 6.906h-6.62V6.24h6.62v.667ZM17.948 10.293h-6.62v-.667h6.62v.667Z"
fill="#616161"
/>
<path
d="M16.175 29.403H13.62V3.442s.238-.554 1.278-.554 1.278.554 1.278.554v25.96h-.002Z"
fill="#9E9E9E"
/>
<path
d="M15.52 2.971v20.974H13.62v1.91c.323.001.614.04.876.097a1.3 1.3 0 0 1 1.024 1.269v2.182h.656V3.442c-.002 0-.14-.318-.658-.471Z"
fill="#757575"
/>
<path
d="M22.363 2.737a5.382 5.382 0 0 0-5.382 5.402 5.365 5.365 0 0 0 5.382 5.382 5.378 5.378 0 0 0 5.382-5.382c0-2.975-2.407-5.402-5.382-5.402Z"
fill="#2196F3"
/>
<path
d="M24.714 4.749c-.338-.085-1.01-.174-2.349-.174-1.337 0-2.01.087-2.348.174-.2.05-.87.328-.87 1.077v4.618c0 .253.205.46.46.46h.17v.51c0 .15.12.27.268.27h.545c.149 0 .269-.12.269-.27v-.51h3.008v.51c0 .15.12.27.27.27h.544c.149 0 .269-.12.269-.27v-.51h.173a.46.46 0 0 0 .46-.46V5.826c0-.717-.669-1.029-.869-1.077Zm-3.802.366c0-.113.091-.204.205-.204h2.493c.113 0 .204.09.204.204v.362a.204.204 0 0 1-.204.205h-2.493a.204.204 0 0 1-.205-.205v-.362Zm.042 4.864a.138.138 0 0 1-.137.138h-.55a.469.469 0 0 1-.468-.469v-.246c0-.076.062-.138.138-.138h.548c.258 0 .47.209.47.469v.246Zm3.973-.33a.469.469 0 0 1-.468.468h-.55a.138.138 0 0 1-.137-.138v-.246c0-.258.209-.47.469-.47h.549c.075 0 .137.063.137.139v.246Zm.125-2.007c0 .297-.645.817-2.689.817-2.046 0-2.689-.482-2.689-.817V6.277c0-.089.09-.31.311-.31h4.791c.223 0 .276.224.276.31v1.365Z"
fill="#fff"
/>
<path
d="M18.861 24.652h-7.926a.506.506 0 0 1-.507-.507V14.99c0-.28.227-.507.507-.507h7.924c.28 0 .507.227.507.507v9.155c0 .28-.227.507-.505.507Z"
fill="#F5F5F5"
/>
<path
opacity=".8"
d="M18.006 23.139H11.79l-.017-7.236h6.217l.016 7.236Z"
fill="#82AEC0"
/>
<path d="M14.66 23.234v-7.33h.444v7.33h-.445Z" fill="#F5F5F5" />
<path d="M14.66 15.903h-2.887v.605h2.886v-.605Z" fill="#616161" />
<path
d="M18.03 21.878h-6.264v-.222h6.264v.223ZM18.03 20.412h-6.264v-.222h6.264v.222ZM17.99 18.945h-6.217v-.222h6.217v.222ZM17.99 17.481h-6.224v-.222h6.224v.222Z"
fill="#F5F5F5"
/>
<path
d="M17.988 18.534H15.1v.605h2.887v-.605ZM14.672 19.999h-2.877v.604h2.877V20Z"
fill="#616161"
/>
<path
d="M10.935 14.817a.173.173 0 0 0-.173.173v9.155c0 .096.077.174.173.174h7.926a.173.173 0 0 0 .171-.174V14.99a.173.173 0 0 0-.173-.173h-7.924Zm-.84.173a.84.84 0 0 1 .84-.84h7.924a.84.84 0 0 1 .84.84v9.155a.84.84 0 0 1-.838.84h-7.926a.84.84 0 0 1-.84-.84V14.99Z"
fill="#9E9E9E"
/>
<path
d="M10.968 24.323c-.344.031-.344-.195-.344-.258V15.1c0-.251.204-.455.455-.455h7.693c.21 0 .305.126.262.384 0 0-.026-.165-.226-.165h-7.726a.237.237 0 0 0-.236.236v8.968c0 .21.122.256.122.256Z"
fill="#757575"
/>
<path d="M12.268 4.933H4.695v6.577h7.573V4.933Z" fill="#FFCA28" />
<path
d="M11.846 4.933c.233 0 .422.189.422.422v5.733a.422.422 0 0 1-.422.422H5.12a.422.422 0 0 1-.423-.422V5.355c0-.233.19-.422.423-.422h6.726Zm0-.445H5.12a.868.868 0 0 0-.867.867v5.733c0 .478.389.867.867.867h6.726a.868.868 0 0 0 .867-.867V5.355a.866.866 0 0 0-.867-.867Z"
fill="#9E9E9E"
/>
<path
d="M12.27 7.308H4.696v-.444h7.575v.444ZM12.27 9.579H4.696v-.444h7.575v.444Z"
fill="#FFFDE7"
/>
<path
d="M7.066 5.664H5.162v.502h1.904v-.502ZM6.597 10.27H5.162v.503h1.435v-.502ZM11.648 10.27h-1.435v.503h1.435v-.502ZM11.648 5.664h-1.435v.502h1.435v-.502ZM10.302 7.968h-.827v.502h.827v-.502ZM11.648 7.968h-.826v.502h.826v-.502ZM7.802 7.968h-2.64v.502h2.64v-.502Z"
fill="#757575"
/>
</svg>
);
}
EmptyQuickFilterIcon.defaultProps = {
width: 32,
height: 32,
className: '',
};
export default EmptyQuickFilterIcon;

View File

@ -130,6 +130,39 @@
cursor: pointer;
}
}
.go-to-docs {
display: flex;
flex-direction: column;
gap: 64px;
&__container {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 4px;
&-message {
color: var(--bg-vanilla-400);
font-size: 14px;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
&__button {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
margin: 0 0 4px;
padding: 0;
&-text {
color: var(--bg-robin-400);
font-family: Inter;
font-size: 14px;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
}
}
.lightMode {

View File

@ -6,7 +6,10 @@ import './Checkbox.styles.scss';
import { Button, Checkbox, Input, Skeleton, Typography } from 'antd';
import cx from 'classnames';
import { IQuickFiltersConfig } from 'components/QuickFilters/QuickFilters';
import {
IQuickFiltersConfig,
QuickFiltersSource,
} from 'components/QuickFilters/types';
import { OPERATORS } from 'constants/queryBuilder';
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
@ -21,9 +24,13 @@ import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { v4 as uuid } from 'uuid';
import LogsQuickFilterEmptyState from './LogsQuickFilterEmptyState';
const SELECTED_OPERATORS = [OPERATORS['='], 'in'];
const NON_SELECTED_OPERATORS = [OPERATORS['!='], 'nin'];
const SOURCES_WITH_EMPTY_STATE_ENABLED = [QuickFiltersSource.LOGS_EXPLORER];
function setDefaultValues(
values: string[],
trueOrFalse: boolean,
@ -36,11 +43,13 @@ function setDefaultValues(
}
interface ICheckboxProps {
filter: IQuickFiltersConfig;
source: QuickFiltersSource;
onFilterChange?: (query: Query) => void;
}
// eslint-disable-next-line sonarjs/cognitive-complexity
export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
const { filter, onFilterChange } = props;
const { source, filter, onFilterChange } = props;
const [searchText, setSearchText] = useState<string>('');
const [isOpen, setIsOpen] = useState<boolean>(filter.defaultOpen);
const [visibleItemsCount, setVisibleItemsCount] = useState<number>(10);
@ -410,6 +419,11 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
}
};
const isEmptyStateWithDocsEnabled =
SOURCES_WITH_EMPTY_STATE_ENABLED.includes(source) &&
!searchText &&
!attributeValues.length;
return (
<div className="checkbox-filter">
<section
@ -432,7 +446,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
<Typography.Text className="title">{filter.title}</Typography.Text>
</section>
<section className="right-action">
{isOpen && (
{isOpen && !!attributeValues.length && (
<Typography.Text
className="clear-all"
onClick={(e): void => {
@ -453,13 +467,15 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
)}
{isOpen && !isLoading && (
<>
<section className="search">
<Input
placeholder="Filter values"
onChange={(e): void => setSearchTextDebounced(e.target.value)}
disabled={isFilterDisabled}
/>
</section>
{!isEmptyStateWithDocsEnabled && (
<section className="search">
<Input
placeholder="Filter values"
onChange={(e): void => setSearchTextDebounced(e.target.value)}
disabled={isFilterDisabled}
/>
</section>
)}
{attributeValues.length > 0 ? (
<section className="values">
{currentAttributeKeys.map((value: string) => (
@ -507,6 +523,8 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
</div>
))}
</section>
) : isEmptyStateWithDocsEnabled ? (
<LogsQuickFilterEmptyState attributeKey={filter.attributeKey.key} />
) : (
<section className="no-data">
<Typography.Text>No values found</Typography.Text>{' '}

View File

@ -0,0 +1,53 @@
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import EmptyQuickFilterIcon from 'assets/CustomIcons/EmptyQuickFilterIcon';
import { ArrowUpRight } from 'lucide-react';
const QUICK_FILTER_DOC_PATHS: Record<string, string> = {
severity_text: 'severity-text',
'deployment.environment': 'environment',
'service.name': 'service-name',
'host.name': 'hostname',
'k8s.cluster.name': 'k8s-cluster-name',
'k8s.deployment.name': 'k8s-deployment-name',
'k8s.namespace.name': 'k8s-namespace-name',
'k8s.pod.name': 'k8s-pod-name',
};
function LogsQuickFilterEmptyState({
attributeKey,
}: {
attributeKey: string;
}): JSX.Element {
const handleLearnMoreClick = (): void => {
const section = QUICK_FILTER_DOC_PATHS[attributeKey];
window.open(
`https://signoz.io/docs/logs-management/features/logs-quick-filters#${section}`,
'_blank',
);
};
return (
<section className="go-to-docs">
<div className="go-to-docs__container">
<div className="go-to-docs__container-icon">
<EmptyQuickFilterIcon />
</div>
<div className="go-to-docs__container-message">
{`You'd need to parse out this attribute to start getting them as a fast
filter.`}
</div>
</div>
<Button
type="link"
className="go-to-docs__button"
onClick={handleLearnMoreClick}
>
<div className="go-to-docs__button-text">Learn more</div>
<ArrowUpRight size={14} color={Color.BG_ROBIN_400} />
</Button>
</section>
);
}
export default LogsQuickFilterEmptyState;

View File

@ -1,6 +1,6 @@
import './Slider.styles.scss';
import { IQuickFiltersConfig } from 'components/QuickFilters/QuickFilters';
import { IQuickFiltersConfig } from 'components/QuickFilters/types';
interface ISliderProps {
filter: IQuickFiltersConfig;

View File

@ -8,45 +8,11 @@ import {
import { Tooltip, Typography } from 'antd';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { cloneDeep, isFunction } from 'lodash-es';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import Checkbox from './FilterRenderers/Checkbox/Checkbox';
import Slider from './FilterRenderers/Slider/Slider';
export enum FiltersType {
SLIDER = 'SLIDER',
CHECKBOX = 'CHECKBOX',
}
export enum MinMax {
MIN = 'MIN',
MAX = 'MAX',
}
export enum SpecficFilterOperations {
ALL = 'ALL',
ONLY = 'ONLY',
}
export interface IQuickFiltersConfig {
type: FiltersType;
title: string;
attributeKey: BaseAutocompleteData;
aggregateOperator?: string;
aggregateAttribute?: string;
dataSource?: DataSource;
customRendererForValue?: (value: string) => JSX.Element;
defaultOpen: boolean;
}
interface IQuickFiltersProps {
config: IQuickFiltersConfig[];
handleFilterVisibilityChange: () => void;
source?: string | null;
onFilterChange?: (query: Query) => void;
}
import { FiltersType, IQuickFiltersProps, QuickFiltersSource } from './types';
export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
const { config, handleFilterVisibilityChange, source, onFilterChange } = props;
@ -95,11 +61,9 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
const lastQueryName =
currentQuery.builder.queryData?.[lastUsedQuery || 0]?.queryName;
const isInfraMonitoring = source === 'infra-monitoring';
return (
<div className="quick-filters">
{!isInfraMonitoring && (
{source !== QuickFiltersSource.INFRA_MONITORING && (
<section className="header">
<section className="left-actions">
<FilterOutlined />
@ -128,11 +92,24 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
{config.map((filter) => {
switch (filter.type) {
case FiltersType.CHECKBOX:
return <Checkbox filter={filter} onFilterChange={onFilterChange} />;
return (
<Checkbox
source={source}
filter={filter}
onFilterChange={onFilterChange}
/>
);
case FiltersType.SLIDER:
return <Slider filter={filter} />;
// eslint-disable-next-line sonarjs/no-duplicated-branches
default:
return <Checkbox filter={filter} onFilterChange={onFilterChange} />;
return (
<Checkbox
source={source}
filter={filter}
onFilterChange={onFilterChange}
/>
);
}
})}
</section>
@ -141,6 +118,5 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
}
QuickFilters.defaultProps = {
source: null,
onFilterChange: null,
};

View File

@ -0,0 +1,42 @@
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
export enum FiltersType {
SLIDER = 'SLIDER',
CHECKBOX = 'CHECKBOX',
}
export enum MinMax {
MIN = 'MIN',
MAX = 'MAX',
}
export enum SpecficFilterOperations {
ALL = 'ALL',
ONLY = 'ONLY',
}
export interface IQuickFiltersConfig {
type: FiltersType;
title: string;
attributeKey: BaseAutocompleteData;
aggregateOperator?: string;
aggregateAttribute?: string;
dataSource?: DataSource;
customRendererForValue?: (value: string) => JSX.Element;
defaultOpen: boolean;
}
export interface IQuickFiltersProps {
config: IQuickFiltersConfig[];
handleFilterVisibilityChange: () => void;
source: QuickFiltersSource;
onFilterChange?: (query: Query) => void;
}
export enum QuickFiltersSource {
LOGS_EXPLORER = 'logs-explorer',
INFRA_MONITORING = 'infra-monitoring',
TRACES_EXPLORER = 'traces-explorer',
}

View File

@ -5,6 +5,7 @@ import * as Sentry from '@sentry/react';
import type { CollapseProps } from 'antd';
import { Collapse, Tooltip, Typography } from 'antd';
import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource } from 'components/QuickFilters/types';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import {
@ -83,7 +84,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
showArrow: false,
children: (
<QuickFilters
source="infra-monitoring"
source={QuickFiltersSource.INFRA_MONITORING}
config={PodsQuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange}
@ -103,7 +104,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
showArrow: false,
children: (
<QuickFilters
source="infra-monitoring"
source={QuickFiltersSource.INFRA_MONITORING}
config={NodesQuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange}
@ -126,7 +127,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
showArrow: false,
children: (
<QuickFilters
source="infra-monitoring"
source={QuickFiltersSource.INFRA_MONITORING}
config={NamespaceQuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange}
@ -146,7 +147,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
showArrow: false,
children: (
<QuickFilters
source="infra-monitoring"
source={QuickFiltersSource.INFRA_MONITORING}
config={ClustersQuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange}
@ -166,7 +167,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
showArrow: false,
children: (
<QuickFilters
source="infra-monitoring"
source={QuickFiltersSource.INFRA_MONITORING}
config={DeploymentsQuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange}
@ -186,7 +187,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
showArrow: false,
children: (
<QuickFilters
source="infra-monitoring"
source={QuickFiltersSource.INFRA_MONITORING}
config={JobsQuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange}
@ -206,7 +207,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
showArrow: false,
children: (
<QuickFilters
source="infra-monitoring"
source={QuickFiltersSource.INFRA_MONITORING}
config={DaemonSetsQuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange}
@ -229,7 +230,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
showArrow: false,
children: (
<QuickFilters
source="infra-monitoring"
source={QuickFiltersSource.INFRA_MONITORING}
config={StatefulsetsQuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange}
@ -249,7 +250,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
showArrow: false,
children: (
<QuickFilters
source="infra-monitoring"
source={QuickFiltersSource.INFRA_MONITORING}
config={VolumesQuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange}
@ -273,7 +274,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
// showArrow: false,
// children: (
// <QuickFilters
// source="infra-monitoring"
// source={QuickFiltersSource.INFRA_MONITORING}
// config={ContainersQuickFiltersConfig}
// handleFilterVisibilityChange={handleFilterVisibilityChange}
// onFilterChange={handleFilterChange}

View File

@ -2,7 +2,7 @@
import {
FiltersType,
IQuickFiltersConfig,
} from 'components/QuickFilters/QuickFilters';
} from 'components/QuickFilters/types';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource } from 'types/common/queryBuilder';

View File

@ -6,6 +6,7 @@ import setLocalStorageApi from 'api/browser/localstorage/set';
import cx from 'classnames';
import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource } from 'components/QuickFilters/types';
import { LOCALSTORAGE } from 'constants/localStorage';
import LogExplorerQuerySection from 'container/LogExplorerQuerySection';
import LogsExplorerViews from 'container/LogsExplorerViews';
@ -157,6 +158,7 @@ function LogsExplorer(): JSX.Element {
{showFilters && (
<section className={cx('log-quick-filter-left-section')}>
<QuickFilters
source={QuickFiltersSource.LOGS_EXPLORER}
config={LogsQuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
/>

View File

@ -1,7 +1,7 @@
import {
FiltersType,
IQuickFiltersConfig,
} from 'components/QuickFilters/QuickFilters';
} from 'components/QuickFilters/types';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData';