Merge pull request #2379 from SigNoz/release/v0.16.2

Release/v0.16.2
This commit is contained in:
Ankit Nayan 2023-02-24 19:31:06 +05:30 committed by GitHub
commit 51721f97c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 1297 additions and 328 deletions

View File

@ -137,7 +137,7 @@ services:
condition: on-failure condition: on-failure
query-service: query-service:
image: signoz/query-service:0.16.1 image: signoz/query-service:0.16.2
command: ["-config=/root/config/prometheus.yml"] command: ["-config=/root/config/prometheus.yml"]
# ports: # ports:
# - "6060:6060" # pprof port # - "6060:6060" # pprof port
@ -166,7 +166,7 @@ services:
<<: *clickhouse-depend <<: *clickhouse-depend
frontend: frontend:
image: signoz/frontend:0.16.1 image: signoz/frontend:0.16.2
deploy: deploy:
restart_policy: restart_policy:
condition: on-failure condition: on-failure
@ -179,7 +179,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:0.66.4 image: signoz/signoz-otel-collector:0.66.5
command: ["--config=/etc/otel-collector-config.yaml"] command: ["--config=/etc/otel-collector-config.yaml"]
user: root # required for reading docker container logs user: root # required for reading docker container logs
volumes: volumes:
@ -208,7 +208,7 @@ services:
<<: *clickhouse-depend <<: *clickhouse-depend
otel-collector-metrics: otel-collector-metrics:
image: signoz/signoz-otel-collector:0.66.4 image: signoz/signoz-otel-collector:0.66.5
command: ["--config=/etc/otel-collector-metrics-config.yaml"] command: ["--config=/etc/otel-collector-metrics-config.yaml"]
volumes: volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml

View File

