Merge branch 'develop' into release/v0.11.0

This commit is contained in:
Prashant Shahi 2022-08-23 22:22:49 +05:30
commit c2f95dc727
65 changed files with 2733 additions and 2310 deletions

View File

@ -13,6 +13,8 @@ FRONTEND_DIRECTORY ?= frontend
QUERY_SERVICE_DIRECTORY ?= pkg/query-service QUERY_SERVICE_DIRECTORY ?= pkg/query-service
STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup
SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup
LOCAL_GOOS ?= $(shell go env GOOS)
LOCAL_GOARCH ?= $(shell go env GOARCH)
REPONAME ?= signoz REPONAME ?= signoz
DOCKER_TAG ?= latest DOCKER_TAG ?= latest
@ -79,11 +81,25 @@ dev-setup:
@echo "--> Local Setup completed" @echo "--> Local Setup completed"
@echo "------------------" @echo "------------------"
run-local:
@LOCAL_GOOS=$(LOCAL_GOOS) LOCAL_GOARCH=$(LOCAL_GOARCH) docker-compose -f \
$(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-local.yaml \
up --build -d
down-local:
@docker-compose -f \
$(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-local.yaml \
down -v
run-x86: run-x86:
@docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.yaml up -d @docker-compose -f \
$(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-prod.yaml \
up --build -d
down-x86: down-x86:
@docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.yaml down -v @docker-compose -f \
$(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-prod.yaml \
down -v
clear-standalone-data: clear-standalone-data:
@docker run --rm -v "$(PWD)/$(STANDALONE_DIRECTORY)/data:/pwd" busybox \ @docker run --rm -v "$(PWD)/$(STANDALONE_DIRECTORY)/data:/pwd" busybox \

View File

@ -0,0 +1,109 @@
version: "2.4"
services:
clickhouse:
image: clickhouse/clickhouse-server:22.4.5-alpine
container_name: clickhouse
# ports:
# - "9000:9000"
# - "8123:8123"
tty: true
volumes:
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
- ./clickhouse-users.xml:/etc/clickhouse-server/users.xml
# - ./clickhouse-storage.xml:/etc/clickhouse-server/config.d/storage.xml
- ./data/clickhouse/:/var/lib/clickhouse/
restart: on-failure
logging:
options:
max-size: 50m
max-file: "3"
healthcheck:
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
interval: 30s
timeout: 5s
retries: 3
alertmanager:
container_name: alertmanager
image: signoz/alertmanager:0.23.0-0.2
volumes:
- ./data/alertmanager:/data
depends_on:
query-service:
condition: service_healthy
restart: on-failure
command:
- --queryService.url=http://query-service:8085
- --storage.path=/data
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
otel-collector:
container_name: otel-collector
image: signoz/signoz-otel-collector:0.55.0-rc.3
command: ["--config=/etc/otel-collector-config.yaml"]
# user: root # required for reading docker container logs
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
ports:
# - "1777:1777" # pprof extension
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver
# - "8888:8888" # OtelCollector internal metrics
# - "8889:8889" # signoz spanmetrics exposed by the agent
# - "9411:9411" # Zipkin port
# - "13133:13133" # health check extension
# - "14250:14250" # Jaeger gRPC
# - "14268:14268" # Jaeger thrift HTTP
# - "55678:55678" # OpenCensus receiver
# - "55679:55679" # zPages extension
mem_limit: 2000m
restart: on-failure
depends_on:
clickhouse:
condition: service_healthy
otel-collector-metrics:
container_name: otel-collector-metrics
image: signoz/signoz-otel-collector:0.55.0-rc.3
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
# ports:
# - "1777:1777" # pprof extension
# - "8888:8888" # OtelCollector internal metrics
# - "13133:13133" # Health check extension
# - "55679:55679" # zPages extension
restart: on-failure
depends_on:
clickhouse:
condition: service_healthy
hotrod:
image: jaegertracing/example-hotrod:1.30
container_name: hotrod
logging:
options:
max-size: 50m
max-file: "3"
command: ["all"]
environment:
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
load-hotrod:
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
container_name: load-hotrod
hostname: load-hotrod
environment:
ATTACKED_HOST: http://hotrod:8080
LOCUST_MODE: standalone
NO_PROXY: standalone
TASK_DELAY_FROM: 5
TASK_DELAY_TO: 30
QUIET_MODE: "${QUIET_MODE:-false}"
LOCUST_OPTS: "--headless -u 10 -r 1"
volumes:
- ../common/locust-scripts:/locust

View File

@ -0,0 +1,51 @@
version: "2.4"
services:
query-service:
hostname: query-service
build:
context: "../../../pkg/query-service"
dockerfile: "./Dockerfile"
args:
LDFLAGS: ""
TARGETPLATFORM: "${LOCAL_GOOS}/${LOCAL_GOARCH}"
container_name: query-service
environment:
- ClickHouseUrl=tcp://clickhouse:9000
- STORAGE=clickhouse
- SIGNOZ_LOCAL_DB_PATH=/root/signoz.db
volumes:
- ./prometheus.yml:/root/config/prometheus.yml
- ../dashboards:/root/config/dashboards
command: ["-config=/root/config/prometheus.yml"]
ports:
- "6060:6060"
- "8080:8080"
restart: on-failure
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"]
interval: 30s
timeout: 5s
retries: 3
depends_on:
clickhouse:
condition: service_healthy
frontend:
build:
context: "../../../frontend"
dockerfile: "./Dockerfile"
args:
TARGETOS: "${LOCAL_GOOS}"
TARGETPLATFORM: "${LOCAL_GOARCH}"
container_name: frontend
environment:
- FRONTEND_API_ENDPOINT=http://query-service:8080
restart: on-failure
depends_on:
- alertmanager
- query-service
ports:
- "3301:3301"
volumes:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf

View File

@ -0,0 +1,41 @@
version: "2.4"
services:
query-service:
image: signoz/query-service:0.10.2
container_name: query-service
command: ["-config=/root/config/prometheus.yml"]
# ports:
# - "6060:6060" # pprof port
# - "8080:8080" # query-service port
volumes:
- ./prometheus.yml:/root/config/prometheus.yml
- ../dashboards:/root/config/dashboards
- ./data/signoz/:/var/lib/signoz/
environment:
- ClickHouseUrl=tcp://clickhouse:9000/?database=signoz_traces
- STORAGE=clickhouse
- GODEBUG=netdns=go
- TELEMETRY_ENABLED=true
- DEPLOYMENT_TYPE=docker-standalone-amd
restart: on-failure
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"]
interval: 30s
timeout: 5s
retries: 3
depends_on:
clickhouse:
condition: service_healthy
frontend:
image: signoz/frontend:0.10.2
container_name: frontend
restart: on-failure
depends_on:
- alertmanager
- query-service
ports:
- "3301:3301"
volumes:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf

View File

@ -56,7 +56,7 @@
"d3-tip": "^0.9.1", "d3-tip": "^0.9.1",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"dotenv": "8.2.0", "dotenv": "8.2.0",
"eventsource": "^2.0.2", "event-source-polyfill": "1.0.31",
"file-loader": "6.1.1", "file-loader": "6.1.1",
"flat": "^5.0.2", "flat": "^5.0.2",
"history": "4.10.1", "history": "4.10.1",
@ -126,6 +126,8 @@
"@types/copy-webpack-plugin": "^8.0.1", "@types/copy-webpack-plugin": "^8.0.1",
"@types/d3": "^6.2.0", "@types/d3": "^6.2.0",
"@types/d3-tip": "^3.5.5", "@types/d3-tip": "^3.5.5",
"@types/event-source-polyfill": "^1.0.0",
"@types/flat": "^5.0.2",
"@types/jest": "^27.5.1", "@types/jest": "^27.5.1",
"@types/lodash-es": "^4.17.4", "@types/lodash-es": "^4.17.4",
"@types/mini-css-extract-plugin": "^2.5.1", "@types/mini-css-extract-plugin": "^2.5.1",

View File

@ -1,18 +1,17 @@
import 'eventsource/example/eventsource-polyfill';
import apiV1 from 'api/apiV1'; import apiV1 from 'api/apiV1';
import getLocalStorageKey from 'api/browser/localstorage/get'; import getLocalStorageKey from 'api/browser/localstorage/get';
import { ENVIRONMENT } from 'constants/env'; import { ENVIRONMENT } from 'constants/env';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
import { EventSourcePolyfill } from 'event-source-polyfill';
export const LiveTail = (queryParams) => { export const LiveTail = (queryParams: string): EventSourcePolyfill => {
const dict = { const dict = {
headers: { headers: {
Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`, Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`,
}, },
}; };
return new EventSourcePolyfill( return new EventSourcePolyfill(
`${ENVIRONMENT.baseURL}${apiV1}/logs/tail?${queryParams}`, `${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`,
dict, dict,
); );
}; };

View File

@ -1,4 +1,4 @@
import { Button, Popover, Tag, Tooltip } from 'antd'; import { Button, Popover } from 'antd';
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery'; import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
import React, { memo, useCallback, useMemo } from 'react'; import React, { memo, useCallback, useMemo } from 'react';
@ -11,15 +11,24 @@ import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs'; import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
interface AddToQueryHOCProps {
fieldKey: string;
fieldValue: string;
children: React.ReactNode;
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => ReturnType<typeof getLogsAggregate>;
}
function AddToQueryHOC({ function AddToQueryHOC({
fieldKey, fieldKey,
fieldValue, fieldValue,
children, children,
getLogs, getLogs,
getLogsAggregate, getLogsAggregate,
}) { }: AddToQueryHOCProps): JSX.Element {
const { const {
searchFilter: { queryString }, searchFilter: { queryString },
logLinesPerPage, logLinesPerPage,
@ -72,9 +81,7 @@ function AddToQueryHOC({
...(idStart ? { idGt: idStart } : {}), ...(idStart ? { idGt: idStart } : {}),
...(idEnd ? { idLt: idEnd } : {}), ...(idEnd ? { idLt: idEnd } : {}),
}); });
} } else if (liveTail === 'PLAYING') {
else if (liveTail === 'PLAYING') {
dispatch({ dispatch({
type: TOGGLE_LIVE_TAIL, type: TOGGLE_LIVE_TAIL,
payload: 'PAUSED', payload: 'PAUSED',
@ -88,6 +95,7 @@ function AddToQueryHOC({
0, 0,
); );
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
dispatch, dispatch,
generatedQuery, generatedQuery,
@ -121,8 +129,12 @@ function AddToQueryHOC({
} }
interface DispatchProps { interface DispatchProps {
getLogs: () => (dispatch: Dispatch<AppActions>) => void; getLogs: (
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void; props: Parameters<typeof getLogs>[0],
) => (dispatch: Dispatch<AppActions>) => void;
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => (dispatch: Dispatch<AppActions>) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -1,19 +1,28 @@
import { Button, Popover, Tooltip } from 'antd'; import { Popover } from 'antd';
import React from 'react'; import React from 'react';
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
function CopyClipboardHOC({ textToCopy, children }) { interface CopyClipboardHOCProps {
const [_state, setCopy] = useCopyToClipboard(); textToCopy: string;
children: React.ReactNode;
}
function CopyClipboardHOC({
textToCopy,
children,
}: CopyClipboardHOCProps): JSX.Element {
const [, setCopy] = useCopyToClipboard();
return ( return (
<span <span
style={{ style={{
margin: 0, margin: 0,
padding: 0, padding: 0,
cursor: 'pointer' cursor: 'pointer',
}} }}
onClick={() => setCopy(textToCopy)} onClick={(): void => setCopy(textToCopy)}
onKeyDown={(): void => setCopy(textToCopy)}
role="button" role="button"
tabIndex={0}
> >
<Popover <Popover
placement="top" placement="top"

View File

@ -1,21 +1,26 @@
import { blue, grey, orange } from '@ant-design/colors'; import { blue, grey, orange } from '@ant-design/colors';
import { CopyFilled, CopyrightCircleFilled, ExpandAltOutlined } from '@ant-design/icons'; import { CopyFilled, ExpandAltOutlined } from '@ant-design/icons';
import { Button, Card, Divider, Row, Typography } from 'antd'; import { Button, Divider, Row, Typography } from 'antd';
import { map } from 'd3'; import { map } from 'd3';
import dayjs from 'dayjs';
import { FlatLogData } from 'lib/logs/flatLogData'; import { FlatLogData } from 'lib/logs/flatLogData';
import { flatMap, flatMapDeep } from 'lodash-es';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs'; import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import ILogsReducer from 'types/reducer/logs'; import { ILog } from 'types/api/logs/log';
import { ILogsReducer } from 'types/reducer/logs';
import AddToQueryHOC from '../AddToQueryHOC'; import AddToQueryHOC from '../AddToQueryHOC';
import CopyClipboardHOC from '../CopyClipboardHOC'; import CopyClipboardHOC from '../CopyClipboardHOC';
import { Container } from './styles'; import { Container } from './styles';
function LogGeneralField({ fieldKey, fieldValue }) { interface LogFieldProps {
fieldKey: string;
fieldValue: string;
}
function LogGeneralField({ fieldKey, fieldValue }: LogFieldProps): JSX.Element {
return ( return (
<div <div
style={{ style={{
@ -34,8 +39,10 @@ function LogGeneralField({ fieldKey, fieldValue }) {
</div> </div>
); );
} }
function LogSelectedField({
function LogSelectedField({ fieldKey = '', fieldValue = '' }) { fieldKey = '',
fieldValue = '',
}: LogFieldProps): JSX.Element {
return ( return (
<div <div
style={{ style={{
@ -65,13 +72,16 @@ function LogSelectedField({ fieldKey = '', fieldValue = '' }) {
); );
} }
function LogItem({ logData }) { interface LogItemProps {
logData: ILog;
}
function LogItem({ logData }: LogItemProps): JSX.Element {
const { const {
fields: { selected }, fields: { selected },
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const dispatch = useDispatch(); const dispatch = useDispatch();
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]); const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
const [_state, setCopy] = useCopyToClipboard(); const [, setCopy] = useCopyToClipboard();
const handleDetailedView = useCallback(() => { const handleDetailedView = useCallback(() => {
dispatch({ dispatch({
@ -80,22 +90,28 @@ function LogItem({ logData }) {
}); });
}, [dispatch, logData]); }, [dispatch, logData]);
const handleCopyJSON = () => { const handleCopyJSON = (): void => {
setCopy(JSON.stringify(logData, null, 2)) setCopy(JSON.stringify(logData, null, 2));
} };
return ( return (
<Container> <Container>
<div style={{ maxWidth: '100%' }}> <div style={{ maxWidth: '100%' }}>
<div> <div>
{'{'} {'{'}
<div style={{ marginLeft: '0.5rem' }}> <div style={{ marginLeft: '0.5rem' }}>
<LogGeneralField fieldKey="log" fieldValue={flattenLogData.body} /> <LogGeneralField
fieldKey="log"
fieldValue={flattenLogData.body as never}
/>
{flattenLogData.stream && ( {flattenLogData.stream && (
<LogGeneralField fieldKey="stream" fieldValue={flattenLogData.stream} /> <LogGeneralField
fieldKey="stream"
fieldValue={flattenLogData.stream as never}
/>
)} )}
<LogGeneralField <LogGeneralField
fieldKey="timestamp" fieldKey="timestamp"
fieldValue={flattenLogData.timestamp} fieldValue={dayjs((flattenLogData.timestamp as never) / 1e6).format()}
/> />
</div> </div>
{'}'} {'}'}
@ -106,7 +122,7 @@ function LogItem({ logData }) {
<LogSelectedField <LogSelectedField
key={field.name} key={field.name}
fieldKey={field.name} fieldKey={field.name}
fieldValue={flattenLogData[field.name]} fieldValue={flattenLogData[field.name] as never}
/> />
) : null; ) : null;
})} })}

View File

@ -30,4 +30,5 @@ export const QueryBuilderQueryTemplate = {
export const QueryBuilderFormulaTemplate = { export const QueryBuilderFormulaTemplate = {
expression: '', expression: '',
disabled: false, disabled: false,
legend: '',
}; };

View File

@ -68,15 +68,20 @@ function QuerySection({
const handleFormulaChange = ({ const handleFormulaChange = ({
formulaIndex, formulaIndex,
expression, expression,
legend,
toggleDisable, toggleDisable,
toggleDelete, toggleDelete,
}: IQueryBuilderFormulaHandleChange): void => { }: IQueryBuilderFormulaHandleChange): void => {
const allFormulas = formulaQueries; const allFormulas = formulaQueries;
const current = allFormulas[formulaIndex]; const current = allFormulas[formulaIndex];
if (expression) { if (expression !== undefined) {
current.expression = expression; current.expression = expression;
} }
if (legend !== undefined) {
current.legend = legend;
}
if (toggleDisable) { if (toggleDisable) {
current.disabled = !current.disabled; current.disabled = !current.disabled;
} }
@ -179,6 +184,7 @@ function QuerySection({
formulaOnly: true, formulaOnly: true,
expression: 'A', expression: 'A',
disabled: false, disabled: false,
legend: '',
}; };
setFormulaQueries({ ...formulas }); setFormulaQueries({ ...formulas });

View File

@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { import {
Button, Button,

View File

@ -87,7 +87,7 @@ function Retention({
<Col span={12} style={{ display: 'flex' }}> <Col span={12} style={{ display: 'flex' }}>
<RetentionFieldLabel>{text}</RetentionFieldLabel> <RetentionFieldLabel>{text}</RetentionFieldLabel>
</Col> </Col>
<Row span={12} justify="end"> <Row justify="end">
<RetentionFieldInputContainer> <RetentionFieldInputContainer>
<Input <Input
value={selectedValue && selectedValue >= 0 ? selectedValue : ''} value={selectedValue && selectedValue >= 0 ? selectedValue : ''}

View File

@ -1,5 +1,4 @@
import { import {
ArrowLeftOutlined,
FastBackwardOutlined, FastBackwardOutlined,
LeftOutlined, LeftOutlined,
RightOutlined, RightOutlined,
@ -19,7 +18,7 @@ import {
SET_LOG_LINES_PER_PAGE, SET_LOG_LINES_PER_PAGE,
} from 'types/actions/logs'; } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { Container } from './styles'; import { Container } from './styles';
@ -27,7 +26,10 @@ const { Option } = Select;
const ITEMS_PER_PAGE_OPTIONS = [25, 50, 100, 200]; const ITEMS_PER_PAGE_OPTIONS = [25, 50, 100, 200];
function LogControls({ getLogs }) { interface LogControlsProps {
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
}
function LogControls({ getLogs }: LogControlsProps): JSX.Element | null {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
@ -40,14 +42,14 @@ function LogControls({ getLogs }) {
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleLogLinesPerPageChange = (e: number) => { const handleLogLinesPerPageChange = (e: number): void => {
dispatch({ dispatch({
type: SET_LOG_LINES_PER_PAGE, type: SET_LOG_LINES_PER_PAGE,
payload: e, payload: e,
}); });
}; };
const handleGoToLatest = () => { const handleGoToLatest = (): void => {
dispatch({ dispatch({
type: RESET_ID_START_AND_END, type: RESET_ID_START_AND_END,
}); });
@ -65,12 +67,12 @@ function LogControls({ getLogs }) {
}); });
}; };
const handleNavigatePrevious = () => { const handleNavigatePrevious = (): void => {
dispatch({ dispatch({
type: GET_PREVIOUS_LOG_LINES, type: GET_PREVIOUS_LOG_LINES,
}); });
}; };
const handleNavigateNext = () => { const handleNavigateNext = (): void => {
dispatch({ dispatch({
type: GET_NEXT_LOG_LINES, type: GET_NEXT_LOG_LINES,
}); });
@ -105,7 +107,9 @@ function LogControls({ getLogs }) {
} }
interface DispatchProps { interface DispatchProps {
getLogs: () => (dispatch: Dispatch<AppActions>) => void; getLogs: (
props: Parameters<typeof getLogs>[0],
) => (dispatch: Dispatch<AppActions>) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -2,7 +2,7 @@ import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
import { Button, Col, Popover } from 'antd'; import { Button, Col, Popover } from 'antd';
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery'; import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
import React, { Dispatch, memo, useCallback, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux'; import { connect, useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
@ -12,9 +12,9 @@ import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs'; import { SET_SEARCH_QUERY_STRING, TOGGLE_LIVE_TAIL } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
const removeJSONStringifyQuotes = (s: string) => { const removeJSONStringifyQuotes = (s: string): string => {
if (!s || !s.length) { if (!s || !s.length) {
return s; return s;
} }
@ -24,7 +24,21 @@ const removeJSONStringifyQuotes = (s: string) => {
} }
return s; return s;
}; };
function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
interface ActionItemProps {
fieldKey: string;
fieldValue: string;
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => ReturnType<typeof getLogsAggregate>;
}
function ActionItem({
fieldKey,
fieldValue,
getLogs,
getLogsAggregate,
}: ActionItemProps): JSX.Element | unknown {
const { const {
searchFilter: { queryString }, searchFilter: { queryString },
logLinesPerPage, logLinesPerPage,
@ -38,7 +52,7 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
(state) => state.globalTime, (state) => state.globalTime,
); );
const handleQueryAdd = (newQueryString) => { const handleQueryAdd = (newQueryString: string): void => {
let updatedQueryString = queryString || ''; let updatedQueryString = queryString || '';
if (updatedQueryString.length === 0) { if (updatedQueryString.length === 0) {
@ -94,7 +108,7 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
<Button <Button
type="text" type="text"
size="small" size="small"
onClick={() => onClick={(): void =>
handleQueryAdd( handleQueryAdd(
generateFilterQuery({ generateFilterQuery({
fieldKey, fieldKey,
@ -110,7 +124,7 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
<Button <Button
type="text" type="text"
size="small" size="small"
onClick={() => onClick={(): void =>
handleQueryAdd( handleQueryAdd(
generateFilterQuery({ generateFilterQuery({
fieldKey, fieldKey,
@ -124,7 +138,8 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
</Button> </Button>
</Col> </Col>
), ),
[], // eslint-disable-next-line react-hooks/exhaustive-deps
[fieldKey, validatedFieldValue],
); );
return ( return (
<Popover placement="bottomLeft" content={PopOverMenuContent} trigger="click"> <Popover placement="bottomLeft" content={PopOverMenuContent} trigger="click">
@ -134,10 +149,11 @@ function ActionItem({ fieldKey, fieldValue, getLogs, getLogsAggregate }) {
</Popover> </Popover>
); );
} }
interface DispatchProps { interface DispatchProps {
getLogs: () => (dispatch: Dispatch<AppActions>) => void; getLogs: (props: Parameters<typeof getLogs>[0]) => (dispatch: never) => void;
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void; getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => (dispatch: never) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (
@ -147,4 +163,5 @@ const mapDispatchToProps = (
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch), getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
}); });
export default connect(null, mapDispatchToProps)(memo(ActionItem)); // eslint-disable-next-line @typescript-eslint/no-explicit-any
export default connect(null, mapDispatchToProps)(memo(ActionItem as any));

View File

@ -4,9 +4,13 @@ import { Button, Row } from 'antd';
import Editor from 'components/Editor'; import Editor from 'components/Editor';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
import { ILog } from 'types/api/logs/log';
function JSONView({ logData }) { interface JSONViewProps {
const [_state, copyToClipboard] = useCopyToClipboard(); logData: ILog;
}
function JSONView({ logData }: JSONViewProps): JSX.Element {
const [, copyToClipboard] = useCopyToClipboard();
const LogJsonData = useMemo(() => JSON.stringify(logData, null, 2), [logData]); const LogJsonData = useMemo(() => JSON.stringify(logData, null, 2), [logData]);
return ( return (
<div> <div>
@ -25,7 +29,13 @@ function JSONView({ logData }) {
</Button> </Button>
</Row> </Row>
<div style={{ marginTop: '0.5rem' }}> <div style={{ marginTop: '0.5rem' }}>
<Editor value={LogJsonData} language="json" height="70vh" readOnly /> <Editor
value={LogJsonData}
language="json"
height="70vh"
readOnly
onChange={(): void => {}}
/>
</div> </div>
</div> </div>
); );

View File

@ -1,30 +1,34 @@
import { blue, orange } from '@ant-design/colors'; import { blue, orange } from '@ant-design/colors';
import { import { Input, Table } from 'antd';
MenuFoldOutlined,
MinusCircleOutlined,
PlusCircleFilled,
PlusCircleOutlined,
} from '@ant-design/icons';
import { Button, Col, Input, Popover, Table, Typography } from 'antd';
import AddToQueryHOC from 'components/Logs/AddToQueryHOC'; import AddToQueryHOC from 'components/Logs/AddToQueryHOC';
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC'; import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
import flatten from 'flat'; import flatten from 'flat';
import { fieldSearchFilter } from 'lib/logs/fieldSearch'; import { fieldSearchFilter } from 'lib/logs/fieldSearch';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { ILog } from 'types/api/logs/log';
import ActionItem from './ActionItem'; import ActionItem from './ActionItem';
function TableView({ logData }) { // Fields which should be restricted from adding it to query
const RESTRICTED_FIELDS = ['timestamp'];
interface TableViewProps {
logData: ILog;
}
function TableView({ logData }: TableViewProps): JSX.Element | null {
const [fieldSearchInput, setFieldSearchInput] = useState<string>(''); const [fieldSearchInput, setFieldSearchInput] = useState<string>('');
const flattenLogData = useMemo(() => (logData ? flatten(logData) : null), [ const flattenLogData: Record<string, never> | null = useMemo(
logData, () => (logData ? flatten(logData) : null),
]); [logData],
);
if (logData === null) { if (logData === null) {
return null; return null;
} }
const dataSource = Object.keys(flattenLogData) const dataSource =
flattenLogData !== null &&
Object.keys(flattenLogData)
.filter((field) => fieldSearchFilter(field, fieldSearchInput)) .filter((field) => fieldSearchFilter(field, fieldSearchInput))
.map((key) => { .map((key) => {
return { return {
@ -34,38 +38,48 @@ function TableView({ logData }) {
}; };
}); });
if (!dataSource) {
return null;
}
const columns = [ const columns = [
{ {
title: 'Action', title: 'Action',
width: 75, width: 75,
render: (fieldData) => ( render: (fieldData: Record<string, string>): JSX.Element | null => {
<ActionItem const fieldKey = fieldData.field.split('.').slice(-1);
fieldKey={fieldData.field.split('.').slice(-1)} if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
fieldValue={fieldData.value} return <ActionItem fieldKey={fieldKey} fieldValue={fieldData.value} />;
/> }
), return null;
},
}, },
{ {
title: 'Field', title: 'Field',
dataIndex: 'field', dataIndex: 'field',
key: 'field', key: 'field',
width: '35%', width: '35%',
render: (field: string) => ( render: (field: string): JSX.Element => {
<AddToQueryHOC const fieldKey = field.split('.').slice(-1);
fieldKey={field.split('.').slice(-1)} const renderedField = <span style={{ color: blue[4] }}>{field}</span>;
fieldValue={flattenLogData[field]}
> if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
return (
<AddToQueryHOC fieldKey={fieldKey[0]} fieldValue={flattenLogData[field]}>
{' '} {' '}
<span style={{ color: blue[4] }}>{field}</span> {renderedField}
</AddToQueryHOC> </AddToQueryHOC>
), );
}
return renderedField;
},
}, },
{ {
title: 'Value', title: 'Value',
dataIndex: 'value', dataIndex: 'value',
key: 'value', key: 'value',
ellipsis: false, ellipsis: false,
render: (field) => ( render: (field: never): JSX.Element => (
<CopyClipboardHOC textToCopy={field}> <CopyClipboardHOC textToCopy={field}>
<span style={{ color: orange[6] }}>{field}</span> <span style={{ color: orange[6] }}>{field}</span>
</CopyClipboardHOC> </CopyClipboardHOC>
@ -79,13 +93,13 @@ function TableView({ logData }) {
placeholder="Search field names" placeholder="Search field names"
size="large" size="large"
value={fieldSearchInput} value={fieldSearchInput}
onChange={(e) => setFieldSearchInput(e.target.value)} onChange={(e): void => setFieldSearchInput(e.target.value)}
/> />
<Table <Table
// scroll={{ x: true }} // scroll={{ x: true }}
tableLayout='fixed' tableLayout="fixed"
dataSource={dataSource} dataSource={dataSource}
columns={columns} columns={columns as never}
pagination={false} pagination={false}
/> />
</div> </div>

View File

@ -3,19 +3,19 @@ import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs'; import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import JSONView from './JsonView'; import JSONView from './JsonView';
import TableView from './TableView'; import TableView from './TableView';
const { TabPane } = Tabs; const { TabPane } = Tabs;
function LogDetailedView() { function LogDetailedView(): JSX.Element {
const { detailedLog } = useSelector<AppState, ILogsReducer>( const { detailedLog } = useSelector<AppState, ILogsReducer>(
(state) => state.logs, (state) => state.logs,
); );
const dispatch = useDispatch(); const dispatch = useDispatch();
const onDrawerClose = () => { const onDrawerClose = (): void => {
dispatch({ dispatch({
type: SET_DETAILED_LOG_DATA, type: SET_DETAILED_LOG_DATA,
payload: null, payload: null,
@ -35,6 +35,7 @@ function LogDetailedView() {
getContainer={false} getContainer={false}
style={{ overscrollBehavior: 'contain' }} style={{ overscrollBehavior: 'contain' }}
> >
{detailedLog && (
<Tabs defaultActiveKey="1"> <Tabs defaultActiveKey="1">
<TabPane tab="Table" key="1"> <TabPane tab="Table" key="1">
<TableView logData={detailedLog} /> <TableView logData={detailedLog} />
@ -43,6 +44,7 @@ function LogDetailedView() {
<JSONView logData={detailedLog} /> <JSONView logData={detailedLog} />
</TabPane> </TabPane>
</Tabs> </Tabs>
)}
</Drawer> </Drawer>
</div> </div>
); );

View File

@ -1,6 +1,9 @@
import React from 'react'; import React from 'react';
function OptionIcon({ isDarkMode }) { interface OptionIconProps {
isDarkMode: boolean;
}
function OptionIcon({ isDarkMode }: OptionIconProps): JSX.Element {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -10,7 +13,7 @@ function OptionIcon({ isDarkMode }) {
height="1rem" height="1rem"
viewBox="0 0 52 52" viewBox="0 0 52 52"
enableBackground="new 0 0 52 52" enableBackground="new 0 0 52 52"
fill={isDarkMode ? "#eee" : '#222'} fill={isDarkMode ? '#eee' : '#222'}
> >
<path <path
d="M20,44c0-3.3,2.7-6,6-6s6,2.7,6,6s-2.7,6-6,6S20,47.3,20,44z M20,26c0-3.3,2.7-6,6-6s6,2.7,6,6s-2.7,6-6,6 d="M20,44c0-3.3,2.7-6,6-6s6,2.7,6,6s-2.7,6-6,6S20,47.3,20,44z M20,26c0-3.3,2.7-6,6-6s6,2.7,6,6s-2.7,6-6,6

View File

@ -1,27 +1,11 @@
import { green, red } from '@ant-design/colors'; /* eslint-disable react-hooks/exhaustive-deps */
import { import { green } from '@ant-design/colors';
ArrowRightOutlined, import { PauseOutlined, PlayCircleOutlined } from '@ant-design/icons';
IeSquareFilled, import { Button, Popover, Row, Select } from 'antd';
PauseOutlined,
PlayCircleOutlined,
PlaySquareFilled,
ReloadOutlined,
StopFilled,
} from '@ant-design/icons';
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { Button, Card, Popover, Row, Select, Typography } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import { LiveTail } from 'api/logs/livetail'; import { LiveTail } from 'api/logs/livetail';
import { LOCALSTORAGE } from 'constants/localStorage';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { debounce, throttle } from 'lodash-es'; import { throttle } from 'lodash-es';
import React, { import React, { useCallback, useEffect, useMemo, useRef } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { import {
@ -32,7 +16,7 @@ import {
} from 'types/actions/logs'; } from 'types/actions/logs';
import { TLogsLiveTailState } from 'types/api/logs/liveTail'; import { TLogsLiveTailState } from 'types/api/logs/liveTail';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import OptionIcon from './OptionIcon'; import OptionIcon from './OptionIcon';
import { TimePickerCard, TimePickerSelect } from './styles'; import { TimePickerCard, TimePickerSelect } from './styles';
@ -66,7 +50,7 @@ const TIME_PICKER_OPTIONS = [
}, },
]; ];
function LogLiveTail() { function LogLiveTail(): JSX.Element {
const { const {
liveTail, liveTail,
searchFilter: { queryString }, searchFilter: { queryString },
@ -75,15 +59,16 @@ function LogLiveTail() {
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app); const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleLiveTail = (toggleState: TLogsLiveTailState) => { const handleLiveTail = (toggleState: TLogsLiveTailState): void => {
dispatch({ dispatch({
type: TOGGLE_LIVE_TAIL, type: TOGGLE_LIVE_TAIL,
payload: toggleState, payload: toggleState,
}); });
}; };
const batchedEventsRef = useRef([]); const batchedEventsRef = useRef<Record<string, unknown>[]>([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const pushLiveLog = useCallback( const pushLiveLog = useCallback(
throttle(() => { throttle(() => {
dispatch({ dispatch({
@ -96,21 +81,21 @@ function LogLiveTail() {
[], [],
); );
const batchLiveLog = (e) => { const batchLiveLog = (e: { data: string }): void => {
// console.log('EVENT BATCHED'); // console.log('EVENT BATCHED');
batchedEventsRef.current.push(JSON.parse(e.data)); batchedEventsRef.current.push(JSON.parse(e.data as string) as never);
pushLiveLog(); pushLiveLog();
}; };
// This ref depicts thats whether the live tail is played from paused state or not. // This ref depicts thats whether the live tail is played from paused state or not.
const liveTailSourceRef = useRef(null); const liveTailSourceRef = useRef<EventSource | null>(null);
useEffect(() => { useEffect(() => {
if (liveTail === 'PLAYING') { if (liveTail === 'PLAYING') {
// console.log('Starting Live Tail', logs.length); // console.log('Starting Live Tail', logs.length);
const timeStamp = dayjs().subtract(liveTailStartRange, 'minute').valueOf(); const timeStamp = dayjs().subtract(liveTailStartRange, 'minute').valueOf();
const queryParams = new URLSearchParams({ const queryParams = new URLSearchParams({
...(queryString ? { q: queryString } : {}), ...(queryString ? { q: queryString } : {}),
timestampStart: timeStamp, timestampStart: (timeStamp * 1e6) as never,
...(liveTailSourceRef.current && logs.length > 0 ...(liveTailSourceRef.current && logs.length > 0
? { ? {
idGt: logs[0].id, idGt: logs[0].id,
@ -119,23 +104,19 @@ function LogLiveTail() {
}); });
const source = LiveTail(queryParams.toString()); const source = LiveTail(queryParams.toString());
liveTailSourceRef.current = source; liveTailSourceRef.current = source;
source.onmessage = function (e) { source.onmessage = function connectionMessage(e): void {
// pushLiveLog(e)
batchLiveLog(e); batchLiveLog(e);
}; };
source.onopen = function (event) { // source.onopen = function connectionOpen(): void { };
// console.log('open event'); source.onerror = function connectionError(event: unknown): void {
// console.log(event); console.error(event);
};
source.onerror = function (event) {
// console.log(event);
source.close(); source.close();
dispatch({ dispatch({
type: TOGGLE_LIVE_TAIL, type: TOGGLE_LIVE_TAIL,
payload: false, payload: false,
}); });
}; };
} else if (liveTailSourceRef.current) { } else if (liveTailSourceRef.current && liveTailSourceRef.current.close) {
liveTailSourceRef.current?.close(); liveTailSourceRef.current?.close();
} }
@ -144,7 +125,7 @@ function LogLiveTail() {
} }
}, [liveTail]); }, [liveTail]);
const handleLiveTailStart = () => { const handleLiveTailStart = (): void => {
handleLiveTail('PLAYING'); handleLiveTail('PLAYING');
if (!liveTailSourceRef.current) { if (!liveTailSourceRef.current) {
dispatch({ dispatch({
@ -158,7 +139,7 @@ function LogLiveTail() {
<TimePickerSelect <TimePickerSelect
disabled={liveTail === 'PLAYING'} disabled={liveTail === 'PLAYING'}
value={liveTailStartRange} value={liveTailStartRange}
onChange={(value) => { onChange={(value): void => {
dispatch({ dispatch({
type: SET_LIVE_TAIL_START_TIME, type: SET_LIVE_TAIL_START_TIME,
payload: value, payload: value,
@ -183,7 +164,7 @@ function LogLiveTail() {
{liveTail === 'PLAYING' ? ( {liveTail === 'PLAYING' ? (
<Button <Button
type="primary" type="primary"
onClick={() => handleLiveTail('PAUSED')} onClick={(): void => handleLiveTail('PAUSED')}
title="Pause live tail" title="Pause live tail"
style={{ background: green[6] }} style={{ background: green[6] }}
> >
@ -201,7 +182,7 @@ function LogLiveTail() {
{liveTail !== 'STOPPED' && ( {liveTail !== 'STOPPED' && (
<Button <Button
type="dashed" type="dashed"
onClick={() => handleLiveTail('STOPPED')} onClick={(): void => handleLiveTail('STOPPED')}
title="Exit live tail" title="Exit live tail"
> >
<div <div

View File

@ -15,7 +15,10 @@ import { GetLogsFields } from 'store/actions/logs/getFields';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs'; import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
function Logs({ getLogsFields }) { interface LogsProps {
getLogsFields: VoidFunction;
}
function Logs({ getLogsFields }: LogsProps): JSX.Element {
const { search } = useLocation(); const { search } = useLocation();
const urlQuery = useMemo(() => { const urlQuery = useMemo(() => {
@ -29,7 +32,7 @@ function Logs({ getLogsFields }) {
type: SET_SEARCH_QUERY_STRING, type: SET_SEARCH_QUERY_STRING,
payload: urlQuery.get('q'), payload: urlQuery.get('q'),
}); });
}, [dispatch]); }, [dispatch, urlQuery]);
useEffect(() => { useEffect(() => {
getLogsFields(); getLogsFields();
@ -39,16 +42,16 @@ function Logs({ getLogsFields }) {
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<Row style={{ justifyContent: 'center', alignItems: 'center' }}> <Row style={{ justifyContent: 'center', alignItems: 'center' }}>
<SearchFilter /> <SearchFilter />
<Divider type='vertical' style={{ height: '2rem' }} /> <Divider type="vertical" style={{ height: '2rem' }} />
<LogLiveTail /> <LogLiveTail />
</Row> </Row>
<LogsAggregate /> <LogsAggregate />
<LogControls /> <LogControls />
<Divider style={{ margin: 0 }} /> <Divider style={{ margin: 0 }} />
<Row gutter={20} style={{ flexWrap: 'nowrap' }}> <Row gutter={20} style={{ flexWrap: 'nowrap' }}>
<LogsFilters flex="450px" /> <LogsFilters />
<Divider type="vertical" style={{ height: '100%', margin: 0 }} /> <Divider type="vertical" style={{ height: '100%', margin: 0 }} />
<LogsTable flex="auto" /> <LogsTable />
</Row> </Row>
<LogDetailedView /> <LogDetailedView />
</div> </div>

View File

@ -1,42 +1,41 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { blue } from '@ant-design/colors'; import { blue } from '@ant-design/colors';
import Graph from 'components/Graph'; import Graph from 'components/Graph';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import React, { memo, useEffect, useRef } from 'react'; import React, { memo, useEffect, useRef } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux'; import { connect, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate'; import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { Container } from './styles'; import { Container } from './styles';
function LogsAggregate({ getLogsAggregate }) { interface LogsAggregateProps {
getLogsAggregate: (arg0: Parameters<typeof getLogsAggregate>[0]) => void;
}
function LogsAggregate({ getLogsAggregate }: LogsAggregateProps): JSX.Element {
const { const {
searchFilter: { queryString }, searchFilter: { queryString },
logs,
logLinesPerPage,
idEnd, idEnd,
idStart, idStart,
isLoading,
isLoadingAggregate, isLoadingAggregate,
logsAggregate, logsAggregate,
liveTail, liveTail,
liveTailStartRange, liveTailStartRange,
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const dispatch = useDispatch();
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
const reFetchIntervalRef = useRef(null); const reFetchIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => { useEffect(() => {
// console.log('LIVE TAIL LOG AGG', liveTail)
switch (liveTail) { switch (liveTail) {
case 'STOPPED': { case 'STOPPED': {
if (reFetchIntervalRef.current) { if (reFetchIntervalRef.current) {
@ -59,7 +58,7 @@ function LogsAggregate({ getLogsAggregate }) {
} }
case 'PLAYING': { case 'PLAYING': {
const aggregateCall = () => { const aggregateCall = (): void => {
const startTime = const startTime =
dayjs().subtract(liveTailStartRange, 'minute').valueOf() * 1e6; dayjs().subtract(liveTailStartRange, 'minute').valueOf() * 1e6;
const endTime = dayjs().valueOf() * 1e6; const endTime = dayjs().valueOf() * 1e6;
@ -72,17 +71,15 @@ function LogsAggregate({ getLogsAggregate }) {
inputFormat: 'ns', inputFormat: 'ns',
}), }),
q: queryString, q: queryString,
...(idStart ? {idGt: idStart } : {}), ...(idStart ? { idGt: idStart } : {}),
...(idEnd ? { idLt: idEnd } : {}), ...(idEnd ? { idLt: idEnd } : {}),
}); });
}; };
aggregateCall(); aggregateCall();
reFetchIntervalRef.current = setInterval(aggregateCall, 60000); reFetchIntervalRef.current = setInterval(aggregateCall, 60000);
// console.log('LA Play', reFetchIntervalRef.current);
break; break;
} }
case 'PAUSED': { case 'PAUSED': {
// console.log('LA Pause', reFetchIntervalRef.current);
if (reFetchIntervalRef.current) { if (reFetchIntervalRef.current) {
clearInterval(reFetchIntervalRef.current); clearInterval(reFetchIntervalRef.current);
} }
@ -98,7 +95,6 @@ function LogsAggregate({ getLogsAggregate }) {
labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)), labels: logsAggregate.map((s) => new Date(s.timestamp / 1000000)),
datasets: [ datasets: [
{ {
// label: 'Span Count',
data: logsAggregate.map((s) => s.value), data: logsAggregate.map((s) => s.value),
backgroundColor: blue[4], backgroundColor: blue[4],
}, },
@ -123,7 +119,9 @@ function LogsAggregate({ getLogsAggregate }) {
} }
interface DispatchProps { interface DispatchProps {
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void; getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => (dispatch: Dispatch<AppActions>) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -1,18 +1,21 @@
import { import { LoadingOutlined } from '@ant-design/icons';
CloseCircleFilled,
CloseOutlined,
LoadingOutlined,
} from '@ant-design/icons';
import { Button, Popover, Spin } from 'antd'; import { Button, Popover, Spin } from 'antd';
import Spinner from 'components/Spinner'; import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useHover, useHoverDirty } from 'react-use';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import { Field } from './styles'; import { Field } from './styles';
interface FieldItemProps {
name: string;
buttonIcon: React.ReactNode;
buttonOnClick: (arg0: Record<string, unknown>) => void;
fieldData: Record<string, never>;
fieldIndex: number;
isLoading: boolean;
iconHoverText: string;
}
export function FieldItem({ export function FieldItem({
name, name,
buttonIcon, buttonIcon,
@ -20,16 +23,16 @@ export function FieldItem({
fieldData, fieldData,
fieldIndex, fieldIndex,
isLoading, isLoading,
iconHoverText iconHoverText,
}) { }: FieldItemProps): JSX.Element {
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app); const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
return ( return (
<Field <Field
onMouseEnter={() => { onMouseEnter={(): void => {
setIsHovered(true); setIsHovered(true);
}} }}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={(): void => setIsHovered(false)}
isDarkMode={isDarkMode} isDarkMode={isDarkMode}
> >
<span>{name}</span> <span>{name}</span>
@ -43,7 +46,7 @@ export function FieldItem({
type="text" type="text"
size="small" size="small"
icon={buttonIcon} icon={buttonIcon}
onClick={() => buttonOnClick({ fieldData, fieldIndex })} onClick={(): void => buttonOnClick({ fieldData, fieldIndex })}
style={{ color: 'inherit', padding: 0, height: '1rem', width: '1rem' }} style={{ color: 'inherit', padding: 0, height: '1rem', width: '1rem' }}
/> />
</Popover> </Popover>

View File

@ -1,10 +1,6 @@
/* eslint-disable react/no-array-index-key */
import { red } from '@ant-design/colors'; import { red } from '@ant-design/colors';
import { import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
CloseCircleFilled,
CloseOutlined,
PlusCircleFilled,
PlusCircleOutlined,
} from '@ant-design/icons';
import { Input } from 'antd'; import { Input } from 'antd';
import AddToSelectedFields from 'api/logs/AddToSelectedField'; import AddToSelectedFields from 'api/logs/AddToSelectedField';
import RemoveSelectedField from 'api/logs/RemoveFromSelectedField'; import RemoveSelectedField from 'api/logs/RemoveFromSelectedField';
@ -17,34 +13,40 @@ import { ThunkDispatch } from 'redux-thunk';
import { GetLogsFields } from 'store/actions/logs/getFields'; import { GetLogsFields } from 'store/actions/logs/getFields';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import ILogsReducer from 'types/reducer/logs'; import { IInterestingFields, ISelectedFields } from 'types/api/logs/fields';
import { ILogsReducer } from 'types/reducer/logs';
import { FieldItem } from './FieldItem'; import { FieldItem } from './FieldItem';
import { import { CategoryContainer, Container, FieldContainer } from './styles';
CategoryContainer,
Container,
ExtractField,
Field,
FieldContainer,
} from './styles';
const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id']; const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
function LogsFilters({ getLogsFields }) { interface LogsFiltersProps {
getLogsFields: () => void;
}
function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
const { const {
fields: { interesting, selected }, fields: { interesting, selected },
} = useSelector<AppState, ILogsReducer>((state) => state.logs); } = useSelector<AppState, ILogsReducer>((state) => state.logs);
const [selectedFieldLoading, setSelectedFieldLoading] = useState([]); const [selectedFieldLoading, setSelectedFieldLoading] = useState<number[]>([]);
const [interestingFieldLoading, setInterestingFieldLoading] = useState([]); const [interestingFieldLoading, setInterestingFieldLoading] = useState<
number[]
>([]);
const [filterValuesInput, setFilterValuesInput] = useState(''); const [filterValuesInput, setFilterValuesInput] = useState('');
const handleSearch = (e) => { const handleSearch = (e: React.ChangeEvent<HTMLInputElement>): void => {
setFilterValuesInput(e.target.value); setFilterValuesInput((e.target as HTMLInputElement).value);
}; };
const handleAddInterestingToSelected = async ({ fieldData, fieldIndex }) => { const handleAddInterestingToSelected = async ({
setInterestingFieldLoading((prevState) => { fieldData,
fieldIndex,
}: {
fieldData: IInterestingFields;
fieldIndex: number;
}): Promise<void> => {
setInterestingFieldLoading((prevState: number[]) => {
prevState.push(fieldIndex); prevState.push(fieldIndex);
return [...prevState]; return [...prevState];
}); });
@ -59,7 +61,13 @@ function LogsFilters({ getLogsFields }) {
interestingFieldLoading.filter((e) => e !== fieldIndex), interestingFieldLoading.filter((e) => e !== fieldIndex),
); );
}; };
const handleRemoveSelectedField = async ({ fieldData, fieldIndex }) => { const handleRemoveSelectedField = async ({
fieldData,
fieldIndex,
}: {
fieldData: ISelectedFields;
fieldIndex: number;
}): Promise<void> => {
setSelectedFieldLoading((prevState) => { setSelectedFieldLoading((prevState) => {
prevState.push(fieldIndex); prevState.push(fieldIndex);
return [...prevState]; return [...prevState];
@ -77,7 +85,7 @@ function LogsFilters({ getLogsFields }) {
); );
}; };
return ( return (
<Container> <Container flex="450px">
<Input <Input
placeholder="Filter Values" placeholder="Filter Values"
onInput={handleSearch} onInput={handleSearch}
@ -93,14 +101,14 @@ function LogsFilters({ getLogsFields }) {
.filter((field) => fieldSearchFilter(field.name, filterValuesInput)) .filter((field) => fieldSearchFilter(field.name, filterValuesInput))
.map((field, idx) => ( .map((field, idx) => (
<FieldItem <FieldItem
key={field + idx} key={`${JSON.stringify(field)}-${idx}`}
name={field.name} name={field.name}
fieldData={field} fieldData={field as never}
fieldIndex={idx} fieldIndex={idx}
buttonIcon={<CloseOutlined style={{ color: red[5] }} />} buttonIcon={<CloseOutlined style={{ color: red[5] }} />}
buttonOnClick={ buttonOnClick={
!RESTRICTED_SELECTED_FIELDS.includes(field.name) && (!RESTRICTED_SELECTED_FIELDS.includes(field.name) &&
handleRemoveSelectedField handleRemoveSelectedField) as never
} }
isLoading={selectedFieldLoading.includes(idx)} isLoading={selectedFieldLoading.includes(idx)}
iconHoverText="Remove from Selected Fields" iconHoverText="Remove from Selected Fields"
@ -115,12 +123,12 @@ function LogsFilters({ getLogsFields }) {
.filter((field) => fieldSearchFilter(field.name, filterValuesInput)) .filter((field) => fieldSearchFilter(field.name, filterValuesInput))
.map((field, idx) => ( .map((field, idx) => (
<FieldItem <FieldItem
key={field + idx} key={`${JSON.stringify(field)}-${idx}`}
name={field.name} name={field.name}
fieldData={field} fieldData={field as never}
fieldIndex={idx} fieldIndex={idx}
buttonIcon={<PlusCircleFilled />} buttonIcon={<PlusCircleFilled />}
buttonOnClick={handleAddInterestingToSelected} buttonOnClick={handleAddInterestingToSelected as never}
isLoading={interestingFieldLoading.includes(idx)} isLoading={interestingFieldLoading.includes(idx)}
iconHoverText="Add to Selected Fields" iconHoverText="Add to Selected Fields"
/> />

View File

@ -1,7 +1,12 @@
import { Typography } from 'antd'; import { Typography } from 'antd';
import React from 'react'; import React from 'react';
function FieldKey({ name, type }) { interface FieldKeyProps {
name: string;
type: string;
}
function FieldKey({ name, type }: FieldKeyProps): JSX.Element {
return ( return (
<span style={{ margin: '0.25rem 0', display: 'flex', gap: '0.5rem' }}> <span style={{ margin: '0.25rem 0', display: 'flex', gap: '0.5rem' }}>
<Typography.Text>{name}</Typography.Text> <Typography.Text>{name}</Typography.Text>

View File

@ -1,24 +1,22 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-bitwise */
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable no-param-reassign */
/* eslint-disable react/no-array-index-key */
/* eslint-disable react-hooks/exhaustive-deps */
import { CloseOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons';
import { Button, Input, Select, Typography } from 'antd'; import { Button, Input, Select } from 'antd';
import CategoryHeading from 'components/Logs/CategoryHeading'; import CategoryHeading from 'components/Logs/CategoryHeading';
import { import {
ConditionalOperators, ConditionalOperators,
QueryOperatorsMultiVal, QueryOperatorsMultiVal,
QueryOperatorsSingleVal, QueryOperatorsSingleVal,
QueryTypes,
} from 'lib/logql/tokens'; } from 'lib/logql/tokens';
import { chunk, cloneDeep, debounce, flatten } from 'lodash-es'; import { flatten } from 'lodash-es';
import React, { import React, { useEffect, useMemo, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useHoverDirty, useLocation } from 'react-use';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import FieldKey from '../FieldKey'; import FieldKey from '../FieldKey';
@ -26,12 +24,24 @@ import { QueryConditionContainer, QueryFieldContainer } from '../styles';
import { createParsedQueryStructure } from '../utils'; import { createParsedQueryStructure } from '../utils';
const { Option } = Select; const { Option } = Select;
function QueryField({ query, queryIndex, onUpdate, onDelete }) { interface QueryFieldProps {
query: { value: string | string[]; type: string }[];
queryIndex: number;
onUpdate: (query: unknown, queryIndex: number) => void;
onDelete: (queryIndex: number) => void;
}
function QueryField({
query,
queryIndex,
onUpdate,
onDelete,
}: QueryFieldProps): JSX.Element | null {
const { const {
fields: { selected }, fields: { selected },
} = useSelector<AppState, ILogsReducer>((store) => store.logs); } = useSelector<AppState, ILogsReducer>((store) => store.logs);
const getFieldType = (inputKey) => { const getFieldType = (inputKey: string): string => {
// eslint-disable-next-line no-restricted-syntax
for (const selectedField of selected) { for (const selectedField of selected) {
if (inputKey === selectedField.name) { if (inputKey === selectedField.name) {
return selectedField.type; return selectedField.type;
@ -39,11 +49,10 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
} }
return ''; return '';
}; };
const fieldType = useMemo(() => getFieldType(query[0].value), [ const fieldType = useMemo(() => getFieldType(query[0].value as string), [
query, query,
selected,
]); ]);
const handleChange = (qIdx, value) => { const handleChange = (qIdx: number, value: string): void => {
query[qIdx].value = value || ''; query[qIdx].value = value || '';
if (qIdx === 1) { if (qIdx === 1) {
@ -51,16 +60,17 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
if (!Array.isArray(query[2].value)) { if (!Array.isArray(query[2].value)) {
query[2].value = []; query[2].value = [];
} }
} else if (Object.values(QueryOperatorsSingleVal).includes(value)) { } else if (
if (Array.isArray(query[2].value)) { Object.values(QueryOperatorsSingleVal).includes(value) &&
Array.isArray(query[2].value)
) {
query[2].value = ''; query[2].value = '';
} }
} }
}
onUpdate(query, queryIndex); onUpdate(query, queryIndex);
}; };
const handleClear = () => { const handleClear = (): void => {
onDelete(queryIndex); onDelete(queryIndex);
}; };
if (!Array.isArray(query)) { if (!Array.isArray(query)) {
@ -72,39 +82,43 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
style={{ ...(queryIndex === 0 && { gridColumnStart: 2 }) }} style={{ ...(queryIndex === 0 && { gridColumnStart: 2 }) }}
> >
<div style={{ flex: 1, minWidth: 100 }}> <div style={{ flex: 1, minWidth: 100 }}>
<FieldKey name={query[0] && query[0].value} type={fieldType} /> <FieldKey name={(query[0] && query[0].value) as string} type={fieldType} />
</div> </div>
<Select <Select
defaultActiveFirstOption={false} defaultActiveFirstOption={false}
placeholder="Select Operator" placeholder="Select Operator"
defaultValue={ defaultValue={
query[1] && query[1].value ? query[1].value.toUpperCase() : null query[1] && query[1].value
? (query[1].value as string).toUpperCase()
: null
} }
onChange={(e) => handleChange(1, e)} onChange={(e): void => handleChange(1, e)}
style={{ minWidth: 150 }} style={{ minWidth: 150 }}
> >
{Object.values({ {Object.values({
...QueryOperatorsMultiVal, ...QueryOperatorsMultiVal,
...QueryOperatorsSingleVal, ...QueryOperatorsSingleVal,
}).map((cond) => ( }).map((cond) => (
<Option key={cond} value={cond} label={cond} /> <Option key={cond} value={cond} label={cond}>
{cond}
</Option>
))} ))}
</Select> </Select>
<div style={{ flex: 2 }}> <div style={{ flex: 2 }}>
{Array.isArray(query[2].value) || {Array.isArray(query[2].value) ||
Object.values(QueryOperatorsMultiVal).some( Object.values(QueryOperatorsMultiVal).some(
(op) => op.toUpperCase() === query[1].value?.toUpperCase(), (op) => op.toUpperCase() === (query[1].value as string)?.toUpperCase(),
) ? ( ) ? (
<Select <Select
mode="tags" mode="tags"
style={{ width: '100%' }} style={{ width: '100%' }}
onChange={(e) => handleChange(2, e)} onChange={(e): void => handleChange(2, e as never)}
defaultValue={(query[2] && query[2].value) || []} defaultValue={(query[2] && query[2].value) || []}
notFoundContent={null} notFoundContent={null}
/> />
) : ( ) : (
<Input <Input
onChange={(e) => handleChange(2, e.target.value)} onChange={(e): void => handleChange(2, e.target.value)}
style={{ width: '100%' }} style={{ width: '100%' }}
defaultValue={query[2] && query[2].value} defaultValue={query[2] && query[2].value}
/> />
@ -120,24 +134,39 @@ function QueryField({ query, queryIndex, onUpdate, onDelete }) {
</QueryFieldContainer> </QueryFieldContainer>
); );
} }
function QueryConditionField({ query, queryIndex, onUpdate }) {
interface QueryConditionFieldProps {
query: { value: string | string[]; type: string }[];
queryIndex: number;
onUpdate: (arg0: unknown, arg1: number) => void;
}
function QueryConditionField({
query,
queryIndex,
onUpdate,
}: QueryConditionFieldProps): JSX.Element {
return ( return (
<QueryConditionContainer> <QueryConditionContainer>
<Select <Select
defaultValue={query.value.toUpperCase()} defaultValue={
onChange={(e) => { (query as any).value &&
(((query as any)?.value as any) as string).toUpperCase()
}
onChange={(e): void => {
onUpdate({ ...query, value: e }, queryIndex); onUpdate({ ...query, value: e }, queryIndex);
}} }}
style={{ width: '100%' }} style={{ width: '100%' }}
> >
{Object.values(ConditionalOperators).map((cond) => ( {Object.values(ConditionalOperators).map((cond) => (
<Option key={cond} value={cond} label={cond} /> <Option key={cond} value={cond} label={cond}>
{cond}
</Option>
))} ))}
</Select> </Select>
</QueryConditionContainer> </QueryConditionContainer>
); );
} }
const hashCode = (s) => { const hashCode = (s: string): string => {
if (!s) { if (!s) {
return '0'; return '0';
} }
@ -149,14 +178,20 @@ const hashCode = (s) => {
)}`; )}`;
}; };
function QueryBuilder({ updateParsedQuery }) { function QueryBuilder({
updateParsedQuery,
}: {
updateParsedQuery: (arg0: unknown) => void;
}): JSX.Element {
const { const {
searchFilter: { parsedQuery }, searchFilter: { parsedQuery },
} = useSelector<AppState, ILogsReducer>((store) => store.logs); } = useSelector<AppState, ILogsReducer>((store) => store.logs);
const keyPrefixRef = useRef(hashCode(JSON.stringify(parsedQuery))); const keyPrefixRef = useRef(hashCode(JSON.stringify(parsedQuery)));
const [keyPrefix, setKeyPrefix] = useState(keyPrefixRef.current); const [keyPrefix, setKeyPrefix] = useState(keyPrefixRef.current);
const generatedQueryStructure = createParsedQueryStructure(parsedQuery); const generatedQueryStructure = createParsedQueryStructure(
parsedQuery as never[],
);
useEffect(() => { useEffect(() => {
const incomingHashCode = hashCode(JSON.stringify(parsedQuery)); const incomingHashCode = hashCode(JSON.stringify(parsedQuery));
@ -166,18 +201,19 @@ function QueryBuilder({ updateParsedQuery }) {
} }
}, [parsedQuery]); }, [parsedQuery]);
const handleUpdate = (
query: { value: string | string[]; type: string }[],
const handleUpdate = (query, queryIndex): void => { queryIndex: number,
): void => {
const updatedParsedQuery = generatedQueryStructure; const updatedParsedQuery = generatedQueryStructure;
updatedParsedQuery[queryIndex] = query; updatedParsedQuery[queryIndex] = query as never;
const flatParsedQuery = flatten(updatedParsedQuery).filter((q) => q.value); const flatParsedQuery = flatten(updatedParsedQuery).filter((q) => q.value);
keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery)); keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery));
updateParsedQuery(flatParsedQuery); updateParsedQuery(flatParsedQuery);
}; };
const handleDelete = (queryIndex) => { const handleDelete = (queryIndex: number): void => {
const updatedParsedQuery = generatedQueryStructure; const updatedParsedQuery = generatedQueryStructure;
updatedParsedQuery.splice(queryIndex - 1, 2); updatedParsedQuery.splice(queryIndex - 1, 2);
@ -186,15 +222,15 @@ function QueryBuilder({ updateParsedQuery }) {
updateParsedQuery(flatParsedQuery); updateParsedQuery(flatParsedQuery);
}; };
const QueryUI = () => const QueryUI = (): JSX.Element | JSX.Element[] =>
generatedQueryStructure.map((query, idx) => { generatedQueryStructure.map((query, idx) => {
if (Array.isArray(query)) if (Array.isArray(query))
return ( return (
<QueryField <QueryField
key={keyPrefix + idx} key={keyPrefix + idx}
query={query} query={query as never}
queryIndex={idx} queryIndex={idx}
onUpdate={handleUpdate} onUpdate={handleUpdate as never}
onDelete={handleDelete} onDelete={handleDelete}
/> />
); );
@ -204,7 +240,7 @@ function QueryBuilder({ updateParsedQuery }) {
key={keyPrefix + idx} key={keyPrefix + idx}
query={query} query={query}
queryIndex={idx} queryIndex={idx}
onUpdate={handleUpdate} onUpdate={handleUpdate as never}
/> />
); );
}); });

View File

@ -1,18 +1,22 @@
import { Button, Typography } from 'antd'; import { Button } from 'antd';
import CategoryHeading from 'components/Logs/CategoryHeading'; import CategoryHeading from 'components/Logs/CategoryHeading';
import { map } from 'lodash-es'; import { map } from 'lodash-es';
import React from 'react'; import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { ADD_SEARCH_FIELD_QUERY_STRING } from 'types/actions/logs'; import { ADD_SEARCH_FIELD_QUERY_STRING } from 'types/actions/logs';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import FieldKey from './FieldKey'; import FieldKey from './FieldKey';
function SuggestedItem({ name, type }) { interface SuggestedItemProps {
name: string;
type: string;
}
function SuggestedItem({ name, type }: SuggestedItemProps): JSX.Element {
const dispatch = useDispatch(); const dispatch = useDispatch();
const addSuggestedField = () => { const addSuggestedField = (): void => {
dispatch({ dispatch({
type: ADD_SEARCH_FIELD_QUERY_STRING, type: ADD_SEARCH_FIELD_QUERY_STRING,
payload: name, payload: name,
@ -29,7 +33,7 @@ function SuggestedItem({ name, type }) {
); );
} }
function Suggestions() { function Suggestions(): JSX.Element {
const { const {
fields: { selected }, fields: { selected },
} = useSelector<AppState, ILogsReducer>((store) => store.logs); } = useSelector<AppState, ILogsReducer>((store) => store.logs);

View File

@ -1,9 +1,12 @@
import React from 'react'; import React from 'react';
import QueryBuilder from './QueryBuilder/QueryBuilder'; import QueryBuilder from './QueryBuilder/QueryBuilder';
import Suggestions from './Suggestions'; import Suggestions from './Suggestions';
function SearchFields({updateParsedQuery}): JSX.Element { interface SearchFieldsProps {
updateParsedQuery: () => void;
}
function SearchFields({ updateParsedQuery }: SearchFieldsProps): JSX.Element {
return ( return (
<> <>
<QueryBuilder updateParsedQuery={updateParsedQuery} /> <QueryBuilder updateParsedQuery={updateParsedQuery} />

View File

@ -1,3 +1,7 @@
/* eslint-disable */
// @ts-ignore
// @ts-nocheck
import { QueryTypes } from 'lib/logql/tokens'; import { QueryTypes } from 'lib/logql/tokens';
export const queryKOVPair = () => [ export const queryKOVPair = () => [
@ -30,7 +34,7 @@ export const createParsedQueryStructure = (parsedQuery = []) => {
cond = null; cond = null;
qCtr = -1; qCtr = -1;
} }
let stagingArr = structuredArray[structuredArray.length - 1]; const stagingArr = structuredArray[structuredArray.length - 1];
const prevQuery = const prevQuery =
Array.isArray(stagingArr) && qCtr >= 0 ? stagingArr[qCtr] : null; Array.isArray(stagingArr) && qCtr >= 0 ? stagingArr[qCtr] : null;

View File

@ -1,23 +1,11 @@
import { /* eslint-disable react-hooks/exhaustive-deps */
CloseCircleFilled, import { CloseSquareOutlined } from '@ant-design/icons';
CloseCircleOutlined,
CloseSquareOutlined,
} from '@ant-design/icons';
import { Button, Input } from 'antd'; import { Button, Input } from 'antd';
import useClickOutside from 'hooks/useClickOutside'; import useClickOutside from 'hooks/useClickOutside';
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import { debounce, throttle } from 'lodash-es'; import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
import React, {
memo,
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import { connect, useDispatch, useSelector } from 'react-redux'; import { connect, useDispatch, useSelector } from 'react-redux';
import { useClickAway, useLocation } from 'react-use'; import { useLocation } from 'react-use';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { getLogs } from 'store/actions/logs/getLogs'; import { getLogs } from 'store/actions/logs/getLogs';
@ -26,7 +14,7 @@ import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { TOGGLE_LIVE_TAIL } from 'types/actions/logs'; import { TOGGLE_LIVE_TAIL } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import SearchFields from './SearchFields'; import SearchFields from './SearchFields';
import { DropDownContainer } from './styles'; import { DropDownContainer } from './styles';
@ -34,7 +22,16 @@ import { useSearchParser } from './useSearchParser';
const { Search } = Input; const { Search } = Input;
function SearchFilter({ getLogs, getLogsAggregate }) { interface SearchFilterProps {
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => ReturnType<typeof getLogsAggregate>;
}
function SearchFilter({
getLogs,
getLogsAggregate,
}: SearchFilterProps): JSX.Element {
const { const {
queryString, queryString,
updateParsedQuery, updateParsedQuery,
@ -146,26 +143,29 @@ function SearchFilter({ getLogs, getLogsAggregate }) {
<DropDownContainer> <DropDownContainer>
<Button <Button
type="text" type="text"
onClick={() => setShowDropDown(false)} onClick={(): void => setShowDropDown(false)}
style={{ style={{
position: 'absolute', position: 'absolute',
top: 0, top: 0,
right: 0, right: 0,
}} }}
> >
<CloseSquareOutlined size="large" /> <CloseSquareOutlined />
</Button> </Button>
<SearchFields updateParsedQuery={updateParsedQuery} /> <SearchFields updateParsedQuery={updateParsedQuery as never} />
</DropDownContainer> </DropDownContainer>
)} )}
</div> </div>
</div> </div>
); );
} }
interface DispatchProps { interface DispatchProps {
getLogs: () => (dispatch: Dispatch<AppActions>) => void; getLogs: (
getLogsAggregate: () => (dispatch: Dispatch<AppActions>) => void; props: Parameters<typeof getLogs>[0],
) => (dispatch: Dispatch<AppActions>) => void;
getLogsAggregate: (
props: Parameters<typeof getLogsAggregate>[0],
) => (dispatch: Dispatch<AppActions>) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -1,16 +1,21 @@
import history from 'lib/history'; import history from 'lib/history';
import { parseQuery, reverseParser } from 'lib/logql'; import { parseQuery, reverseParser } from 'lib/logql';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { useCallback, useEffect, useReducer, useState } from 'react'; import { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { import {
SET_SEARCH_QUERY_PARSED_PAYLOAD, SET_SEARCH_QUERY_PARSED_PAYLOAD,
SET_SEARCH_QUERY_STRING, SET_SEARCH_QUERY_STRING,
} from 'types/actions/logs'; } from 'types/actions/logs';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
export function useSearchParser() { export function useSearchParser(): {
queryString: string;
parsedQuery: unknown;
updateParsedQuery: (arg0: unknown) => void;
updateQueryString: (arg0: unknown) => void;
} {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { const {
searchFilter: { parsedQuery, queryString }, searchFilter: { parsedQuery, queryString },

View File

@ -1,23 +1,24 @@
import { fetchEventSource } from '@microsoft/fetch-event-source'; /* eslint-disable no-nested-ternary */
import { Card, Typography } from 'antd'; import { Typography } from 'antd';
import { LiveTail } from 'api/logs/livetail';
import LogItem from 'components/Logs/LogItem'; import LogItem from 'components/Logs/LogItem';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { map } from 'lodash-es'; import { map } from 'lodash-es';
import React, { memo, useEffect, useRef } from 'react'; import React, { memo, useEffect } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux'; import { connect, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { getLogs } from 'store/actions/logs/getLogs'; import { getLogs } from 'store/actions/logs/getLogs';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { PUSH_LIVE_TAIL_EVENT } from 'types/actions/logs';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
import { Container, Heading } from './styles'; import { Container, Heading } from './styles';
function LogsTable({ getLogs }) { interface LogsTableProps {
getLogs: (props: Parameters<typeof getLogs>[0]) => ReturnType<typeof getLogs>;
}
function LogsTable({ getLogs }: LogsTableProps): JSX.Element {
const { const {
searchFilter: { queryString }, searchFilter: { queryString },
logs, logs,
@ -51,7 +52,7 @@ function LogsTable({ getLogs }) {
return <Spinner height={20} tip="Getting Logs" />; return <Spinner height={20} tip="Getting Logs" />;
} }
return ( return (
<Container> <Container flex="auto">
<Heading> <Heading>
<Typography.Text <Typography.Text
style={{ style={{
@ -74,7 +75,9 @@ function LogsTable({ getLogs }) {
} }
interface DispatchProps { interface DispatchProps {
getLogs: () => (dispatch: Dispatch<AppActions>) => void; getLogs: (
props: Parameters<typeof getLogs>[0],
) => (dispatch: Dispatch<AppActions>) => void;
} }
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -1,4 +1,3 @@
import { grey } from '@ant-design/colors';
import { Card, Col } from 'antd'; import { Card, Col } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
@ -9,7 +8,6 @@ export const Container = styled(Col)`
`; `;
export const Heading = styled(Card)` export const Heading = styled(Card)`
margin-bottom: 0.1rem; margin-bottom: 0.1rem;
.ant-card-body { .ant-card-body {
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;

View File

@ -49,7 +49,8 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString()); urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
history.replace( history.replace(
`${ROUTES.TRACE `${
ROUTES.TRACE
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, }?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
); );
}; };
@ -100,17 +101,12 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString()); urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
history.replace( history.replace(
`${ROUTES.TRACE `${
ROUTES.TRACE
}?${urlParams.toString()}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, }?${urlParams.toString()}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
); );
}; };
console.log(getWidget([
{
query: `max(sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", status_code="STATUS_CODE_ERROR"${resourceAttributePromQLQuery}}[5m]) OR rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", http_status_code=~"5.."${resourceAttributePromQLQuery}}[5m]))*100/sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"${resourceAttributePromQLQuery}}[5m]))) < 1000 OR vector(0)`,
legend: 'Error Percentage',
},
]))
debugger;
return ( return (
<> <>
<Row gutter={24}> <Row gutter={24}>

View File

@ -1,9 +1,13 @@
import Table, { ColumnsType } from 'antd/lib/table'; import { blue } from '@ant-design/colors';
import { SearchOutlined } from '@ant-design/icons';
import { Button, Input, Space, Table } from 'antd';
import type { ColumnsType, ColumnType } from 'antd/es/table';
import type { FilterConfirmProps } from 'antd/es/table/interface';
import localStorageGet from 'api/browser/localstorage/get'; import localStorageGet from 'api/browser/localstorage/get';
import localStorageSet from 'api/browser/localstorage/set'; import localStorageSet from 'api/browser/localstorage/set';
import { SKIP_ONBOARDING } from 'constants/onboarding'; import { SKIP_ONBOARDING } from 'constants/onboarding';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import React, { useState } from 'react'; import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@ -27,6 +31,57 @@ function Metrics(): JSX.Element {
localStorageSet(SKIP_ONBOARDING, 'true'); localStorageSet(SKIP_ONBOARDING, 'true');
setSkipOnboarding(true); setSkipOnboarding(true);
}; };
const handleSearch = (confirm: (param?: FilterConfirmProps) => void): void => {
confirm();
};
const FilterIcon = useCallback(
({ filtered }) => (
<SearchOutlined
style={{
color: filtered ? blue[6] : undefined,
}}
/>
),
[],
);
const filterDropdown = useCallback(
({ setSelectedKeys, selectedKeys, confirm }) => (
<div
style={{
padding: 8,
}}
>
<Input
placeholder="Search by service"
value={selectedKeys[0]}
onChange={(e): void =>
setSelectedKeys(e.target.value ? [e.target.value] : [])
}
allowClear
onPressEnter={(): void => handleSearch(confirm)}
style={{
marginBottom: 8,
}}
/>
<Space>
<Button
type="primary"
onClick={(): void => handleSearch(confirm)}
icon={<SearchOutlined />}
size="small"
style={{
width: 90,
}}
>
Search
</Button>
</Space>
</div>
),
[],
);
if ( if (
services.length === 0 && services.length === 0 &&
@ -37,21 +92,37 @@ function Metrics(): JSX.Element {
return <SkipBoardModal onContinueClick={onContinueClick} />; return <SkipBoardModal onContinueClick={onContinueClick} />;
} }
const columns: ColumnsType<DataProps> = [ type DataIndex = keyof ServicesList;
{
title: 'Application', const getColumnSearchProps = (
dataIndex: 'serviceName', dataIndex: DataIndex,
key: 'serviceName', ): ColumnType<DataProps> => ({
filterDropdown,
filterIcon: FilterIcon,
onFilter: (value: string | number | boolean, record: DataProps): boolean =>
record[dataIndex]
.toString()
.toLowerCase()
.includes(value.toString().toLowerCase()),
render: (text: string): JSX.Element => ( render: (text: string): JSX.Element => (
<Link to={`${ROUTES.APPLICATION}/${text}${search}`}> <Link to={`${ROUTES.APPLICATION}/${text}${search}`}>
<Name>{text}</Name> <Name>{text}</Name>
</Link> </Link>
), ),
});
const columns: ColumnsType<DataProps> = [
{
title: 'Application',
dataIndex: 'serviceName',
key: 'serviceName',
...getColumnSearchProps('serviceName'),
}, },
{ {
title: 'P99 latency (in ms)', title: 'P99 latency (in ms)',
dataIndex: 'p99', dataIndex: 'p99',
key: 'p99', key: 'p99',
defaultSortOrder: 'descend',
sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99, sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99,
render: (value: number): string => (value / 1000000).toFixed(2), render: (value: number): string => (value / 1000000).toFixed(2),
}, },

View File

@ -37,6 +37,14 @@ function MetricsBuilderFormula({
style={{ marginBottom: '0.5rem' }} style={{ marginBottom: '0.5rem' }}
rows={2} rows={2}
/> />
<Input
onChange={(event): void => {
handleFormulaChange({ formulaIndex, legend: event.target.value });
}}
size="middle"
defaultValue={formulaData.legend}
addonBefore="Legend Format"
/>
</QueryHeader> </QueryHeader>
); );
} }

View File

@ -85,6 +85,7 @@ function QueryBuilderQueryContainer({
const handleQueryBuilderFormulaChange = ({ const handleQueryBuilderFormulaChange = ({
formulaIndex, formulaIndex,
expression, expression,
legend,
toggleDisable, toggleDisable,
toggleDelete, toggleDelete,
}: IQueryBuilderFormulaHandleChange): void => { }: IQueryBuilderFormulaHandleChange): void => {
@ -94,9 +95,12 @@ function QueryBuilderQueryContainer({
]; ];
const currentIndexFormula = allFormulas[formulaIndex as number]; const currentIndexFormula = allFormulas[formulaIndex as number];
if (expression) { if (expression !== undefined) {
currentIndexFormula.expression = expression; currentIndexFormula.expression = expression;
} }
if (legend !== undefined) {
currentIndexFormula.legend = legend;
}
if (toggleDisable) { if (toggleDisable) {
currentIndexFormula.disabled = !currentIndexFormula.disabled; currentIndexFormula.disabled = !currentIndexFormula.disabled;

View File

@ -19,5 +19,6 @@ export interface IQueryBuilderFormulaHandleChange {
formulaIndex: number | string; formulaIndex: number | string;
expression?: IMetricsBuilderFormula['expression']; expression?: IMetricsBuilderFormula['expression'];
toggleDisable?: IMetricsBuilderFormula['disabled']; toggleDisable?: IMetricsBuilderFormula['disabled'];
legend?: IMetricsBuilderFormula['legend'];
toggleDelete?: boolean; toggleDelete?: boolean;
} }

View File

@ -18,7 +18,7 @@ const breadcrumbNameMap = {
[ROUTES.ERROR_DETAIL]: 'Errors', [ROUTES.ERROR_DETAIL]: 'Errors',
[ROUTES.LIST_ALL_ALERT]: 'Alerts', [ROUTES.LIST_ALL_ALERT]: 'Alerts',
[ROUTES.ALL_DASHBOARD]: 'Dashboard', [ROUTES.ALL_DASHBOARD]: 'Dashboard',
[ROUTES.LOGS]: 'Logs' [ROUTES.LOGS]: 'Logs',
}; };
function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element { function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element {

View File

@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint no-useless-escape: 0 */ /* eslint no-useless-escape: 0 */
const logqlQueries = [ const logqlQueries = [

View File

@ -1,5 +1,5 @@
import { logqlQueries } from 'lib/__fixtures__/logql'; import { logqlQueries } from 'lib/__fixtures__/logql';
import reverseParser from 'lib/logql/reverseParser'; import { reverseParser } from 'lib/logql/reverseParser';
describe('lib/logql/reverseParser', () => { describe('lib/logql/reverseParser', () => {
test('reverse parse valid queries', () => { test('reverse parse valid queries', () => {

View File

@ -1,5 +1,6 @@
import splitter from 'lib/logql/splitter';
import { logqlQueries } from 'lib/__fixtures__/logql'; import { logqlQueries } from 'lib/__fixtures__/logql';
import { splitter } from 'lib/logql/splitter';
describe('lib/logql/splitter', () => { describe('lib/logql/splitter', () => {
test('splitter valid quereies', () => { test('splitter valid quereies', () => {
logqlQueries.forEach((queryObject) => { logqlQueries.forEach((queryObject) => {

View File

@ -1,4 +1,11 @@
/* eslint-disable */
// @ts-ignore
// @ts-nocheck // @ts-nocheck
import {
ErrorConvertToFullText,
ErrorInvalidQueryPair,
} from 'lib/logql/errors';
import splitter from 'lib/logql/splitter'; import splitter from 'lib/logql/splitter';
import { import {
ConditionalOperators, ConditionalOperators,
@ -6,10 +13,6 @@ import {
QueryOperatorsSingleVal, QueryOperatorsSingleVal,
QueryTypes, QueryTypes,
} from 'lib/logql/tokens'; } from 'lib/logql/tokens';
import {
ErrorConvertToFullText,
ErrorInvalidQueryPair,
} from 'lib/logql/errors';
const validateMultiValue = (queryToken: string): boolean => { const validateMultiValue = (queryToken: string): boolean => {
const queryValues = []; const queryValues = [];

View File

@ -1,3 +1,5 @@
/* eslint-disable */
// @ts-ignore
// @ts-nocheck // @ts-nocheck
export const reverseParser = ( export const reverseParser = (

View File

@ -1,7 +1,9 @@
/* eslint-disable */
// @ts-ignore
// @ts-nocheck // @ts-nocheck
export const splitter = (queryString) => { export const splitter = (queryString: string): string[] => {
const splittedParts = []; const splittedParts: string[] = [];
let start = 0; let start = 0;
let isBracketStart = false; let isBracketStart = false;
let isQuoteStart = false; let isQuoteStart = false;
@ -13,10 +15,6 @@ export const splitter = (queryString) => {
for (let idx = 0; idx < queryString.length; idx += 1) { for (let idx = 0; idx < queryString.length; idx += 1) {
const currentChar = queryString[idx]; const currentChar = queryString[idx];
// if (start === null) {
// start = idx;
// }
if (currentChar === ' ') { if (currentChar === ' ') {
if (!isBracketStart && !isQuoteStart) { if (!isBracketStart && !isQuoteStart) {
pushPart(idx); pushPart(idx);
@ -48,7 +46,6 @@ export const splitter = (queryString) => {
pushPart(queryString.length); pushPart(queryString.length);
} }
// console.log(splittedParts.filter(Boolean));
return splittedParts.map((s) => String.raw`${s}`).filter(Boolean); return splittedParts.map((s) => String.raw`${s}`).filter(Boolean);
}; };

View File

@ -17,7 +17,6 @@ export const ConditionalOperators = {
OR: 'OR', OR: 'OR',
}; };
export const QueryTypes = { export const QueryTypes = {
QUERY_KEY: 'QUERY_KEY', QUERY_KEY: 'QUERY_KEY',
QUERY_OPERATOR: 'QUERY_OPERATOR', QUERY_OPERATOR: 'QUERY_OPERATOR',

View File

@ -1,4 +1,7 @@
export const fieldSearchFilter = (searchSpace = '', currentValue = '') => { export const fieldSearchFilter = (
searchSpace = '',
currentValue = '',
): boolean => {
if (!currentValue || !searchSpace) { if (!currentValue || !searchSpace) {
return true; return true;
} }

View File

@ -1,14 +1,14 @@
import { ILog } from 'types/api/logs/log'; import { ILog } from 'types/api/logs/log';
export function FlatLogData(log: ILog): Record<string, never> { export function FlatLogData(log: ILog): Record<string, unknown> {
let flattenLogObject: Record<string, never> = {}; const flattenLogObject: Record<string, unknown> = {};
Object.keys(log).forEach((key: string) => { Object.keys(log).forEach((key: string): void => {
if (typeof log[key] !== 'object') { if (typeof log[key as never] !== 'object') {
flattenLogObject[key] = log[key]; flattenLogObject[key] = log[key as never];
} else { } else {
Object.keys(log[key]).forEach((childKey) => { Object.keys(log[key as never]).forEach((childKey) => {
flattenLogObject[childKey] = log[key][childKey] flattenLogObject[childKey] = log[key as never][childKey];
}); });
} }
}); });

View File

@ -1,4 +1,18 @@
export const generateFilterQuery = ({ fieldKey, fieldValue, type }) => { import { QueryOperatorsMultiVal } from 'lib/logql/tokens';
type Keys = keyof typeof QueryOperatorsMultiVal;
type Values = typeof QueryOperatorsMultiVal[Keys];
interface GenerateFilterQueryParams {
fieldKey: string;
fieldValue: string;
type: Values;
}
export const generateFilterQuery = ({
fieldKey,
fieldValue,
type,
}: GenerateFilterQueryParams): string => {
let generatedQueryString = `${fieldKey} ${type} `; let generatedQueryString = `${fieldKey} ${type} `;
if (typeof fieldValue === 'number') { if (typeof fieldValue === 'number') {
generatedQueryString += `(${fieldValue})`; generatedQueryString += `(${fieldValue})`;

View File

@ -1,7 +1,7 @@
import Logs from 'container/Logs'; import Logs from 'container/Logs';
import React from 'react'; import React from 'react';
function LogsHome() { function LogsHome(): JSX.Element {
return <Logs />; return <Logs />;
} }

View File

@ -76,9 +76,11 @@ export async function GetMetricQueryRange({
queryData[WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME].map((formula) => { queryData[WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME].map((formula) => {
const generatedFormulaPayload = {}; const generatedFormulaPayload = {};
legendMap[formula.name] = formula.legend || formula.name;
generatedFormulaPayload.queryName = formula.name; generatedFormulaPayload.queryName = formula.name;
generatedFormulaPayload.expression = formula.expression; generatedFormulaPayload.expression = formula.expression;
generatedFormulaPayload.disabled = formula.disabled; generatedFormulaPayload.disabled = formula.disabled;
generatedFormulaPayload.legend = formula.legend;
builderQueries[formula.name] = generatedFormulaPayload; builderQueries[formula.name] = generatedFormulaPayload;
}); });
QueryPayload.compositeMetricQuery.builderQueries = builderQueries; QueryPayload.compositeMetricQuery.builderQueries = builderQueries;

View File

@ -1,13 +1,14 @@
import GetSearchFields from 'api/logs/GetSearchFields'; import GetSearchFields from 'api/logs/GetSearchFields';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { GET_FIELDS, SET_FIELDS } from 'types/actions/logs'; import { SET_FIELDS } from 'types/actions/logs';
import { TraceReducer } from 'types/reducer/trace';
export const AddToSelectedField = (): ((dispatch: Dispatch<AppActions>) => void) => { export const AddToSelectedField = (): ((
return async (dispatch): void => { dispatch: Dispatch<AppActions>,
) => void) => {
return async (dispatch): Promise<void> => {
const response = await GetSearchFields(); const response = await GetSearchFields();
if (response.payload)
dispatch({ dispatch({
type: SET_FIELDS, type: SET_FIELDS,
payload: response.payload, payload: response.payload,

View File

@ -1,13 +1,12 @@
import GetSearchFields from 'api/logs/GetSearchFields'; import GetSearchFields from 'api/logs/GetSearchFields';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { GET_FIELDS, SET_FIELDS } from 'types/actions/logs'; import { SET_FIELDS } from 'types/actions/logs';
import { TraceReducer } from 'types/reducer/trace';
const IGNORED_SELECTED_FIELDS = ['timestamp']; const IGNORED_SELECTED_FIELDS = ['timestamp'];
export const GetLogsFields = (): ((dispatch: Dispatch<AppActions>) => void) => { export const GetLogsFields = (): ((dispatch: Dispatch<AppActions>) => void) => {
return async (dispatch): void => { return async (dispatch): Promise<void> => {
const response = await GetSearchFields(); const response = await GetSearchFields();
if (response.payload) { if (response.payload) {
dispatch({ dispatch({

View File

@ -2,9 +2,12 @@ import GetLogs from 'api/logs/GetLogs';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { SET_LOADING, SET_LOGS } from 'types/actions/logs'; import { SET_LOADING, SET_LOGS } from 'types/actions/logs';
import { Props } from 'types/api/logs/getLogs';
export const getLogs = (props): ((dispatch: Dispatch<AppActions>) => void) => { export const getLogs = (
return async (dispatch): void => { props: Props,
): ((dispatch: Dispatch<AppActions>) => void) => {
return async (dispatch): Promise<void> => {
dispatch({ dispatch({
type: SET_LOADING, type: SET_LOADING,
payload: true, payload: true,

View File

@ -1,19 +1,17 @@
import GetLogs from 'api/logs/GetLogs';
import GetLogsAggregate from 'api/logs/GetLogsAggregate'; import GetLogsAggregate from 'api/logs/GetLogsAggregate';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { import {
SET_LOADING,
SET_LOADING_AGGREGATE, SET_LOADING_AGGREGATE,
SET_LOGS,
SET_LOGS_AGGREGATE_SERIES, SET_LOGS_AGGREGATE_SERIES,
} from 'types/actions/logs'; } from 'types/actions/logs';
import { Props } from 'types/api/logs/getLogsAggregate';
import { ILogsAggregate } from 'types/api/logs/logAggregate'; import { ILogsAggregate } from 'types/api/logs/logAggregate';
export const getLogsAggregate = ( export const getLogsAggregate = (
props, props: Props,
): ((dispatch: Dispatch<AppActions>) => void) => { ): ((dispatch: Dispatch<AppActions>) => void) => {
return async (dispatch): void => { return async (dispatch): Promise<void> => {
dispatch({ dispatch({
type: SET_LOADING_AGGREGATE, type: SET_LOADING_AGGREGATE,
payload: true, payload: true,

View File

@ -3,7 +3,7 @@ import { combineReducers } from 'redux';
import appReducer from './app'; import appReducer from './app';
import dashboardReducer from './dashboard'; import dashboardReducer from './dashboard';
import globalTimeReducer from './global'; import globalTimeReducer from './global';
import LogsReducer from './logs'; import { LogsReducer } from './logs';
import metricsReducers from './metric'; import metricsReducers from './metric';
import { ServiceMapReducer } from './serviceMap'; import { ServiceMapReducer } from './serviceMap';
import traceReducer from './trace'; import traceReducer from './trace';

View File

@ -21,7 +21,7 @@ import {
STOP_LIVE_TAIL, STOP_LIVE_TAIL,
TOGGLE_LIVE_TAIL, TOGGLE_LIVE_TAIL,
} from 'types/actions/logs'; } from 'types/actions/logs';
import ILogsReducer from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
const initialState: ILogsReducer = { const initialState: ILogsReducer = {
fields: { fields: {
@ -39,33 +39,10 @@ const initialState: ILogsReducer = {
isLoading: false, isLoading: false,
isLoadingAggregate: false, isLoadingAggregate: false,
logsAggregate: [], logsAggregate: [],
detailedLog: null,
liveTail: 'STOPPED', liveTail: 'STOPPED',
liveTailStartRange: 15, liveTailStartRange: 15,
// detailedLog: { selectedLogId: null,
// timestamp: 1659360016955270100, detailedLog: null,
// id: '2CkBCauK8m3nkyKR19YhCw6WfvD',
// traceId: '',
// spanId: '',
// traceFlags: 0,
// severityText: '',
// severityNumber: 0,
// body:
// '49.207.215.17 - - [01/Aug/2022:13:20:16 +0000] "OPTIONS /api/v1/logs/fields HTTP/1.1" 200 23 "http://localhost:3301/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" "-"\n',
// resourcesString: {
// source: 'docker',
// },
// attributesString: {
// container_id:
// 'b3c6808322609f7671c18a09515d9c84909c873471b560da65c1afe0dfd933ea',
// log_file_path:
// '/var/lib/docker/containers/b3c6808322609f7671c18a09515d9c84909c873471b560da65c1afe0dfd933ea/b3c6808322609f7671c18a09515d9c84909c873471b560da65c1afe0dfd933ea-json.log',
// stream: 'stdout',
// time: '2022-08-01T13:20:16.955270245Z',
// },
// attributesInt: {},
// attributesFloat: {},
// },
}; };
export const LogsReducer = ( export const LogsReducer = (
@ -123,9 +100,12 @@ export const LogsReducer = (
case ADD_SEARCH_FIELD_QUERY_STRING: { case ADD_SEARCH_FIELD_QUERY_STRING: {
const updatedQueryString = const updatedQueryString =
state.searchFilter.queryString + state.searchFilter.queryString ||
(state.searchFilter.queryString.length > 0 ? ' and ' : '') + `${
action.payload; state.searchFilter.queryString && state.searchFilter.queryString.length > 0
? ' and '
: ''
}${action.payload}`;
const updatedParsedQuery = parseQuery(updatedQueryString); const updatedParsedQuery = parseQuery(updatedQueryString);
return { return {

View File

@ -123,42 +123,6 @@ export interface SetLiveTailStartTime {
type: typeof SET_LIVE_TAIL_START_TIME; type: typeof SET_LIVE_TAIL_START_TIME;
payload: number; payload: number;
} }
// export interface GetServiceListLoading {
// type:
// | typeof GET_SERVICE_LIST_LOADING_START
// | typeof GET_INITIAL_APPLICATION_LOADING;
// }
// export interface GetServiceListError {
// type: typeof GET_SERVICE_LIST_ERROR | typeof GET_INITIAL_APPLICATION_ERROR;
// payload: {
// errorMessage: string;
// };
// }
// export interface GetInitialApplicationData {
// type: typeof GET_INTIAL_APPLICATION_DATA;
// payload: {
// topEndPoints: TopEndPoints[];
// // dbOverView: DBOverView[];
// // externalService: ExternalService[];
// // externalAverageDuration: ExternalAverageDuration[];
// // externalError: ExternalError[];
// serviceOverview: ServiceOverview[];
// };
// }
// export interface ResetInitialApplicationData {
// type: typeof RESET_INITIAL_APPLICATION_DATA;
// }
// export interface SetResourceAttributeQueries {
// type: typeof SET_RESOURCE_ATTRIBUTE_QUERIES;
// payload: {
// queries: IResourceAttributeQuery[];
// promQLQuery: string;
// };
// }
export type LogsActions = export type LogsActions =
| GetFields | GetFields

View File

@ -30,8 +30,7 @@ export interface IBuilderQuery
extends Omit< extends Omit<
IMetricQuery, IMetricQuery,
'aggregateOperator' | 'legend' | 'metricName' | 'tagFilters' 'aggregateOperator' | 'legend' | 'metricName' | 'tagFilters'
>, > {
Omit<IFormulaQuery, 'expression'> {
aggregateOperator: EAggregateOperator | undefined; aggregateOperator: EAggregateOperator | undefined;
disabled: boolean; disabled: boolean;
name: string; name: string;

View File

@ -71,6 +71,7 @@ export interface IMetricsBuilderFormula {
expression: string; expression: string;
disabled: boolean; disabled: boolean;
name: string; name: string;
legend: string;
} }
export interface IMetricsBuilderQuery { export interface IMetricsBuilderQuery {
aggregateOperator: EAggregateOperator; aggregateOperator: EAggregateOperator;

View File

@ -6,4 +6,8 @@ export type Props = {
limit: number; limit: number;
orderBy: string; orderBy: string;
order: string; order: string;
idGt?: string;
idLt?: string;
timestampStart?: number;
timestampEnd?: number;
}; };

View File

@ -9,4 +9,7 @@ export type Props = {
timestampStart: number; timestampStart: number;
timestampEnd: number; timestampEnd: number;
step: number; step: number;
q?: string;
idGt?: string;
idLt?: string;
}; };

View File

@ -17,7 +17,7 @@ export interface ILogsReducer {
isLoading: boolean; isLoading: boolean;
isLoadingAggregate: boolean; isLoadingAggregate: boolean;
logsAggregate: ILogsAggregate[]; logsAggregate: ILogsAggregate[];
selectedLogId: string; selectedLogId: string | null;
detailedLog: null | ILog; detailedLog: null | ILog;
liveTail: TLogsLiveTailState; liveTail: TLogsLiveTailState;
liveTailStartRange: number; // time in minutes liveTailStartRange: number; // time in minutes

File diff suppressed because it is too large Load Diff

View File

@ -762,11 +762,16 @@ func (r *ClickHouseReader) GetServices(ctx context.Context, queryParams *model.G
args..., args...,
).ScanStruct(&serviceItem) ).ScanStruct(&serviceItem)
if serviceItem.NumCalls == 0 {
return
}
if err != nil { if err != nil {
zap.S().Error("Error in processing sql query: ", err) zap.S().Error("Error in processing sql query: ", err)
return return
} }
args, errStatus = buildQueryWithTagParams(ctx, queryParams.Tags, &errorQuery, args)
err = r.db.QueryRow(ctx, errorQuery, args...).Scan(&numErrors) err = r.db.QueryRow(ctx, errorQuery, args...).Scan(&numErrors)
if err != nil { if err != nil {
zap.S().Error("Error in processing sql query: ", err) zap.S().Error("Error in processing sql query: ", err)