Merge branch 'develop' into chore/analytics

This commit is contained in:
Ankit Nayan 2022-12-28 15:26:59 +05:30
commit 1e035be978
9 changed files with 321 additions and 61 deletions

View File

@ -0,0 +1,9 @@
const DEFAULT_FILTER_VALUE = '';
const EXCEPTION_TYPE_FILTER_NAME = 'exceptionType';
const SERVICE_NAME_FILTER_NAME = 'serviceName';
export {
DEFAULT_FILTER_VALUE,
EXCEPTION_TYPE_FILTER_NAME,
SERVICE_NAME_FILTER_NAME,
};

View File

@ -17,6 +17,7 @@ import getAll from 'api/errors/getAll';
import getErrorCounts from 'api/errors/getErrorCounts'; import getErrorCounts from 'api/errors/getErrorCounts';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams'; import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history'; import history from 'lib/history';
import React, { useCallback, useEffect, useMemo } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react';
@ -30,7 +31,11 @@ import { Exception, PayloadProps } from 'types/api/errors/getAll';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { import {
extractFilterValues,
getDefaultFilterValue,
getDefaultOrder, getDefaultOrder,
getFilterString,
getFilterValues,
getNanoSeconds, getNanoSeconds,
getOffSet, getOffSet,
getOrder, getOrder,
@ -43,15 +48,27 @@ function AllErrors(): JSX.Element {
const { maxTime, minTime, loading } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime, loading } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
const { search, pathname } = useLocation(); const { pathname } = useLocation();
const params = useMemo(() => new URLSearchParams(search), [search]); const params = useUrlQuery();
const { t } = useTranslation(['common']); const { t } = useTranslation(['common']);
const {
const updatedOrder = getOrder(params.get(urlKey.order)); updatedOrder,
const getUpdatedOffset = getOffSet(params.get(urlKey.offset)); getUpdatedOffset,
const getUpdatedParams = getOrderParams(params.get(urlKey.orderParam)); getUpdatedParams,
const getUpdatedPageSize = getUpdatePageSize(params.get(urlKey.pageSize)); getUpdatedPageSize,
getUpdatedExceptionType,
getUpdatedServiceName,
} = useMemo(
() => ({
updatedOrder: getOrder(params.get(urlKey.order)),
getUpdatedOffset: getOffSet(params.get(urlKey.offset)),
getUpdatedParams: getOrderParams(params.get(urlKey.orderParam)),
getUpdatedPageSize: getUpdatePageSize(params.get(urlKey.pageSize)),
getUpdatedExceptionType: getFilterString(params.get(urlKey.exceptionType)),
getUpdatedServiceName: getFilterString(params.get(urlKey.serviceName)),
}),
[params],
);
const updatedPath = useMemo( const updatedPath = useMemo(
() => () =>
@ -60,6 +77,8 @@ function AllErrors(): JSX.Element {
offset: getUpdatedOffset, offset: getUpdatedOffset,
orderParam: getUpdatedParams, orderParam: getUpdatedParams,
pageSize: getUpdatedPageSize, pageSize: getUpdatedPageSize,
exceptionType: getUpdatedExceptionType,
serviceName: getUpdatedServiceName,
})}`, })}`,
[ [
pathname, pathname,
@ -67,6 +86,8 @@ function AllErrors(): JSX.Element {
getUpdatedOffset, getUpdatedOffset,
getUpdatedParams, getUpdatedParams,
getUpdatedPageSize, getUpdatedPageSize,
getUpdatedExceptionType,
getUpdatedServiceName,
], ],
); );
@ -81,6 +102,8 @@ function AllErrors(): JSX.Element {
limit: getUpdatedPageSize, limit: getUpdatedPageSize,
offset: getUpdatedOffset, offset: getUpdatedOffset,
orderParam: getUpdatedParams, orderParam: getUpdatedParams,
exceptionType: getUpdatedExceptionType,
serviceName: getUpdatedServiceName,
}), }),
enabled: !loading, enabled: !loading,
}, },
@ -108,14 +131,43 @@ function AllErrors(): JSX.Element {
const filterIcon = useCallback(() => <SearchOutlined />, []); const filterIcon = useCallback(() => <SearchOutlined />, []);
const handleSearch = ( const handleSearch = useCallback(
confirm: (param?: FilterConfirmProps) => void, (
): VoidFunction => (): void => { confirm: (param?: FilterConfirmProps) => void,
confirm(); filterValue: string,
}; filterKey: string,
): VoidFunction => (): void => {
const { exceptionFilterValue, serviceFilterValue } = getFilterValues(
getUpdatedServiceName,
getUpdatedExceptionType,
filterKey,
filterValue,
);
history.replace(
`${pathname}?${createQueryParams({
order: updatedOrder,
offset: getUpdatedOffset,
orderParam: getUpdatedParams,
pageSize: getUpdatedPageSize,
exceptionType: exceptionFilterValue,
serviceName: serviceFilterValue,
})}`,
);
confirm();
},
[
getUpdatedExceptionType,
getUpdatedOffset,
getUpdatedPageSize,
getUpdatedParams,
getUpdatedServiceName,
pathname,
updatedOrder,
],
);
const filterDropdownWrapper = useCallback( const filterDropdownWrapper = useCallback(
({ setSelectedKeys, selectedKeys, confirm, placeholder }) => { ({ setSelectedKeys, selectedKeys, confirm, placeholder, filterKey }) => {
return ( return (
<Card size="small"> <Card size="small">
<Space align="start" direction="vertical"> <Space align="start" direction="vertical">
@ -126,11 +178,16 @@ function AllErrors(): JSX.Element {
setSelectedKeys(e.target.value ? [e.target.value] : []) setSelectedKeys(e.target.value ? [e.target.value] : [])
} }
allowClear allowClear
onPressEnter={handleSearch(confirm)} defaultValue={getDefaultFilterValue(
filterKey,
getUpdatedServiceName,
getUpdatedExceptionType,
)}
onPressEnter={handleSearch(confirm, selectedKeys[0], filterKey)}
/> />
<Button <Button
type="primary" type="primary"
onClick={handleSearch(confirm)} onClick={handleSearch(confirm, selectedKeys[0], filterKey)}
icon={<SearchOutlined />} icon={<SearchOutlined />}
size="small" size="small"
> >
@ -140,7 +197,7 @@ function AllErrors(): JSX.Element {
</Card> </Card>
); );
}, },
[], [getUpdatedExceptionType, getUpdatedServiceName, handleSearch],
); );
const onExceptionTypeFilter = useCallback( const onExceptionTypeFilter = useCallback(
@ -167,6 +224,7 @@ function AllErrors(): JSX.Element {
( (
onFilter: ColumnType<Exception>['onFilter'], onFilter: ColumnType<Exception>['onFilter'],
placeholder: string, placeholder: string,
filterKey: string,
): ColumnType<Exception> => ({ ): ColumnType<Exception> => ({
onFilter, onFilter,
filterIcon, filterIcon,
@ -176,6 +234,7 @@ function AllErrors(): JSX.Element {
selectedKeys, selectedKeys,
confirm, confirm,
placeholder, placeholder,
filterKey,
}), }),
}), }),
[filterIcon, filterDropdownWrapper], [filterIcon, filterDropdownWrapper],
@ -186,7 +245,7 @@ function AllErrors(): JSX.Element {
title: 'Exception Type', title: 'Exception Type',
dataIndex: 'exceptionType', dataIndex: 'exceptionType',
key: 'exceptionType', key: 'exceptionType',
...getFilter(onExceptionTypeFilter, 'Search By Exception'), ...getFilter(onExceptionTypeFilter, 'Search By Exception', 'exceptionType'),
render: (value, record): JSX.Element => ( render: (value, record): JSX.Element => (
<Tooltip overlay={(): JSX.Element => value}> <Tooltip overlay={(): JSX.Element => value}>
<Link <Link
@ -266,26 +325,35 @@ function AllErrors(): JSX.Element {
updatedOrder, updatedOrder,
'serviceName', 'serviceName',
), ),
...getFilter(onApplicationTypeFilter, 'Search By Application'), ...getFilter(
onApplicationTypeFilter,
'Search By Application',
'serviceName',
),
}, },
]; ];
const onChangeHandler: TableProps<Exception>['onChange'] = ( const onChangeHandler: TableProps<Exception>['onChange'] = (
paginations, paginations,
_, filters,
sorter, sorter,
) => { ) => {
if (!Array.isArray(sorter)) { if (!Array.isArray(sorter)) {
const { pageSize = 0, current = 0 } = paginations; const { pageSize = 0, current = 0 } = paginations;
const { columnKey = '', order } = sorter; const { columnKey = '', order } = sorter;
const updatedOrder = order === 'ascend' ? 'ascending' : 'descending'; const updatedOrder = order === 'ascend' ? 'ascending' : 'descending';
const { exceptionType, serviceName } = extractFilterValues(filters, {
serviceName: getUpdatedServiceName,
exceptionType: getUpdatedExceptionType,
});
history.replace( history.replace(
`${pathname}?${createQueryParams({ `${pathname}?${createQueryParams({
order: updatedOrder, order: updatedOrder,
offset: (current - 1) * pageSize, offset: (current - 1) * pageSize,
orderParam: columnKey, orderParam: columnKey,
pageSize, pageSize,
exceptionType,
serviceName,
})}`, })}`,
); );
} }

View File

@ -1,7 +1,13 @@
import { SortOrder } from 'antd/lib/table/interface'; import { FilterValue, SortOrder } from 'antd/lib/table/interface';
import Timestamp from 'timestamp-nano'; import Timestamp from 'timestamp-nano';
import { Order, OrderBy } from 'types/api/errors/getAll'; import { Order, OrderBy } from 'types/api/errors/getAll';
import {
DEFAULT_FILTER_VALUE,
EXCEPTION_TYPE_FILTER_NAME,
SERVICE_NAME_FILTER_NAME,
} from './constant';
export const isOrder = (order: string | null): order is Order => export const isOrder = (order: string | null): order is Order =>
!!(order === 'ascending' || order === 'descending'); !!(order === 'ascending' || order === 'descending');
@ -10,6 +16,8 @@ export const urlKey = {
offset: 'offset', offset: 'offset',
orderParam: 'orderParam', orderParam: 'orderParam',
pageSize: 'pageSize', pageSize: 'pageSize',
exceptionType: 'exceptionType',
serviceName: 'serviceName',
}; };
export const isOrderParams = (orderBy: string | null): orderBy is OrderBy => { export const isOrderParams = (orderBy: string | null): orderBy is OrderBy => {
@ -87,3 +95,94 @@ export const getUpdatePageSize = (pageSize: string | null): number => {
} }
return 10; return 10;
}; };
export const getFilterString = (filter: string | null): string => {
if (filter) {
return filter;
}
return '';
};
export const getDefaultFilterValue = (
filterKey: string | null,
serviceName: string,
exceptionType: string,
): string | undefined => {
let defaultValue: string | undefined;
switch (filterKey) {
case SERVICE_NAME_FILTER_NAME:
defaultValue = serviceName;
break;
case EXCEPTION_TYPE_FILTER_NAME:
defaultValue = exceptionType;
break;
default:
break;
}
return defaultValue;
};
export const getFilterValues = (
serviceName: string,
exceptionType: string,
filterKey: string,
filterValue: string,
): { exceptionFilterValue: string; serviceFilterValue: string } => {
let serviceFilterValue = serviceName;
let exceptionFilterValue = exceptionType;
switch (filterKey) {
case EXCEPTION_TYPE_FILTER_NAME:
exceptionFilterValue = filterValue;
break;
case SERVICE_NAME_FILTER_NAME:
serviceFilterValue = filterValue;
break;
default:
break;
}
return { exceptionFilterValue, serviceFilterValue };
};
type FilterValues = { exceptionType: string; serviceName: string };
const extractSingleFilterValue = (
filterName: string,
filters: Filter,
): string => {
const filterValues = filters[filterName];
if (
!filterValues ||
!Array.isArray(filterValues) ||
filterValues.length === 0
) {
return DEFAULT_FILTER_VALUE;
}
return String(filterValues[0]);
};
type Filter = Record<string, FilterValue | null>;
export const extractFilterValues = (
filters: Filter,
prefilledFilters: FilterValues,
): FilterValues => {
const filterValues: FilterValues = {
exceptionType: prefilledFilters.exceptionType,
serviceName: prefilledFilters.serviceName,
};
if (filters[EXCEPTION_TYPE_FILTER_NAME]) {
filterValues.exceptionType = extractSingleFilterValue(
EXCEPTION_TYPE_FILTER_NAME,
filters,
);
}
if (filters[SERVICE_NAME_FILTER_NAME]) {
filterValues.serviceName = extractSingleFilterValue(
SERVICE_NAME_FILTER_NAME,
filters,
);
}
return filterValues;
};

View File

@ -0,0 +1,36 @@
import { Button, Row } from 'antd';
import React from 'react';
import { QueryFields } from './utils';
interface SearchFieldsActionBarProps {
fieldsQuery: QueryFields[][];
applyUpdate: () => void;
clearFilters: () => void;
}
export function SearchFieldsActionBar({
fieldsQuery,
applyUpdate,
clearFilters,
}: SearchFieldsActionBarProps): JSX.Element | null {
if (fieldsQuery.length === 0) {
return null;
}
return (
<Row style={{ justifyContent: 'flex-end', paddingRight: '2.4rem' }}>
<Button
type="default"
onClick={clearFilters}
style={{ marginRight: '1rem' }}
>
Clear Filter
</Button>
<Button type="primary" onClick={applyUpdate}>
Apply
</Button>
</Row>
);
}
export default SearchFieldsActionBar;

View File

@ -1,10 +1,11 @@
import { Button, notification, Row } from 'antd'; import { notification } from 'antd';
import { flatten } from 'lodash-es'; import { flatten } from 'lodash-es';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { ILogsReducer } from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { SearchFieldsActionBar } from './ActionBar';
import QueryBuilder from './QueryBuilder/QueryBuilder'; import QueryBuilder from './QueryBuilder/QueryBuilder';
import Suggestions from './Suggestions'; import Suggestions from './Suggestions';
import { import {
@ -68,24 +69,26 @@ function SearchFields({
[fieldsQuery, setFieldsQuery], [fieldsQuery, setFieldsQuery],
); );
const applyUpdate = useCallback( const applyUpdate = useCallback((): void => {
(e): void => { const flatParsedQuery = flatten(fieldsQuery);
e.preventDefault();
const flatParsedQuery = flatten(fieldsQuery);
if (!fieldsQueryIsvalid(flatParsedQuery)) { if (!fieldsQueryIsvalid(flatParsedQuery)) {
notification.error({ notification.error({
message: 'Please enter a valid criteria for each of the selected fields', message: 'Please enter a valid criteria for each of the selected fields',
}); });
return; return;
} }
keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery)); keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery));
updateParsedQuery(flatParsedQuery); updateParsedQuery(flatParsedQuery);
onDropDownToggleHandler(false)(); onDropDownToggleHandler(false)();
}, }, [onDropDownToggleHandler, fieldsQuery, updateParsedQuery]);
[onDropDownToggleHandler, fieldsQuery, updateParsedQuery],
); const clearFilters = useCallback((): void => {
keyPrefixRef.current = hashCode(JSON.stringify([]));
updateParsedQuery([]);
onDropDownToggleHandler(false)();
}, [onDropDownToggleHandler, updateParsedQuery]);
return ( return (
<> <>
@ -96,11 +99,11 @@ function SearchFields({
fieldsQuery={fieldsQuery} fieldsQuery={fieldsQuery}
setFieldsQuery={setFieldsQuery} setFieldsQuery={setFieldsQuery}
/> />
<Row style={{ justifyContent: 'flex-end', paddingRight: '2.4rem' }}> <SearchFieldsActionBar
<Button type="primary" onClick={applyUpdate}> applyUpdate={applyUpdate}
Apply clearFilters={clearFilters}
</Button> fieldsQuery={fieldsQuery}
</Row> />
<Suggestions applySuggestion={addSuggestedField} /> <Suggestions applySuggestion={addSuggestedField} />
</> </>
); );

View File

@ -15,6 +15,8 @@ export interface Props {
orderParam?: OrderBy; orderParam?: OrderBy;
limit?: number; limit?: number;
offset?: number; offset?: number;
exceptionType?: string;
serviceName?: string;
} }
export interface Exception { export interface Exception {

View File

@ -2528,8 +2528,35 @@ func (r *ClickHouseReader) ListErrors(ctx context.Context, queryParams *model.Li
var getErrorResponses []model.Error var getErrorResponses []model.Error
query := fmt.Sprintf("SELECT any(exceptionType) as exceptionType, any(exceptionMessage) as exceptionMessage, count() AS exceptionCount, min(timestamp) as firstSeen, max(timestamp) as lastSeen, any(serviceName) as serviceName, groupID FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU GROUP BY groupID", r.TraceDB, r.errorTable) query := "SELECT any(exceptionMessage) as exceptionMessage, count() AS exceptionCount, min(timestamp) as firstSeen, max(timestamp) as lastSeen, groupID"
if len(queryParams.ServiceName) != 0 {
query = query + ", serviceName"
} else {
query = query + ", any(serviceName) as serviceName"
}
if len(queryParams.ExceptionType) != 0 {
query = query + ", exceptionType"
} else {
query = query + ", any(exceptionType) as exceptionType"
}
query += fmt.Sprintf(" FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.errorTable)
args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))}
if len(queryParams.ServiceName) != 0 {
query = query + " AND serviceName ilike @serviceName"
args = append(args, clickhouse.Named("serviceName", "%"+queryParams.ServiceName+"%"))
}
if len(queryParams.ExceptionType) != 0 {
query = query + " AND exceptionType ilike @exceptionType"
args = append(args, clickhouse.Named("exceptionType", "%"+queryParams.ExceptionType+"%"))
}
query = query + " GROUP BY groupID"
if len(queryParams.ServiceName) != 0 {
query = query + ", serviceName"
}
if len(queryParams.ExceptionType) != 0 {
query = query + ", exceptionType"
}
if len(queryParams.OrderParam) != 0 { if len(queryParams.OrderParam) != 0 {
if queryParams.Order == constants.Descending { if queryParams.Order == constants.Descending {
query = query + " ORDER BY " + queryParams.OrderParam + " DESC" query = query + " ORDER BY " + queryParams.OrderParam + " DESC"
@ -2564,7 +2591,14 @@ func (r *ClickHouseReader) CountErrors(ctx context.Context, queryParams *model.C
query := fmt.Sprintf("SELECT count(distinct(groupID)) FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.errorTable) query := fmt.Sprintf("SELECT count(distinct(groupID)) FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.errorTable)
args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))}
if len(queryParams.ServiceName) != 0 {
query = query + " AND serviceName = @serviceName"
args = append(args, clickhouse.Named("serviceName", queryParams.ServiceName))
}
if len(queryParams.ExceptionType) != 0 {
query = query + " AND exceptionType = @exceptionType"
args = append(args, clickhouse.Named("exceptionType", queryParams.ExceptionType))
}
err := r.db.QueryRow(ctx, query, args...).Scan(&errorCount) err := r.db.QueryRow(ctx, query, args...).Scan(&errorCount)
zap.S().Info(query) zap.S().Info(query)
@ -3233,7 +3267,8 @@ func (r *ClickHouseReader) UpdateLogField(ctx context.Context, field *model.Upda
// remove index // remove index
query := fmt.Sprintf("ALTER TABLE %s.%s ON CLUSTER %s DROP INDEX IF EXISTS %s_idx", r.logsDB, r.logsLocalTable, cluster, field.Name) query := fmt.Sprintf("ALTER TABLE %s.%s ON CLUSTER %s DROP INDEX IF EXISTS %s_idx", r.logsDB, r.logsLocalTable, cluster, field.Name)
err := r.db.Exec(ctx, query) err := r.db.Exec(ctx, query)
if err != nil { // we are ignoring errors with code 341 as it is an error with updating old part https://github.com/SigNoz/engineering-pod/issues/919#issuecomment-1366344346
if err != nil && !strings.HasPrefix(err.Error(), "code: 341") {
return &model.ApiError{Err: err, Typ: model.ErrorInternal} return &model.ApiError{Err: err, Typ: model.ErrorInternal}
} }
} }

View File

@ -480,14 +480,18 @@ func parseListErrorsRequest(r *http.Request) (*model.ListErrorsParams, error) {
if err != nil { if err != nil {
return nil, errors.New("offset param is not in correct format") return nil, errors.New("offset param is not in correct format")
} }
serviceName := r.URL.Query().Get("serviceName")
exceptionType := r.URL.Query().Get("exceptionType")
params := &model.ListErrorsParams{ params := &model.ListErrorsParams{
Start: startTime, Start: startTime,
End: endTime, End: endTime,
OrderParam: orderParam, OrderParam: orderParam,
Order: order, Order: order,
Limit: int64(limitInt), Limit: int64(limitInt),
Offset: int64(offsetInt), Offset: int64(offsetInt),
ServiceName: serviceName,
ExceptionType: exceptionType,
} }
return params, nil return params, nil

View File

@ -296,17 +296,21 @@ type GetTTLParams struct {
} }
type ListErrorsParams struct { type ListErrorsParams struct {
Start *time.Time Start *time.Time
End *time.Time End *time.Time
Limit int64 Limit int64
OrderParam string OrderParam string
Order string Order string
Offset int64 Offset int64
ServiceName string
ExceptionType string
} }
type CountErrorsParams struct { type CountErrorsParams struct {
Start *time.Time Start *time.Time
End *time.Time End *time.Time
ServiceName string
ExceptionType string
} }
type GetErrorParams struct { type GetErrorParams struct {