@ -41,7 +41,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` # 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: otel-collector:
container_name: otel-collector container_name: otel-collector
image: signoz/signoz-otel-collector:0.66.4 image: signoz/signoz-otel-collector:0.66.5
command: ["--config=/etc/otel-collector-config.yaml"] command: ["--config=/etc/otel-collector-config.yaml"]
# user: root # required for reading docker container logs # user: root # required for reading docker container logs
volumes: volumes:
@ -67,7 +67,7 @@ services:
otel-collector-metrics: otel-collector-metrics:
container_name: otel-collector-metrics container_name: otel-collector-metrics
image: signoz/signoz-otel-collector:0.66.4 image: signoz/signoz-otel-collector:0.66.5
command: ["--config=/etc/otel-collector-metrics-config.yaml"] command: ["--config=/etc/otel-collector-metrics-config.yaml"]
volumes: volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml

View File

@ -153,7 +153,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service: query-service:
image: signoz/query-service:${DOCKER_TAG:-0.16.1} image: signoz/query-service:${DOCKER_TAG:-0.16.2}
container_name: query-service container_name: query-service
command: ["-config=/root/config/prometheus.yml"] command: ["-config=/root/config/prometheus.yml"]
# ports: # ports:
@ -181,7 +181,7 @@ services:
<<: *clickhouse-depend <<: *clickhouse-depend
frontend: frontend:
image: signoz/frontend:${DOCKER_TAG:-0.16.1} image: signoz/frontend:${DOCKER_TAG:-0.16.2}
container_name: frontend container_name: frontend
restart: on-failure restart: on-failure
depends_on: depends_on:
@ -193,7 +193,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.4} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.5}
command: ["--config=/etc/otel-collector-config.yaml"] command: ["--config=/etc/otel-collector-config.yaml"]
user: root # required for reading docker container logs user: root # required for reading docker container logs
volumes: volumes:
@ -219,7 +219,7 @@ services:
<<: *clickhouse-depend <<: *clickhouse-depend
otel-collector-metrics: otel-collector-metrics:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.4} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.5}
command: ["--config=/etc/otel-collector-metrics-config.yaml"] command: ["--config=/etc/otel-collector-metrics-config.yaml"]
volumes: volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml

View File

@ -9,6 +9,7 @@ import (
"go.signoz.io/signoz/ee/query-service/license" "go.signoz.io/signoz/ee/query-service/license"
baseapp "go.signoz.io/signoz/pkg/query-service/app" baseapp "go.signoz.io/signoz/pkg/query-service/app"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces" baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
rules "go.signoz.io/signoz/pkg/query-service/rules" rules "go.signoz.io/signoz/pkg/query-service/rules"
"go.signoz.io/signoz/pkg/query-service/version" "go.signoz.io/signoz/pkg/query-service/version"
) )
@ -127,5 +128,11 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router) {
func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) { func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
version := version.GetVersion() version := version.GetVersion()
ah.WriteJSON(w, r, map[string]string{"version": version, "ee": "Y"}) versionResponse := basemodel.GetVersionResponse{
Version: version,
EE: "Y",
SetupCompleted: ah.SetupCompleted,
}
ah.WriteJSON(w, r, versionResponse)
} }

View File

@ -8,6 +8,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"go.signoz.io/signoz/ee/query-service/constants" "go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model" "go.signoz.io/signoz/ee/query-service/model"
@ -87,9 +88,16 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
// get invite object // get invite object
invite, err := baseauth.ValidateInvite(ctx, req) invite, err := baseauth.ValidateInvite(ctx, req)
if err != nil || invite == nil { if err != nil {
zap.S().Errorf("failed to validate invite token", err) zap.S().Errorf("failed to validate invite token", err)
RespondError(w, model.BadRequest(err), nil)
return
}
if invite == nil {
zap.S().Errorf("failed to validate invite token: it is either empty or invalid", err)
RespondError(w, model.BadRequest(basemodel.ErrSignupFailed{}), nil) RespondError(w, model.BadRequest(basemodel.ErrSignupFailed{}), nil)
return
} }
// get auth domain from email domain // get auth domain from email domain
@ -250,15 +258,12 @@ func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request)
http.Redirect(w, r, nextPage, http.StatusSeeOther) http.Redirect(w, r, nextPage, http.StatusSeeOther)
} }
// receiveSAML completes a SAML request and gets user logged in // receiveSAML completes a SAML request and gets user logged in
func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) { func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) {
// this is the source url that initiated the login request // this is the source url that initiated the login request
redirectUri := constants.GetDefaultSiteURL() redirectUri := constants.GetDefaultSiteURL()
ctx := context.Background() ctx := context.Background()
if !ah.CheckFeature(model.SSO) { if !ah.CheckFeature(model.SSO) {
zap.S().Errorf("[receiveSAML] sso requested but feature unavailable %s in org domain %s", model.SSO) zap.S().Errorf("[receiveSAML] sso requested but feature unavailable %s in org domain %s", model.SSO)
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently) http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently)

View File

@ -58,7 +58,7 @@ module.exports = {
'react/no-array-index-key': 'error', 'react/no-array-index-key': 'error',
'linebreak-style': [ 'linebreak-style': [
'error', 'error',
process.platform === 'win32' ? 'windows' : 'unix', process.env.platform === 'win32' ? 'windows' : 'unix',
], ],
'@typescript-eslint/default-param-last': 'off', '@typescript-eslint/default-param-last': 'off',

View File

@ -15,7 +15,7 @@ const config: Config.InitialOptions = {
useESM: true, useESM: true,
}, },
}, },
testMatch: ['<rootDir>/src/**/?(*.)(test).(ts|js)?(x)'], testMatch: ['<rootDir>/src/**/*?(*.)(test).(ts|js)?(x)'],
preset: 'ts-jest/presets/js-with-ts-esm', preset: 'ts-jest/presets/js-with-ts-esm',
transform: { transform: {
'^.+\\.(ts|tsx)?$': 'ts-jest', '^.+\\.(ts|tsx)?$': 'ts-jest',

View File

@ -55,6 +55,7 @@
"event-source-polyfill": "1.0.31", "event-source-polyfill": "1.0.31",
"file-loader": "6.1.1", "file-loader": "6.1.1",
"flat": "^5.0.2", "flat": "^5.0.2",
"fontfaceobserver": "2.3.0",
"history": "4.10.1", "history": "4.10.1",
"html-webpack-plugin": "5.1.0", "html-webpack-plugin": "5.1.0",
"i18next": "^21.6.12", "i18next": "^21.6.12",
@ -127,6 +128,7 @@
"@types/d3-tip": "^3.5.5", "@types/d3-tip": "^3.5.5",
"@types/event-source-polyfill": "^1.0.0", "@types/event-source-polyfill": "^1.0.0",
"@types/flat": "^5.0.2", "@types/flat": "^5.0.2",
"@types/fontfaceobserver": "2.1.0",
"@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",
@ -137,7 +139,6 @@
"@types/react-redux": "^7.1.11", "@types/react-redux": "^7.1.11",
"@types/react-resizable": "3.0.3", "@types/react-resizable": "3.0.3",
"@types/react-router-dom": "^5.1.6", "@types/react-router-dom": "^5.1.6",
"@types/redux": "^3.6.0",
"@types/styled-components": "^5.1.4", "@types/styled-components": "^5.1.4",
"@types/uuid": "^8.3.1", "@types/uuid": "^8.3.1",
"@types/vis": "^4.21.21", "@types/vis": "^4.21.21",

View File

@ -32,6 +32,7 @@ const getFilters = async (
maxDuration: String((duration.duration || [])[0] || ''), maxDuration: String((duration.duration || [])[0] || ''),
minDuration: String((duration.duration || [])[1] || ''), minDuration: String((duration.duration || [])[1] || ''),
exclude, exclude,
spanKind: props.spanKind,
}); });
return { return {

View File

@ -44,6 +44,7 @@ const getSpans = async (
maxDuration: String((duration.duration || [])[0] || ''), maxDuration: String((duration.duration || [])[0] || ''),
minDuration: String((duration.duration || [])[1] || ''), minDuration: String((duration.duration || [])[1] || ''),
exclude, exclude,
spanKind: props.spanKind,
}, },
); );

View File

@ -48,6 +48,7 @@ const getSpanAggregate = async (
maxDuration: String((duration.duration || [])[0] || ''), maxDuration: String((duration.duration || [])[0] || ''),
minDuration: String((duration.duration || [])[1] || ''), minDuration: String((duration.duration || [])[1] || ''),
exclude, exclude,
spanKind: props.spanKind,
}); });
return { return {

View File

@ -32,6 +32,7 @@ const getTagFilters = async (
maxDuration: String((duration.duration || [])[0] || ''), maxDuration: String((duration.duration || [])[0] || ''),
minDuration: String((duration.duration || [])[1] || ''), minDuration: String((duration.duration || [])[1] || ''),
exclude, exclude,
spanKind: props.spanKind,
}); });
return { return {

View File

@ -15,6 +15,7 @@ const getTagValue = async (
Key: props.tagKey.Key, Key: props.tagKey.Key,
Type: props.tagKey.Type, Type: props.tagKey.Type,
}, },
spanKind: props.spanKind,
}); });
return { return {
statusCode: 200, statusCode: 200,

View File

@ -6,7 +6,10 @@ export const LegendsContainer = styled.div`
* { * {
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 0.5rem; width: 0.3rem;
}
::-webkit-scrollbar:horizontal {
height: 0.3rem;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: transparent; background: transparent;
@ -18,5 +21,8 @@ export const LegendsContainer = styled.div`
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: ${themeColors.matterhornGrey}; background: ${themeColors.matterhornGrey};
} }
::-webkit-scrollbar-corner {
background: transparent;
}
} }
`; `;

View File

@ -4,17 +4,21 @@ import { Button, Divider, Row, Typography } from 'antd';
import { map } from 'd3'; import { map } from 'd3';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
// utils
import { FlatLogData } from 'lib/logs/flatLogData'; import { FlatLogData } from 'lib/logs/flatLogData';
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';
// interfaces
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 { ILog } from 'types/api/logs/log'; import { ILog } from 'types/api/logs/log';
import { ILogsReducer } from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
// components
import AddToQueryHOC from '../AddToQueryHOC'; import AddToQueryHOC from '../AddToQueryHOC';
import CopyClipboardHOC from '../CopyClipboardHOC'; import CopyClipboardHOC from '../CopyClipboardHOC';
// styles
import { Container, LogContainer, Text, TextContainer } from './styles'; import { Container, LogContainer, Text, TextContainer } from './styles';
import { isValidLogField } from './util'; import { isValidLogField } from './util';
@ -37,6 +41,7 @@ function LogGeneralField({ fieldKey, fieldValue }: LogFieldProps): JSX.Element {
</TextContainer> </TextContainer>
); );
} }
function LogSelectedField({ function LogSelectedField({
fieldKey = '', fieldKey = '',
fieldValue = '', fieldValue = '',
@ -70,15 +75,17 @@ function LogSelectedField({
); );
} }
interface LogItemProps { interface ListLogViewProps {
logData: ILog; logData: ILog;
} }
function LogItem({ logData }: LogItemProps): JSX.Element { function ListLogView({ logData }: ListLogViewProps): 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 [, setCopy] = useCopyToClipboard(); const [, setCopy] = useCopyToClipboard();
const { notifications } = useNotifications(); const { notifications } = useNotifications();
@ -152,4 +159,4 @@ function LogItem({ logData }: LogItemProps): JSX.Element {
); );
} }
export default LogItem; export default ListLogView;

View File

@ -0,0 +1,5 @@
export const rawLineStyle: React.CSSProperties = {
marginBottom: 0,
fontFamily: "'Fira Code', monospace",
fontWeight: 300,
};

View File

@ -0,0 +1,48 @@
import { ExpandAltOutlined } from '@ant-design/icons';
import { Typography } from 'antd';
import dayjs from 'dayjs';
// hooks
import { useIsDarkMode } from 'hooks/useDarkMode';
import React, { useCallback, useMemo } from 'react';
// interfaces
import { ILog } from 'types/api/logs/log';
import { rawLineStyle } from './config';
// styles
import { ExpandIconWrapper, RawLogViewContainer } from './styles';
interface RawLogViewProps {
data: ILog;
linesPerRow: number;
onClickExpand: (log: ILog) => void;
}
function RawLogView(props: RawLogViewProps): JSX.Element {
const { data, linesPerRow, onClickExpand } = props;
const isDarkMode = useIsDarkMode();
const text = useMemo(
() => `${dayjs(data.timestamp / 1e6).format()} | ${data.body}`,
[data.timestamp, data.body],
);
const ellipsis = useMemo(() => ({ rows: linesPerRow }), [linesPerRow]);
const handleClickExpand = useCallback(() => {
onClickExpand(data);
}, [onClickExpand, data]);
return (
<RawLogViewContainer wrap={false} align="middle" $isDarkMode={isDarkMode}>
<ExpandIconWrapper flex="30px" onClick={handleClickExpand}>
<ExpandAltOutlined />
</ExpandIconWrapper>
<Typography.Paragraph style={rawLineStyle} ellipsis={ellipsis}>
{text}
</Typography.Paragraph>
</RawLogViewContainer>
);
}
export default RawLogView;

View File

@ -0,0 +1,24 @@
import { blue } from '@ant-design/colors';
import { Col, Row } from 'antd';
import styled from 'styled-components';
export const RawLogViewContainer = styled(Row)<{ $isDarkMode: boolean }>`
width: 100%;
font-weight: 700;
font-size: 0.625rem;
line-height: 1.25rem;
transition: background-color 0.2s ease-in;
&:hover {
background-color: ${({ $isDarkMode }): string =>
$isDarkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0, 0, 0, 0.1)'};
}
`;
export const ExpandIconWrapper = styled(Col)`
color: ${blue[6]};
padding: 0.25rem 0.375rem;
cursor: pointer;
font-size: 12px;
`;

View File

@ -0,0 +1,12 @@
import { TableProps } from 'antd';
export const defaultCellStyle: React.CSSProperties = {
paddingTop: 4,
paddingBottom: 6,
paddingRight: 8,
paddingLeft: 8,
};
export const tableScroll: TableProps<Record<string, unknown>>['scroll'] = {
x: true,
};

View File

@ -0,0 +1,106 @@
import { ExpandAltOutlined } from '@ant-design/icons';
import { Table, Typography } from 'antd';
import { ColumnsType, ColumnType } from 'antd/es/table';
import dayjs from 'dayjs';
// utils
import { FlatLogData } from 'lib/logs/flatLogData';
import React, { useMemo } from 'react';
import { IField } from 'types/api/logs/fields';
// interfaces
import { ILog } from 'types/api/logs/log';
// styles
import { ExpandIconWrapper } from '../RawLogView/styles';
// config
import { defaultCellStyle, tableScroll } from './config';
type ColumnTypeRender<T = unknown> = ReturnType<
NonNullable<ColumnType<T>['render']>
>;
type LogsTableViewProps = {
logs: ILog[];
fields: IField[];
linesPerRow: number;
onClickExpand: (log: ILog) => void;
};
function LogsTableView(props: LogsTableViewProps): JSX.Element {
const { logs, fields, linesPerRow, onClickExpand } = props;
const flattenLogData = useMemo(() => logs.map((log) => FlatLogData(log)), [
logs,
]);
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
const fieldColumns: ColumnsType<Record<string, unknown>> = fields.map(
({ name }) => ({
title: name,
dataIndex: name,
key: name,
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
props: {
style: defaultCellStyle,
},
children: (
<Typography.Paragraph ellipsis={{ rows: linesPerRow }}>
{field}
</Typography.Paragraph>
),
}),
}),
);
return [
{
title: '',
dataIndex: 'id',
key: 'expand',
// https://github.com/ant-design/ant-design/discussions/36886
render: (_, item): ColumnTypeRender<Record<string, unknown>> => ({
props: {
style: defaultCellStyle,
},
children: (
<ExpandIconWrapper
onClick={(): void => {
onClickExpand((item as unknown) as ILog);
}}
>
<ExpandAltOutlined />
</ExpandIconWrapper>
),
}),
},
{
title: 'Timestamp',
dataIndex: 'timestamp',
key: 'timestamp',
// https://github.com/ant-design/ant-design/discussions/36886
render: (field): ColumnTypeRender<Record<string, unknown>> => {
const date = dayjs(field / 1e6).format();
return {
props: {
style: defaultCellStyle,
},
children: <span>{date}</span>,
};
},
},
...fieldColumns,
];
}, [fields, linesPerRow, onClickExpand]);
return (
<Table
columns={columns}
dataSource={flattenLogData}
pagination={false}
rowKey="id"
bordered
scroll={tableScroll}
/>
);
}
export default LogsTableView;

View File

@ -17,13 +17,6 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
[], [],
); );
const draggableOpts = useMemo(
() => ({
enableUserSelectHack,
}),
[],
);
if (!width) { if (!width) {
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
return <th {...restProps} />; return <th {...restProps} />;
@ -35,7 +28,7 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
height={0} height={0}
handle={handle} handle={handle}
onResize={onResize} onResize={onResize}
draggableOpts={draggableOpts} draggableOpts={enableUserSelectHack}
> >
{/* eslint-disable-next-line react/jsx-props-no-spreading */} {/* eslint-disable-next-line react/jsx-props-no-spreading */}
<th {...restProps} /> <th {...restProps} />

View File

@ -4,4 +4,6 @@ export enum LOCALSTORAGE {
AUTH_TOKEN = 'AUTH_TOKEN', AUTH_TOKEN = 'AUTH_TOKEN',
REFRESH_AUTH_TOKEN = 'REFRESH_AUTH_TOKEN', REFRESH_AUTH_TOKEN = 'REFRESH_AUTH_TOKEN',
THEME = 'THEME', THEME = 'THEME',
LOGS_VIEW_MODE = 'LOGS_VIEW_MODE',
LOGS_LINES_PER_ROW = 'LOGS_LINES_PER_ROW',
} }

View File

@ -153,6 +153,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
type: UPDATE_CURRENT_VERSION, type: UPDATE_CURRENT_VERSION,
payload: { payload: {
currentVersion: getUserVersionResponse.data.payload.version, currentVersion: getUserVersionResponse.data.payload.version,
ee: getUserVersionResponse.data.payload.ee,
setupCompleted: getUserVersionResponse.data.payload.setupCompleted,
}, },
}); });
} }

View File

@ -104,7 +104,7 @@ function RuleOptions({
<Option value="5m0s">{t('option_5min')}</Option> <Option value="5m0s">{t('option_5min')}</Option>
<Option value="10m0s">{t('option_10min')}</Option> <Option value="10m0s">{t('option_10min')}</Option>
<Option value="15m0s">{t('option_15min')}</Option> <Option value="15m0s">{t('option_15min')}</Option>
<Option value="60m0s">{t('option_60min')}</Option> <Option value="1h0m0s">{t('option_60min')}</Option>
<Option value="4h0m0s">{t('option_4hours')}</Option> <Option value="4h0m0s">{t('option_4hours')}</Option>
<Option value="24h0m0s">{t('option_24hours')}</Option> <Option value="24h0m0s">{t('option_24hours')}</Option>
</InlineSelect> </InlineSelect>

View File

@ -132,7 +132,7 @@ export const toChartInterval = (evalWindow: string | undefined): Time => {
return '15min'; return '15min';
case '30m0s': case '30m0s':
return '30min'; return '30min';
case '60m0s': case '1h0m0s':
return '1hr'; return '1hr';
case '4h0m0s': case '4h0m0s':
return '4hr'; return '4hr';

View File

@ -16,6 +16,7 @@ export const SpanLine = styled.div<Props>`
top: 50%; top: 50%;
position: absolute; position: absolute;
`; `;
export const SpanBorder = styled.div<Props>` export const SpanBorder = styled.div<Props>`
background: ${({ bgColor }): string => bgColor}; background: ${({ bgColor }): string => bgColor};
border-radius: 5px; border-radius: 5px;
@ -34,27 +35,18 @@ export const SpanWrapper = styled.div`
position: relative; position: relative;
z-index: 2; z-index: 2;
min-height: 2rem; min-height: 2rem;
/* &:before {
display: inline-block;
content: '';
border-bottom: 1px solid #303030;
position: absolute;
left: -30px;
width: 30px;
z-index: 0;
} */
`; `;
interface SpanTextProps extends Pick<Props, 'leftOffset'> { interface SpanTextProps extends Pick<Props, 'leftOffset'> {
isDarkMode: boolean; isDarkMode: boolean;
} }
export const SpanText = styled(Typography)<SpanTextProps>` export const SpanText = styled(Typography.Paragraph)<SpanTextProps>`
&&& { &&& {
left: ${({ leftOffset }): string => `${leftOffset}%`}; left: ${({ leftOffset }): string => `${leftOffset}%`};
top: 65%; top: 65%;
position: absolute; position: absolute;
color: ${({ isDarkMode }): string => (isDarkMode ? '##ACACAC' : '#666')}; width: max-content;
color: ${({ isDarkMode }): string => (isDarkMode ? '#ACACAC' : '#666')};
font-size: 0.75rem; font-size: 0.75rem;
} }
`; `;

View File

@ -67,7 +67,7 @@ function GridCardGraph({
const queryResponse = useQuery( const queryResponse = useQuery(
[ [
`GetMetricsQueryRange-${widget.timePreferance}-${globalSelectedInterval}-${widget.id}`, `GetMetricsQueryRange-${widget?.timePreferance}-${globalSelectedInterval}-${widget.id}`,
{ {
widget, widget,
maxTime, maxTime,
@ -78,7 +78,7 @@ function GridCardGraph({
], ],
() => () =>
GetMetricQueryRange({ GetMetricQueryRange({
selectedTime: widget.timePreferance, selectedTime: widget?.timePreferance,
graphType: widget.panelTypes, graphType: widget.panelTypes,
query: widget.query, query: widget.query,
globalSelectedInterval, globalSelectedInterval,

View File

@ -77,7 +77,9 @@ function GridGraph(props: Props): JSX.Element {
const startTimestamp = Math.trunc(start); const startTimestamp = Math.trunc(start);
const endTimestamp = Math.trunc(end); const endTimestamp = Math.trunc(end);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp])); dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
}, },
[dispatch], [dispatch],
); );

View File

@ -6,9 +6,12 @@ import {
import { Button, Divider, Select } from 'antd'; import { Button, Divider, Select } from 'antd';
import { getGlobalTime } from 'container/LogsSearchFilter/utils'; import { getGlobalTime } from 'container/LogsSearchFilter/utils';
import { getMinMax } from 'container/TopNav/AutoRefresh/config'; import { getMinMax } from 'container/TopNav/AutoRefresh/config';
import { defaultSelectStyle } from 'pages/Logs/config';
import React, { memo, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { import {
GET_NEXT_LOG_LINES, GET_NEXT_LOG_LINES,
GET_PREVIOUS_LOG_LINES, GET_PREVIOUS_LOG_LINES,
@ -21,8 +24,6 @@ import { ILogsReducer } from 'types/reducer/logs';
import { ITEMS_PER_PAGE_OPTIONS } from './config'; import { ITEMS_PER_PAGE_OPTIONS } from './config';
import { Container } from './styles'; import { Container } from './styles';
const { Option } = Select;
function LogControls(): JSX.Element | null { function LogControls(): JSX.Element | null {
const { const {
logLinesPerPage, logLinesPerPage,
@ -34,13 +35,14 @@ function LogControls(): JSX.Element | null {
const globalTime = useSelector<AppState, GlobalReducer>( const globalTime = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
const dispatch = useDispatch();
const dispatch = useDispatch<Dispatch<AppActions>>();
const handleLogLinesPerPageChange = (e: number): void => { const handleLogLinesPerPageChange = (e: number): void => {
dispatch({ dispatch({
type: SET_LOG_LINES_PER_PAGE, type: SET_LOG_LINES_PER_PAGE,
payload: { payload: {
logLinesPerPage: e, logsLinesPerPage: e,
}, },
}); });
}; };
@ -52,13 +54,17 @@ function LogControls(): JSX.Element | null {
globalTime.maxTime, globalTime.maxTime,
); );
dispatch({ const updatedGlobalTime = getGlobalTime(globalTime.selectedTime, {
type: RESET_ID_START_AND_END,
payload: getGlobalTime(globalTime.selectedTime, {
maxTime, maxTime,
minTime, minTime,
}),
}); });
if (updatedGlobalTime) {
dispatch({
type: RESET_ID_START_AND_END,
payload: updatedGlobalTime,
});
}
}; };
const handleNavigatePrevious = (): void => { const handleNavigatePrevious = (): void => {
@ -117,12 +123,16 @@ function LogControls(): JSX.Element | null {
Next <RightOutlined /> Next <RightOutlined />
</Button> </Button>
<Select <Select
style={defaultSelectStyle}
loading={isLoading} loading={isLoading}
value={logLinesPerPage} value={logLinesPerPage}
onChange={handleLogLinesPerPageChange} onChange={handleLogLinesPerPageChange}
> >
{ITEMS_PER_PAGE_OPTIONS.map((count) => ( {ITEMS_PER_PAGE_OPTIONS.map((count) => (
<Option key={count} value={count}>{`${count} / page`}</Option> <Select.Option
key={count}
value={count}
>{`${count} / page`}</Select.Option>
))} ))}
</Select> </Select>
</Container> </Container>

View File

@ -1,4 +1,5 @@
import { Button, Input, Space, Tooltip, Typography } from 'antd'; import { Button, Input, Space, Tooltip, Typography } from 'antd';
import getUserVersion from 'api/user/getVersion';
import loginApi from 'api/user/login'; import loginApi from 'api/user/login';
import loginPrecheckApi from 'api/user/loginPrecheck'; import loginPrecheckApi from 'api/user/loginPrecheck';
import afterLogin from 'AppRoutes/utils'; import afterLogin from 'AppRoutes/utils';
@ -7,6 +8,7 @@ import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history'; import history from 'lib/history';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { PayloadProps as PrecheckResultType } from 'types/api/user/loginPrecheck'; import { PayloadProps as PrecheckResultType } from 'types/api/user/loginPrecheck';
import { FormContainer, FormWrapper, Label, ParentContainer } from './styles'; import { FormContainer, FormWrapper, Label, ParentContainer } from './styles';
@ -45,6 +47,26 @@ function Login({
const { notifications } = useNotifications(); const { notifications } = useNotifications();
const getUserVersionResponse = useQuery({
queryFn: getUserVersion,
queryKey: 'getUserVersion',
enabled: true,
});
useEffect(() => {
if (
getUserVersionResponse.isFetched &&
getUserVersionResponse.data &&
getUserVersionResponse.data.payload
) {
const { setupCompleted } = getUserVersionResponse.data.payload;
if (!setupCompleted) {
// no org account registered yet, re-route user to sign up first
history.push(ROUTES.SIGN_UP);
}
}
}, [getUserVersionResponse]);
useEffect(() => { useEffect(() => {
if (withPassword === 'Y') { if (withPassword === 'Y') {
setPrecheckComplete(true); setPrecheckComplete(true);
@ -255,20 +277,6 @@ function Login({
</Typography.Paragraph> </Typography.Paragraph>
)} )}
{!canSelfRegister && (
<Typography.Paragraph italic style={{ color: '#ACACAC' }}>
{t('prompt_create_account')}{' '}
<Typography.Link
onClick={(): void => {
history.push(ROUTES.SIGN_UP);
}}
style={{ fontWeight: 700 }}
>
{t('create_an_account')}
</Typography.Link>
</Typography.Paragraph>
)}
{canSelfRegister && ( {canSelfRegister && (
<Typography.Paragraph italic style={{ color: '#ACACAC' }}> <Typography.Paragraph italic style={{ color: '#ACACAC' }}>
{t('prompt_if_admin')}{' '} {t('prompt_if_admin')}{' '}

View File

@ -1,5 +1,5 @@
import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons'; import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
import { Input } from 'antd'; import { Col, Input } from 'antd';
import CategoryHeading from 'components/Logs/CategoryHeading'; import CategoryHeading from 'components/Logs/CategoryHeading';
import { fieldSearchFilter } from 'lib/logs/fieldSearch'; import { fieldSearchFilter } from 'lib/logs/fieldSearch';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
@ -9,7 +9,7 @@ import { ILogsReducer } from 'types/reducer/logs';
import { ICON_STYLE } from './config'; import { ICON_STYLE } from './config';
import FieldItem from './FieldItem'; import FieldItem from './FieldItem';
import { CategoryContainer, Container, FieldContainer } from './styles'; import { CategoryContainer, FieldContainer } from './styles';
import { IHandleInterestProps, IHandleRemoveInterestProps } from './types'; import { IHandleInterestProps, IHandleRemoveInterestProps } from './types';
import { onHandleAddInterest, onHandleRemoveInterest } from './utils'; import { onHandleAddInterest, onHandleRemoveInterest } from './utils';
@ -58,7 +58,7 @@ function LogsFilters(): JSX.Element {
); );
return ( return (
<Container flex="450px"> <Col flex="250px">
<Input <Input
placeholder="Filter Values" placeholder="Filter Values"
onInput={handleSearch} onInput={handleSearch}
@ -110,7 +110,7 @@ function LogsFilters(): JSX.Element {
))} ))}
</FieldContainer> </FieldContainer>
</CategoryContainer> </CategoryContainer>
</Container> </Col>
); );
} }

View File

@ -1,13 +1,7 @@
import { blue, grey } from '@ant-design/colors'; import { blue, grey } from '@ant-design/colors';
import { Col, Typography } from 'antd'; import { Typography } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
export const Container = styled(Col)`
padding-top: 0.3rem;
min-width: 15.625rem;
max-width: 21.875rem;
`;
export const CategoryContainer = styled.div` export const CategoryContainer = styled.div`
margin: 1rem 0; margin: 1rem 0;
padding-left: 0.2rem; padding-left: 0.2rem;

View File

@ -48,6 +48,7 @@ function SearchFilter({
AppState, AppState,
ILogsReducer ILogsReducer
>((state) => state.logs); >((state) => state.logs);
const globalTime = useSelector<AppState, GlobalReducer>( const globalTime = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
@ -185,8 +186,8 @@ function SearchFilter({
onChange={(e): void => { onChange={(e): void => {
const { value } = e.target; const { value } = e.target;
setSearchText(value); setSearchText(value);
debouncedupdateQueryString(value);
}} }}
onSearch={debouncedupdateQueryString}
allowClear allowClear
/> />
</Popover> </Popover>

View File

@ -1,19 +1,53 @@
import { Typography } from 'antd'; import { Card, Typography } from 'antd';
import LogItem from 'components/Logs/LogItem'; // components
import ListLogView from 'components/Logs/ListLogView';
import RawLogView from 'components/Logs/RawLogView';
import LogsTableView from 'components/Logs/TableView';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { contentStyle } from 'container/Trace/Search/config';
import useFontFaceObserver from 'hooks/useFontObserver';
import React, { memo, useCallback, useMemo } from 'react'; import React, { memo, useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { Virtuoso } from 'react-virtuoso'; import { Virtuoso } from 'react-virtuoso';
// interfaces
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { ILog } from 'types/api/logs/log';
import { ILogsReducer } from 'types/reducer/logs'; import { ILogsReducer } from 'types/reducer/logs';
// styles
import { Container, Heading } from './styles'; import { Container, Heading } from './styles';
function LogsTable(): JSX.Element { export type LogViewMode = 'raw' | 'table' | 'list';
const { logs, isLoading, liveTail } = useSelector<AppState, ILogsReducer>(
(state) => state.logs, type LogsTableProps = {
viewMode: LogViewMode;
linesPerRow: number;
onClickExpand: (logData: ILog) => void;
};
function LogsTable(props: LogsTableProps): JSX.Element {
const { viewMode, onClickExpand, linesPerRow } = props;
useFontFaceObserver(
[
{
family: 'Fira Code',
weight: '300',
},
],
viewMode === 'raw',
{
timeout: 5000,
},
); );
const {
logs,
fields: { selected },
isLoading,
liveTail,
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
const isLiveTail = useMemo(() => logs.length === 0 && liveTail === 'PLAYING', [ const isLiveTail = useMemo(() => logs.length === 0 && liveTail === 'PLAYING', [
logs?.length, logs?.length,
liveTail, liveTail,
@ -27,29 +61,63 @@ function LogsTable(): JSX.Element {
const getItemContent = useCallback( const getItemContent = useCallback(
(index: number): JSX.Element => { (index: number): JSX.Element => {
const log = logs[index]; const log = logs[index];
return <LogItem key={log.id} logData={log} />;
}, if (viewMode === 'raw') {
[logs], return (
<RawLogView
key={log.id}
data={log}
linesPerRow={linesPerRow}
onClickExpand={onClickExpand}
/>
); );
}
return <ListLogView key={log.id} logData={log} />;
},
[logs, linesPerRow, viewMode, onClickExpand],
);
const renderContent = useMemo(() => {
if (viewMode === 'table') {
return (
<LogsTableView
logs={logs}
fields={selected}
linesPerRow={linesPerRow}
onClickExpand={onClickExpand}
/>
);
}
return (
<Card bodyStyle={contentStyle}>
<Virtuoso
useWindowScroll
totalCount={logs.length}
itemContent={getItemContent}
/>
</Card>
);
}, [getItemContent, linesPerRow, logs, onClickExpand, selected, viewMode]);
if (isLoading) { if (isLoading) {
return <Spinner height={20} tip="Getting Logs" />; return <Spinner height={20} tip="Getting Logs" />;
} }
return ( return (
<Container flex="auto"> <Container>
{viewMode !== 'table' && (
<Heading> <Heading>
<Typography.Text>Event</Typography.Text> <Typography.Text>Event</Typography.Text>
</Heading> </Heading>
)}
{isLiveTail && <Typography>Getting live logs...</Typography>} {isLiveTail && <Typography>Getting live logs...</Typography>}
{isNoLogs && <Typography>No log lines found</Typography>} {isNoLogs && <Typography>No logs lines found</Typography>}
<Virtuoso {renderContent}
useWindowScroll
totalCount={logs.length}
itemContent={getItemContent}
/>
</Container> </Container>
); );
} }

View File

@ -1,14 +1,16 @@
import { Card, Col } from 'antd'; import { Card } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
export const Container = styled(Col)` export const Container = styled.div`
overflow-x: hidden; overflow-x: hidden;
width: 100%; width: 100%;
margin-bottom: 1rem; margin-bottom: 1rem;
margin-top: 0.5rem;
`; `;
export const Heading = styled(Card)` export const Heading = styled(Card)`
margin-bottom: 0.1rem; margin-bottom: 0.1rem;
height: 32px;
.ant-card-body { .ant-card-body {
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;
} }

View File

@ -161,7 +161,7 @@ export const externalCallDurationByAddress = ({
}; };
interface ExternalCallDurationByAddressProps extends ExternalCallProps { interface ExternalCallDurationByAddressProps extends ExternalCallProps {
legend: '{{address}}'; legend: string;
} }
export interface ExternalCallProps { export interface ExternalCallProps {

View File

@ -26,7 +26,7 @@ export const operationPerSec = ({
{ {
id: '', id: '',
key: 'operation', key: 'operation',
op: 'MATCH', op: 'IN',
value: topLevelOperations, value: topLevelOperations,
}, },
...tagFilterItems, ...tagFilterItems,
@ -56,7 +56,7 @@ export const errorPercentage = ({
{ {
id: '', id: '',
key: 'operation', key: 'operation',
op: 'MATCH', op: 'IN',
value: topLevelOperations, value: topLevelOperations,
}, },
{ {
@ -78,7 +78,7 @@ export const errorPercentage = ({
{ {
id: '', id: '',
key: 'operation', key: 'operation',
op: 'MATCH', op: 'IN',
value: topLevelOperations, value: topLevelOperations,
}, },
...tagFilterItems, ...tagFilterItems,

View File

@ -79,11 +79,11 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
type="default" type="default"
size="small" size="small"
id="database_call_rps_button" id="database_call_rps_button"
onClick={onViewTracePopupClick( onClick={onViewTracePopupClick({
servicename, servicename,
selectedTraceTags, selectedTraceTags,
selectedTimeStamp, timestamp: selectedTimeStamp,
)} })}
> >
View Traces View Traces
</Button> </Button>
@ -114,11 +114,11 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
type="default" type="default"
size="small" size="small"
id="database_call_avg_duration_button" id="database_call_avg_duration_button"
onClick={onViewTracePopupClick( onClick={onViewTracePopupClick({
servicename, servicename,
selectedTraceTags, selectedTraceTags,
selectedTimeStamp, timestamp: selectedTimeStamp,
)} })}
> >
View Traces View Traces
</Button> </Button>

View File

@ -6,8 +6,11 @@ import {
externalCallErrorPercent, externalCallErrorPercent,
externalCallRpsByAddress, externalCallRpsByAddress,
} from 'container/MetricsApplication/MetricsPageQueries/ExternalQueries'; } from 'container/MetricsApplication/MetricsPageQueries/ExternalQueries';
import { resourceAttributesToTagFilterItems } from 'lib/resourceAttributes'; import {
import React, { useMemo } from 'react'; convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'lib/resourceAttributes';
import React, { useMemo, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@ -15,8 +18,13 @@ import { Widgets } from 'types/api/dashboard/getAll';
import MetricReducer from 'types/reducer/metrics'; import MetricReducer from 'types/reducer/metrics';
import { Card, GraphContainer, GraphTitle, Row } from '../styles'; import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { legend } from './constant';
import { Button } from './styles';
import { onGraphClickHandler, onViewTracePopupClick } from './util';
function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element { function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { servicename } = useParams<{ servicename?: string }>(); const { servicename } = useParams<{ servicename?: string }>();
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>( const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
(state) => state.metrics, (state) => state.metrics,
@ -27,8 +35,6 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
[resourceAttributeQueries], [resourceAttributeQueries],
); );
const legend = '{{address}}';
const externalCallErrorWidget = useMemo( const externalCallErrorWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder({
@ -36,7 +42,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
promQL: [], promQL: [],
metricsBuilder: externalCallErrorPercent({ metricsBuilder: externalCallErrorPercent({
servicename, servicename,
legend, legend: legend.address,
tagFilterItems, tagFilterItems,
}), }),
clickHouse: [], clickHouse: [],
@ -44,6 +50,14 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
[getWidgetQueryBuilder, servicename, tagFilterItems], [getWidgetQueryBuilder, servicename, tagFilterItems],
); );
const selectedTraceTags = useMemo(
() =>
JSON.stringify(
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
),
[resourceAttributeQueries],
);
const externalCallDurationWidget = useMemo( const externalCallDurationWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder({
@ -65,7 +79,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
promQL: [], promQL: [],
metricsBuilder: externalCallRpsByAddress({ metricsBuilder: externalCallRpsByAddress({
servicename, servicename,
legend, legend: legend.address,
tagFilterItems, tagFilterItems,
}), }),
clickHouse: [], clickHouse: [],
@ -80,7 +94,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
promQL: [], promQL: [],
metricsBuilder: externalCallDurationByAddress({ metricsBuilder: externalCallDurationByAddress({
servicename, servicename,
legend, legend: legend.address,
tagFilterItems, tagFilterItems,
}), }),
clickHouse: [], clickHouse: [],
@ -92,6 +106,19 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
<> <>
<Row gutter={24}> <Row gutter={24}>
<Col span={12}> <Col span={12}>
<Button
type="default"
size="small"
id="external_call_error_percentage_button"
onClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
isExternalCall: true,
})}
>
View Traces
</Button>
<Card> <Card>
<GraphTitle>External Call Error Percentage</GraphTitle> <GraphTitle>External Call Error Percentage</GraphTitle>
<GraphContainer> <GraphContainer>
@ -100,12 +127,35 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
fullViewOptions={false} fullViewOptions={false}
widget={externalCallErrorWidget} widget={externalCallErrorWidget}
yAxisUnit="%" yAxisUnit="%"
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'external_call_error_percentage',
);
}}
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Button
type="default"
size="small"
id="external_call_duration_button"
onClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
isExternalCall: true,
})}
>
View Traces
</Button>
<Card> <Card>
<GraphTitle>External Call duration</GraphTitle> <GraphTitle>External Call duration</GraphTitle>
<GraphContainer> <GraphContainer>
@ -114,6 +164,15 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
fullViewOptions={false} fullViewOptions={false}
widget={externalCallDurationWidget} widget={externalCallDurationWidget}
yAxisUnit="ms" yAxisUnit="ms"
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'external_call_duration',
);
}}
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>
@ -122,6 +181,19 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
<Row gutter={24}> <Row gutter={24}>
<Col span={12}> <Col span={12}>
<Button
type="default"
size="small"
id="external_call_rps_by_address_button"
onClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
isExternalCall: true,
})}
>
View Traces
</Button>
<Card> <Card>
<GraphTitle>External Call RPS(by Address)</GraphTitle> <GraphTitle>External Call RPS(by Address)</GraphTitle>
<GraphContainer> <GraphContainer>
@ -130,12 +202,35 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
fullViewOptions={false} fullViewOptions={false}
widget={externalCallRPSWidget} widget={externalCallRPSWidget}
yAxisUnit="reqps" yAxisUnit="reqps"
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'external_call_rps_by_address',
);
}}
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Button
type="default"
size="small"
id="external_call_duration_by_address_button"
onClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
isExternalCall: true,
})}
>
View Traces
</Button>
<Card> <Card>
<GraphTitle>External Call duration(by Address)</GraphTitle> <GraphTitle>External Call duration(by Address)</GraphTitle>
<GraphContainer> <GraphContainer>
@ -144,6 +239,15 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
fullViewOptions={false} fullViewOptions={false}
widget={externalCallDurationAddressWidget} widget={externalCallDurationAddressWidget}
yAxisUnit="ms" yAxisUnit="ms"
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'external_call_duration_by_address',
);
}}
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>

View File

@ -1,3 +1,4 @@
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
import Graph from 'components/Graph'; import Graph from 'components/Graph';
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query'; import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
@ -29,7 +30,29 @@ import { onGraphClickHandler, onViewTracePopupClick } from './util';
function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element { function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
const { servicename } = useParams<{ servicename?: string }>(); const { servicename } = useParams<{ servicename?: string }>();
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0); const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const handleSetTimeStamp = useCallback((selectTime: number) => {
setSelectedTimeStamp(selectTime);
}, []);
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleGraphClick = useCallback(
(type: string): ClickHandlerType => (
ChartEvent: ChartEvent,
activeElements: ActiveElement[],
chart: Chart,
data: ChartData,
): void => {
onGraphClickHandler(handleSetTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
type,
);
},
[handleSetTimeStamp],
);
const { const {
topOperations, topOperations,
@ -82,14 +105,16 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
const startTimestamp = Math.trunc(start); const startTimestamp = Math.trunc(start);
const endTimestamp = Math.trunc(end); const endTimestamp = Math.trunc(end);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp])); dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
}, },
[dispatch], [dispatch],
); );
const onErrorTrackHandler = (timestamp: number): void => { const onErrorTrackHandler = (timestamp: number): void => {
const currentTime = timestamp; const currentTime = timestamp;
const tPlusOne = timestamp + 1 * 60 * 1000; const tPlusOne = timestamp + 60 * 1000;
const urlParams = new URLSearchParams(); const urlParams = new URLSearchParams();
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString()); urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
@ -102,6 +127,52 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
); );
}; };
const generalChartDataProperties = useCallback(
(title: string, colorIndex: number) => ({
borderColor: colors[colorIndex],
label: title,
showLine: true,
borderWidth: 1.5,
spanGaps: true,
pointRadius: 2,
pointHoverRadius: 4,
}),
[],
);
const dataSets = useMemo(
() => [
{
data: serviceOverview.map((e) =>
parseFloat(convertToNanoSecondsToSecond(e.p99)),
),
...generalChartDataProperties('p99 Latency', 0),
},
{
data: serviceOverview.map((e) =>
parseFloat(convertToNanoSecondsToSecond(e.p95)),
),
...generalChartDataProperties('p95 Latency', 1),
},
{
data: serviceOverview.map((e) =>
parseFloat(convertToNanoSecondsToSecond(e.p50)),
),
...generalChartDataProperties('p50 Latency', 2),
},
],
[generalChartDataProperties, serviceOverview],
);
const data = useMemo(
() => ({
datasets: dataSets,
labels: serviceOverview.map(
(e) => new Date(parseFloat(convertToNanoSecondsToSecond(e.timestamp))),
),
}),
[serviceOverview, dataSets],
);
return ( return (
<> <>
<Row gutter={24}> <Row gutter={24}>
@ -110,11 +181,11 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
type="default" type="default"
size="small" size="small"
id="Service_button" id="Service_button"
onClick={onViewTracePopupClick( onClick={onViewTracePopupClick({
servicename, servicename,
selectedTraceTags, selectedTraceTags,
selectedTimeStamp, timestamp: selectedTimeStamp,
)} })}
> >
View Traces View Traces
</Button> </Button>
@ -122,58 +193,11 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
<GraphTitle>Latency</GraphTitle> <GraphTitle>Latency</GraphTitle>
<GraphContainer> <GraphContainer>
<Graph <Graph
onClickHandler={(ChartEvent, activeElements, chart, data): void => { animate={false}
onGraphClickHandler(setSelectedTimeStamp)( onClickHandler={handleGraphClick('Service')}
ChartEvent,
activeElements,
chart,
data,
'Service',
);
}}
name="service_latency" name="service_latency"
type="line" type="line"
data={{ data={data}
datasets: [
{
data: serviceOverview.map((e) =>
parseFloat(convertToNanoSecondsToSecond(e.p99)),
),
borderColor: colors[0],
label: 'p99 Latency',
showLine: true,
borderWidth: 1.5,
spanGaps: true,
pointRadius: 1.5,
},
{
data: serviceOverview.map((e) =>
parseFloat(convertToNanoSecondsToSecond(e.p95)),
),
borderColor: colors[1],
label: 'p95 Latency',
showLine: true,
borderWidth: 1.5,
spanGaps: true,
pointRadius: 1.5,
},
{
data: serviceOverview.map((e) =>
parseFloat(convertToNanoSecondsToSecond(e.p50)),
),
borderColor: colors[2],
label: 'p50 Latency',
showLine: true,
borderWidth: 1.5,
spanGaps: true,
pointRadius: 1.5,
},
],
labels: serviceOverview.map(
(e) =>
new Date(parseFloat(convertToNanoSecondsToSecond(e.timestamp))),
),
}}
yAxisUnit="ms" yAxisUnit="ms"
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
/> />
@ -186,11 +210,11 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
type="default" type="default"
size="small" size="small"
id="Rate_button" id="Rate_button"
onClick={onViewTracePopupClick( onClick={onViewTracePopupClick({
servicename, servicename,
selectedTraceTags, selectedTraceTags,
selectedTimeStamp, timestamp: selectedTimeStamp,
)} })}
> >
View Traces View Traces
</Button> </Button>
@ -200,15 +224,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
<FullView <FullView
name="operations_per_sec" name="operations_per_sec"
fullViewOptions={false} fullViewOptions={false}
onClickHandler={(event, element, chart, data): void => { onClickHandler={handleGraphClick('Rate')}
onGraphClickHandler(setSelectedTimeStamp)(
event,
element,
chart,
data,
'Rate',
);
}}
widget={operationPerSecWidget} widget={operationPerSecWidget}
yAxisUnit="ops" yAxisUnit="ops"
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
@ -236,15 +252,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
<FullView <FullView
name="error_percentage_%" name="error_percentage_%"
fullViewOptions={false} fullViewOptions={false}
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={handleGraphClick('Error')}
onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent,
activeElements,
chart,
data,
'Error',
);
}}
widget={errorPercentageWidget} widget={errorPercentageWidget}
yAxisUnit="%" yAxisUnit="%"
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
@ -267,4 +275,12 @@ interface DashboardProps {
getWidgetQueryBuilder: (query: Widgets['query']) => Widgets; getWidgetQueryBuilder: (query: Widgets['query']) => Widgets;
} }
type ClickHandlerType = (
ChartEvent: ChartEvent,
activeElements: ActiveElement[],
chart: Chart,
data: ChartData,
type?: string,
) => void;
export default Application; export default Application;

View File

@ -0,0 +1,3 @@
export const legend = {
address: '{{address}}',
};

View File

@ -14,14 +14,21 @@ export const dbSystemTags: Tags[] = [
}, },
]; ];
export function onViewTracePopupClick( interface OnViewTracePopupClickProps {
servicename: string | undefined, servicename: string | undefined;
selectedTraceTags: string, selectedTraceTags: string;
timestamp: number, timestamp: number;
): VoidFunction { isExternalCall?: boolean;
}
export function onViewTracePopupClick({
selectedTraceTags,
servicename,
timestamp,
isExternalCall,
}: OnViewTracePopupClickProps): VoidFunction {
return (): void => { return (): void => {
const currentTime = timestamp; const currentTime = timestamp;
const tPlusOne = timestamp + 1 * 60 * 1000; const tPlusOne = timestamp + 60 * 1000;
const urlParams = new URLSearchParams(); const urlParams = new URLSearchParams();
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString()); urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
@ -30,13 +37,17 @@ export function onViewTracePopupClick(
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`, }?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1${
isExternalCall ? '&spanKind=3' : ''
}`,
); );
}; };
} }
export function onGraphClickHandler( export function onGraphClickHandler(
setSelectedTimeStamp: React.Dispatch<React.SetStateAction<number>>, setSelectedTimeStamp: (
n: number,
) => void | React.Dispatch<React.SetStateAction<number>>,
) { ) {
return async ( return async (
event: ChartEvent, event: ChartEvent,
@ -49,7 +60,7 @@ export function onGraphClickHandler(
const points = chart.getElementsAtEventForMode( const points = chart.getElementsAtEventForMode(
event.native, event.native,
'nearest', 'nearest',
{ intersect: true }, { intersect: false },
true, true,
); );
const id = `${from}_button`; const id = `${from}_button`;

View File

@ -76,7 +76,11 @@ function Timeline({
},0)`} },0)`}
key={`${interval.label + interval.percentage + index}`} key={`${interval.label + interval.percentage + index}`}
> >
<text y={13} fill={isDarkMode ? 'white' : 'black'}> <text
y={13}
x={index === intervals.length - 1 ? -10 : 0}
fill={isDarkMode ? 'white' : 'black'}
>
{interval.label} {interval.label}
</text> </text>
<line <line

View File

@ -24,6 +24,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
filter, filter,
userSelectedFilter, userSelectedFilter,
isFilterExclude, isFilterExclude,
spanKind,
} = useSelector<AppState, TraceReducer>((state) => state.traces); } = useSelector<AppState, TraceReducer>((state) => state.traces);
const globalTime = useSelector<AppState, GlobalReducer>( const globalTime = useSelector<AppState, GlobalReducer>(
@ -91,6 +92,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
start: String(globalTime.minTime), start: String(globalTime.minTime),
getFilters: filterToFetchData.filter((e) => e !== name), getFilters: filterToFetchData.filter((e) => e !== name),
isFilterExclude: preIsFilterExclude, isFilterExclude: preIsFilterExclude,
spanKind,
}); });
if (response.statusCode === 200) { if (response.statusCode === 200) {
@ -125,6 +127,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
order: spansAggregate.order, order: spansAggregate.order,
orderParam: spansAggregate.orderParam, orderParam: spansAggregate.orderParam,
pageSize: spansAggregate.pageSize, pageSize: spansAggregate.pageSize,
spanKind,
}, },
}); });

View File

@ -30,6 +30,7 @@ function Duration(): JSX.Element {
selectedTags, selectedTags,
userSelectedFilter, userSelectedFilter,
isFilterExclude, isFilterExclude,
spanKind,
} = useSelector<AppState, TraceReducer>((state) => state.traces); } = useSelector<AppState, TraceReducer>((state) => state.traces);
const dispatch = useDispatch<Dispatch<AppActions>>(); const dispatch = useDispatch<Dispatch<AppActions>>();
@ -88,6 +89,7 @@ function Duration(): JSX.Element {
other: Object.fromEntries(preSelectedFilter), other: Object.fromEntries(preSelectedFilter),
start: String(globalTime.minTime), start: String(globalTime.minTime),
isFilterExclude, isFilterExclude,
spanKind,
}); });
if (response.statusCode === 200) { if (response.statusCode === 200) {
@ -113,6 +115,7 @@ function Duration(): JSX.Element {
order: spansAggregate.order, order: spansAggregate.order,
pageSize: spansAggregate.pageSize, pageSize: spansAggregate.pageSize,
orderParam: spansAggregate.orderParam, orderParam: spansAggregate.orderParam,
spanKind,
}, },
}); });

View File

@ -22,6 +22,7 @@ function TraceID(): JSX.Element {
selectedTags, selectedTags,
userSelectedFilter, userSelectedFilter,
isFilterExclude, isFilterExclude,
spanKind,
} = useSelector<AppState, TraceReducer>((state) => state.traces); } = useSelector<AppState, TraceReducer>((state) => state.traces);
const dispatch = useDispatch<Dispatch<AppActions>>(); const dispatch = useDispatch<Dispatch<AppActions>>();
const globalTime = useSelector<AppState, GlobalReducer>( const globalTime = useSelector<AppState, GlobalReducer>(
@ -52,6 +53,7 @@ function TraceID(): JSX.Element {
start: String(globalTime.minTime), start: String(globalTime.minTime),
getFilters: filterToFetchData, getFilters: filterToFetchData,
isFilterExclude, isFilterExclude,
spanKind,
}); });
if (response.statusCode === 200) { if (response.statusCode === 200) {
@ -77,6 +79,7 @@ function TraceID(): JSX.Element {
order: spansAggregate.order, order: spansAggregate.order,
pageSize: spansAggregate.pageSize, pageSize: spansAggregate.pageSize,
orderParam: spansAggregate.orderParam, orderParam: spansAggregate.orderParam,
spanKind,
}, },
}); });

View File

@ -37,6 +37,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
filter, filter,
isFilterExclude, isFilterExclude,
userSelectedFilter, userSelectedFilter,
spanKind,
} = useSelector<AppState, TraceReducer>((state) => state.traces); } = useSelector<AppState, TraceReducer>((state) => state.traces);
const { name: PanelName, isOpen: IsPanelOpen } = props; const { name: PanelName, isOpen: IsPanelOpen } = props;
@ -75,6 +76,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
getFilters: updatedFilterData, getFilters: updatedFilterData,
other: Object.fromEntries(getprepdatedSelectedFilter), other: Object.fromEntries(getprepdatedSelectedFilter),
isFilterExclude, isFilterExclude,
spanKind,
}); });
if (response.statusCode === 200) { if (response.statusCode === 200) {
@ -107,6 +109,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
order: spansAggregate.order, order: spansAggregate.order,
pageSize: spansAggregate.pageSize, pageSize: spansAggregate.pageSize,
orderParam: spansAggregate.orderParam, orderParam: spansAggregate.orderParam,
spanKind,
}, },
}); });
@ -160,6 +163,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
order: spansAggregate.order, order: spansAggregate.order,
pageSize: spansAggregate.pageSize, pageSize: spansAggregate.pageSize,
orderParam: spansAggregate.orderParam, orderParam: spansAggregate.orderParam,
spanKind,
}, },
}); });
@ -195,6 +199,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
getFilters: filterToFetchData, getFilters: filterToFetchData,
other: Object.fromEntries(updatedFilter), other: Object.fromEntries(updatedFilter),
isFilterExclude: postIsFilterExclude, isFilterExclude: postIsFilterExclude,
spanKind,
}); });
if (response.statusCode === 200 && response.payload) { if (response.statusCode === 200 && response.payload) {
@ -213,6 +218,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
order: spansAggregate.order, order: spansAggregate.order,
pageSize: spansAggregate.pageSize, pageSize: spansAggregate.pageSize,
orderParam: spansAggregate.orderParam, orderParam: spansAggregate.orderParam,
spanKind,
}, },
}); });

View File

@ -27,6 +27,7 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
globalTime.maxTime, globalTime.maxTime,
traces.selectedFilter, traces.selectedFilter,
traces.isFilterExclude, traces.isFilterExclude,
traces.spanKind,
], ],
{ {
queryFn: () => queryFn: () =>
@ -35,6 +36,7 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
end: globalTime.maxTime, end: globalTime.maxTime,
other: Object.fromEntries(traces.selectedFilter), other: Object.fromEntries(traces.selectedFilter),
isFilterExclude: traces.isFilterExclude, isFilterExclude: traces.isFilterExclude,
spanKind: traces.spanKind,
}), }),
cacheTime: 120000, cacheTime: 120000,
}, },

View File

@ -1,7 +1,7 @@
import { Select } from 'antd'; import { Select } from 'antd';
import { BaseOptionType } from 'antd/es/select'; import { BaseOptionType } from 'antd/es/select';
import getTagValue from 'api/trace/getTagValue'; import getTagValue from 'api/trace/getTagValue';
import React, { useCallback, useMemo, useState } from 'react'; import React, { memo, useCallback, useMemo, useState } from 'react';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@ -17,6 +17,7 @@ import {
getTagValueOptions, getTagValueOptions,
onTagValueChange, onTagValueChange,
selectOptions, selectOptions,
separateTagValues,
TagValueTypes, TagValueTypes,
} from './utils'; } from './utils';
@ -30,6 +31,8 @@ function TagValue(props: TagValueProps): JSX.Element {
BoolValues: selectedBoolValues, BoolValues: selectedBoolValues,
} = tag; } = tag;
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
const [localTagValue, setLocalTagValue] = useState<TagValueTypes[]>( const [localTagValue, setLocalTagValue] = useState<TagValueTypes[]>(
getInitialLocalValue( getInitialLocalValue(
selectedNumberValues, selectedNumberValues,
@ -45,7 +48,14 @@ function TagValue(props: TagValueProps): JSX.Element {
const tagType = useMemo(() => extractTagType(tagKey), [tagKey]); const tagType = useMemo(() => extractTagType(tagKey), [tagKey]);
const { isLoading, data } = useQuery( const { isLoading, data } = useQuery(
['tagKey', globalReducer.minTime, globalReducer.maxTime, tagKey, tagType], [
'tagKey',
globalReducer.minTime,
globalReducer.maxTime,
tagKey,
tagType,
traces.spanKind,
],
{ {
queryFn: () => queryFn: () =>
getTagValue({ getTagValue({
@ -55,6 +65,7 @@ function TagValue(props: TagValueProps): JSX.Element {
Key: extractTagKey(tagKey), Key: extractTagKey(tagKey),
Type: tagType, Type: tagType,
}, },
spanKind: traces.spanKind,
}), }),
}, },
); );
@ -71,66 +82,27 @@ function TagValue(props: TagValueProps): JSX.Element {
[index, selectedKey, selectedOperator, setLocalSelectedTags], [index, selectedKey, selectedOperator, setLocalSelectedTags],
); );
const onSetLocalValue = useCallback(() => { const onChangeHandler = useCallback(
setLocalTagValue([]);
}, []);
const onSelectedHandler = useCallback(
(value: unknown) => { (value: unknown) => {
if ( const updatedValues = onTagValueChange(value);
typeof value === 'number' || setLocalTagValue(updatedValues);
(typeof value === 'string' && !Number.isNaN(Number(value)) && value !== ' ') const { boolValues, numberValues, stringValues } = separateTagValues(
) { updatedValues,
setLocalTagValue([value]); selectedKey,
setLocalSelectedTags((tags) => [
...tags.slice(0, index),
{
Key: selectedKey,
Operator: selectedOperator,
StringValues: [],
NumberValues: [Number(value)],
BoolValues: [],
},
...tags.slice(index + 1, tags.length),
]);
} else if (
typeof value === 'boolean' ||
value === 'true' ||
value === 'false'
) {
setLocalTagValue([value]);
setLocalSelectedTags((tags) => [
...tags.slice(0, index),
{
Key: selectedKey,
Operator: selectedOperator,
StringValues: [],
NumberValues: [],
BoolValues: [value === 'true' || value === true],
},
...tags.slice(index + 1, tags.length),
]);
} else if (typeof value === 'string') {
setLocalTagValue([value]);
setLocalSelectedTags((tags) => [
...tags.slice(0, index),
{
Key: selectedKey,
Operator: selectedOperator,
StringValues: [value],
NumberValues: [],
BoolValues: [],
},
...tags.slice(index + 1, tags.length),
]);
}
},
[index, selectedKey, selectedOperator, setLocalSelectedTags],
); );
const onChangeHandler = useCallback( setLocalSelectedTags((tags) => [
(value: unknown) => onTagValueChange(value, setLocalTagValue), ...tags.slice(0, index),
[], {
...tags[index],
BoolValues: boolValues,
NumberValues: numberValues,
StringValues: stringValues,
},
...tags.slice(index + 1),
]);
},
[index, setLocalSelectedTags, selectedKey],
); );
const getFilterOptions = useCallback( const getFilterOptions = useCallback(
@ -149,14 +121,11 @@ function TagValue(props: TagValueProps): JSX.Element {
options={getTagValueOptions(data?.payload, tagType)} options={getTagValueOptions(data?.payload, tagType)}
mode="tags" mode="tags"
allowClear allowClear
onClear={onSetLocalValue}
onDeselect={onSetLocalValue}
showSearch showSearch
filterOption={getFilterOptions} filterOption={getFilterOptions}
disabled={isLoading || tagValueDisabled} disabled={isLoading || tagValueDisabled}
value={localTagValue} value={localTagValue}
onChange={onChangeHandler} onChange={onChangeHandler}
onSelect={onSelectedHandler}
> >
{selectOptions(data?.payload, tagType)?.map((suggestion) => ( {selectOptions(data?.payload, tagType)?.map((suggestion) => (
<Select.Option key={suggestion.toString()} value={suggestion}> <Select.Option key={suggestion.toString()} value={suggestion}>
@ -176,4 +145,4 @@ interface TagValueProps {
tagKey: string; tagKey: string;
} }
export default TagValue; export default memo(TagValue);

View File

@ -50,23 +50,56 @@ export const extractTagKey = (tagKey: string): string => {
return ''; return '';
}; };
export function onTagValueChange( export function onTagValueChange(values: unknown): TagValueTypes[] {
values: unknown, const stringValues = values as string[];
setLocalValue: React.Dispatch<React.SetStateAction<TagValueTypes[]>>,
): void { if (!Array.isArray(stringValues) || stringValues.length === 0) {
if (Array.isArray(values) && values.length > 0) { return [];
if (typeof values[0] === 'number' || typeof values[0] === 'boolean') {
setLocalValue(values);
} else if (typeof values[0] === 'string') {
if (values[0] === 'true' || values[0] === 'false') {
setLocalValue([values[0] === 'true']);
} else if (values[0] !== ' ' && !Number.isNaN(Number(values[0]))) {
setLocalValue([Number(values[0])]);
} else {
setLocalValue([values[0]]);
} }
return values as TagValueTypes[];
} }
export function separateTagValues(
values: TagValueTypes[],
selectedKey: string,
): { boolValues: boolean[]; numberValues: number[]; stringValues: string[] } {
if (selectedKey.includes('.(bool)')) {
const boolValues = values.filter(
(value) => typeof value === 'boolean',
) as boolean[];
return {
boolValues,
numberValues: [],
stringValues: [],
};
} }
if (selectedKey.includes('.(number)')) {
const numberValues = values
.filter((value) => typeof value === 'number' || !Number.isNaN(Number(value)))
.map((value) => Number(value)) as number[];
return {
boolValues: [],
numberValues,
stringValues: [],
};
}
const stringValues = values.filter(
(value) =>
typeof value === 'string' &&
value !== 'true' &&
value !== 'false' &&
Number.isNaN(Number(value)),
) as string[];
return {
boolValues: [],
numberValues: [],
stringValues,
};
} }
export function disableTagValue( export function disableTagValue(
@ -93,22 +126,16 @@ export function disableTagValue(
} }
return false; return false;
} }
export function getInitialLocalValue( export function getInitialLocalValue(
selectedNumberValues: number[], selectedNumberValues: number[],
selectedBoolValues: boolean[], selectedBoolValues: boolean[],
selectedStringValues: string[], selectedStringValues: string[],
): TagValueTypes[] { ): TagValueTypes[] {
if (selectedStringValues && selectedStringValues.length > 0) { return [
return selectedStringValues; ...selectedBoolValues,
} ...selectedNumberValues,
if (selectedNumberValues && selectedNumberValues.length > 0) { ...selectedStringValues,
return selectedNumberValues; ];
}
if (selectedBoolValues && selectedBoolValues.length > 0) {
return selectedBoolValues;
}
return selectedStringValues;
} }
export function getTagValueOptions( export function getTagValueOptions(

View File

@ -67,6 +67,7 @@ function Search({
order: traces.spansAggregate.order, order: traces.spansAggregate.order,
pageSize: traces.spansAggregate.pageSize, pageSize: traces.spansAggregate.pageSize,
orderParam: traces.spansAggregate.orderParam, orderParam: traces.spansAggregate.orderParam,
spanKind: traces.spanKind,
}, },
}); });

View File

@ -40,6 +40,7 @@ function TraceGraphFilter(): JSX.Element {
globalTime.maxTime, globalTime.maxTime,
traces.selectedFilter, traces.selectedFilter,
traces.isFilterExclude, traces.isFilterExclude,
traces.spanKind,
], ],
{ {
queryFn: () => queryFn: () =>
@ -48,8 +49,10 @@ function TraceGraphFilter(): JSX.Element {
end: globalTime.maxTime, end: globalTime.maxTime,
other: Object.fromEntries(traces.selectedFilter), other: Object.fromEntries(traces.selectedFilter),
isFilterExclude: traces.isFilterExclude, isFilterExclude: traces.isFilterExclude,
spanKind: traces.spanKind,
}), }),
cacheTime: 120000, cacheTime: 120000,
enabled: traces.filter.size > 0,
}, },
); );

View File

@ -0,0 +1,69 @@
import FontFaceObserver from 'fontfaceobserver';
import { useEffect, useState } from 'react';
export interface FontFace {
family: string;
weight?:
| `light`
| `normal`
| `bold`
| `bolder`
| `100`
| `200`
| `300`
| `400`
| `500`
| `600`
| `700`
| `800`
| `900`;
style?: `normal` | `italic` | `oblique`;
stretch?:
| `normal`
| `ultra-condensed`
| `extra-condensed`
| `condensed`
| `semi-condensed`
| `semi-expanded`
| `expanded`
| `extra-expanded`
| `ultra-expanded`;
}
export interface Options {
testString?: string;
timeout?: number;
}
export interface Config {
showErrors: boolean;
}
function useFontFaceObserver(
fontFaces: FontFace[] = [],
isEnabled = true,
{ testString, timeout }: Options = {},
{ showErrors }: Config = { showErrors: false },
): boolean {
const [isResolved, setIsResolved] = useState(false);
const fontFacesString = JSON.stringify(fontFaces);
useEffect(() => {
if (isEnabled) {
const promises = JSON.parse(fontFacesString).map(
({ family, weight, style, stretch }: FontFace) =>
new FontFaceObserver(family, {
weight,
style,
stretch,
}).load(testString, timeout),
);
Promise.all(promises).then(() => setIsResolved(true));
}
}, [fontFacesString, testString, timeout, showErrors, isEnabled]);
return isResolved;
}
export default useFontFaceObserver;

View File

@ -50,8 +50,14 @@
<meta data-react-helmet="true" name="docusaurus_locale" content="en" /> <meta data-react-helmet="true" name="docusaurus_locale" content="en" />
<meta data-react-helmet="true" name="docusaurus_tag" content="default" /> <meta data-react-helmet="true" name="docusaurus_tag" content="default" />
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" /> <link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css?family=Fira+Code"
rel="stylesheet"
/>
</head> </head>
<body style="margin: 0; padding: 0; box-sizing: border-box;"> <body style="margin: 0; padding: 0; box-sizing: border-box">
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
</body> </body>

View File

@ -0,0 +1,28 @@
import { InputNumber, Row, Space, Typography } from 'antd';
import React from 'react';
interface PopoverContentProps {
linesPerRow: number;
handleLinesPerRowChange: (l: unknown) => void;
}
function PopoverContent({
linesPerRow,
handleLinesPerRowChange,
}: PopoverContentProps): JSX.Element {
return (
<Row align="middle">
<Space align="center">
<Typography>Max lines per Row </Typography>
<InputNumber
min={1}
max={10}
value={linesPerRow}
onChange={handleLinesPerRowChange}
/>
</Space>
</Row>
);
}
export default PopoverContent;

View File

@ -0,0 +1,25 @@
import { ViewModeOption } from './types';
export const viewModeOptionList: ViewModeOption[] = [
{
key: 'raw',
label: 'Raw',
value: 'raw',
},
{
key: 'table',
label: 'Table',
value: 'table',
},
{
key: 'list',
label: 'List',
value: 'list',
},
];
export const logsOptions = ['raw', 'table'];
export const defaultSelectStyle: React.CSSProperties = {
minWidth: '6rem',
};

View File

@ -0,0 +1,78 @@
// utils
import get from 'api/browser/localstorage/get';
import { LOCALSTORAGE } from 'constants/localStorage';
// interfaces
import { LogViewMode } from 'container/LogsTable';
import { useCallback, useLayoutEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setLinesPerRow } from 'store/actions/logs/setLInesPerRow';
// actions
import { setViewMode } from 'store/actions/logs/setViewMode';
import { AppState } from 'store/reducers';
import { viewModeOptionList } from './config';
import { SelectedLogViewData } from './types';
import { isLogViewMode } from './utils';
export const useSelectedLogView = (): SelectedLogViewData => {
const dispatch = useDispatch();
const viewMode = useSelector<AppState, LogViewMode>(
(state) => state.logs.viewMode,
);
const linesPerRow = useSelector<AppState, number>(
(state) => state.logs.linesPerRow,
);
const viewModeOption = useMemo(
() =>
viewModeOptionList.find(
(viewModeOption) => viewModeOption.value === viewMode,
) ?? viewModeOptionList[0],
[viewMode],
);
const handleViewModeChange = useCallback(
(selectedViewMode: LogViewMode) => {
dispatch(setViewMode(selectedViewMode));
},
[dispatch],
);
const handleViewModeOptionChange = useCallback(
({ key }: { key: string }) => {
if (isLogViewMode(key)) handleViewModeChange(key);
},
[handleViewModeChange],
);
const handleLinesPerRowChange = useCallback(
(selectedLinesPerRow: unknown) => {
if (typeof selectedLinesPerRow === 'number') {
dispatch(setLinesPerRow(selectedLinesPerRow));
}
},
[dispatch],
);
useLayoutEffect(() => {
const storedViewMode = get(LOCALSTORAGE.LOGS_VIEW_MODE);
if (storedViewMode) {
handleViewModeChange(storedViewMode as LogViewMode);
}
const storedLinesPerRow = get(LOCALSTORAGE.LOGS_LINES_PER_ROW);
if (storedLinesPerRow) {
handleLinesPerRowChange(+storedLinesPerRow);
}
}, [handleViewModeChange, handleLinesPerRowChange]);
return {
viewModeOptionList,
viewModeOption,
viewMode,
handleViewModeChange,
handleViewModeOptionChange,
linesPerRow,
handleLinesPerRowChange,
};
};

View File

@ -1,4 +1,4 @@
import { Divider, Row } from 'antd'; import { Button, Col, Divider, Popover, Row, Select, Space } from 'antd';
import LogControls from 'container/LogControls'; import LogControls from 'container/LogControls';
import LogDetailedView from 'container/LogDetailedView'; import LogDetailedView from 'container/LogDetailedView';
import LogLiveTail from 'container/LogLiveTail'; import LogLiveTail from 'container/LogLiveTail';
@ -6,11 +6,67 @@ import LogsAggregate from 'container/LogsAggregate';
import LogsFilters from 'container/LogsFilters'; import LogsFilters from 'container/LogsFilters';
import LogsSearchFilter from 'container/LogsSearchFilter'; import LogsSearchFilter from 'container/LogsSearchFilter';
import LogsTable from 'container/LogsTable'; import LogsTable from 'container/LogsTable';
import React from 'react'; import React, { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import { ILog } from 'types/api/logs/log';
import { defaultSelectStyle, logsOptions } from './config';
import { useSelectedLogView } from './hooks';
import PopoverContent from './PopoverContent';
import SpaceContainer from './styles'; import SpaceContainer from './styles';
function Logs(): JSX.Element { function Logs(): JSX.Element {
const dispatch = useDispatch<Dispatch<AppActions>>();
const showExpandedLog = useCallback(
(logData: ILog) => {
dispatch({
type: SET_DETAILED_LOG_DATA,
payload: logData,
});
},
[dispatch],
);
const {
viewModeOptionList,
viewModeOption,
viewMode,
handleViewModeOptionChange,
linesPerRow,
handleLinesPerRowChange,
} = useSelectedLogView();
const renderPopoverContent = useCallback(
() => (
<PopoverContent
linesPerRow={linesPerRow}
handleLinesPerRowChange={handleLinesPerRowChange}
/>
),
[linesPerRow, handleLinesPerRowChange],
);
const isFormatButtonVisible = useMemo(() => logsOptions.includes(viewMode), [
viewMode,
]);
const selectedViewModeOption = useMemo(() => viewModeOption.value.toString(), [
viewModeOption.value,
]);
const onChangeVeiwMode = useCallback(
(key: string) => {
handleViewModeOptionChange({
key,
});
},
[handleViewModeOptionChange],
);
return ( return (
<> <>
<SpaceContainer <SpaceContainer
@ -23,13 +79,44 @@ function Logs(): JSX.Element {
</SpaceContainer> </SpaceContainer>
<LogsAggregate /> <LogsAggregate />
<LogControls />
<Divider plain orientationMargin={1} />
<Row gutter={20} wrap={false}> <Row gutter={20} wrap={false}>
<LogsFilters /> <LogsFilters />
<Divider type="vertical" /> <Col flex={1}>
<LogsTable /> <Row>
<Col flex={1}>
<Space align="baseline" direction="horizontal">
<Select
style={defaultSelectStyle}
value={selectedViewModeOption}
onChange={onChangeVeiwMode}
>
{viewModeOptionList.map((option) => (
<Select.Option key={option.value}>{option.label}</Select.Option>
))}
</Select>
{isFormatButtonVisible && (
<Popover placement="right" content={renderPopoverContent}>
<Button>Format</Button>
</Popover>
)}
</Space>
</Col>
<Col>
<LogControls />
</Col>
</Row> </Row>
<LogsTable
viewMode={viewMode}
linesPerRow={linesPerRow}
onClickExpand={showExpandedLog}
/>
</Col>
</Row>
<LogDetailedView /> <LogDetailedView />
</> </>
); );

View File

@ -0,0 +1,17 @@
import { ItemType } from 'antd/es/menu/hooks/useItems';
import { LogViewMode } from 'container/LogsTable';
export type ViewModeOption = ItemType & {
label: string;
value: LogViewMode;
};
export type SelectedLogViewData = {
viewModeOptionList: ViewModeOption[];
viewModeOption: ViewModeOption;
viewMode: LogViewMode;
handleViewModeChange: (s: LogViewMode) => void;
handleViewModeOptionChange: ({ key }: { key: string }) => void;
linesPerRow: number;
handleLinesPerRowChange: (l: unknown) => void;
};

View File

@ -0,0 +1,7 @@
import { LogViewMode } from 'container/LogsTable';
import { viewModeOptionList } from './config';
export const isLogViewMode = (value: unknown): value is LogViewMode =>
typeof value === 'string' &&
viewModeOptionList.some((option) => option.key === value);

View File

@ -52,6 +52,7 @@ function Trace({
selectedFunction, selectedFunction,
selectedGroupBy, selectedGroupBy,
isFilterExclude, isFilterExclude,
spanKind,
} = useSelector<AppState, TraceReducer>((state) => state.traces); } = useSelector<AppState, TraceReducer>((state) => state.traces);
const { notifications } = useNotifications(); const { notifications } = useNotifications();
@ -71,6 +72,7 @@ function Trace({
selectedTags, selectedTags,
order: spansAggregate.order, order: spansAggregate.order,
orderParam: spansAggregate.orderParam, orderParam: spansAggregate.orderParam,
spanKind,
}, },
notifications, notifications,
); );
@ -85,6 +87,7 @@ function Trace({
spansAggregate.order, spansAggregate.order,
spansAggregate.orderParam, spansAggregate.orderParam,
notifications, notifications,
spanKind,
]); ]);
useEffect(() => { useEffect(() => {
@ -98,6 +101,7 @@ function Trace({
start: minTime, start: minTime,
step: getStep({ start: minTime, end: maxTime, inputFormat: 'ns' }), step: getStep({ start: minTime, end: maxTime, inputFormat: 'ns' }),
isFilterExclude, isFilterExclude,
spanKind,
}, },
notifications, notifications,
); );
@ -111,6 +115,7 @@ function Trace({
getSpans, getSpans,
isFilterExclude, isFilterExclude,
notifications, notifications,
spanKind,
]); ]);
useEffect( useEffect(

View File

@ -0,0 +1,14 @@
import set from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import { SET_LINES_PER_ROW } from 'types/actions/logs';
type ActionSetLinesPerRow = { type: typeof SET_LINES_PER_ROW; payload: number };
export function setLinesPerRow(lines: number): ActionSetLinesPerRow {
set(LOCALSTORAGE.LOGS_LINES_PER_ROW, lines.toString());
return {
type: SET_LINES_PER_ROW,
payload: lines,
};
}

View File

@ -0,0 +1,15 @@
import set from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import { LogViewMode } from 'container/LogsTable';
import { SET_VIEW_MODE } from 'types/actions/logs';
type ActionSetViewMode = { type: typeof SET_VIEW_MODE; payload: LogViewMode };
export function setViewMode(viewMode: LogViewMode): ActionSetViewMode {
set(LOCALSTORAGE.LOGS_VIEW_MODE, viewMode);
return {
type: SET_VIEW_MODE,
payload: viewMode,
};
}

View File

@ -12,6 +12,7 @@ import {
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace'; import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
import { parseQueryIntoSpanKind } from './parseFilter/parseSpanKind';
import { import {
isTraceFilterEnum, isTraceFilterEnum,
parseAggregateOrderParams, parseAggregateOrderParams,
@ -55,6 +56,8 @@ export const GetInitialTraceFilter = (
traces.filterToFetchData, traces.filterToFetchData,
); );
const parsedSpanKind = parseQueryIntoSpanKind(query, traces.spanKind);
const getUserSelected = parseSelectedFilter(query, traces.userSelectedFilter); const getUserSelected = parseSelectedFilter(query, traces.userSelectedFilter);
const getIsFilterExcluded = parseFilterExclude(query, traces.isFilterExclude); const getIsFilterExcluded = parseFilterExclude(query, traces.isFilterExclude);
@ -102,6 +105,7 @@ export const GetInitialTraceFilter = (
start: String(minTime), start: String(minTime),
other: Object.fromEntries(getSelectedFilter.currentValue), other: Object.fromEntries(getSelectedFilter.currentValue),
isFilterExclude: getIsFilterExcluded.currentValue, isFilterExclude: getIsFilterExcluded.currentValue,
spanKind: parsedSpanKind.currentValue,
}); });
const preSelectedFilter: Map<TraceFilterEnum, string[]> = new Map( const preSelectedFilter: Map<TraceFilterEnum, string[]> = new Map(
@ -164,6 +168,7 @@ export const GetInitialTraceFilter = (
order: parsedQueryOrder.currentValue, order: parsedQueryOrder.currentValue,
pageSize: parsedPageSize.currentValue, pageSize: parsedPageSize.currentValue,
orderParam: parsedOrderParams.currentValue, orderParam: parsedOrderParams.currentValue,
spanKind: parsedSpanKind.currentValue,
}, },
}); });
} else { } else {

View File

@ -61,6 +61,7 @@ export const GetSpansAggregate = (
isFilterExclude: traces.isFilterExclude, isFilterExclude: traces.isFilterExclude,
order, order,
orderParam: props.orderParam, orderParam: props.orderParam,
spanKind: props.spanKind,
}); });
if (response.statusCode === 200) { if (response.statusCode === 200) {
@ -140,4 +141,5 @@ export interface GetSpansAggregateProps {
selectedTags: TraceReducer['selectedTags']; selectedTags: TraceReducer['selectedTags'];
order: GetSpanAggregateProps['order']; order: GetSpanAggregateProps['order'];
orderParam: GetSpanAggregateProps['orderParam']; orderParam: GetSpanAggregateProps['orderParam'];
spanKind: TraceReducer['spanKind'];
} }

View File

@ -63,6 +63,7 @@ export const GetSpans = (
start, start,
step: props.step, step: props.step,
isFilterExclude: props.isFilterExclude, isFilterExclude: props.isFilterExclude,
spanKind: props.spanKind,
}); });
if (response.statusCode === 200) { if (response.statusCode === 200) {

View File

@ -0,0 +1,39 @@
import { TraceReducer } from 'types/reducer/trace';
import { ParsedUrl } from '../util';
export const parseQueryIntoSpanKind = (
query: string,
stateCurrent: TraceReducer['spanKind'],
): ParsedUrl<TraceReducer['spanKind']> => {
const url = new URLSearchParams(query);
let current = '';
const selected = url.get('spanKind');
if (selected) {
try {
const parsedValue = selected;
if (parsedValue && typeof parsedValue === 'string') {
current = parsedValue;
}
} catch (error) {
console.log(error);
console.log('error while parsing json');
}
}
if (selected) {
return {
currentValue: current,
urlValue: current,
};
}
return {
currentValue: stateCurrent,
urlValue: current,
};
};

View File

@ -57,6 +57,8 @@ const InitialValue: InitialValueTypes = {
role: null, role: null,
configs: {}, configs: {},
userFlags: {}, userFlags: {},
ee: 'Y',
setupCompleted: true,
}; };
const appReducer = ( const appReducer = (
@ -89,6 +91,8 @@ const appReducer = (
return { return {
...state, ...state,
currentVersion: action.payload.currentVersion, currentVersion: action.payload.currentVersion,
ee: action.payload.ee,
setupCompleted: action.payload.setupCompleted,
}; };
} }

View File

@ -10,6 +10,7 @@ import {
RESET_ID_START_AND_END, RESET_ID_START_AND_END,
SET_DETAILED_LOG_DATA, SET_DETAILED_LOG_DATA,
SET_FIELDS, SET_FIELDS,
SET_LINES_PER_ROW,
SET_LIVE_TAIL_START_TIME, SET_LIVE_TAIL_START_TIME,
SET_LOADING, SET_LOADING,
SET_LOADING_AGGREGATE, SET_LOADING_AGGREGATE,
@ -18,6 +19,7 @@ import {
SET_LOGS_AGGREGATE_SERIES, SET_LOGS_AGGREGATE_SERIES,
SET_SEARCH_QUERY_PARSED_PAYLOAD, SET_SEARCH_QUERY_PARSED_PAYLOAD,
SET_SEARCH_QUERY_STRING, SET_SEARCH_QUERY_STRING,
SET_VIEW_MODE,
STOP_LIVE_TAIL, STOP_LIVE_TAIL,
TOGGLE_LIVE_TAIL, TOGGLE_LIVE_TAIL,
UPDATE_INTERESTING_FIELDS, UPDATE_INTERESTING_FIELDS,
@ -36,6 +38,8 @@ const initialState: ILogsReducer = {
}, },
logs: [], logs: [],
logLinesPerPage: 25, logLinesPerPage: 25,
linesPerRow: 2,
viewMode: 'raw',
idEnd: '', idEnd: '',
idStart: '', idStart: '',
isLoading: false, isLoading: false,
@ -205,6 +209,20 @@ export const LogsReducer = (
}; };
} }
case SET_LINES_PER_ROW: {
return {
...state,
linesPerRow: action.payload,
};
}
case SET_VIEW_MODE: {
return {
...state,
viewMode: action.payload,
};
}
case UPDATE_INTERESTING_FIELDS: { case UPDATE_INTERESTING_FIELDS: {
return { return {
...state, ...state,

View File

@ -70,6 +70,7 @@ const initialValue: TraceReducer = {
['status', INITIAL_FILTER_VALUE], ['status', INITIAL_FILTER_VALUE],
['traceID', INITIAL_FILTER_VALUE], ['traceID', INITIAL_FILTER_VALUE],
]), ]),
spanKind: undefined,
}; };
const traceReducer = ( const traceReducer = (
@ -97,6 +98,7 @@ const traceReducer = (
order, order,
pageSize, pageSize,
orderParam, orderParam,
spanKind,
} = payload; } = payload;
return { return {
@ -114,6 +116,7 @@ const traceReducer = (
order, order,
orderParam, orderParam,
}, },
spanKind,
}; };
} }

View File

@ -46,6 +46,8 @@ export interface UpdateAppVersion {
type: typeof UPDATE_CURRENT_VERSION; type: typeof UPDATE_CURRENT_VERSION;
payload: { payload: {
currentVersion: AppReducer['currentVersion']; currentVersion: AppReducer['currentVersion'];
ee: AppReducer['ee'];
setupCompleted: AppReducer['setupCompleted'];
}; };
} }

View File

@ -1,3 +1,4 @@
import { LogViewMode } from 'container/LogsTable';
import { ILogQLParsedQueryItem } from 'lib/logql/types'; import { ILogQLParsedQueryItem } from 'lib/logql/types';
import { IField, IFieldMoveToSelected, IFields } from 'types/api/logs/fields'; import { IField, IFieldMoveToSelected, IFields } from 'types/api/logs/fields';
import { TLogsLiveTailState } from 'types/api/logs/liveTail'; import { TLogsLiveTailState } from 'types/api/logs/liveTail';
@ -28,6 +29,8 @@ export const PUSH_LIVE_TAIL_EVENT = 'LOGS_PUSH_LIVE_TAIL_EVENT';
export const STOP_LIVE_TAIL = 'LOGS_STOP_LIVE_TAIL'; export const STOP_LIVE_TAIL = 'LOGS_STOP_LIVE_TAIL';
export const FLUSH_LOGS = 'LOGS_FLUSH_LOGS'; export const FLUSH_LOGS = 'LOGS_FLUSH_LOGS';
export const SET_LIVE_TAIL_START_TIME = 'LOGS_SET_LIVE_TAIL_START_TIME'; export const SET_LIVE_TAIL_START_TIME = 'LOGS_SET_LIVE_TAIL_START_TIME';
export const SET_LINES_PER_ROW = 'SET_LINES_PER_ROW';
export const SET_VIEW_MODE = 'SET_VIEW_MODE';
export const UPDATE_SELECTED_FIELDS = 'LOGS_UPDATE_SELECTED_FIELDS'; export const UPDATE_SELECTED_FIELDS = 'LOGS_UPDATE_SELECTED_FIELDS';
export const UPDATE_INTERESTING_FIELDS = 'LOGS_UPDATE_INTERESTING_FIELDS'; export const UPDATE_INTERESTING_FIELDS = 'LOGS_UPDATE_INTERESTING_FIELDS';
@ -118,6 +121,15 @@ export interface SetLiveTailStartTime {
payload: number; payload: number;
} }
export interface SetLinesPerRow {
type: typeof SET_LINES_PER_ROW;
payload: number;
}
export interface SetViewMode {
type: typeof SET_VIEW_MODE;
payload: LogViewMode;
}
type IFieldType = 'interesting' | 'selected'; type IFieldType = 'interesting' | 'selected';
export interface UpdateSelectedInterestFields { export interface UpdateSelectedInterestFields {
@ -149,4 +161,6 @@ export type LogsActions =
| StopLiveTail | StopLiveTail
| FlushLogs | FlushLogs
| SetLiveTailStartTime | SetLiveTailStartTime
| SetLinesPerRow
| SetViewMode
| UpdateSelectedInterestFields; | UpdateSelectedInterestFields;

View File

@ -82,6 +82,7 @@ export interface UpdateAllFilters {
order: TraceReducer['spansAggregate']['order']; order: TraceReducer['spansAggregate']['order'];
pageSize: TraceReducer['spansAggregate']['pageSize']; pageSize: TraceReducer['spansAggregate']['pageSize'];
orderParam: TraceReducer['spansAggregate']['orderParam']; orderParam: TraceReducer['spansAggregate']['orderParam'];
spanKind?: TraceReducer['spanKind'];
}; };
} }

View File

@ -8,6 +8,7 @@ export interface Props {
[k: string]: string[]; [k: string]: string[];
}; };
isFilterExclude: TraceReducer['isFilterExclude']; isFilterExclude: TraceReducer['isFilterExclude'];
spanKind?: TraceReducer['spanKind'];
} }
export interface PayloadProps { export interface PayloadProps {

View File

@ -10,6 +10,7 @@ export interface Props {
order?: TraceReducer['spansAggregate']['order']; order?: TraceReducer['spansAggregate']['order'];
isFilterExclude: TraceReducer['isFilterExclude']; isFilterExclude: TraceReducer['isFilterExclude'];
orderParam: TraceReducer['spansAggregate']['orderParam']; orderParam: TraceReducer['spansAggregate']['orderParam'];
spanKind?: TraceReducer['spanKind'];
} }
export type PayloadProps = { export type PayloadProps = {

View File

@ -9,6 +9,7 @@ export interface Props {
selectedFilter: TraceReducer['selectedFilter']; selectedFilter: TraceReducer['selectedFilter'];
selectedTags: TraceReducer['selectedTags']; selectedTags: TraceReducer['selectedTags'];
isFilterExclude: TraceReducer['isFilterExclude']; isFilterExclude: TraceReducer['isFilterExclude'];
spanKind?: TraceReducer['spanKind'];
} }
export interface PayloadProps { export interface PayloadProps {

View File

@ -7,6 +7,7 @@ export interface Props {
[k: string]: string[]; [k: string]: string[];
}; };
isFilterExclude: TraceReducer['isFilterExclude']; isFilterExclude: TraceReducer['isFilterExclude'];
spanKind?: TraceReducer['spanKind'];
} }
export interface PayloadProps { export interface PayloadProps {

View File

@ -1,4 +1,5 @@
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { TraceReducer } from 'types/reducer/trace';
export interface Props { export interface Props {
start: GlobalReducer['minTime']; start: GlobalReducer['minTime'];
@ -7,6 +8,7 @@ export interface Props {
Key: string; Key: string;
Type: string; Type: string;
}; };
spanKind?: TraceReducer['spanKind'];
} }
export interface PayloadProps { export interface PayloadProps {

View File

@ -1,4 +1,5 @@
export interface PayloadProps { export interface PayloadProps {
version: string; version: string;
ee: string; ee: 'Y' | 'N';
setupCompleted: boolean;
} }

View File

@ -29,4 +29,6 @@ export default interface AppReducer {
featureFlags: null | FeatureFlagPayload; featureFlags: null | FeatureFlagPayload;
configs: ConfigPayload; configs: ConfigPayload;
userFlags: null | UserFlags; userFlags: null | UserFlags;
ee: 'Y' | 'N';
setupCompleted: boolean;
} }

View File

@ -1,3 +1,4 @@
import { LogViewMode } from 'container/LogsTable';
import { ILogQLParsedQueryItem } from 'lib/logql/types'; import { ILogQLParsedQueryItem } from 'lib/logql/types';
import { IFields } from 'types/api/logs/fields'; import { IFields } from 'types/api/logs/fields';
import { TLogsLiveTailState } from 'types/api/logs/liveTail'; import { TLogsLiveTailState } from 'types/api/logs/liveTail';
@ -12,6 +13,8 @@ export interface ILogsReducer {
}; };
logs: ILog[]; logs: ILog[];
logLinesPerPage: number; logLinesPerPage: number;
linesPerRow: number;
viewMode: LogViewMode;
idEnd: string; idEnd: string;
idStart: string; idStart: string;
isLoading: boolean; isLoading: boolean;

View File

@ -33,6 +33,7 @@ export interface TraceReducer {
}; };
yAxisUnit: string | undefined; yAxisUnit: string | undefined;
filterDisplayValue: Map<TraceFilterEnum, number>; filterDisplayValue: Map<TraceFilterEnum, number>;
spanKind?: string;
} }
interface SpansAggregateData { interface SpansAggregateData {

View File

@ -31,7 +31,7 @@ const (
defaultLogsDB string = "signoz_logs" defaultLogsDB string = "signoz_logs"
defaultLogsTable string = "distributed_logs" defaultLogsTable string = "distributed_logs"
defaultLogsLocalTable string = "logs" defaultLogsLocalTable string = "logs"
defaultLogAttributeKeysTable string = "distributed_logs_atrribute_keys" defaultLogAttributeKeysTable string = "distributed_logs_attribute_keys"
defaultLogResourceKeysTable string = "distributed_logs_resource_keys" defaultLogResourceKeysTable string = "distributed_logs_resource_keys"
defaultLiveTailRefreshSeconds int = 10 defaultLiveTailRefreshSeconds int = 10
defaultWriteBatchDelay time.Duration = 5 * time.Second defaultWriteBatchDelay time.Duration = 5 * time.Second

View File

@ -1001,6 +1001,11 @@ func (r *ClickHouseReader) GetSpanFilters(ctx context.Context, queryParams *mode
args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration)) args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration))
} }
if len(queryParams.SpanKind) != 0 {
query = query + " AND kind = @kind"
args = append(args, clickhouse.Named("kind", queryParams.SpanKind))
}
query = getStatusFilters(query, queryParams.Status, excludeMap) query = getStatusFilters(query, queryParams.Status, excludeMap)
traceFilterReponse := model.SpanFiltersResponse{ traceFilterReponse := model.SpanFiltersResponse{
@ -1364,9 +1369,9 @@ func (r *ClickHouseReader) GetFilteredSpans(ctx context.Context, queryParams *mo
} }
query = getStatusFilters(query, queryParams.Status, excludeMap) query = getStatusFilters(query, queryParams.Status, excludeMap)
if len(queryParams.Kind) != 0 { if len(queryParams.SpanKind) != 0 {
query = query + " AND kind = @kind" query = query + " AND kind = @kind"
args = append(args, clickhouse.Named("kind", queryParams.Kind)) args = append(args, clickhouse.Named("kind", queryParams.SpanKind))
} }
// create TagQuery from TagQueryParams // create TagQuery from TagQueryParams
@ -1666,6 +1671,10 @@ func (r *ClickHouseReader) GetTagFilters(ctx context.Context, queryParams *model
query = query + " AND durationNano <= @durationNanoMax" query = query + " AND durationNano <= @durationNanoMax"
args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration)) args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration))
} }
if len(queryParams.SpanKind) != 0 {
query = query + " AND kind = @kind"
args = append(args, clickhouse.Named("kind", queryParams.SpanKind))
}
query = getStatusFilters(query, queryParams.Status, excludeMap) query = getStatusFilters(query, queryParams.Status, excludeMap)
@ -1781,6 +1790,10 @@ func (r *ClickHouseReader) GetTagValues(ctx context.Context, queryParams *model.
query = query + " AND durationNano <= @durationNanoMax" query = query + " AND durationNano <= @durationNanoMax"
args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration)) args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration))
} }
if len(queryParams.SpanKind) != 0 {
query = query + " AND kind = @kind"
args = append(args, clickhouse.Named("kind", queryParams.SpanKind))
}
query = getStatusFilters(query, queryParams.Status, excludeMap) query = getStatusFilters(query, queryParams.Status, excludeMap)
@ -1981,7 +1994,7 @@ func (r *ClickHouseReader) GetDependencyGraph(ctx context.Context, queryParams *
result[5] AS p99, result[5] AS p99,
sum(total_count) as callCount, sum(total_count) as callCount,
sum(total_count)/ @duration AS callRate, sum(total_count)/ @duration AS callRate,
sum(error_count)/sum(total_count) as errorRate sum(error_count)/sum(total_count) * 100 as errorRate
FROM %s.%s FROM %s.%s
WHERE toUInt64(toDateTime(timestamp)) >= @start AND toUInt64(toDateTime(timestamp)) <= @end WHERE toUInt64(toDateTime(timestamp)) >= @start AND toUInt64(toDateTime(timestamp)) <= @end
GROUP BY GROUP BY
@ -2113,9 +2126,9 @@ func (r *ClickHouseReader) GetFilteredSpansAggregates(ctx context.Context, query
} }
query = getStatusFilters(query, queryParams.Status, excludeMap) query = getStatusFilters(query, queryParams.Status, excludeMap)
if len(queryParams.Kind) != 0 { if len(queryParams.SpanKind) != 0 {
query = query + " AND kind = @kind" query = query + " AND kind = @kind"
args = append(args, clickhouse.Named("kind", queryParams.Kind)) args = append(args, clickhouse.Named("kind", queryParams.SpanKind))
} }
// create TagQuery from TagQueryParams // create TagQuery from TagQueryParams
tags := createTagQueryFromTagQueryParams(queryParams.Tags) tags := createTagQueryFromTagQueryParams(queryParams.Tags)

View File

@ -61,6 +61,11 @@ type APIHandler struct {
ruleManager *rules.Manager ruleManager *rules.Manager
featureFlags interfaces.FeatureLookup featureFlags interfaces.FeatureLookup
ready func(http.HandlerFunc) http.HandlerFunc ready func(http.HandlerFunc) http.HandlerFunc
// SetupCompleted indicates if SigNoz is ready for general use.
// at the moment, we mark the app ready when the first user
// is registers.
SetupCompleted bool
} }
type APIHandlerOpts struct { type APIHandlerOpts struct {
@ -100,6 +105,19 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
// if errReadingDashboards != nil { // if errReadingDashboards != nil {
// return nil, errReadingDashboards // return nil, errReadingDashboards
// } // }
// check if at least one user is created
hasUsers, err := aH.appDao.GetUsersWithOpts(context.Background(), 1)
if err.Error() != "" {
// raise warning but no panic as this is a recoverable condition
zap.S().Warnf("unexpected error while fetch user count while initializing base api handler", err.Error())
}
if len(hasUsers) != 0 {
// first user is already created, we can mark the app ready for general use.
// this means, we disable self-registration and expect new users
// to signup signoz through invite link only.
aH.SetupCompleted = true
}
return aH, nil return aH, nil
} }
@ -1645,7 +1663,13 @@ func (aH *APIHandler) getDisks(w http.ResponseWriter, r *http.Request) {
func (aH *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
version := version.GetVersion() version := version.GetVersion()
aH.WriteJSON(w, r, map[string]string{"version": version, "ee": "N"}) versionResponse := model.GetVersionResponse{
Version: version,
EE: "Y",
SetupCompleted: aH.SetupCompleted,
}
aH.WriteJSON(w, r, versionResponse)
} }
func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
@ -1777,6 +1801,12 @@ func (aH *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
return return
} }
if !aH.SetupCompleted {
// since the first user is now created, we can disable self-registration as
// from here onwards, we expect admin (owner) to invite other users.
aH.SetupCompleted = true
}
aH.Respond(w, nil) aH.Respond(w, nil)
} }

View File

@ -267,7 +267,7 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*model.User,
func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword bool) (*model.User, *model.ApiError) { func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword bool) (*model.User, *model.ApiError) {
if req.InviteToken == "" { if req.InviteToken == "" {
return nil, model.BadRequest(fmt.Errorf("invite token is required")) return nil, model.BadRequest(ErrorAskAdmin)
} }
if !nopassword && req.Password == "" { if !nopassword && req.Password == "" {

View File

@ -15,7 +15,7 @@ var (
ErrorInvalidRole = errors.New("Invalid role") ErrorInvalidRole = errors.New("Invalid role")
ErrorInvalidInviteToken = errors.New("Invalid invite token") ErrorInvalidInviteToken = errors.New("Invalid invite token")
ErrorAskAdmin = errors.New("You are not allowed to create an account. Please ask your admin to send an invite link") ErrorAskAdmin = errors.New("An invitation is needed to create an account. Please ask your admin (the person who has first installed SIgNoz) to send an invite.")
) )
func randomHex(sz int) (string, error) { func randomHex(sz int) (string, error) {

View File

@ -19,6 +19,7 @@ type Queries interface {
GetUser(ctx context.Context, id string) (*model.UserPayload, *model.ApiError) GetUser(ctx context.Context, id string) (*model.UserPayload, *model.ApiError)
GetUserByEmail(ctx context.Context, email string) (*model.UserPayload, *model.ApiError) GetUserByEmail(ctx context.Context, email string) (*model.UserPayload, *model.ApiError)
GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError) GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError)
GetUsersWithOpts(ctx context.Context, limit int) ([]model.UserPayload, *model.ApiError)
GetGroup(ctx context.Context, id string) (*model.Group, *model.ApiError) GetGroup(ctx context.Context, id string) (*model.Group, *model.ApiError)
GetGroupByName(ctx context.Context, name string) (*model.Group, *model.ApiError) GetGroupByName(ctx context.Context, name string) (*model.Group, *model.ApiError)

View File

@ -345,7 +345,13 @@ func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
return &users[0], nil return &users[0], nil
} }
// GetUsers fetches total user count
func (mds *ModelDaoSqlite) GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError) { func (mds *ModelDaoSqlite) GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError) {
return mds.GetUsersWithOpts(ctx, 0)
}
// GetUsersWithOpts fetches users and supports additional search options
func (mds *ModelDaoSqlite) GetUsersWithOpts(ctx context.Context, limit int) ([]model.UserPayload, *model.ApiError) {
users := []model.UserPayload{} users := []model.UserPayload{}
query := `select query := `select
@ -364,6 +370,9 @@ func (mds *ModelDaoSqlite) GetUsers(ctx context.Context) ([]model.UserPayload, *
g.id = u.group_id and g.id = u.group_id and
o.id = u.org_id` o.id = u.org_id`
if limit > 0 {
query = fmt.Sprintf("%s LIMIT %d", query, limit)
}
err := mds.db.Select(&users, query) err := mds.db.Select(&users, query)
if err != nil { if err != nil {

View File

@ -312,7 +312,7 @@ type GetFilteredSpansParams struct {
TraceID []string `json:"traceID"` TraceID []string `json:"traceID"`
ServiceName []string `json:"serviceName"` ServiceName []string `json:"serviceName"`
Operation []string `json:"operation"` Operation []string `json:"operation"`
Kind string `json:"kind"` SpanKind string `json:"spanKind"`
Status []string `json:"status"` Status []string `json:"status"`
HttpRoute []string `json:"httpRoute"` HttpRoute []string `json:"httpRoute"`
HttpCode []string `json:"httpCode"` HttpCode []string `json:"httpCode"`
@ -340,7 +340,7 @@ type GetFilteredSpanAggregatesParams struct {
TraceID []string `json:"traceID"` TraceID []string `json:"traceID"`
ServiceName []string `json:"serviceName"` ServiceName []string `json:"serviceName"`
Operation []string `json:"operation"` Operation []string `json:"operation"`
Kind string `json:"kind"` SpanKind string `json:"spanKind"`
Status []string `json:"status"` Status []string `json:"status"`
HttpRoute []string `json:"httpRoute"` HttpRoute []string `json:"httpRoute"`
HttpCode []string `json:"httpCode"` HttpCode []string `json:"httpCode"`
@ -369,6 +369,7 @@ type SpanFilterParams struct {
TraceID []string `json:"traceID"` TraceID []string `json:"traceID"`
Status []string `json:"status"` Status []string `json:"status"`
ServiceName []string `json:"serviceName"` ServiceName []string `json:"serviceName"`
SpanKind string `json:"spanKind"`
HttpRoute []string `json:"httpRoute"` HttpRoute []string `json:"httpRoute"`
HttpCode []string `json:"httpCode"` HttpCode []string `json:"httpCode"`
HttpUrl []string `json:"httpUrl"` HttpUrl []string `json:"httpUrl"`
@ -394,6 +395,7 @@ type TagFilterParams struct {
ServiceName []string `json:"serviceName"` ServiceName []string `json:"serviceName"`
HttpRoute []string `json:"httpRoute"` HttpRoute []string `json:"httpRoute"`
HttpCode []string `json:"httpCode"` HttpCode []string `json:"httpCode"`
SpanKind string `json:"spanKind"`
HttpUrl []string `json:"httpUrl"` HttpUrl []string `json:"httpUrl"`
HttpHost []string `json:"httpHost"` HttpHost []string `json:"httpHost"`
HttpMethod []string `json:"httpMethod"` HttpMethod []string `json:"httpMethod"`

View File

@ -585,3 +585,9 @@ func (ci *ClusterInfo) GetMapFromStruct() map[string]interface{} {
json.Unmarshal(data, &clusterInfoMap) json.Unmarshal(data, &clusterInfoMap)
return clusterInfoMap return clusterInfoMap
} }
type GetVersionResponse struct {
Version string `json:"version"`
EE string `json:"ee"`
SetupCompleted bool `json:"setupCompleted"`
}

View File

@ -169,7 +169,7 @@ services:
<<: *clickhouse-depends <<: *clickhouse-depends
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:0.66.4 image: signoz/signoz-otel-collector:0.66.5
command: ["--config=/etc/otel-collector-config.yaml"] command: ["--config=/etc/otel-collector-config.yaml"]
user: root # required for reading docker container logs user: root # required for reading docker container logs
volumes: volumes:
@ -195,7 +195,7 @@ services:
<<: *clickhouse-depends <<: *clickhouse-depends
otel-collector-metrics: otel-collector-metrics:
image: signoz/signoz-otel-collector:0.66.4 image: signoz/signoz-otel-collector:0.66.5
command: ["--config=/etc/otel-collector-metrics-config.yaml"] command: ["--config=/etc/otel-collector-metrics-config.yaml"]
volumes: volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml