Compare commits

..

3 Commits

Author SHA1 Message Date
nityanandagohain
ef8a3ca9a0 Merge remote-tracking branch 'origin/develop' into feat/read-in-order 2024-04-26 15:23:58 +05:30
nityanandagohain
cb9e255413 fix: regex for clickhouse read in order 2024-04-26 15:23:42 +05:30
nityanandagohain
3ade9b0c2b fix: add read-in-order config 2024-04-25 18:53:47 +05:30
123 changed files with 1735 additions and 3048 deletions

View File

@@ -9,46 +9,34 @@ jobs:
name: Deploy latest develop branch to staging
runs-on: ubuntu-latest
environment: staging
permissions:
contents: 'read'
id-token: 'write'
steps:
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: 'sdk'
uses: 'google-github-actions/setup-gcloud@v2'
- name: 'ssh'
shell: bash
- name: Executing remote ssh commands using ssh key
uses: appleboy/ssh-action@v1.0.3
env:
GITHUB_BRANCH: ${{ github.head_ref || github.ref_name }}
GITHUB_BRANCH: develop
GITHUB_SHA: ${{ github.sha }}
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
GCP_ZONE: ${{ secrets.GCP_ZONE }}
GCP_INSTANCE: ${{ secrets.GCP_INSTANCE }}
run: |
read -r -d '' COMMAND <<EOF || true
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
echo "GITHUB_SHA: ${GITHUB_SHA}"
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
export OTELCOL_TAG="main"
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
docker system prune --force
docker pull signoz/signoz-otel-collector:main
docker pull signoz/signoz-schema-migrator:main
cd ~/signoz
git status
git add .
git stash push -m "stashed on $(date --iso-8601=seconds)"
git fetch origin
git checkout ${GITHUB_BRANCH}
git pull
make build-ee-query-service-amd64
make build-frontend-amd64
make run-signoz
EOF
gcloud compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"
with:
host: ${{ secrets.HOST_DNS }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
envs: GITHUB_BRANCH,GITHUB_SHA
command_timeout: 60m
script: |
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
echo "GITHUB_SHA: ${GITHUB_SHA}"
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
export OTELCOL_TAG="main"
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
docker system prune --force
docker pull signoz/signoz-otel-collector:main
docker pull signoz/signoz-schema-migrator:main
cd ~/signoz
git status
git add .
git stash push -m "stashed on $(date --iso-8601=seconds)"
git fetch origin
git checkout ${GITHUB_BRANCH}
git pull
make build-ee-query-service-amd64
make build-frontend-amd64
make run-signoz

View File

@@ -9,47 +9,35 @@ jobs:
runs-on: ubuntu-latest
environment: testing
if: ${{ github.event.label.name == 'testing-deploy' }}
permissions:
contents: 'read'
id-token: 'write'
steps:
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: 'sdk'
uses: 'google-github-actions/setup-gcloud@v2'
- name: 'ssh'
shell: bash
- name: Executing remote ssh commands using ssh key
uses: appleboy/ssh-action@v1.0.3
env:
GITHUB_BRANCH: ${{ github.head_ref || github.ref_name }}
GITHUB_SHA: ${{ github.sha }}
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
GCP_ZONE: ${{ secrets.GCP_ZONE }}
GCP_INSTANCE: ${{ secrets.GCP_INSTANCE }}
run: |
read -r -d '' COMMAND <<EOF || true
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
echo "GITHUB_SHA: ${GITHUB_SHA}"
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
export DEV_BUILD="1"
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
docker system prune --force
cd ~/signoz
git status
git add .
git stash push -m "stashed on $(date --iso-8601=seconds)"
git fetch origin
git checkout develop
git pull
# This is added to include the scenerio when new commit in PR is force-pushed
git branch -D ${GITHUB_BRANCH}
git checkout --track origin/${GITHUB_BRANCH}
make build-ee-query-service-amd64
make build-frontend-amd64
make run-signoz
EOF
gcloud compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"
with:
host: ${{ secrets.HOST_DNS }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
envs: GITHUB_BRANCH,GITHUB_SHA
command_timeout: 60m
script: |
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
echo "GITHUB_SHA: ${GITHUB_SHA}"
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
export DEV_BUILD="1"
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
docker system prune --force
cd ~/signoz
git status
git add .
git stash push -m "stashed on $(date --iso-8601=seconds)"
git fetch origin
git checkout develop
git pull
# This is added to include the scenerio when new commit in PR is force-pushed
git branch -D ${GITHUB_BRANCH}
git checkout --track origin/${GITHUB_BRANCH}
make build-ee-query-service-amd64
make build-frontend-amd64
make run-signoz

View File

@@ -1,8 +0,0 @@
#### Auto generated by make versions/golang. DO NOT EDIT! ####
amd64=128d7baad667abc0e41a85673026a2cf9449ef40f384baf424aee45bc13f9235
arm=a5f77dc34ccae0d43269675508aab8fa9078ded6fa3e2dcee54f7c230018100d
arm64=1cdad16d01542a57caca4b0a6893a5b69d711d69dd6bb4483c77c1d092baec41
386=0c82e5195d14caa5daa01ea06a70139e7ea1edbd366c83259227c7d9965d4c5a
mips64le=25967f27f76031f31cd3ae2173958e151d8d961ca186ab4328af7a1895139a66
ppc64le=6fa49b4730622b79560a1fc2677b02a1ee7aac5b28490a2bda6134050108fb3a
s390x=4e2c0198c3db1c769e8e2e8a1e504dbb5e3eff0dad62f8f5c543b4823a89d81b

View File

@@ -146,7 +146,7 @@ services:
condition: on-failure
query-service:
image: signoz/query-service:0.46.0
image: signoz/query-service:0.44.0
command:
[
"-config=/root/config/prometheus.yml",
@@ -186,7 +186,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:0.46.0
image: signoz/frontend:0.44.0
deploy:
restart_policy:
condition: on-failure
@@ -199,7 +199,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
image: signoz/signoz-otel-collector:0.88.24
image: signoz/signoz-otel-collector:0.88.21
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -237,7 +237,7 @@ services:
- query-service
otel-collector-migrator:
image: signoz/signoz-schema-migrator:0.88.24
image: signoz/signoz-schema-migrator:0.88.21
deploy:
restart_policy:
condition: on-failure

View File

@@ -66,7 +66,7 @@ services:
- --storage.path=/data
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.21}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -81,7 +81,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`
otel-collector:
container_name: signoz-otel-collector
image: signoz/signoz-otel-collector:0.88.24
image: signoz/signoz-otel-collector:0.88.21
command:
[
"--config=/etc/otel-collector-config.yaml",

View File

@@ -164,7 +164,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`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.46.0}
image: signoz/query-service:${DOCKER_TAG:-0.44.0}
container_name: signoz-query-service
command:
[
@@ -203,7 +203,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.46.0}
image: signoz/frontend:${DOCKER_TAG:-0.44.0}
container_name: signoz-frontend
restart: on-failure
depends_on:
@@ -215,7 +215,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.21}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -229,7 +229,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.24}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.21}
container_name: signoz-otel-collector
command:
[

View File

@@ -1,64 +0,0 @@
<clickhouse>
<logger>
<!-- Possible levels [1]:
- none (turns off logging)
- fatal
- critical
- error
- warning
- notice
- information
- debug
- trace
[1]: https://github.com/pocoproject/poco/blob/poco-1.9.4-release/Foundation/include/Poco/Logger.h#L105-L114
-->
<level>information</level>
<log>/var/log/clickhouse-keeper/clickhouse-keeper.log</log>
<errorlog>/var/log/clickhouse-keeper/clickhouse-keeper.err.log</errorlog>
<!-- Rotation policy
See https://github.com/pocoproject/poco/blob/poco-1.9.4-release/Foundation/include/Poco/FileChannel.h#L54-L85
-->
<size>1000M</size>
<count>10</count>
<!-- <console>1</console> --> <!-- Default behavior is autodetection (log to console if not daemon mode and is tty) -->
</logger>
<listen_host>0.0.0.0</listen_host>
<max_connections>4096</max_connections>
<keeper_server>
<tcp_port>9181</tcp_port>
<!-- Must be unique among all keeper serves -->
<server_id>1</server_id>
<log_storage_path>/var/lib/clickhouse/coordination/logs</log_storage_path>
<snapshot_storage_path>/var/lib/clickhouse/coordination/snapshots</snapshot_storage_path>
<coordination_settings>
<operation_timeout_ms>10000</operation_timeout_ms>
<min_session_timeout_ms>10000</min_session_timeout_ms>
<session_timeout_ms>100000</session_timeout_ms>
<raft_logs_level>information</raft_logs_level>
<compress_logs>false</compress_logs>
<!-- All settings listed in https://github.com/ClickHouse/ClickHouse/blob/master/src/Coordination/CoordinationSettings.h -->
</coordination_settings>
<!-- enable sanity hostname checks for cluster configuration (e.g. if localhost is used with remote endpoints) -->
<hostname_checks_enabled>true</hostname_checks_enabled>
<raft_configuration>
<server>
<id>1</id>
<!-- Internal port and hostname -->
<hostname>clickhouses-keeper-1</hostname>
<port>9234</port>
</server>
<!-- Add more servers here -->
</raft_configuration>
</keeper_server>
</clickhouse>

View File

@@ -14,9 +14,7 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.signoz.io/signoz/ee/query-service/app"
"go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/constants"
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/migrate"
"go.signoz.io/signoz/pkg/query-service/version"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
@@ -145,12 +143,6 @@ func main() {
zap.L().Info("JWT secret key set successfully.")
}
if err := migrate.Migrate(constants.RELATIONAL_DATASOURCE_PATH); err != nil {
zap.L().Error("Failed to migrate", zap.Error(err))
} else {
zap.L().Info("Migration successful")
}
server, err := app.NewServer(serverOptions)
if err != nil {
zap.L().Fatal("Failed to create server", zap.Error(err))

View File

@@ -52,14 +52,14 @@ var BasicPlan = basemodel.FeatureSet{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: -1,
UsageLimit: 20,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: -1,
UsageLimit: 10,
Route: "",
},
basemodel.Feature{

View File

@@ -4,7 +4,6 @@ const config: Config.InitialOptions = {
clearMocks: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'cobertura', 'html', 'json-summary'],
collectCoverageFrom: ['src/**/*.{ts,tsx}'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
modulePathIgnorePatterns: ['dist'],
moduleNameMapper: {
@@ -21,6 +20,8 @@ const config: Config.InitialOptions = {
transform: {
'^.+\\.(ts|tsx)?$': 'ts-jest',
'^.+\\.(js|jsx)$': 'babel-jest',
'^.+\\.(css|scss|sass|less)$': 'jest-preview/transforms/css',
'^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': 'jest-preview/transforms/file',
},
transformIgnorePatterns: [
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|d3-interpolate|d3-color)/)',

View File

@@ -13,6 +13,8 @@
"jest": "jest",
"jest:coverage": "jest --coverage",
"jest:watch": "jest --watch",
"jest-preview": "jest-preview",
"test:debug": "npm-run-all -p test jest-preview",
"postinstall": "is-ci || yarn husky:configure",
"playwright": "npm run i18n:generate-hash && NODE_ENV=testing playwright test --config=./playwright.config.ts",
"playwright:local:debug": "PWDEBUG=console yarn playwright --headed --browser=chromium",
@@ -181,7 +183,7 @@
"@types/webpack-dev-server": "^4.7.2",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"autoprefixer": "10.4.19",
"autoprefixer": "^9.0.0",
"babel-plugin-styled-components": "^1.12.0",
"compression-webpack-plugin": "9.0.0",
"copy-webpack-plugin": "^8.1.0",
@@ -204,12 +206,12 @@
"husky": "^7.0.4",
"is-ci": "^3.0.1",
"jest-playwright-preset": "^1.7.2",
"jest-preview": "0.3.1",
"jest-styled-components": "^7.0.8",
"lint-staged": "^12.5.0",
"msw": "1.3.2",
"npm-run-all": "latest",
"portfinder-sync": "^0.0.2",
"postcss": "8.4.38",
"prettier": "2.2.1",
"raw-loader": "4.0.2",
"react-hooks-testing-library": "0.6.0",

View File

@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c1 2.538 2.5 2.962 3.5 3.808.942.78 1.481 1.845 1.5 2.961 0 1.122-.527 2.198-1.464 2.992C14.598 12.554 13.326 13 12 13s-2.598-.446-3.536-1.24C7.527 10.968 7 9.892 7 8.77c0-.255 0-.508.1-.762.085.25.236.48.443.673.207.193.463.342.75.437a2.334 2.334 0 001.767-.128c.263-.135.485-.32.65-.539.166-.22.269-.468.301-.727a1.452 1.452 0 00-.11-.765 1.699 1.699 0 00-.501-.644C8 4.115 11 2 12 2zM17 16l-5 6-5-6h10z" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 581 B

View File

@@ -16,7 +16,6 @@
"new_dashboard_title": "Sample Title",
"layout_saved_successfully": "Layout saved successfully",
"add_panel": "Add Panel",
"add_row": "Add Row",
"save_layout": "Save Layout",
"variable_updated_successfully": "Variable updated successfully",
"error_while_updating_variable": "Error while updating variable",

View File

@@ -16,7 +16,6 @@
"new_dashboard_title": "Sample Title",
"layout_saved_successfully": "Layout saved successfully",
"add_panel": "Add Panel",
"add_row": "Add Row",
"save_layout": "Save Layout",
"full_view": "Full Screen View",
"variable_updated_successfully": "Variable updated successfully",

View File

@@ -1,27 +0,0 @@
import { CSSProperties } from 'react';
function PromQLIcon({
fillColor,
}: {
fillColor: CSSProperties['color'];
}): JSX.Element {
return (
<svg
width="14"
height="14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
d="M12 2c1 2.538 2.5 2.962 3.5 3.808.942.78 1.481 1.845 1.5 2.961 0 1.122-.527 2.198-1.464 2.992C14.598 12.554 13.326 13 12 13s-2.598-.446-3.536-1.24C7.527 10.968 7 9.892 7 8.77c0-.255 0-.508.1-.762.085.25.236.48.443.673.207.193.463.342.75.437a2.334 2.334 0 001.767-.128c.263-.135.485-.32.65-.539.166-.22.269-.468.301-.727a1.452 1.452 0 00-.11-.765 1.699 1.699 0 00-.501-.644C8 4.115 11 2 12 2zM17 16l-5 6-5-6h10z"
stroke={fillColor}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export default PromQLIcon;

View File

@@ -16,7 +16,6 @@ import { useCallback, useMemo, useState } from 'react';
// interfaces
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
// components
import AddToQueryHOC, { AddToQueryHOCProps } from '../AddToQueryHOC';
@@ -51,11 +50,7 @@ function LogGeneralField({
}: LogFieldProps): JSX.Element {
const html = useMemo(
() => ({
__html: convert.toHtml(
dompurify.sanitize(fieldValue, {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}),
),
__html: convert.toHtml(dompurify.sanitize(fieldValue)),
}),
[fieldValue],
);
@@ -162,8 +157,8 @@ function ListLogView({
const timestampValue = useMemo(
() =>
typeof flattenLogData.timestamp === 'string'
? dayjs(flattenLogData.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(flattenLogData.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'),
? dayjs(flattenLogData.timestamp).format()
: dayjs(flattenLogData.timestamp / 1e6).format(),
[flattenLogData.timestamp],
);

View File

@@ -21,7 +21,6 @@ import {
useMemo,
useState,
} from 'react';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
import LogLinesActionButtons from '../LogLinesActionButtons/LogLinesActionButtons';
import LogStateIndicator from '../LogStateIndicator/LogStateIndicator';
@@ -91,12 +90,12 @@ function RawLogView({
const text = useMemo(
() =>
typeof data.timestamp === 'string'
? `${dayjs(data.timestamp).format(
'YYYY-MM-DD HH:mm:ss.SSS',
)} | ${attributesText} ${severityText} ${data.body}`
: `${dayjs(data.timestamp / 1e6).format(
'YYYY-MM-DD HH:mm:ss.SSS',
)} | ${attributesText} ${severityText} ${data.body}`,
? `${dayjs(data.timestamp).format()} | ${attributesText} ${severityText} ${
data.body
}`
: `${dayjs(
data.timestamp / 1e6,
).format()} | ${attributesText} ${severityText} ${data.body}`,
[data.timestamp, data.body, severityText, attributesText],
);
@@ -145,9 +144,7 @@ function RawLogView({
const html = useMemo(
() => ({
__html: convert.toHtml(
dompurify.sanitize(text, { FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS] }),
),
__html: convert.toHtml(dompurify.sanitize(text)),
}),
[text],
);

View File

@@ -8,7 +8,6 @@ import dompurify from 'dompurify';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { FlatLogData } from 'lib/logs/flatLogData';
import { useMemo } from 'react';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
import LogStateIndicator from '../LogStateIndicator/LogStateIndicator';
import { getLogIndicatorTypeForTable } from '../LogStateIndicator/utils';
@@ -77,8 +76,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
render: (field, item): ColumnTypeRender<Record<string, unknown>> => {
const date =
typeof field === 'string'
? dayjs(field).format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(field / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
? dayjs(field).format()
: dayjs(field / 1e6).format();
return {
children: (
<div className="table-timestamp">
@@ -108,11 +107,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
children: (
<TableBodyContent
dangerouslySetInnerHTML={{
__html: convert.toHtml(
dompurify.sanitize(field, {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}),
),
__html: convert.toHtml(dompurify.sanitize(field)),
}}
linesPerRow={linesPerRow}
isDarkMode={isDarkMode}

View File

@@ -1,6 +1,6 @@
import './FacingIssueBtn.style.scss';
import { Button, Tooltip } from 'antd';
import { Button } from 'antd';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import { FeatureKeys } from 'constants/features';
@@ -15,7 +15,6 @@ export interface FacingIssueBtnProps {
message?: string;
buttonText?: string;
className?: string;
onHoverText?: string;
}
function FacingIssueBtn({
@@ -24,7 +23,6 @@ function FacingIssueBtn({
message = '',
buttonText = '',
className = '',
onHoverText = '',
}: FacingIssueBtnProps): JSX.Element | null {
const handleFacingIssuesClick = (): void => {
logEvent(eventName, attributes);
@@ -39,15 +37,13 @@ function FacingIssueBtn({
return isCloudUserVal && isChatSupportEnabled ? ( // Note: we would need to move this condition to license based in future
<div className="facing-issue-button">
<Tooltip title={onHoverText} autoAdjustOverflow>
<Button
className={cx('periscope-btn', 'facing-issue-button', className)}
onClick={handleFacingIssuesClick}
icon={<HelpCircle size={14} />}
>
{buttonText || 'Facing issues?'}
</Button>
</Tooltip>
<Button
className={cx('periscope-btn', 'facing-issue-button', className)}
onClick={handleFacingIssuesClick}
icon={<HelpCircle size={14} />}
>
{buttonText || 'Facing issues?'}
</Button>
</div>
) : null;
}
@@ -56,7 +52,6 @@ FacingIssueBtn.defaultProps = {
message: '',
buttonText: '',
className: '',
onHoverText: '',
};
export default FacingIssueBtn;

View File

@@ -1,57 +0,0 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { AlertDef } from 'types/api/alerts/def';
import { Dashboard, DashboardData } from 'types/api/dashboard/getAll';
export const chartHelpMessage = (
selectedDashboard: Dashboard | undefined,
graphType: PANEL_TYPES,
): string => `
Hi Team,
I need help in creating this chart. Here are my dashboard details
Name: ${selectedDashboard?.data.title || ''}
Panel type: ${graphType}
Dashboard Id: ${selectedDashboard?.uuid || ''}
Thanks`;
export const dashboardHelpMessage = (
data: DashboardData | undefined,
selectedDashboard: Dashboard | undefined,
): string => `
Hi Team,
I need help with this dashboard. Here are my dashboard details
Name: ${data?.title || ''}
Dashboard Id: ${selectedDashboard?.uuid || ''}
Thanks`;
export const dashboardListMessage = `Hi Team,
I need help with dashboards.
Thanks`;
export const listAlertMessage = `Hi Team,
I need help with managing alerts.
Thanks`;
export const alertHelpMessage = (
alertDef: AlertDef,
ruleId: number,
): string => `
Hi Team,
I need help in configuring this alert. Here are my alert rule details
Name: ${alertDef?.alert || ''}
Alert Type: ${alertDef?.alertType || ''}
State: ${(alertDef as any)?.state || ''}
Alert Id: ${ruleId}
Thanks`;

View File

@@ -30,5 +30,4 @@ export enum QueryParams {
integration = 'integration',
pagination = 'pagination',
relativeTime = 'relativeTime',
alertType = 'alertType',
}

View File

@@ -289,11 +289,6 @@ export enum PANEL_TYPES {
EMPTY_WIDGET = 'EMPTY_WIDGET',
}
// eslint-disable-next-line @typescript-eslint/naming-convention
export enum PANEL_GROUP_TYPES {
ROW = 'row',
}
// eslint-disable-next-line @typescript-eslint/naming-convention
export enum ATTRIBUTE_TYPES {
SUM = 'Sum',

View File

@@ -59,8 +59,8 @@ function CreateAlertChannels({
*Summary:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
*RelatedLogs:* {{ if gt (len .Annotations.related_logs) 0 -}} View in <{{ .Annotations.related_logs }}|logs explorer> {{- end}}
*RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end}}
*RelatedLogs:* {{ .Annotations.related_logs }}
*RelatedTraces:* {{ .Annotations.related_traces }}
*Details:*
{{ range .Labels.SortedPairs }} • *{{ .Name }}:* {{ .Value }}

View File

@@ -1,9 +1,8 @@
import { Form, Row } from 'antd';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { QueryParams } from 'constants/query';
import FormAlertRules from 'container/FormAlertRules';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import history from 'lib/history';
import { isEqual } from 'lodash-es';
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { AlertTypes } from 'types/api/alerts/alertTypes';
@@ -20,25 +19,13 @@ import SelectAlertType from './SelectAlertType';
function CreateRules(): JSX.Element {
const [initValues, setInitValues] = useState<AlertDef | null>(null);
const [alertType, setAlertType] = useState<AlertTypes>();
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const version = queryParams.get('version');
const alertTypeFromParams = queryParams.get(QueryParams.alertType);
const compositeQuery = useGetCompositeQueryParam();
function getAlertTypeFromDataSource(): AlertTypes | null {
if (!compositeQuery) {
return null;
}
const dataSource = compositeQuery?.builder?.queryData[0]?.dataSource;
return ALERT_TYPE_VS_SOURCE_MAPPING[dataSource];
}
const [alertType, setAlertType] = useState<AlertTypes>(
(alertTypeFromParams as AlertTypes) || getAlertTypeFromDataSource(),
);
const [formInstance] = Form.useForm();
@@ -60,17 +47,21 @@ function CreateRules(): JSX.Element {
version: version || ENTITY_VERSION_V4,
});
}
queryParams.set(QueryParams.alertType, typ);
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
history.replace(generatedUrl);
};
useEffect(() => {
if (alertType) {
onSelectType(alertType);
if (!compositeQuery) {
return;
}
const dataSource = compositeQuery?.builder?.queryData[0]?.dataSource;
const alertTypeFromQuery = ALERT_TYPE_VS_SOURCE_MAPPING[dataSource];
if (alertTypeFromQuery && !isEqual(alertType, alertTypeFromQuery)) {
onSelectType(alertTypeFromQuery);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [alertType]);
}, [compositeQuery]);
if (!initValues) {
return (

View File

@@ -64,10 +64,6 @@
.view-options,
.actions {
.info-icon {
padding: 8px;
}
.hidden {
display: none;
}
@@ -256,10 +252,6 @@
color: var(--bg-ink-200);
background-color: var(--bg-vanilla-300);
}
.info-icon {
color: var(--bg-ink-200);
}
}
}

View File

@@ -1,7 +1,6 @@
/* eslint-disable react/jsx-props-no-spreading */
import './ExplorerOptions.styles.scss';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Color } from '@signozhq/design-tokens';
import {
Button,
@@ -403,28 +402,6 @@ function ExplorerOptions({
</Button>
</div>
<div className="actions">
<Tooltip
title={
<div>
{sourcepage === DataSource.LOGS
? 'Learn more about Logs explorer '
: 'Learn more about Traces explorer '}
<Typography.Link
href={
sourcepage === DataSource.LOGS
? 'https://signoz.io/docs/product-features/logs-explorer/?utm_source=product&utm_medium=logs-explorer-toolbar'
: 'https://signoz.io/docs/product-features/trace-explorer/?utm_source=product&utm_medium=trace-explorer-toolbar'
}
target="_blank"
>
{' '}
here
</Typography.Link>{' '}
</div>
}
>
<InfoCircleOutlined className="info-icon" />
</Tooltip>
<Tooltip title="Hide">
<Button
disabled={disabled}

View File

@@ -1,15 +1,12 @@
import './QuerySection.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, Tabs, Tooltip } from 'antd';
import PromQLIcon from 'assets/Dashboard/PromQl';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
import { QueryBuilder } from 'container/QueryBuilder';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { Atom, Play, Terminal } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -51,8 +48,6 @@ function QuerySection({
const renderChQueryUI = (): JSX.Element => <ChQuerySection />;
const isDarkMode = useIsDarkMode();
const renderMetricUI = (): JSX.Element => (
<QueryBuilder
panelType={panelType}
@@ -118,16 +113,14 @@ function QuerySection({
label: (
<Tooltip title="PromQL">
<Button className="nav-btns">
<PromQLIcon
fillColor={isDarkMode ? Color.BG_VANILLA_200 : Color.BG_INK_300}
/>
<img src="/Icons/promQL.svg" alt="Prom Ql" className="prom-ql-icon" />
</Button>
</Tooltip>
),
key: EQueryType.PROM,
},
],
[isDarkMode],
[],
);
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();

View File

@@ -147,7 +147,7 @@ function UserGuide({ queryType }: UserGuideProps): JSX.Element {
<Col flex="none">
<TextToolTip
text={t('user_tooltip_more_help')}
url="https://signoz.io/docs/userguide/alerts-management/?utm_source=product&utm_medium=create-alert#creating-a-new-alert-in-signoz"
url="https://signoz.io/docs/userguide/alerts-management/#create-alert-rules"
/>
</Col>
</Row>

View File

@@ -12,7 +12,6 @@ import {
import saveAlertApi from 'api/alerts/save';
import testAlertApi from 'api/alerts/testAlert';
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { alertHelpMessage } from 'components/facingIssueBtn/util';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -524,7 +523,6 @@ function FormAlertRules({
runQuery={handleRunQuery}
alertDef={alertDef}
panelType={panelType || PANEL_TYPES.TIME_SERIES}
key={currentQuery.queryType}
/>
<RuleOptions
@@ -586,9 +584,17 @@ function FormAlertRules({
}}
className="facing-issue-btn"
eventName="Alert: Facing Issues in alert"
buttonText="Need help with this alert?"
message={alertHelpMessage(alertDef, ruleId)}
onHoverText="Click here to get help with this alert"
buttonText="Facing Issues in alert"
message={`Hi Team,
I am facing issues configuring alerts in SigNoz. Here are my alert rule details
Name: ${alertDef?.alert || ''}
Alert Type: ${alertDef?.alertType || ''}
State: ${(alertDef as any)?.state || ''}
Alert Id: ${ruleId}
Thanks`}
/>
</Col>
</PanelContainer>

View File

@@ -59,7 +59,7 @@ function WidgetGraphComponent({
const lineChartRef = useRef<ToggleGraphProps>();
const [graphVisibility, setGraphVisibility] = useState<boolean[]>(
Array(queryResponse.data?.payload?.data?.result?.length || 0).fill(true),
Array(queryResponse.data?.payload?.data.result.length || 0).fill(true),
);
const graphRef = useRef<HTMLDivElement>(null);
@@ -135,7 +135,7 @@ function WidgetGraphComponent({
i: uuid,
w: 6,
x: 0,
h: 6,
h: 3,
y: 0,
},
];

View File

@@ -1,14 +1,11 @@
import './GridCardLayout.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Flex, Form, Input, Modal, Tooltip, Typography } from 'antd';
import { useForm } from 'antd/es/form/Form';
import cx from 'classnames';
import { Flex, Tooltip } from 'antd';
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { dashboardHelpMessage } from 'components/facingIssueBtn/util';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query';
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
@@ -16,21 +13,12 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { defaultTo } from 'lodash-es';
import isEqual from 'lodash-es/isEqual';
import {
FullscreenIcon,
GripVertical,
MoveDown,
MoveUp,
Settings,
Trash2,
} from 'lucide-react';
import { FullscreenIcon } from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { sortLayout } from 'providers/Dashboard/util';
import { useCallback, useEffect, useState } from 'react';
import { FullScreen, useFullScreenHandle } from 'react-full-screen';
import { ItemCallback, Layout } from 'react-grid-layout';
import { Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
@@ -40,7 +28,6 @@ import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app';
import { ROLES, USER_ROLES } from 'types/roles';
import { ComponentTypes } from 'utils/permission';
import { v4 as uuid } from 'uuid';
import { EditMenuAction, ViewMenuAction } from './config';
import GridCard from './GridCard';
@@ -59,8 +46,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
selectedDashboard,
layouts,
setLayouts,
panelMap,
setPanelMap,
setSelectedDashboard,
isDashboardLocked,
} = useDashboard();
@@ -81,26 +66,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
const [dashboardLayout, setDashboardLayout] = useState<Layout[]>([]);
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState<boolean>(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
const [currentSelectRowId, setCurrentSelectRowId] = useState<string | null>(
null,
);
const [currentPanelMap, setCurrentPanelMap] = useState<
Record<string, { widgets: Layout[]; collapsed: boolean }>
>({});
useEffect(() => {
setCurrentPanelMap(panelMap);
}, [panelMap]);
const [form] = useForm<{
title: string;
}>();
const updateDashboardMutation = useUpdateDashboard();
const { notifications } = useNotifications();
@@ -123,7 +88,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
);
useEffect(() => {
setDashboardLayout(sortLayout(layouts));
setDashboardLayout(layouts);
}, [layouts]);
const onSaveHandler = (): void => {
@@ -133,7 +98,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
...selectedDashboard,
data: {
...selectedDashboard.data,
panelMap: { ...currentPanelMap },
layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
},
uuid: selectedDashboard.uuid,
@@ -143,9 +107,8 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
onSuccess: (updatedDashboard) => {
if (updatedDashboard.payload) {
if (updatedDashboard.payload.data.layout)
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
setLayouts(updatedDashboard.payload.data.layout);
setSelectedDashboard(updatedDashboard.payload);
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
}
featureResponse.refetch();
@@ -168,8 +131,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
dashboardLayout,
);
if (!isEqual(filterLayout, filterDashboardLayout)) {
const updatedLayout = sortLayout(layout);
setDashboardLayout(updatedLayout);
setDashboardLayout(layout);
}
};
@@ -206,283 +168,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dashboardLayout]);
function handleAddRow(): void {
if (!selectedDashboard) return;
const id = uuid();
const newRowWidgetMap: { widgets: Layout[]; collapsed: boolean } = {
widgets: [],
collapsed: false,
};
const currentRowIdx = 0;
for (let j = currentRowIdx; j < dashboardLayout.length; j++) {
if (!currentPanelMap[dashboardLayout[j].i]) {
newRowWidgetMap.widgets.push(dashboardLayout[j]);
} else {
break;
}
}
const updatedDashboard: Dashboard = {
...selectedDashboard,
data: {
...selectedDashboard.data,
layout: [
{
i: id,
w: 12,
minW: 12,
minH: 1,
maxH: 1,
x: 0,
h: 1,
y: 0,
},
...dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
],
panelMap: { ...currentPanelMap, [id]: newRowWidgetMap },
widgets: [
...(selectedDashboard.data.widgets || []),
{
id,
title: 'Sample Row',
description: '',
panelTypes: PANEL_GROUP_TYPES.ROW,
},
],
},
uuid: selectedDashboard.uuid,
};
updateDashboardMutation.mutate(updatedDashboard, {
// eslint-disable-next-line sonarjs/no-identical-functions
onSuccess: (updatedDashboard) => {
if (updatedDashboard.payload) {
if (updatedDashboard.payload.data.layout)
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
setSelectedDashboard(updatedDashboard.payload);
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
}
featureResponse.refetch();
},
// eslint-disable-next-line sonarjs/no-identical-functions
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
});
}
const handleRowSettingsClick = (id: string): void => {
setIsSettingsModalOpen(true);
setCurrentSelectRowId(id);
};
const onSettingsModalSubmit = (): void => {
const newTitle = form.getFieldValue('title');
if (!selectedDashboard) return;
if (!currentSelectRowId) return;
const currentWidget = selectedDashboard?.data?.widgets?.find(
(e) => e.id === currentSelectRowId,
);
if (!currentWidget) return;
currentWidget.title = newTitle;
const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
(e) => e.id !== currentSelectRowId,
);
updatedWidgets?.push(currentWidget);
const updatedSelectedDashboard: Dashboard = {
...selectedDashboard,
data: {
...selectedDashboard.data,
widgets: updatedWidgets,
},
uuid: selectedDashboard.uuid,
};
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
onSuccess: (updatedDashboard) => {
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
if (setSelectedDashboard && updatedDashboard.payload) {
setSelectedDashboard(updatedDashboard.payload);
}
if (setPanelMap)
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
form.setFieldValue('title', '');
setIsSettingsModalOpen(false);
setCurrentSelectRowId(null);
featureResponse.refetch();
},
// eslint-disable-next-line sonarjs/no-identical-functions
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
});
};
// eslint-disable-next-line sonarjs/cognitive-complexity
const handleRowCollapse = (id: string): void => {
if (!selectedDashboard) return;
const rowProperties = { ...currentPanelMap[id] };
const updatedPanelMap = { ...currentPanelMap };
let updatedDashboardLayout = [...dashboardLayout];
if (rowProperties.collapsed === true) {
rowProperties.collapsed = false;
const widgetsInsideTheRow = rowProperties.widgets;
let maxY = 0;
widgetsInsideTheRow.forEach((w) => {
maxY = Math.max(maxY, w.y + w.h);
});
const currentRowWidget = dashboardLayout.find((w) => w.i === id);
if (currentRowWidget && widgetsInsideTheRow.length) {
maxY -= currentRowWidget.h + currentRowWidget.y;
}
const idxCurrentRow = dashboardLayout.findIndex((w) => w.i === id);
for (let j = idxCurrentRow + 1; j < dashboardLayout.length; j++) {
updatedDashboardLayout[j].y += maxY;
if (updatedPanelMap[updatedDashboardLayout[j].i]) {
updatedPanelMap[updatedDashboardLayout[j].i].widgets = updatedPanelMap[
updatedDashboardLayout[j].i
// eslint-disable-next-line @typescript-eslint/no-loop-func
].widgets.map((w) => ({
...w,
y: w.y + maxY,
}));
}
}
updatedDashboardLayout = [...updatedDashboardLayout, ...widgetsInsideTheRow];
} else {
rowProperties.collapsed = true;
const currentIdx = dashboardLayout.findIndex((w) => w.i === id);
let widgetsInsideTheRow: Layout[] = [];
let isPanelMapUpdated = false;
for (let j = currentIdx + 1; j < dashboardLayout.length; j++) {
if (currentPanelMap[dashboardLayout[j].i]) {
rowProperties.widgets = widgetsInsideTheRow;
widgetsInsideTheRow = [];
isPanelMapUpdated = true;
break;
} else {
widgetsInsideTheRow.push(dashboardLayout[j]);
}
}
if (!isPanelMapUpdated) {
rowProperties.widgets = widgetsInsideTheRow;
}
let maxY = 0;
widgetsInsideTheRow.forEach((w) => {
maxY = Math.max(maxY, w.y + w.h);
});
const currentRowWidget = dashboardLayout[currentIdx];
if (currentRowWidget && widgetsInsideTheRow.length) {
maxY -= currentRowWidget.h + currentRowWidget.y;
}
for (let j = currentIdx + 1; j < updatedDashboardLayout.length; j++) {
updatedDashboardLayout[j].y += maxY;
if (updatedPanelMap[updatedDashboardLayout[j].i]) {
updatedPanelMap[updatedDashboardLayout[j].i].widgets = updatedPanelMap[
updatedDashboardLayout[j].i
// eslint-disable-next-line @typescript-eslint/no-loop-func
].widgets.map((w) => ({
...w,
y: w.y + maxY,
}));
}
}
updatedDashboardLayout = updatedDashboardLayout.filter(
(widget) => !rowProperties.widgets.some((w: Layout) => w.i === widget.i),
);
}
setCurrentPanelMap((prev) => ({
...prev,
...updatedPanelMap,
[id]: {
...rowProperties,
},
}));
setDashboardLayout(sortLayout(updatedDashboardLayout));
};
const handleDragStop: ItemCallback = (_, oldItem, newItem): void => {
if (currentPanelMap[oldItem.i]) {
const differenceY = newItem.y - oldItem.y;
const widgetsInsideRow = currentPanelMap[oldItem.i].widgets.map((w) => ({
...w,
y: w.y + differenceY,
}));
setCurrentPanelMap((prev) => ({
...prev,
[oldItem.i]: {
...prev[oldItem.i],
widgets: widgetsInsideRow,
},
}));
}
};
const handleRowDelete = (): void => {
if (!selectedDashboard) return;
if (!currentSelectRowId) return;
const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
(e) => e.id !== currentSelectRowId,
);
const updatedLayout =
selectedDashboard.data.layout?.filter((e) => e.i !== currentSelectRowId) ||
[];
const updatedPanelMap = { ...currentPanelMap };
delete updatedPanelMap[currentSelectRowId];
const updatedSelectedDashboard: Dashboard = {
...selectedDashboard,
data: {
...selectedDashboard.data,
widgets: updatedWidgets,
layout: updatedLayout,
panelMap: updatedPanelMap,
},
uuid: selectedDashboard.uuid,
};
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
onSuccess: (updatedDashboard) => {
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
if (setSelectedDashboard && updatedDashboard.payload) {
setSelectedDashboard(updatedDashboard.payload);
}
if (setPanelMap)
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
setIsDeleteModalOpen(false);
setCurrentSelectRowId(null);
featureResponse.refetch();
},
// eslint-disable-next-line sonarjs/no-identical-functions
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
});
};
return (
<>
<Flex justify="flex-end" gap={8} align="center">
@@ -493,9 +178,15 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
screen: 'Dashboard Details',
}}
eventName="Dashboard: Facing Issues in dashboard"
buttonText="Need help with this dashboard?"
message={dashboardHelpMessage(data, selectedDashboard)}
onHoverText="Click here to get help for this dashboard"
buttonText="Facing Issues in dashboard"
message={`Hi Team,
I am facing issues configuring dashboard in SigNoz. Here are my dashboard details
Name: ${data?.title || ''}
Dashboard Id: ${selectedDashboard?.uuid || ''}
Thanks`}
/>
<ButtonContainer>
<Tooltip title="Open in Full Screen">
@@ -518,23 +209,13 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
{t('dashboard:add_panel')}
</Button>
)}
{!isDashboardLocked && addPanelPermission && (
<Button
className="periscope-btn"
onClick={(): void => handleAddRow()}
icon={<PlusOutlined />}
data-testid="add-row"
>
{t('dashboard:add_row')}
</Button>
)}
</ButtonContainer>
</Flex>
<FullScreen handle={handle} className="fullscreen-grid-container">
<ReactGridLayout
cols={12}
rowHeight={45}
rowHeight={100}
autoSize
width={100}
useCSSTransforms
@@ -543,7 +224,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
isResizable={!isDashboardLocked && addPanelPermission}
allowOverlap={false}
onLayoutChange={handleLayoutChange}
onDragStop={handleDragStop}
draggableHandle=".drag-handle"
layout={dashboardLayout}
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
@@ -552,58 +232,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
const { i: id } = layout;
const currentWidget = (widgets || [])?.find((e) => e.id === id);
if (currentWidget?.panelTypes === PANEL_GROUP_TYPES.ROW) {
const rowWidgetProperties = currentPanelMap[id] || {};
return (
<CardContainer
className="row-card"
isDarkMode={isDarkMode}
key={id}
data-grid={JSON.stringify(currentWidget)}
>
<div className={cx('row-panel')}>
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
<Button
disabled={updateDashboardMutation.isLoading}
icon={
rowWidgetProperties.collapsed ? (
<MoveDown size={14} />
) : (
<MoveUp size={14} />
)
}
type="text"
onClick={(): void => handleRowCollapse(id)}
/>
<Typography.Text>{currentWidget.title}</Typography.Text>
<Button
icon={<Settings size={14} />}
type="text"
onClick={(): void => handleRowSettingsClick(id)}
/>
</div>
{rowWidgetProperties.collapsed && (
<Button
type="text"
icon={<GripVertical size={14} />}
className="drag-handle"
/>
)}
{!rowWidgetProperties.collapsed && (
<Button
type="text"
icon={<Trash2 size={14} />}
onClick={(): void => {
setIsDeleteModalOpen(true);
setCurrentSelectRowId(id);
}}
/>
)}
</div>
</CardContainer>
);
}
return (
<CardContainer
className={isDashboardLocked ? '' : 'enable-resize'}
@@ -616,7 +244,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
$panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}
>
<GridCard
widget={(currentWidget as Widgets) || ({ id, query: {} } as Widgets)}
widget={currentWidget || ({ id, query: {} } as Widgets)}
headerMenuList={widgetActions}
variables={variables}
version={selectedDashboard?.data?.version}
@@ -627,46 +255,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
);
})}
</ReactGridLayout>
<Modal
open={isSettingsModalOpen}
title="Row Options"
destroyOnClose
footer={null}
onCancel={(): void => {
setIsSettingsModalOpen(false);
setCurrentSelectRowId(null);
}}
>
<Form form={form} onFinish={onSettingsModalSubmit} requiredMark>
<Form.Item required name={['title']}>
<Input
placeholder="Enter row name here..."
defaultValue={defaultTo(
widgets?.find((widget) => widget.id === currentSelectRowId)
?.title as string,
'Sample Title',
)}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Apply Changes
</Button>
</Form.Item>
</Form>
</Modal>
<Modal
open={isDeleteModalOpen}
title="Delete Row"
destroyOnClose
onCancel={(): void => {
setIsDeleteModalOpen(false);
setCurrentSelectRowId(null);
}}
onOk={(): void => handleRowDelete()}
>
<Typography.Text>Are you sure you want to delete this row</Typography.Text>
</Modal>
</FullScreen>
</>
);

View File

@@ -16,6 +16,6 @@ export const EMPTY_WIDGET_LAYOUT = {
i: PANEL_TYPES.EMPTY_WIDGET,
w: 6,
x: 0,
h: 6,
h: 3,
y: 0,
};

View File

@@ -29,17 +29,6 @@ interface Props {
export const CardContainer = styled.div<Props>`
overflow: auto;
&.row-card {
.row-panel {
height: 100%;
display: flex;
justify-content: space-between;
background: var(--bg-ink-400);
align-items: center;
overflow: hidden;
}
}
&.enable-resize {
:hover {
.react-resizable-handle {

View File

@@ -4,7 +4,6 @@ import { Input, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table/interface';
import saveAlertApi from 'api/alerts/save';
import DropDown from 'components/DropDown/DropDown';
import { listAlertMessage } from 'components/facingIssueBtn/util';
import {
DynamicColumnsKey,
TableDataSource,
@@ -338,8 +337,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
<TextToolTip
{...{
text: `More details on how to create alerts`,
url:
'https://signoz.io/docs/alerts/?utm_source=product&utm_medium=list-alerts',
url: 'https://signoz.io/docs/userguide/alerts-management/',
}}
/>
@@ -365,9 +363,12 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
screen: 'Alert list page',
},
eventName: 'Alert: Facing Issues in alert',
buttonText: 'Facing issues with alerts?',
message: listAlertMessage,
onHoverText: 'Click here to get help with alerts',
buttonText: 'Facing Issues in alert',
message: `Hi Team,
I am facing issues with alerts.
Thanks`,
}}
/>
</>

View File

@@ -3,7 +3,6 @@ import { Card, Col, Dropdown, Input, Row, TableColumnProps } from 'antd';
import { ItemType } from 'antd/es/menu/hooks/useItems';
import createDashboard from 'api/dashboard/create';
import { AxiosError } from 'axios';
import { dashboardListMessage } from 'components/facingIssueBtn/util';
import {
DynamicColumnsKey,
TableDataSource,
@@ -322,8 +321,7 @@ function DashboardsList(): JSX.Element {
<TextToolTip
{...{
text: `More details on how to create dashboards`,
url:
'https://signoz.io/docs/userguide/dashboards?utm_source=product&utm_medium=list-dashboard',
url: 'https://signoz.io/docs/userguide/dashboards',
}}
/>
</ButtonContainer>
@@ -392,9 +390,12 @@ function DashboardsList(): JSX.Element {
screen: 'Dashboard list page',
},
eventName: 'Dashboard: Facing Issues in dashboard',
buttonText: 'Facing issues with dashboards?',
message: dashboardListMessage,
onHoverText: 'Click here to get help with dashboards',
buttonText: 'Facing Issues in dashboard',
message: `Hi Team,
I am facing issues with dashboards.
Thanks`,
}}
/>
</TableContainer>

View File

@@ -85,8 +85,8 @@ function LogControls(): JSX.Element | null {
logs.map((log) => {
const timestamp =
typeof log.timestamp === 'string'
? dayjs(log.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(log.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
? dayjs(log.timestamp).format()
: dayjs(log.timestamp / 1e6).format();
return FlatLogData({
...log,

View File

@@ -531,8 +531,8 @@ function LogsExplorerViews({
logs.map((log) => {
const timestamp =
typeof log.timestamp === 'string'
? dayjs(log.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(log.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
? dayjs(log.timestamp).format()
: dayjs(log.timestamp / 1e6).format();
return FlatLogData({
timestamp,

View File

@@ -12,7 +12,6 @@
.prom-ql-icon {
height: 14px;
width: 14px;
color: var(--bg-vanilla-200);
}
}
.ant-btn-default {
@@ -55,10 +54,6 @@
.ant-tabs-tab-active {
.nav-btns {
background: var(--bg-vanilla-300) !important;
.prom-ql-icon {
color: var(--bg-ink-400);
}
}
}
}

View File

@@ -1,8 +1,6 @@
import './QuerySection.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, Tabs, Tooltip, Typography } from 'antd';
import PromQLIcon from 'assets/Dashboard/PromQl';
import TextToolTip from 'components/TextToolTip';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
@@ -13,7 +11,6 @@ import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery';
import { defaultTo } from 'lodash-es';
import { Atom, Play, Terminal } from 'lucide-react';
@@ -56,8 +53,6 @@ function QuerySection({
const { selectedDashboard, setSelectedDashboard } = useDashboard();
const isDarkMode = useIsDarkMode();
const { widgets } = selectedDashboard?.data || {};
const getWidget = useCallback(() => {
@@ -201,9 +196,7 @@ function QuerySection({
label: (
<Tooltip title="PromQL">
<Button className="nav-btns">
<PromQLIcon
fillColor={isDarkMode ? Color.BG_VANILLA_200 : Color.BG_INK_300}
/>
<img src="/Icons/promQL.svg" alt="Prom Ql" className="prom-ql-icon" />
</Button>
</Tooltip>
),
@@ -235,10 +228,7 @@ function QuerySection({
onChange={handleQueryCategoryChange}
tabBarExtraContent={
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<TextToolTip
text="This will temporarily save the current query and graph state. This will persist across tab change"
url="https://signoz.io/docs/userguide/query-builder?utm_source=product&utm_medium=query-builder"
/>
<TextToolTip text="This will temporarily save the current query and graph state. This will persist across tab change" />
<Button
loading={queryResponse.isFetching}
type="primary"

View File

@@ -67,13 +67,12 @@ function LeftContainer({
setRequestData((prev) => ({
...prev,
selectedTime: selectedTime.enum || prev.selectedTime,
globalSelectedInterval,
graphType: getGraphType(selectedGraph || selectedWidget.panelTypes),
query: stagedQuery,
}));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [stagedQuery, selectedTime, globalSelectedInterval]);
}, [stagedQuery, selectedTime]);
const queryResponse = useGetQueryRange(
requestData,

View File

@@ -1,4 +0,0 @@
.facing-issue-btn-container {
display: grid;
grid-template-columns: 1fr max-content;
}

View File

@@ -1,10 +1,7 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './NewWidget.styles.scss';
import { LockFilled, WarningOutlined } from '@ant-design/icons';
import { Button, Modal, Space, Tooltip, Typography } from 'antd';
import { Button, Flex, Modal, Space, Tooltip, Typography } from 'antd';
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { chartHelpMessage } from 'components/facingIssueBtn/util';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
@@ -107,7 +104,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
return defaultTo(
selectedWidget,
getDefaultWidgetData(widgetId || '', selectedGraph),
) as Widgets;
);
}, [query, selectedGraph, widgets]);
const [selectedWidget, setSelectedWidget] = useState(getWidget());
@@ -260,7 +257,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
i: widgetId || '',
w: 6,
x: 0,
h: 6,
h: 3,
y: 0,
},
...updatedLayout,
@@ -271,50 +268,28 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
uuid: selectedDashboard.uuid,
data: {
...selectedDashboard.data,
widgets: isNewDashboard
? [
...afterWidgets,
{
...(selectedWidget || ({} as Widgets)),
description: selectedWidget?.description || '',
timePreferance: selectedTime.enum,
isStacked: selectedWidget?.isStacked || false,
opacity: selectedWidget?.opacity || '1',
nullZeroValues: selectedWidget?.nullZeroValues || 'zero',
title: selectedWidget?.title,
yAxisUnit: selectedWidget?.yAxisUnit,
panelTypes: graphType,
query: currentQuery,
thresholds: selectedWidget?.thresholds,
softMin: selectedWidget?.softMin || 0,
softMax: selectedWidget?.softMax || 0,
fillSpans: selectedWidget?.fillSpans,
selectedLogFields: selectedWidget?.selectedLogFields || [],
selectedTracesFields: selectedWidget?.selectedTracesFields || [],
},
]
: [
...preWidgets,
{
...(selectedWidget || ({} as Widgets)),
description: selectedWidget?.description || '',
timePreferance: selectedTime.enum,
isStacked: selectedWidget?.isStacked || false,
opacity: selectedWidget?.opacity || '1',
nullZeroValues: selectedWidget?.nullZeroValues || 'zero',
title: selectedWidget?.title,
yAxisUnit: selectedWidget?.yAxisUnit,
panelTypes: graphType,
query: currentQuery,
thresholds: selectedWidget?.thresholds,
softMin: selectedWidget?.softMin || 0,
softMax: selectedWidget?.softMax || 0,
fillSpans: selectedWidget?.fillSpans,
selectedLogFields: selectedWidget?.selectedLogFields || [],
selectedTracesFields: selectedWidget?.selectedTracesFields || [],
},
...afterWidgets,
],
widgets: [
...preWidgets,
{
...(selectedWidget || ({} as Widgets)),
description: selectedWidget?.description || '',
timePreferance: selectedTime.enum,
isStacked: selectedWidget?.isStacked || false,
opacity: selectedWidget?.opacity || '1',
nullZeroValues: selectedWidget?.nullZeroValues || 'zero',
title: selectedWidget?.title,
yAxisUnit: selectedWidget?.yAxisUnit,
panelTypes: graphType,
query: currentQuery,
thresholds: selectedWidget?.thresholds,
softMin: selectedWidget?.softMin || 0,
softMax: selectedWidget?.softMax || 0,
fillSpans: selectedWidget?.fillSpans,
selectedLogFields: selectedWidget?.selectedLogFields || [],
selectedTracesFields: selectedWidget?.selectedTracesFields || [],
},
...afterWidgets,
],
layout: [...updatedLayout],
},
};
@@ -427,7 +402,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
return (
<Container>
<div className="facing-issue-btn-container">
<Flex justify="space-between" align="center">
<FacingIssueBtn
attributes={{
uuid: selectedDashboard?.uuid,
@@ -435,12 +410,18 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
panelType: graphType,
widgetId: query.get('widgetId'),
queryType: currentQuery.queryType,
screen: 'Dashboard list page',
}}
eventName="Dashboard: Facing Issues in dashboard"
buttonText="Need help with this chart?"
message={chartHelpMessage(selectedDashboard, graphType)}
onHoverText="Click here to get help in creating chart"
buttonText="Facing Issues in dashboard"
message={`Hi Team,
I am facing issues configuring dashboard in SigNoz. Here are my dashboard details
Name: ${selectedDashboard?.data.title || ''}
Panel type: ${graphType}
Dashboard Id: ${selectedDashboard?.uuid || ''}
Thanks`}
/>
<ButtonContainer>
{isSaveDisabled && (
@@ -469,7 +450,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
)}
<Button onClick={onClickDiscardHandler}>Discard Changes</Button>
</ButtonContainer>
</div>
</Flex>
<PanelContainer>
<LeftContainerWrapper flex={5}>

View File

@@ -50,9 +50,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'having',
'orderBy',
'functions',
'queryName',
'expression',
'disabled',
],
},
},
@@ -68,9 +65,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'orderBy',
'functions',
'spaceAggregation',
'queryName',
'expression',
'disabled',
],
},
},
@@ -84,9 +78,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'limit',
'having',
'orderBy',
'queryName',
'expression',
'disabled',
],
},
},
@@ -103,9 +94,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'having',
'orderBy',
'functions',
'queryName',
'expression',
'disabled',
],
},
},
@@ -121,9 +109,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'orderBy',
'functions',
'spaceAggregation',
'queryName',
'expression',
'disabled',
],
},
},
@@ -137,9 +122,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'limit',
'having',
'orderBy',
'queryName',
'expression',
'disabled',
],
},
},
@@ -156,9 +138,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'having',
'orderBy',
'functions',
'queryName',
'expression',
'disabled',
],
},
},
@@ -174,9 +153,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'orderBy',
'functions',
'spaceAggregation',
'queryName',
'expression',
'disabled',
],
},
},
@@ -190,9 +166,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'limit',
'having',
'orderBy',
'queryName',
'expression',
'disabled',
],
},
},
@@ -209,9 +182,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'having',
'orderBy',
'functions',
'queryName',
'expression',
'disabled',
],
},
},
@@ -227,9 +197,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'orderBy',
'functions',
'spaceAggregation',
'queryName',
'expression',
'disabled',
],
},
},
@@ -243,9 +210,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'limit',
'having',
'orderBy',
'queryName',
'expression',
'disabled',
],
},
},
@@ -277,9 +241,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'reduceTo',
'having',
'functions',
'queryName',
'expression',
'disabled',
],
},
},
@@ -293,9 +254,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'reduceTo',
'functions',
'spaceAggregation',
'queryName',
'expression',
'disabled',
],
},
},
@@ -309,9 +267,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
'limit',
'having',
'orderBy',
'queryName',
'expression',
'disabled',
],
},
},

View File

@@ -1,87 +0,0 @@
import { render } from '@testing-library/react';
import { I18nextProvider } from 'react-i18next';
import { QueryClient, QueryClientProvider } from 'react-query';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import i18n from 'ReactI18';
import store from 'store';
import ChangeHistory from '../index';
import { pipelineData, pipelineDataHistory } from './testUtils';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});
describe('ChangeHistory test', () => {
it('should render changeHistory correctly', () => {
const { getAllByText, getByText } = render(
<MemoryRouter>
<QueryClientProvider client={queryClient}>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<ChangeHistory pipelineData={pipelineData} />
</I18nextProvider>
</Provider>
</QueryClientProvider>
</MemoryRouter>,
);
// change History table headers
[
'Version',
'Deployment Stage',
'Last Deploy Message',
'Last Deployed Time',
'Edited by',
].forEach((text) => expect(getByText(text)).toBeInTheDocument());
// table content
expect(getAllByText('test-user').length).toBe(2);
expect(getAllByText('Deployment was successful').length).toBe(2);
});
it('test deployment stage and icon based on history data', () => {
const { getByText, container } = render(
<MemoryRouter>
<QueryClientProvider client={queryClient}>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<ChangeHistory
pipelineData={{
...pipelineData,
history: pipelineDataHistory,
}}
/>
</I18nextProvider>
</Provider>
</QueryClientProvider>
</MemoryRouter>,
);
// assertion for different deployment stages
expect(container.querySelector('[data-icon="loading"]')).toBeInTheDocument();
expect(getByText('In Progress')).toBeInTheDocument();
expect(
container.querySelector('[data-icon="exclamation-circle"]'),
).toBeInTheDocument();
expect(getByText('Dirty')).toBeInTheDocument();
expect(
container.querySelector('[data-icon="close-circle"]'),
).toBeInTheDocument();
expect(getByText('Failed')).toBeInTheDocument();
expect(
container.querySelector('[data-icon="minus-circle"]'),
).toBeInTheDocument();
expect(getByText('Unknown')).toBeInTheDocument();
expect(container.querySelectorAll('.ant-table-row').length).toBe(5);
});
});

View File

@@ -1,240 +0,0 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { Pipeline } from 'types/api/pipeline/def';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
export const pipelineData: Pipeline = {
id: 'test-id-1',
version: 24,
elementType: 'log_pipelines',
active: false,
is_valid: false,
disabled: false,
deployStatus: 'DEPLOYED',
deployResult: 'Deployment was successful',
lastHash: 'log_pipelines:24',
lastConf: 'oiwernveroi',
createdBy: 'test-created-by',
pipelines: [
{
id: 'test-id-2',
orderId: 1,
name: 'hotrod logs parser',
alias: 'hotrodlogsparser',
description: 'Trying to test Logs Pipeline feature',
enabled: true,
filter: {
op: 'AND',
items: [
{
key: {
key: 'container_name',
dataType: DataTypes.String,
type: 'tag',
isColumn: false,
isJSON: false,
},
id: 'sampleid',
value: 'hotrod',
op: '=',
},
],
},
config: [
{
type: 'regex_parser',
id: 'parsetext(regex)',
output: 'parseattribsjson',
on_error: 'send',
orderId: 1,
enabled: true,
name: 'parse text (regex)',
parse_to: 'attributes',
regex:
'.+\\t+(?P<log_level>.+)\\t+(?P<location>.+)\\t+(?P<message>.+)\\t+(?P<attribs_json>.+)',
parse_from: 'body',
},
{
type: 'json_parser',
id: 'parseattribsjson',
output: 'removetempattribs_json',
orderId: 2,
enabled: true,
name: 'parse attribs json',
parse_to: 'attributes',
parse_from: 'attributes.attribs_json',
},
{
type: 'remove',
id: 'removetempattribs_json',
output: 'c2062723-895e-4614-ba38-29c5d5ee5927',
orderId: 3,
enabled: true,
name: 'remove temp attribs_json',
field: 'attributes.attribs_json',
},
{
type: 'add',
id: 'c2062723-895e-4614-ba38-29c5d5ee5927',
orderId: 4,
enabled: true,
name: 'test add ',
field: 'resource["container.name"]',
value: 'hotrod',
},
],
createdBy: 'test@email',
createdAt: '2024-01-02T13:56:02.858300964Z',
},
{
id: 'tes-id-1',
orderId: 2,
name: 'Logs Parser - test - Customer Service',
alias: 'LogsParser-test-CustomerService',
description: 'Trying to test Logs Pipeline feature',
enabled: true,
filter: {
op: 'AND',
items: [
{
key: {
key: 'service',
dataType: DataTypes.String,
type: 'tag',
isColumn: false,
isJSON: false,
},
id: 'sample-test-1',
value: 'customer',
op: '=',
},
],
},
config: [
{
type: 'grok_parser',
id: 'Testtest',
on_error: 'send',
orderId: 1,
enabled: true,
name: 'Test test',
parse_to: 'attributes',
pattern:
'^%{DATE:date}Z INFO customer/database.go:73 Loading customer {"service": "customer", "component": "mysql", "trace_id": "test-id", "span_id": "1427a3fcad8b1514", "customer_id": "567"}',
parse_from: 'body',
},
],
createdBy: 'test@email',
createdAt: '2024-01-02T13:56:02.863764227Z',
},
],
history: [
{
id: 'test-id-4',
version: 24,
elementType: 'log_pipelines',
active: false,
isValid: false,
disabled: false,
deployStatus: 'DEPLOYED',
deployResult: 'Deployment was successful',
lastHash: 'log_pipelines:24',
lastConf: 'eovineroiv',
createdBy: 'test-created-by',
createdByName: 'test-user',
createdAt: '2024-01-02T13:56:02Z',
},
{
id: 'test-4',
version: 23,
elementType: 'log_pipelines',
active: false,
isValid: false,
disabled: false,
deployStatus: 'DEPLOYED',
deployResult: 'Deployment was successful',
lastHash: 'log_pipelines:23',
lastConf: 'eivrounreovi',
createdBy: 'test-created-by',
createdByName: 'test-user',
createdAt: '2023-12-29T12:59:20Z',
},
],
};
export const pipelineDataHistory: Pipeline['history'] = [
{
id: 'test-id-4',
version: 24,
elementType: 'log_pipelines',
active: false,
isValid: false,
disabled: false,
deployStatus: 'DEPLOYED',
deployResult: 'Deployment was successful',
lastHash: 'log_pipelines:24',
lastConf: 'eovineroiv',
createdBy: 'test-created-by',
createdByName: 'test-user',
createdAt: '2024-01-02T13:56:02Z',
},
{
id: 'test-4',
version: 23,
elementType: 'log_pipelines',
active: false,
isValid: false,
disabled: false,
deployStatus: 'IN_PROGRESS',
deployResult: 'Deployment is in progress',
lastHash: 'log_pipelines:23',
lastConf: 'eivrounreovi',
createdBy: 'test-created-by',
createdByName: 'test-user',
createdAt: '2023-12-29T12:59:20Z',
},
{
id: 'test-4-1',
version: 25,
elementType: 'log_pipelines',
active: false,
isValid: false,
disabled: false,
deployStatus: 'DIRTY',
deployResult: 'Deployment is dirty',
lastHash: 'log_pipelines:23',
lastConf: 'eivrounreovi',
createdBy: 'test-created-by',
createdByName: 'test-user',
createdAt: '2023-12-29T12:59:20Z',
},
{
id: 'test-4-2',
version: 26,
elementType: 'log_pipelines',
active: false,
isValid: false,
disabled: false,
deployStatus: 'FAILED',
deployResult: 'Deployment failed',
lastHash: 'log_pipelines:23',
lastConf: 'eivrounreovi',
createdBy: 'test-created-by',
createdByName: 'test-user',
createdAt: '2023-12-29T12:59:20Z',
},
{
id: 'test-4-3',
version: 27,
elementType: 'log_pipelines',
active: false,
isValid: false,
disabled: false,
deployStatus: 'UNKNOWN',
deployResult: '',
lastHash: 'log_pipelines:23',
lastConf: 'eivrounreovi',
createdBy: 'test-created-by',
createdByName: 'test-user',
createdAt: '2023-12-29T12:59:20Z',
},
];

View File

@@ -43,7 +43,7 @@ function CreatePipelineButton({
<ButtonContainer>
<TextToolTip
text={t('learn_more')}
url="https://signoz.io/docs/logs-pipelines/introduction/?utm_source=product&utm_medium=pipelines-tab"
url="https://signoz.io/docs/logs-pipelines/introduction/"
/>
{isAddNewPipelineVisible && (
<CustomButton

View File

@@ -66,7 +66,7 @@ function PipelinesListEmptyState(): JSX.Element {
<Typography>
{t('learn_more')}&nbsp;
<a
href="https://signoz.io/docs/logs-pipelines/introduction/?utm_source=product&utm_medium=pipelines-tab"
href="https://signoz.io/docs/logs-pipelines/introduction/"
target="_blank"
rel="noreferrer"
>

View File

@@ -1,5 +1,4 @@
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
@@ -9,17 +8,8 @@ import store from 'store';
import CreatePipelineButton from '../Layouts/Pipeline/CreatePipelineButton';
import { pipelineApiResponseMockData } from '../mocks/pipeline';
const trackEventVar = jest.fn();
jest.mock('hooks/analytics/useAnalytics', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
trackEvent: trackEventVar,
trackPageView: jest.fn(),
})),
}));
describe('PipelinePage container test', () => {
it('should render CreatePipelineButton section', async () => {
it('should render CreatePipelineButton section', () => {
const { asFragment } = render(
<MemoryRouter>
<Provider store={store}>
@@ -36,58 +26,4 @@ describe('PipelinePage container test', () => {
);
expect(asFragment()).toMatchSnapshot();
});
it('CreatePipelineButton - edit mode & tracking', async () => {
const { getByText } = render(
<MemoryRouter>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<CreatePipelineButton
setActionType={jest.fn()}
isActionMode="viewing-mode"
setActionMode={jest.fn()}
pipelineData={pipelineApiResponseMockData}
/>
</I18nextProvider>
</Provider>
</MemoryRouter>,
);
// enter_edit_mode click and track event data
const editButton = getByText('enter_edit_mode');
expect(editButton).toBeInTheDocument();
await userEvent.click(editButton);
expect(trackEventVar).toBeCalledWith('Logs: Pipelines: Entered Edit Mode', {
source: 'signoz-ui',
});
});
it('CreatePipelineButton - add new mode & tracking', async () => {
const { getByText } = render(
<MemoryRouter>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<CreatePipelineButton
setActionType={jest.fn()}
isActionMode="viewing-mode"
setActionMode={jest.fn()}
pipelineData={{ ...pipelineApiResponseMockData, pipelines: [] }}
/>
</I18nextProvider>
</Provider>
</MemoryRouter>,
);
// new_pipeline click and track event data
const editButton = getByText('new_pipeline');
expect(editButton).toBeInTheDocument();
await userEvent.click(editButton);
expect(trackEventVar).toBeCalledWith(
'Logs: Pipelines: Clicked Add New Pipeline',
{
source: 'signoz-ui',
},
);
});
});

View File

@@ -1,5 +1,4 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from '@testing-library/react';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
@@ -21,43 +20,4 @@ describe('PipelinePage container test', () => {
);
expect(asFragment()).toMatchSnapshot();
});
it('should handle search', async () => {
const setPipelineValue = jest.fn();
const { getByPlaceholderText, container } = render(
<MemoryRouter>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<PipelinesSearchSection setPipelineSearchValue={setPipelineValue} />
</I18nextProvider>
</Provider>
</MemoryRouter>,
);
const searchInput = getByPlaceholderText('search_pipeline_placeholder');
// Type into the search input
userEvent.type(searchInput, 'sample_pipeline');
jest.advanceTimersByTime(299);
expect(setPipelineValue).not.toHaveBeenCalled();
// Wait for the debounce delay to pass
await waitFor(() => {
// Expect the callback to be called after debounce delay
expect(setPipelineValue).toHaveBeenCalledWith('sample_pipeline');
});
// clear button
fireEvent.click(
container.querySelector(
'span[class*="ant-input-clear-icon"]',
) as HTMLElement,
);
// Wait for the debounce delay to pass
await waitFor(() => {
expect(setPipelineValue).toHaveBeenCalledWith('');
});
});
});

View File

@@ -119,7 +119,7 @@ exports[`PipelinePage container test should render PipelinePageLayout section 1`
>
learn_more 
<a
href="https://signoz.io/docs/logs-pipelines/introduction/?utm_source=product&utm_medium=pipelines-tab"
href="https://signoz.io/docs/logs-pipelines/introduction/"
rel="noreferrer"
target="_blank"
>

View File

@@ -1,6 +1,6 @@
import './QueryBuilder.styles.scss';
import { Button, Col, Divider, Row, Tooltip, Typography } from 'antd';
import { Button, Col, Divider, Row, Tooltip } from 'antd';
import {
MAX_FORMULAS,
MAX_QUERIES,
@@ -133,43 +133,13 @@ export const QueryBuilder = memo(function QueryBuilder({
{!isListViewPanel && (
<div className="new-query-formula-buttons-container">
<Button.Group>
<Tooltip
title={
<div style={{ textAlign: 'center' }}>
Add New Query
<Typography.Link
href="https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=query-builder#multiple-queries-and-functions"
target="_blank"
style={{ textDecoration: 'underline' }}
>
{' '}
<br />
Learn more
</Typography.Link>
</div>
}
>
<Tooltip title="Add Query">
<Button disabled={isDisabledQueryButton} onClick={addNewBuilderQuery}>
<DatabaseZap size={12} />
</Button>
</Tooltip>
<Tooltip
title={
<div style={{ textAlign: 'center' }}>
Add New Formula
<Typography.Link
href="https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=query-builder#multiple-queries-and-functions"
target="_blank"
style={{ textDecoration: 'underline' }}
>
{' '}
<br />
Learn more
</Typography.Link>
</div>
}
>
<Tooltip title="Add Formula">
<Button disabled={isDisabledFormulaButton} onClick={addNewFormula}>
<Sigma size={12} />
</Button>

View File

@@ -66,25 +66,21 @@ export default function QBEntityOptions({
<div className="left-col-items">
<div className="options periscope-btn-group">
<Button.Group>
<Tooltip title={isCollapsed ? 'Uncollapse' : 'Collapse'}>
<Button
value="search"
className="periscope-btn collapse"
onClick={onCollapseEntity}
>
{isCollapsed ? <ChevronRight size={16} /> : <ChevronDown size={16} />}
</Button>
</Tooltip>
<Tooltip title={entityData.disabled ? 'Show' : 'Hide'}>
<Button
value="query-builder"
className="periscope-btn visibility-toggle"
onClick={onToggleVisibility}
disabled={isListViewPanel}
>
{entityData.disabled ? <EyeOff size={16} /> : <Eye size={16} />}
</Button>
</Tooltip>
<Button
value="search"
className="periscope-btn collapse"
onClick={onCollapseEntity}
>
{isCollapsed ? <ChevronRight size={16} /> : <ChevronDown size={16} />}
</Button>
<Button
value="query-builder"
className="periscope-btn visibility-toggle"
onClick={onToggleVisibility}
disabled={isListViewPanel}
>
{entityData.disabled ? <EyeOff size={16} /> : <Eye size={16} />}
</Button>
{entityType === 'query' && (
<Tooltip title={`Clone Query ${entityData.queryName}`}>

View File

@@ -1,7 +1,7 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './Query.styles.scss';
import { Col, Input, Row, Tooltip, Typography } from 'antd';
import { Col, Input, Row } from 'antd';
import { ENTITY_VERSION_V4 } from 'constants/app';
// ** Constants
import { ATTRIBUTE_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
@@ -367,29 +367,11 @@ export const Query = memo(function Query({
<Row gutter={[11, 5]}>
{version && version === 'v3' && (
<Col flex="5.93rem">
<Tooltip
title={
<div style={{ textAlign: 'center' }}>
Select Aggregate Operator
<Typography.Link
className="learn-more"
href="https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=query-builder#aggregation"
target="_blank"
style={{ textDecoration: 'underline' }}
>
{' '}
<br />
Learn more
</Typography.Link>
</div>
}
>
<OperatorsSelect
value={query.aggregateOperator}
onChange={handleChangeOperator}
operators={operators}
/>
</Tooltip>
<OperatorsSelect
value={query.aggregateOperator}
onChange={handleChangeOperator}
operators={operators}
/>
</Col>
)}
@@ -406,30 +388,12 @@ export const Query = memo(function Query({
Array.isArray(operators) &&
operators.length > 0 && (
<Col flex="5.93rem">
<Tooltip
title={
<div style={{ textAlign: 'center' }}>
Select Aggregate Operator
<Typography.Link
className="learn-more"
href="https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=query-builder#aggregation"
target="_blank"
style={{ textDecoration: 'underline' }}
>
{' '}
<br />
Learn more
</Typography.Link>
</div>
}
>
<OperatorsSelect
value={query.aggregateOperator}
onChange={handleChangeOperator}
operators={operators}
disabled={disableOperatorSelector}
/>
</Tooltip>
<OperatorsSelect
value={query.aggregateOperator}
onChange={handleChangeOperator}
operators={operators}
disabled={disableOperatorSelector}
/>
</Col>
)}
</Row>
@@ -458,28 +422,11 @@ export const Query = memo(function Query({
<Col span={11}>
<Row gutter={[11, 5]}>
<Col flex="5.93rem">
<Tooltip
title={
<div style={{ textAlign: 'center' }}>
Select Aggregate Operator
<Typography.Link
href="https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=query-builder#aggregation"
target="_blank"
style={{ textDecoration: 'underline' }}
>
{' '}
<br />
Learn more
</Typography.Link>
</div>
}
>
<OperatorsSelect
value={query.aggregateOperator}
onChange={handleChangeOperator}
operators={operators}
/>
</Tooltip>
<OperatorsSelect
value={query.aggregateOperator}
onChange={handleChangeOperator}
operators={operators}
/>
</Col>
<Col flex="1 1 12.5rem">
<AggregatorFilter
@@ -573,30 +520,12 @@ export const Query = memo(function Query({
)}
{panelType !== PANEL_TYPES.LIST && panelType !== PANEL_TYPES.TRACE && (
<Row style={{ width: '100%' }}>
<Tooltip
placement="right"
title={
<div style={{ textAlign: 'center' }}>
Name of legend
<Typography.Link
style={{ textDecoration: 'underline' }}
href="https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=query-builder#legend-format"
target="_blank"
>
{' '}
<br />
Learn more
</Typography.Link>
</div>
}
>
<Input
onChange={handleChangeQueryLegend}
size="middle"
value={query.legend}
addonBefore="Legend Format"
/>
</Tooltip>
<Input
onChange={handleChangeQueryLegend}
size="middle"
value={query.legend}
addonBefore="Legend Format"
/>
</Row>
)}
</Row>

View File

@@ -1,6 +1,6 @@
import './QueryFunctions.styles.scss';
import { Button, Tooltip, Typography } from 'antd';
import { Button, Tooltip } from 'antd';
import cx from 'classnames';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { cloneDeep, pullAt } from 'lodash-es';
@@ -180,22 +180,9 @@ export default function QueryFunctions({
<Tooltip
title={
functions && functions.length >= 3 ? (
'Functions are in early access. You can add a maximum of 3 function as of now.'
) : (
<div style={{ textAlign: 'center' }}>
Add new function
<Typography.Link
style={{ textDecoration: 'underline' }}
href="https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=query-builder#functions-for-extended-data-analysis"
target="_blank"
>
{' '}
<br />
Learn more
</Typography.Link>
</div>
)
functions && functions.length >= 3
? 'Functions are in early access. You can add a maximum of 3 function as of now.'
: ''
}
placement="right"
>

View File

@@ -278,7 +278,7 @@ function SideNav({
}, [isCloudUserVal, isEnterprise, isFetching]);
useEffect(() => {
if (!(isCloudUserVal || isEECloudUser())) {
if (!isCloudUserVal) {
let updatedMenuItems = [...menuItems];
updatedMenuItems = updatedMenuItems.filter(
(item) => item.key !== ROUTES.INTEGRATIONS,

View File

@@ -13,18 +13,11 @@ function Events({
return <Typography>No events data in selected span</Typography>;
}
const sortedTraceEvents = events.sort((a, b) => {
// Handle undefined names by treating them as empty strings
const nameA = a.name || '';
const nameB = b.name || '';
return nameA.localeCompare(nameB);
});
return (
<ErrorTag
onToggleHandler={onToggleHandler}
setText={setText}
event={sortedTraceEvents}
event={events}
firstSpanStartTime={firstSpanStartTime}
/>
);

View File

@@ -41,9 +41,8 @@ function Tags({
setSearchText(value);
};
const filteredTags = tags
.filter((tag) => tag.key.includes(searchText))
.sort((a, b) => a.key.localeCompare(b.key));
const filteredTags = tags.filter((tag) => tag.key.includes(searchText));
if (tags.length === 0) {
return <Typography>No tags in selected span</Typography>;
}

View File

@@ -16,7 +16,7 @@ export const addEmptyWidgetInDashboardJSONWithQuery = (
i: widgetId,
w: 6,
x: 0,
h: 6,
h: 3,
y: 0,
},
...(dashboard?.data?.layout || []),

View File

@@ -36,7 +36,7 @@ export const getPaginationQueryData: SetupPaginationQueryData = ({
const updatedFilters: TagFilter = {
...filters,
items: filters?.items?.filter((item) => item.key?.key !== 'id'),
items: filters.items.filter((item) => item.key?.key !== 'id'),
};
const tagFilters: TagFilter = {

View File

@@ -100,7 +100,7 @@ export const getUPlotChartOptions = ({
y: {
...getYAxisScale({
thresholds,
series: apiResponse?.data?.newResult?.data?.result || [],
series: apiResponse?.data.newResult.data.result,
yAxisUnit,
softMax,
softMin,

View File

@@ -9,7 +9,6 @@ import history from 'lib/history';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useEffect, useState } from 'react';
import { generatePath, useLocation, useParams } from 'react-router-dom';
import { Widgets } from 'types/api/dashboard/getAll';
function DashboardWidget(): JSX.Element | null {
const { search } = useLocation();
@@ -25,7 +24,7 @@ function DashboardWidget(): JSX.Element | null {
const { data } = selectedDashboard || {};
const { widgets } = data || {};
const selectedWidget = widgets?.find((e) => e.id === widgetId) as Widgets;
const selectedWidget = widgets?.find((e) => e.id === widgetId);
useEffect(() => {
const params = new URLSearchParams(search);

View File

@@ -25,9 +25,6 @@
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.learn-more {
font-size: 14px;
}
.ant-input-affix-wrapper {
margin-top: 16px;

View File

@@ -282,14 +282,7 @@ function SaveView(): JSX.Element {
<div className="save-view-content">
<Typography.Title className="title">Views</Typography.Title>
<Typography.Text className="subtitle">
Manage your saved views for {ROUTES_VS_SOURCEPAGE[pathname]}.{' '}
<Typography.Link
className="learn-more"
href="https://signoz.io/docs/product-features/saved-view/?utm_source=product&utm_medium=views-tab"
target="_blank"
>
Learn more
</Typography.Link>
Manage your saved views for logs.
</Typography.Text>
<Input
placeholder="Search for views..."

View File

@@ -10,7 +10,6 @@ import { useDashboardVariablesFromLocalStorage } from 'hooks/dashboard/useDashbo
import useAxiosError from 'hooks/useAxiosError';
import useTabVisibility from 'hooks/useTabFocus';
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
import { defaultTo } from 'lodash-es';
import isEqual from 'lodash-es/isEqual';
import isUndefined from 'lodash-es/isUndefined';
import omitBy from 'lodash-es/omitBy';
@@ -38,7 +37,6 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 as generateUUID } from 'uuid';
import { IDashboardContext } from './types';
import { sortLayout } from './util';
const DashboardContext = createContext<IDashboardContext>({
isDashboardSliderOpen: false,
@@ -49,8 +47,6 @@ const DashboardContext = createContext<IDashboardContext>({
selectedDashboard: {} as Dashboard,
dashboardId: '',
layouts: [],
panelMap: {},
setPanelMap: () => {},
setLayouts: () => {},
setSelectedDashboard: () => {},
updatedTimeRef: {} as React.MutableRefObject<Dayjs | null>,
@@ -98,10 +94,6 @@ export function DashboardProvider({
const [layouts, setLayouts] = useState<Layout[]>([]);
const [panelMap, setPanelMap] = useState<
Record<string, { widgets: Layout[]; collapsed: boolean }>
>({});
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
const dashboardId =
@@ -207,9 +199,7 @@ export function DashboardProvider({
dashboardRef.current = updatedDashboardData;
setLayouts(sortLayout(getUpdatedLayout(updatedDashboardData.data.layout)));
setPanelMap(defaultTo(updatedDashboardData?.data?.panelMap, {}));
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
}
if (
@@ -245,11 +235,7 @@ export function DashboardProvider({
updatedTimeRef.current = dayjs(updatedDashboardData.updated_at);
setLayouts(
sortLayout(getUpdatedLayout(updatedDashboardData.data.layout)),
);
setPanelMap(defaultTo(updatedDashboardData.data.panelMap, {}));
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
},
});
@@ -270,11 +256,7 @@ export function DashboardProvider({
updatedDashboardData.data.layout,
)
) {
setLayouts(
sortLayout(getUpdatedLayout(updatedDashboardData.data.layout)),
);
setPanelMap(defaultTo(updatedDashboardData.data.panelMap, {}));
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
}
}
},
@@ -341,9 +323,7 @@ export function DashboardProvider({
selectedDashboard,
dashboardId,
layouts,
panelMap,
setLayouts,
setPanelMap,
setSelectedDashboard,
updatedTimeRef,
setToScrollWidgetId,
@@ -359,7 +339,6 @@ export function DashboardProvider({
selectedDashboard,
dashboardId,
layouts,
panelMap,
toScrollWidgetId,
updateLocalStorageDashboardVariables,
currentDashboard,

View File

@@ -12,8 +12,6 @@ export interface IDashboardContext {
selectedDashboard: Dashboard | undefined;
dashboardId: string;
layouts: Layout[];
panelMap: Record<string, { widgets: Layout[]; collapsed: boolean }>;
setPanelMap: React.Dispatch<React.SetStateAction<Record<string, any>>>;
setLayouts: React.Dispatch<React.SetStateAction<Layout[]>>;
setSelectedDashboard: React.Dispatch<
React.SetStateAction<Dashboard | undefined>

View File

@@ -1,34 +1,22 @@
import { Layout } from 'react-grid-layout';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
export const getPreviousWidgets = (
selectedDashboard: Dashboard,
selectedWidgetIndex: number,
): Widgets[] =>
(selectedDashboard.data.widgets?.slice(
0,
selectedWidgetIndex || 0,
) as Widgets[]) || [];
selectedDashboard.data.widgets?.slice(0, selectedWidgetIndex || 0) || [];
export const getNextWidgets = (
selectedDashboard: Dashboard,
selectedWidgetIndex: number,
): Widgets[] =>
(selectedDashboard.data.widgets?.slice(
selectedDashboard.data.widgets?.slice(
(selectedWidgetIndex || 0) + 1, // this is never undefined
selectedDashboard.data.widgets?.length,
) as Widgets[]) || [];
) || [];
export const getSelectedWidgetIndex = (
selectedDashboard: Dashboard,
widgetId: string | null,
): number =>
selectedDashboard.data.widgets?.findIndex((e) => e.id === widgetId) || 0;
export const sortLayout = (layout: Layout[]): Layout[] =>
[...layout].sort((a, b) => {
if (a.y === b.y) {
return a.x - b.x;
}
return a.y - b.y;
});

View File

@@ -229,9 +229,6 @@ export function QueryBuilderProvider({
setCurrentQuery(
timeUpdated ? merge(currentQuery, newQueryState) : newQueryState,
);
setSupersetQuery(
timeUpdated ? merge(currentQuery, newQueryState) : newQueryState,
);
setQueryType(type);
},
[prepareQueryBuilderData, currentQuery],
@@ -805,7 +802,6 @@ export function QueryBuilderProvider({
if (newCurrentQuery) {
setCurrentQuery(newCurrentQuery);
setSupersetQuery(newCurrentQuery);
}
};

View File

@@ -242,7 +242,3 @@ body {
}
}
}
.ant-notification-notice-message {
padding-right: 20px;
}

View File

@@ -1,4 +1,4 @@
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
import { ReactNode } from 'react';
@@ -59,21 +59,13 @@ export interface DashboardData {
description?: string;
tags?: string[];
name?: string;
widgets?: Array<WidgetRow | Widgets>;
widgets?: Widgets[];
title: string;
layout?: Layout[];
panelMap?: Record<string, { widgets: Layout[]; collapsed: boolean }>;
variables: Record<string, IDashboardVariable>;
version?: string;
}
export interface WidgetRow {
id: string;
panelTypes: PANEL_GROUP_TYPES;
title: ReactNode;
description: string;
}
export interface IBaseWidget {
isStacked: boolean;
id: string;

View File

@@ -31,6 +31,3 @@ export const checkVersionState = (
const versionCore = currentVersion?.split('-')[0];
return versionCore === latestVersion;
};
// list of forbidden tags to remove in dompurify
export const FORBID_DOM_PURIFY_TAGS = ['img', 'form'];

File diff suppressed because it is too large Load Diff

2
go.mod
View File

@@ -6,7 +6,7 @@ require (
github.com/ClickHouse/clickhouse-go/v2 v2.20.0
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
github.com/SigNoz/signoz-otel-collector v0.88.24
github.com/SigNoz/signoz-otel-collector v0.88.21
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974
github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974
github.com/antonmedv/expr v1.15.3

4
go.sum
View File

@@ -98,8 +98,8 @@ github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd h1:Bk43AsDYe0fhkb
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc=
github.com/SigNoz/prometheus v1.11.0 h1:toX7fU2wqY1TnzvPzDglIYx6OxpqrZ0NNlM/H5S5+u8=
github.com/SigNoz/prometheus v1.11.0/go.mod h1:MffmFu2qFILQrOHehx3D0XjYtaZMVfI+Ppeiv98x4Ww=
github.com/SigNoz/signoz-otel-collector v0.88.24 h1:6ESLmQtYPHmik9ZZFSJSbfuj4VQ1/0IC3v1qV9hm5Nk=
github.com/SigNoz/signoz-otel-collector v0.88.24/go.mod h1:sT1EM9PFDaOJLbAz5npWpgXK6OhpWJ9PpSwyhHWs9rU=
github.com/SigNoz/signoz-otel-collector v0.88.21 h1:9K1FLUncUZh7cPfOLDPuT8itU8LyCufk4QwGp18hK88=
github.com/SigNoz/signoz-otel-collector v0.88.21/go.mod h1:sT1EM9PFDaOJLbAz5npWpgXK6OhpWJ9PpSwyhHWs9rU=
github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc=
github.com/SigNoz/zap_otlp v0.1.0/go.mod h1:lcHvbDbRgvDnPxo9lDlaL1JK2PyOyouP/C3ynnYIvyo=
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 h1:PKVgdf83Yw+lZJbFtNGBgqXiXNf3+kOXW2qZ7Ms7OaY=

View File

@@ -51,7 +51,6 @@ import (
"go.signoz.io/signoz/pkg/query-service/common"
"go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/dao"
chErrors "go.signoz.io/signoz/pkg/query-service/errors"
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
"go.signoz.io/signoz/pkg/query-service/interfaces"
"go.signoz.io/signoz/pkg/query-service/model"
@@ -72,17 +71,11 @@ const (
signozTraceTableName = "distributed_signoz_index_v2"
signozTraceLocalTableName = "signoz_index_v2"
signozMetricDBName = "signoz_metrics"
signozSampleLocalTableName = "samples_v4"
signozSampleTableName = "distributed_samples_v4"
signozTSLocalTableNameV4 = "time_series_v4"
signozTSTableNameV4 = "distributed_time_series_v4"
signozTSLocalTableNameV46Hrs = "time_series_v4_6hrs"
signozTSTableNameV46Hrs = "distributed_time_series_v4_6hrs"
signozTSLocalTableNameV41Day = "time_series_v4_1day"
signozTSTableNameV41Day = "distributed_time_series_v4_1day"
signozSampleLocalTableName = "samples_v2"
signozSampleTableName = "distributed_samples_v2"
signozTSTableName = "distributed_time_series_v2"
signozTSTableNameV4 = "distributed_time_series_v4"
signozTSTableNameV41Day = "distributed_time_series_v4_1day"
minTimespanForProgressiveSearch = time.Hour
minTimespanForProgressiveSearchMargin = time.Minute
@@ -2388,17 +2381,15 @@ func (r *ClickHouseReader) SetTTL(ctx context.Context,
}
case constants.MetricsTTL:
tableNames := []string{signozMetricDBName + "." + signozSampleLocalTableName, signozMetricDBName + "." + signozTSLocalTableNameV4, signozMetricDBName + "." + signozTSLocalTableNameV46Hrs, signozMetricDBName + "." + signozTSLocalTableNameV41Day}
for _, tableName := range tableNames {
statusItem, err := r.checkTTLStatusItem(ctx, tableName)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing ttl_status check sql query")}
}
if statusItem.Status == constants.StatusPending {
return nil, &model.ApiError{Typ: model.ErrorConflict, Err: fmt.Errorf("TTL is already running")}
}
tableName := signozMetricDBName + "." + signozSampleLocalTableName
statusItem, err := r.checkTTLStatusItem(ctx, tableName)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing ttl_status check sql query")}
}
metricTTL := func(tableName string) {
if statusItem.Status == constants.StatusPending {
return nil, &model.ApiError{Typ: model.ErrorConflict, Err: fmt.Errorf("TTL is already running")}
}
go func(tableName string) {
_, dbErr := r.localDB.Exec("INSERT INTO ttl_status (transaction_id, created_at, updated_at, table_name, ttl, status, cold_storage_ttl) VALUES (?, ?, ?, ?, ?, ?, ?)", uuid, time.Now(), time.Now(), tableName, params.DelDuration, constants.StatusPending, coldStorageDuration)
if dbErr != nil {
zap.L().Error("Error in inserting to ttl_status table", zap.Error(dbErr))
@@ -2442,10 +2433,7 @@ func (r *ClickHouseReader) SetTTL(ctx context.Context,
zap.L().Error("Error in processing ttl_status update sql query", zap.Error(dbErr))
return
}
}
for _, tableName := range tableNames {
go metricTTL(tableName)
}
}(tableName)
case constants.LogsTTL:
tableName := r.logsDB + "." + r.logsLocalTable
statusItem, err := r.checkTTLStatusItem(ctx, tableName)
@@ -3270,7 +3258,7 @@ func (r *ClickHouseReader) FetchTemporality(ctx context.Context, metricNames []s
func (r *ClickHouseReader) GetTimeSeriesInfo(ctx context.Context) (map[string]interface{}, error) {
queryStr := fmt.Sprintf("SELECT countDistinct(fingerprint) as count from %s.%s where metric_name not like 'signoz_%%' group by metric_name order by count desc;", signozMetricDBName, signozTSTableNameV41Day)
queryStr := fmt.Sprintf("SELECT count() as count from %s.%s where metric_name not like 'signoz_%%' group by metric_name order by count desc;", signozMetricDBName, signozTSTableName)
rows, _ := r.db.Query(ctx, queryStr)
@@ -4582,11 +4570,6 @@ func readRowsForTimeSeriesResult(rows driver.Rows, vars []interface{}, columnNam
return nil, err
}
groupBy, groupAttributes, groupAttributesArray, metricPoint := readRow(vars, columnNames)
// skip the point if the value is NaN or Inf
// are they ever useful enough to be returned?
if math.IsNaN(metricPoint.Value) || math.IsInf(metricPoint.Value, 0) {
continue
}
sort.Strings(groupBy)
key := strings.Join(groupBy, "")
if _, exists := seriesToAttrs[key]; !exists {
@@ -4717,11 +4700,11 @@ func getPersonalisedError(err error) error {
}
zap.L().Error("error while reading result", zap.Error(err))
if strings.Contains(err.Error(), "code: 307") {
return chErrors.ErrResourceBytesLimitExceeded
return errors.New("query is consuming too much resources, please reach out to the team")
}
if strings.Contains(err.Error(), "code: 159") {
return chErrors.ErrResourceTimeLimitExceeded
return errors.New("Query is taking too long to run, please reach out to the team")
}
return err
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"regexp"
"strings"
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
@@ -42,6 +43,12 @@ func (c clickhouseConnWrapper) addClickHouseSettings(ctx context.Context, query
settings["log_comment"] = logComment
}
// don't add resource restrictions traces
if strings.Contains(query, "signoz_traces") {
ctx = clickhouse.Context(ctx, clickhouse.WithSettings(settings))
return ctx
}
if c.settings.MaxBytesToRead != "" {
settings["max_bytes_to_read"] = c.settings.MaxBytesToRead
}

View File

@@ -326,15 +326,7 @@ func UpdateDashboard(ctx context.Context, uuid string, data map[string]interface
if existingTotal > newTotal && existingTotal-newTotal > 1 {
// if the total count of panels has reduced by more than 1,
// return error
existingIds := getWidgetIds(dashboard.Data)
newIds := getWidgetIds(data)
differenceIds := getIdDifference(existingIds, newIds)
if len(differenceIds) > 1 {
return nil, model.BadRequest(fmt.Errorf("deleting more than one panel is not supported"))
}
return nil, model.BadRequest(fmt.Errorf("deleting more than one panel is not supported"))
}
dashboard.UpdatedAt = time.Now()
@@ -722,52 +714,3 @@ func countTraceAndLogsPanel(data map[string]interface{}) (int64, int64) {
}
return count, totalPanels
}
func getWidgetIds(data map[string]interface{}) []string {
widgetIds := []string{}
if data != nil && data["widgets"] != nil {
widgets, ok := data["widgets"].(interface{})
if ok {
data, ok := widgets.([]interface{})
if ok {
for _, widget := range data {
sData, ok := widget.(map[string]interface{})
if ok && sData["query"] != nil && sData["id"] != nil {
id, ok := sData["id"].(string)
if ok {
widgetIds = append(widgetIds, id)
}
}
}
}
}
}
return widgetIds
}
func getIdDifference(existingIds []string, newIds []string) []string {
// Convert newIds array to a map for faster lookups
newIdsMap := make(map[string]bool)
for _, id := range newIds {
newIdsMap[id] = true
}
// Initialize a map to keep track of elements in the difference array
differenceMap := make(map[string]bool)
// Initialize the difference array
difference := []string{}
// Iterate through existingIds
for _, id := range existingIds {
// If the id is not found in newIds, and it's not already in the difference array
if _, found := newIdsMap[id]; !found && !differenceMap[id] {
difference = append(difference, id)
differenceMap[id] = true // Mark the id as seen in the difference array
}
}
return difference
}

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"math"
"sort"
"time"
"github.com/SigNoz/govaluate"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
@@ -90,7 +89,6 @@ func joinAndCalculate(results []*v3.Result, uniqueLabelSet map[string]string, ex
resultSeries := &v3.Series{
Labels: uniqueLabelSet,
Points: make([]v3.Point, 0),
}
timestamps := make([]int64, 0)
for timestamp := range uniqueTimestamps {
@@ -160,7 +158,7 @@ func processResults(results []*v3.Result, expression *govaluate.EvaluableExpress
}, nil
}
var SupportedFunctions = []string{"exp", "log", "ln", "exp2", "log2", "exp10", "log10", "sqrt", "cbrt", "erf", "erfc", "lgamma", "tgamma", "sin", "cos", "tan", "asin", "acos", "atan", "degrees", "radians", "now", "toUnixTimestamp"}
var SupportedFunctions = []string{"exp", "log", "ln", "exp2", "log2", "exp10", "log10", "sqrt", "cbrt", "erf", "erfc", "lgamma", "tgamma", "sin", "cos", "tan", "asin", "acos", "atan", "degrees", "radians"}
func evalFuncs() map[string]govaluate.ExpressionFunction {
GoValuateFuncs := make(map[string]govaluate.ExpressionFunction)
@@ -249,21 +247,5 @@ func evalFuncs() map[string]govaluate.ExpressionFunction {
GoValuateFuncs["radians"] = func(args ...interface{}) (interface{}, error) {
return args[0].(float64) * math.Pi / 180, nil
}
GoValuateFuncs["now"] = func(args ...interface{}) (interface{}, error) {
return time.Now().Unix(), nil
}
GoValuateFuncs["toUnixTimestamp"] = func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("toUnixTimestamp requires exactly one argument")
}
t, err := time.Parse(time.RFC3339, args[0].(string))
if err != nil {
return nil, err
}
return t.Unix(), nil
}
return GoValuateFuncs
}

View File

@@ -483,7 +483,49 @@ func (aH *APIHandler) getRule(w http.ResponseWriter, r *http.Request) {
aH.Respond(w, ruleResponse)
}
// populateTemporality adds the temporality to the query if it is not present
func (aH *APIHandler) addTemporality(ctx context.Context, qp *v3.QueryRangeParamsV3) error {
metricNames := make([]string, 0)
metricNameToTemporality := make(map[string]map[v3.Temporality]bool)
if qp.CompositeQuery != nil && len(qp.CompositeQuery.BuilderQueries) > 0 {
for _, query := range qp.CompositeQuery.BuilderQueries {
if query.DataSource == v3.DataSourceMetrics && query.Temporality == "" {
metricNames = append(metricNames, query.AggregateAttribute.Key)
if _, ok := metricNameToTemporality[query.AggregateAttribute.Key]; !ok {
metricNameToTemporality[query.AggregateAttribute.Key] = make(map[v3.Temporality]bool)
}
}
}
}
var err error
if aH.preferDelta {
zap.L().Debug("fetching metric temporality")
metricNameToTemporality, err = aH.reader.FetchTemporality(ctx, metricNames)
if err != nil {
return err
}
}
if qp.CompositeQuery != nil && len(qp.CompositeQuery.BuilderQueries) > 0 {
for name := range qp.CompositeQuery.BuilderQueries {
query := qp.CompositeQuery.BuilderQueries[name]
if query.DataSource == v3.DataSourceMetrics && query.Temporality == "" {
if aH.preferDelta && metricNameToTemporality[query.AggregateAttribute.Key][v3.Delta] {
query.Temporality = v3.Delta
} else if metricNameToTemporality[query.AggregateAttribute.Key][v3.Cumulative] {
query.Temporality = v3.Cumulative
} else {
query.Temporality = v3.Unspecified
}
}
}
}
return nil
}
// populateTemporality same as addTemporality but for v4 and better
func (aH *APIHandler) populateTemporality(ctx context.Context, qp *v3.QueryRangeParamsV3) error {
missingTemporality := make([]string, 0)
@@ -2305,28 +2347,13 @@ func (ah *APIHandler) calculateConnectionStatus(
func (ah *APIHandler) calculateLogsConnectionStatus(
ctx context.Context,
logsConnectionTest *integrations.LogsConnectionTest,
logsConnectionTest *v3.FilterSet,
lookbackSeconds int64,
) (*integrations.SignalConnectionStatus, *model.ApiError) {
if logsConnectionTest == nil {
return nil, nil
}
logsConnTestFilter := &v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: logsConnectionTest.AttributeKey,
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
Operator: "=",
Value: logsConnectionTest.AttributeValue,
},
},
}
qrParams := &v3.QueryRangeParamsV3{
Start: time.Now().UnixMilli() - (lookbackSeconds * 1000),
End: time.Now().UnixMilli(),
@@ -2336,7 +2363,7 @@ func (ah *APIHandler) calculateLogsConnectionStatus(
BuilderQueries: map[string]*v3.BuilderQuery{
"A": {
PageSize: 1,
Filters: logsConnTestFilter,
Filters: logsConnectionTest,
QueryName: "A",
DataSource: v3.DataSourceLogs,
Expression: "A",
@@ -2865,7 +2892,7 @@ func (aH *APIHandler) autoCompleteAttributeValues(w http.ResponseWriter, r *http
aH.Respond(w, response)
}
func (aH *APIHandler) execClickHouseGraphQueries(ctx context.Context, queries map[string]string) ([]*v3.Result, error, map[string]error) {
func (aH *APIHandler) execClickHouseGraphQueries(ctx context.Context, queries map[string]string) ([]*v3.Result, error, map[string]string) {
type channelResult struct {
Series []*v3.Series
Err error
@@ -2895,13 +2922,13 @@ func (aH *APIHandler) execClickHouseGraphQueries(ctx context.Context, queries ma
close(ch)
var errs []error
errQuriesByName := make(map[string]error)
errQuriesByName := make(map[string]string)
res := make([]*v3.Result, 0)
// read values from the channel
for r := range ch {
if r.Err != nil {
errs = append(errs, r.Err)
errQuriesByName[r.Name] = r.Err
errQuriesByName[r.Name] = r.Query
continue
}
res = append(res, &v3.Result{
@@ -2915,7 +2942,7 @@ func (aH *APIHandler) execClickHouseGraphQueries(ctx context.Context, queries ma
return res, nil, nil
}
func (aH *APIHandler) execClickHouseListQueries(ctx context.Context, queries map[string]string) ([]*v3.Result, error, map[string]error) {
func (aH *APIHandler) execClickHouseListQueries(ctx context.Context, queries map[string]string) ([]*v3.Result, error, map[string]string) {
type channelResult struct {
List []*v3.Row
Err error
@@ -2944,13 +2971,13 @@ func (aH *APIHandler) execClickHouseListQueries(ctx context.Context, queries map
close(ch)
var errs []error
errQuriesByName := make(map[string]error)
errQuriesByName := make(map[string]string)
res := make([]*v3.Result, 0)
// read values from the channel
for r := range ch {
if r.Err != nil {
errs = append(errs, r.Err)
errQuriesByName[r.Name] = r.Err
errQuriesByName[r.Name] = r.Query
continue
}
res = append(res, &v3.Result{
@@ -2964,7 +2991,7 @@ func (aH *APIHandler) execClickHouseListQueries(ctx context.Context, queries map
return res, nil, nil
}
func (aH *APIHandler) execPromQueries(ctx context.Context, metricsQueryRangeParams *v3.QueryRangeParamsV3) ([]*v3.Result, error, map[string]error) {
func (aH *APIHandler) execPromQueries(ctx context.Context, metricsQueryRangeParams *v3.QueryRangeParamsV3) ([]*v3.Result, error, map[string]string) {
type channelResult struct {
Series []*v3.Series
Err error
@@ -3024,13 +3051,13 @@ func (aH *APIHandler) execPromQueries(ctx context.Context, metricsQueryRangePara
close(ch)
var errs []error
errQuriesByName := make(map[string]error)
errQuriesByName := make(map[string]string)
res := make([]*v3.Result, 0)
// read values from the channel
for r := range ch {
if r.Err != nil {
errs = append(errs, r.Err)
errQuriesByName[r.Name] = r.Err
errQuriesByName[r.Name] = r.Query
continue
}
res = append(res, &v3.Result{
@@ -3128,7 +3155,7 @@ func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.Que
var result []*v3.Result
var err error
var errQuriesByName map[string]error
var errQuriesByName map[string]string
var spanKeys map[string]v3.AttributeKey
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder {
// check if any enrichment is required for logs if yes then enrich them
@@ -3278,7 +3305,8 @@ func (aH *APIHandler) QueryRangeV3(w http.ResponseWriter, r *http.Request) {
}
// add temporality for each metric
temporalityErr := aH.populateTemporality(r.Context(), queryRangeParams)
temporalityErr := aH.addTemporality(r.Context(), queryRangeParams)
if temporalityErr != nil {
zap.L().Error("Error while adding temporality for metrics", zap.Error(temporalityErr))
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: temporalityErr}, nil)
@@ -3384,7 +3412,7 @@ func (aH *APIHandler) queryRangeV4(ctx context.Context, queryRangeParams *v3.Que
var result []*v3.Result
var err error
var errQuriesByName map[string]error
var errQuriesByName map[string]string
var spanKeys map[string]v3.AttributeKey
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder {
// check if any enrichment is required for logs if yes then enrich them

View File

@@ -1,7 +1,6 @@
package integrations
import (
"bytes"
"context"
"embed"
"strings"
@@ -121,9 +120,7 @@ func readBuiltInIntegration(dirpath string) (
}
var integration IntegrationDetails
decoder := json.NewDecoder(bytes.NewReader(hydratedSpecJson))
decoder.DisallowUnknownFields()
err = decoder.Decode(&integration)
err = json.Unmarshal(hydratedSpecJson, &integration)
if err != nil {
return nil, fmt.Errorf(
"couldn't parse hydrated JSON spec read from %s: %w",

View File

@@ -78,5 +78,3 @@ Make the collector config file available to your otel collector and use it by ad
```
Note: the collector can use multiple config files, specified by multiple occurrences of the --config flag.
Also note that only 1 collector instance should be configured to collect query_logs.
Using multiple collector instances or replicas with this config will lead to duplicate logs.

View File

@@ -30,7 +30,7 @@ To configure metrics and logs collection for a Clickhouse server, you need the f
- **Ensure that an OTEL collector is running in your deployment environment**
If needed, please [install SigNoz OTEL Collector](https://signoz.io/docs/tutorial/opentelemetry-binary-usage-in-virtual-machine/)
If already installed, ensure that the collector version is v0.88.0 or newer.
If collecting logs from system.query_log table, ensure that the collector version is v0.88.23 or newer.
If collecting logs from system.query_log table, ensure that the collector version is v0.88.22 or newer.
Also ensure that you can provide config files to the collector and that you can set environment variables and command line flags used for running it.

View File

@@ -41,8 +41,18 @@
},
"connection_tests": {
"logs": {
"attribute_key": "source",
"attribute_value": "clickhouse"
"op": "AND",
"items": [
{
"key": {
"type": "tag",
"key": "source",
"dataType": "string"
},
"op": "=",
"value": "clickhouse"
}
]
}
},
"data_collected": "file://data-collected.json"

View File

@@ -37,8 +37,18 @@
},
"connection_tests": {
"logs": {
"attribute_key": "source",
"attribute_value": "mongodb"
"op": "AND",
"items": [
{
"key": {
"type": "tag",
"key": "source",
"dataType": "string"
},
"op": "=",
"value": "mongo"
}
]
}
},
"data_collected": {

View File

@@ -32,8 +32,18 @@
},
"connection_tests": {
"logs": {
"attribute_key": "source",
"attribute_value": "nginx"
"op": "AND",
"items": [
{
"key": {
"type": "tag",
"key": "source",
"dataType": "string"
},
"op": "=",
"value": "nginx"
}
]
}
},
"data_collected": {

View File

@@ -37,8 +37,18 @@
},
"connection_tests": {
"logs": {
"attribute_key": "source",
"attribute_value": "postgres"
"op": "AND",
"items": [
{
"key": {
"type": "tag",
"key": "source",
"dataType": "string"
},
"op": "=",
"value": "postgres"
}
]
}
},
"data_collected": {

View File

@@ -37,8 +37,18 @@
},
"connection_tests": {
"logs": {
"attribute_key": "source",
"attribute_value": "redis"
"op": "AND",
"items": [
{
"key": {
"type": "tag",
"key": "source",
"dataType": "string"
},
"op": "=",
"value": "redis"
}
]
}
},
"data_collected": {

View File

@@ -12,6 +12,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/rules"
"go.signoz.io/signoz/pkg/query-service/utils"
)
@@ -59,10 +60,9 @@ type CollectedLogAttribute struct {
}
type CollectedMetric struct {
Name string `json:"name"`
Type string `json:"type"`
Unit string `json:"unit"`
Description string `json:"description"`
Name string `json:"name"`
Type string `json:"type"`
Unit string `json:"unit"`
}
type SignalConnectionStatus struct {
@@ -75,14 +75,9 @@ type IntegrationConnectionStatus struct {
Metrics *SignalConnectionStatus `json:"metrics"`
}
// log attribute value to use for finding logs for the integration.
type LogsConnectionTest struct {
AttributeKey string `json:"attribute_key"`
AttributeValue string `json:"attribute_value"`
}
type IntegrationConnectionTests struct {
Logs *LogsConnectionTest `json:"logs"`
// Filter to use for finding logs for the integration.
Logs *v3.FilterSet `json:"logs"`
// Metric names expected to have been received for the integration.
Metrics []string `json:"metrics"`
@@ -258,7 +253,7 @@ func (m *Manager) UninstallIntegration(
func (m *Manager) GetPipelinesForInstalledIntegrations(
ctx context.Context,
) ([]logparsingpipeline.Pipeline, *model.ApiError) {
installedIntegrations, apiErr := m.getInstalledIntegrations(ctx)
installedIntegrations, apiErr := m.getDetailsForInstalledIntegrations(ctx)
if apiErr != nil {
return nil, apiErr
}
@@ -327,15 +322,10 @@ func (m *Manager) GetInstalledIntegrationDashboardById(
if dId, exists := dd["id"]; exists {
if id, ok := dId.(string); ok && id == dashboardId {
isLocked := 1
author := "integration"
return &dashboards.Dashboard{
Uuid: m.dashboardUuid(integrationId, string(dashboardId)),
Locked: &isLocked,
Data: dd,
CreatedAt: integration.Installation.InstalledAt,
CreateBy: &author,
UpdatedAt: integration.Installation.InstalledAt,
UpdateBy: &author,
Uuid: m.dashboardUuid(integrationId, string(dashboardId)),
Locked: &isLocked,
Data: dd,
}, nil
}
}
@@ -349,7 +339,7 @@ func (m *Manager) GetInstalledIntegrationDashboardById(
func (m *Manager) GetDashboardsForInstalledIntegrations(
ctx context.Context,
) ([]dashboards.Dashboard, *model.ApiError) {
installedIntegrations, apiErr := m.getInstalledIntegrations(ctx)
installedIntegrations, apiErr := m.getDetailsForInstalledIntegrations(ctx)
if apiErr != nil {
return nil, apiErr
}
@@ -361,15 +351,10 @@ func (m *Manager) GetDashboardsForInstalledIntegrations(
if dId, exists := dd["id"]; exists {
if dashboardId, ok := dId.(string); ok {
isLocked := 1
author := "integration"
result = append(result, dashboards.Dashboard{
Uuid: m.dashboardUuid(ii.IntegrationSummary.Id, dashboardId),
Locked: &isLocked,
Data: dd,
CreatedAt: ii.Installation.InstalledAt,
CreateBy: &author,
UpdatedAt: ii.Installation.InstalledAt,
UpdateBy: &author,
Uuid: m.dashboardUuid(ii.IntegrationSummary.Id, dashboardId),
Locked: &isLocked,
Data: dd,
})
}
}
@@ -428,10 +413,10 @@ func (m *Manager) getInstalledIntegration(
return &installation, nil
}
func (m *Manager) getInstalledIntegrations(
func (m *Manager) getDetailsForInstalledIntegrations(
ctx context.Context,
) (
map[string]Integration, *model.ApiError,
map[string]IntegrationDetails, *model.ApiError,
) {
installations, apiErr := m.installedIntegrationsRepo.list(ctx)
if apiErr != nil {
@@ -441,24 +426,5 @@ func (m *Manager) getInstalledIntegrations(
installedIds := utils.MapSlice(installations, func(i InstalledIntegration) string {
return i.IntegrationId
})
integrationDetails, apiErr := m.availableIntegrationsRepo.get(ctx, installedIds)
if apiErr != nil {
return nil, apiErr
}
result := map[string]Integration{}
for _, ii := range installations {
iDetails, exists := integrationDetails[ii.IntegrationId]
if !exists {
return nil, model.InternalError(fmt.Errorf(
"couldn't find integration details for %s", ii.IntegrationId,
))
}
result[ii.IntegrationId] = Integration{
Installation: &ii,
IntegrationDetails: iDetails,
}
}
return result, nil
return m.availableIntegrationsRepo.get(ctx, installedIds)
}

View File

@@ -96,9 +96,19 @@ func (t *TestAvailableIntegrationsRepo) list(
Alerts: []rules.PostableRule{},
},
ConnectionTests: &IntegrationConnectionTests{
Logs: &LogsConnectionTest{
AttributeKey: "source",
AttributeValue: "nginx",
Logs: &v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: "source",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
Operator: "=",
Value: "nginx",
},
},
},
},
}, {
@@ -164,9 +174,19 @@ func (t *TestAvailableIntegrationsRepo) list(
Alerts: []rules.PostableRule{},
},
ConnectionTests: &IntegrationConnectionTests{
Logs: &LogsConnectionTest{
AttributeKey: "source",
AttributeValue: "nginx",
Logs: &v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: "source",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
Operator: "=",
Value: "nginx",
},
},
},
},
},

View File

@@ -1,7 +1,6 @@
package app
import (
"math"
"sort"
"strings"
@@ -40,31 +39,16 @@ func applyMetricLimit(results []*v3.Result, queryRangeParams *v3.QueryRangeParam
}
}
ithSum, jthSum, ithCount, jthCount := 0.0, 0.0, 0.0, 0.0
for _, point := range result.Series[i].Points {
if math.IsNaN(point.Value) || math.IsInf(point.Value, 0) {
continue
}
ithSum += point.Value
ithCount++
// For graph type queries, sort based on GroupingSetsPoint
if result.Series[i].GroupingSetsPoint == nil || result.Series[j].GroupingSetsPoint == nil {
// Handle nil GroupingSetsPoint, if needed
// Here, we assume non-nil values are always less than nil values
return result.Series[i].GroupingSetsPoint != nil
}
for _, point := range result.Series[j].Points {
if math.IsNaN(point.Value) || math.IsInf(point.Value, 0) {
continue
}
jthSum += point.Value
jthCount++
}
// avoid division by zero
ithCount = math.Max(ithCount, 1)
jthCount = math.Max(jthCount, 1)
if orderBy.Order == "asc" {
return ithSum/ithCount < jthSum/jthCount
return result.Series[i].GroupingSetsPoint.Value < result.Series[j].GroupingSetsPoint.Value
} else if orderBy.Order == "desc" {
return ithSum/ithCount > jthSum/jthCount
return result.Series[i].GroupingSetsPoint.Value > result.Series[j].GroupingSetsPoint.Value
}
} else {
// Sort based on Labels map

View File

@@ -145,13 +145,12 @@ func enrichFieldWithMetadata(field v3.AttributeKey, fields map[string]v3.Attribu
// check if the field is present in the fields map
if existingField, ok := fields[field.Key]; ok {
// don't update if type is not the same
if (field.Type == "" && field.DataType == "") ||
(field.Type == existingField.Type && field.DataType == existingField.DataType) ||
(field.Type == "" && field.DataType == existingField.DataType) ||
(field.DataType == "" && field.Type == existingField.Type) {
if existingField.IsColumn {
return existingField
}
field.Type = existingField.Type
field.DataType = existingField.DataType
return field
}
// enrich with default values if metadata is not found

View File

@@ -342,57 +342,6 @@ var testEnrichParamsData = []struct {
},
},
},
{
Name: "Don't enrich if other keys are non empty and not same",
Params: v3.QueryRangeParamsV3{
CompositeQuery: &v3.CompositeQuery{
BuilderQueries: map[string]*v3.BuilderQuery{
"test": {
QueryName: "test",
Expression: "test",
DataSource: v3.DataSourceLogs,
AggregateAttribute: v3.AttributeKey{
Key: "test",
Type: v3.AttributeKeyTypeResource,
DataType: v3.AttributeKeyDataTypeInt64,
},
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "test", Type: v3.AttributeKeyTypeTag}, Value: "test", Operator: "="},
{Key: v3.AttributeKey{Key: "test", DataType: v3.AttributeKeyDataTypeString}, Value: "test1", Operator: "="},
}},
},
},
},
},
Fields: map[string]v3.AttributeKey{
"test": {
Key: "test",
Type: v3.AttributeKeyTypeTag,
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
},
Result: v3.QueryRangeParamsV3{
CompositeQuery: &v3.CompositeQuery{
BuilderQueries: map[string]*v3.BuilderQuery{
"test": {
QueryName: "test",
Expression: "test",
DataSource: v3.DataSourceLogs,
AggregateAttribute: v3.AttributeKey{
Key: "test",
Type: v3.AttributeKeyTypeResource,
DataType: v3.AttributeKeyDataTypeInt64,
},
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "test", Type: v3.AttributeKeyTypeTag, DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Value: "test", Operator: "="},
{Key: v3.AttributeKey{Key: "test", Type: v3.AttributeKeyTypeTag, DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Value: "test1", Operator: "="},
}},
},
},
},
},
},
}
func TestEnrichParams(t *testing.T) {

View File

@@ -5,7 +5,6 @@ import (
"math"
"strings"
"go.signoz.io/signoz/pkg/query-service/app/metrics/v4/helpers"
"go.signoz.io/signoz/pkg/query-service/constants"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/utils"
@@ -29,7 +28,7 @@ func stepForTableCumulative(start, end int64) int64 {
return int64(step)
}
func buildMetricQueryForTable(start, end, _ int64, mq *v3.BuilderQuery) (string, error) {
func buildMetricQueryForTable(start, end, _ int64, mq *v3.BuilderQuery, tableName string) (string, error) {
step := stepForTableCumulative(start, end)
@@ -37,19 +36,46 @@ func buildMetricQueryForTable(start, end, _ int64, mq *v3.BuilderQuery) (string,
metricQueryGroupBy := mq.GroupBy
filterSubQuery, err := helpers.PrepareTimeseriesFilterQueryV3(start, end, mq)
// if the aggregate operator is a histogram quantile, and user has not forgotten
// the le tag in the group by then add the le tag to the group by
if mq.AggregateOperator == v3.AggregateOperatorHistQuant50 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant75 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant90 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant95 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant99 {
found := false
for _, tag := range mq.GroupBy {
if tag.Key == "le" {
found = true
break
}
}
if !found {
metricQueryGroupBy = append(
metricQueryGroupBy,
v3.AttributeKey{
Key: "le",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
IsColumn: false,
},
)
}
}
filterSubQuery, err := buildMetricsTimeSeriesFilterQuery(mq.Filters, metricQueryGroupBy, mq)
if err != nil {
return "", err
}
samplesTableTimeFilter := fmt.Sprintf("metric_name = %s AND unix_milli >= %d AND unix_milli <= %d", utils.ClickHouseFormattedValue(mq.AggregateAttribute.Key), start, end)
samplesTableTimeFilter := fmt.Sprintf("metric_name = %s AND timestamp_ms >= %d AND timestamp_ms <= %d", utils.ClickHouseFormattedValue(mq.AggregateAttribute.Key), start, end)
// Select the aggregate value for interval
queryTmplCounterInner :=
"SELECT %s" +
" toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL %d SECOND) as ts," +
" toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL %d SECOND) as ts," +
" %s as value" +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_V4_TABLENAME +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_TABLENAME +
" INNER JOIN" +
" (%s) as filtered_time_series" +
" USING fingerprint" +
@@ -62,7 +88,7 @@ func buildMetricQueryForTable(start, end, _ int64, mq *v3.BuilderQuery) (string,
"SELECT %s" +
" toStartOfHour(now()) as ts," + // now() has no menaing & used as a placeholder for ts
" %s as value" +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_V4_TABLENAME +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_TABLENAME +
" INNER JOIN" +
" (%s) as filtered_time_series" +
" USING fingerprint" +

View File

@@ -38,7 +38,7 @@ func TestPanelTableForCumulative(t *testing.T) {
},
Expression: "A",
},
expected: "SELECT toStartOfHour(now()) as ts, sum(rate_value)/29 as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT fingerprint FROM signoz_metrics.time_series_v4 WHERE metric_name = 'signoz_latency_count' AND temporality = 'Cumulative' AND unix_milli >= 1689253200000 AND unix_milli < 1689257640000 AND JSONExtractString(labels, 'service_name') IN ['frontend'] AND JSONExtractString(labels, 'operation') IN ['HTTP GET /dispatch']) as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_count' AND unix_milli >= 1689255866000 AND unix_milli <= 1689257640000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts)) WHERE isNaN(rate_value) = 0 GROUP BY ts ORDER BY ts",
expected: "SELECT toStartOfHour(now()) as ts, sum(rate_value)/29 as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'signoz_latency_count' AND temporality IN ['Cumulative', 'Unspecified'] AND JSONExtractString(labels, 'service_name') IN ['frontend'] AND JSONExtractString(labels, 'operation') IN ['HTTP GET /dispatch']) as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_count' AND timestamp_ms >= 1689255866000 AND timestamp_ms <= 1689257640000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts)) WHERE isNaN(rate_value) = 0 GROUP BY ts ORDER BY ts",
},
{
name: "latency p50",
@@ -60,13 +60,8 @@ func TestPanelTableForCumulative(t *testing.T) {
},
},
Expression: "A",
GroupBy: []v3.AttributeKey{
{
Key: "le",
},
},
},
expected: "SELECT toStartOfHour(now()) as ts, histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), 0.500) as value FROM (SELECT le, toStartOfHour(now()) as ts, sum(rate_value)/29 as value FROM (SELECT le, ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, le, toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT JSONExtractString(labels, 'le') as le, fingerprint FROM signoz_metrics.time_series_v4 WHERE metric_name = 'signoz_latency_bucket' AND temporality = 'Cumulative' AND unix_milli >= 1689253200000 AND unix_milli < 1689257640000 AND JSONExtractString(labels, 'service_name') = 'frontend') as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_bucket' AND unix_milli >= 1689255866000 AND unix_milli <= 1689257640000 GROUP BY fingerprint, le,ts ORDER BY fingerprint, le ASC, ts) WINDOW rate_window as (PARTITION BY fingerprint, le ORDER BY fingerprint, le ASC, ts)) WHERE isNaN(rate_value) = 0 GROUP BY le,ts ORDER BY le ASC, ts) GROUP BY ts ORDER BY ts",
expected: "SELECT toStartOfHour(now()) as ts, histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), 0.500) as value FROM (SELECT le, toStartOfHour(now()) as ts, sum(rate_value)/29 as value FROM (SELECT le, ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, le, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT JSONExtractString(labels, 'le') as le, fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'signoz_latency_bucket' AND temporality IN ['Cumulative', 'Unspecified'] AND JSONExtractString(labels, 'service_name') = 'frontend') as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_bucket' AND timestamp_ms >= 1689255866000 AND timestamp_ms <= 1689257640000 GROUP BY fingerprint, le,ts ORDER BY fingerprint, le ASC, ts) WINDOW rate_window as (PARTITION BY fingerprint, le ORDER BY fingerprint, le ASC, ts)) WHERE isNaN(rate_value) = 0 GROUP BY le,ts ORDER BY le ASC, ts) GROUP BY ts ORDER BY ts",
},
{
name: "latency p99 with group by",
@@ -82,19 +77,16 @@ func TestPanelTableForCumulative(t *testing.T) {
{
Key: "service_name",
},
{
Key: "le",
},
},
Expression: "A",
},
expected: "SELECT service_name, toStartOfHour(now()) as ts, histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), 0.990) as value FROM (SELECT service_name,le, toStartOfHour(now()) as ts, sum(rate_value)/29 as value FROM (SELECT service_name,le, ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, service_name,le, toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT JSONExtractString(labels, 'service_name') as service_name, JSONExtractString(labels, 'le') as le, fingerprint FROM signoz_metrics.time_series_v4 WHERE metric_name = 'signoz_latency_bucket' AND temporality = 'Cumulative' AND unix_milli >= 1689253200000 AND unix_milli < 1689257640000) as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_bucket' AND unix_milli >= 1689255866000 AND unix_milli <= 1689257640000 GROUP BY fingerprint, service_name,le,ts ORDER BY fingerprint, service_name ASC,le ASC, ts) WINDOW rate_window as (PARTITION BY fingerprint, service_name,le ORDER BY fingerprint, service_name ASC,le ASC, ts)) WHERE isNaN(rate_value) = 0 GROUP BY service_name,le,ts ORDER BY service_name ASC,le ASC, ts) GROUP BY service_name,ts ORDER BY service_name ASC, ts",
expected: "SELECT service_name, toStartOfHour(now()) as ts, histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), 0.990) as value FROM (SELECT service_name,le, toStartOfHour(now()) as ts, sum(rate_value)/29 as value FROM (SELECT service_name,le, ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, service_name,le, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT JSONExtractString(labels, 'service_name') as service_name, JSONExtractString(labels, 'le') as le, fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'signoz_latency_bucket' AND temporality IN ['Cumulative', 'Unspecified']) as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_bucket' AND timestamp_ms >= 1689255866000 AND timestamp_ms <= 1689257640000 GROUP BY fingerprint, service_name,le,ts ORDER BY fingerprint, service_name ASC,le ASC, ts) WINDOW rate_window as (PARTITION BY fingerprint, service_name,le ORDER BY fingerprint, service_name ASC,le ASC, ts)) WHERE isNaN(rate_value) = 0 GROUP BY service_name,le,ts ORDER BY service_name ASC,le ASC, ts) GROUP BY service_name,ts ORDER BY service_name ASC, ts",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
query, err := buildMetricQueryForTable(1689255866000, 1689257640000, 1800, c.query)
query, err := buildMetricQueryForTable(1689255866000, 1689257640000, 1800, c.query, "distributed_time_series_v2")
if err != nil {
t.Fatalf("unexpected error: %v\n", err)
}

View File

@@ -3,47 +3,63 @@ package v3
import (
"fmt"
"go.signoz.io/signoz/pkg/query-service/app/metrics/v4/helpers"
"go.signoz.io/signoz/pkg/query-service/constants"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/utils"
)
func buildDeltaMetricQuery(start, end, step int64, mq *v3.BuilderQuery) (string, error) {
func buildDeltaMetricQuery(start, end, step int64, mq *v3.BuilderQuery, tableName string) (string, error) {
metricQueryGroupBy := mq.GroupBy
if mq.Filters != nil {
temporalityFound := false
for _, filter := range mq.Filters.Items {
if filter.Key.Key == "__temporality__" {
temporalityFound = true
// if the aggregate operator is a histogram quantile, and user has not forgotten
// the le tag in the group by then add the le tag to the group by
if mq.AggregateOperator == v3.AggregateOperatorHistQuant50 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant75 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant90 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant95 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant99 {
found := false
for _, tag := range mq.GroupBy {
if tag.Key == "le" {
found = true
break
}
}
if !temporalityFound {
mq.Filters.Items = append(mq.Filters.Items, v3.FilterItem{
Key: v3.AttributeKey{Key: "__temporality__"},
Operator: v3.FilterOperatorEqual,
Value: "Delta",
})
if !found {
metricQueryGroupBy = append(
metricQueryGroupBy,
v3.AttributeKey{
Key: "le",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
IsColumn: false,
},
)
}
}
filterSubQuery, err := helpers.PrepareTimeseriesFilterQueryV3(start, end, mq)
if mq.Filters != nil {
mq.Filters.Items = append(mq.Filters.Items, v3.FilterItem{
Key: v3.AttributeKey{Key: "__temporality__"},
Operator: v3.FilterOperatorEqual,
Value: "Delta",
})
}
filterSubQuery, err := buildMetricsTimeSeriesFilterQuery(mq.Filters, metricQueryGroupBy, mq)
if err != nil {
return "", err
}
samplesTableTimeFilter := fmt.Sprintf("metric_name = %s AND unix_milli >= %d AND unix_milli <= %d", utils.ClickHouseFormattedValue(mq.AggregateAttribute.Key), start, end)
samplesTableTimeFilter := fmt.Sprintf("metric_name = %s AND timestamp_ms >= %d AND timestamp_ms <= %d", utils.ClickHouseFormattedValue(mq.AggregateAttribute.Key), start, end)
// Select the aggregate value for interval
queryTmpl :=
"SELECT %s" +
" toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL %d SECOND) as ts," +
" toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL %d SECOND) as ts," +
" %s as value" +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_V4_TABLENAME +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_TABLENAME +
" INNER JOIN" +
" (%s) as filtered_time_series" +
" USING fingerprint" +
@@ -141,9 +157,9 @@ func buildDeltaMetricQuery(start, end, step int64, mq *v3.BuilderQuery) (string,
case v3.AggregateOperatorNoOp:
queryTmpl :=
"SELECT fingerprint, labels as fullLabels," +
" toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL %d SECOND) as ts," +
" toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL %d SECOND) as ts," +
" any(value) as value" +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_V4_TABLENAME +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_TABLENAME +
" INNER JOIN" +
" (%s) as filtered_time_series" +
" USING fingerprint" +

View File

@@ -4,13 +4,12 @@ import (
"fmt"
"math"
"go.signoz.io/signoz/pkg/query-service/app/metrics/v4/helpers"
"go.signoz.io/signoz/pkg/query-service/constants"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/utils"
)
func buildDeltaMetricQueryForTable(start, end, _ int64, mq *v3.BuilderQuery) (string, error) {
func buildDeltaMetricQueryForTable(start, end, _ int64, mq *v3.BuilderQuery, tableName string) (string, error) {
// round up to the nearest multiple of 60
step := int64(math.Ceil(float64(end-start+1)/1000/60) * 60)
@@ -44,17 +43,17 @@ func buildDeltaMetricQueryForTable(start, end, _ int64, mq *v3.BuilderQuery) (st
}
}
filterSubQuery, err := helpers.PrepareTimeseriesFilterQueryV3(start, end, mq)
filterSubQuery, err := buildMetricsTimeSeriesFilterQuery(mq.Filters, metricQueryGroupBy, mq)
if err != nil {
return "", err
}
samplesTableTimeFilter := fmt.Sprintf("metric_name = %s AND unix_milli >= %d AND unix_milli <= %d", utils.ClickHouseFormattedValue(mq.AggregateAttribute.Key), start, end)
samplesTableTimeFilter := fmt.Sprintf("metric_name = %s AND timestamp_ms >= %d AND timestamp_ms <= %d", utils.ClickHouseFormattedValue(mq.AggregateAttribute.Key), start, end)
queryTmpl :=
"SELECT %s toStartOfHour(now()) as ts," + // now() has no menaing & used as a placeholder for ts
" %s as value" +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_V4_TABLENAME +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_TABLENAME +
" INNER JOIN" +
" (%s) as filtered_time_series" +
" USING fingerprint" +

View File

@@ -38,7 +38,7 @@ func TestPanelTableForDelta(t *testing.T) {
},
Expression: "A",
},
expected: "SELECT toStartOfHour(now()) as ts, sum(value)/1800 as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT fingerprint FROM signoz_metrics.time_series_v4 WHERE metric_name = 'signoz_latency_count' AND temporality = 'Delta' AND unix_milli >= 1689253200000 AND unix_milli < 1689257640000 AND JSONExtractString(labels, 'service_name') IN ['frontend'] AND JSONExtractString(labels, 'operation') IN ['HTTP GET /dispatch']) as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_count' AND unix_milli >= 1689255866000 AND unix_milli <= 1689257640000 GROUP BY ts ORDER BY ts",
expected: "SELECT toStartOfHour(now()) as ts, sum(value)/1800 as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'signoz_latency_count' AND temporality = 'Delta' AND JSONExtractString(labels, 'service_name') IN ['frontend'] AND JSONExtractString(labels, 'operation') IN ['HTTP GET /dispatch']) as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_count' AND timestamp_ms >= 1689255866000 AND timestamp_ms <= 1689257640000 GROUP BY ts ORDER BY ts",
},
{
name: "latency p50",
@@ -61,7 +61,7 @@ func TestPanelTableForDelta(t *testing.T) {
},
Expression: "A",
},
expected: "SELECT ts, histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), 0.500) as value FROM (SELECT le, toStartOfHour(now()) as ts, sum(value)/1800 as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT fingerprint FROM signoz_metrics.time_series_v4 WHERE metric_name = 'signoz_latency_bucket' AND temporality = 'Delta' AND unix_milli >= 1689253200000 AND unix_milli < 1689257640000 AND JSONExtractString(labels, 'service_name') = 'frontend') as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_bucket' AND unix_milli >= 1689255866000 AND unix_milli <= 1689257640000 GROUP BY le,ts ORDER BY le ASC, ts) GROUP BY ts ORDER BY ts",
expected: "SELECT ts, histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), 0.500) as value FROM (SELECT le, toStartOfHour(now()) as ts, sum(value)/1800 as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT JSONExtractString(labels, 'le') as le, fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'signoz_latency_bucket' AND temporality = 'Delta' AND JSONExtractString(labels, 'service_name') = 'frontend') as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_bucket' AND timestamp_ms >= 1689255866000 AND timestamp_ms <= 1689257640000 GROUP BY le,ts ORDER BY le ASC, ts) GROUP BY ts ORDER BY ts",
},
{
name: "latency p99 with group by",
@@ -80,13 +80,13 @@ func TestPanelTableForDelta(t *testing.T) {
},
Expression: "A",
},
expected: "SELECT service_name, ts, histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), 0.990) as value FROM (SELECT service_name,le, toStartOfHour(now()) as ts, sum(value)/1800 as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT JSONExtractString(labels, 'service_name') as service_name, fingerprint FROM signoz_metrics.time_series_v4 WHERE metric_name = 'signoz_latency_bucket' AND temporality = 'Delta' AND unix_milli >= 1689253200000 AND unix_milli < 1689257640000) as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_bucket' AND unix_milli >= 1689255866000 AND unix_milli <= 1689257640000 GROUP BY service_name,le,ts ORDER BY service_name ASC,le ASC, ts) GROUP BY service_name,ts ORDER BY service_name ASC, ts",
expected: "SELECT service_name, ts, histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), 0.990) as value FROM (SELECT service_name,le, toStartOfHour(now()) as ts, sum(value)/1800 as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT JSONExtractString(labels, 'service_name') as service_name, JSONExtractString(labels, 'le') as le, fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'signoz_latency_bucket' AND temporality = 'Delta' ) as filtered_time_series USING fingerprint WHERE metric_name = 'signoz_latency_bucket' AND timestamp_ms >= 1689255866000 AND timestamp_ms <= 1689257640000 GROUP BY service_name,le,ts ORDER BY service_name ASC,le ASC, ts) GROUP BY service_name,ts ORDER BY service_name ASC, ts",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
query, err := buildDeltaMetricQueryForTable(1689255866000, 1689257640000, 1800, c.query)
query, err := buildDeltaMetricQueryForTable(1689255866000, 1689257640000, 1800, c.query, "distributed_time_series_v2")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

View File

@@ -6,7 +6,6 @@ import (
"strings"
"time"
"go.signoz.io/signoz/pkg/query-service/app/metrics/v4/helpers"
"go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
@@ -52,23 +51,136 @@ var aggregateOperatorToSQLFunc = map[v3.AggregateOperator]string{
// See https://github.com/SigNoz/signoz/issues/2151#issuecomment-1467249056
var rateWithoutNegative = `If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) `
func buildMetricQuery(start, end, step int64, mq *v3.BuilderQuery) (string, error) {
// buildMetricsTimeSeriesFilterQuery builds the sub-query to be used for filtering
// timeseries based on search criteria
func buildMetricsTimeSeriesFilterQuery(fs *v3.FilterSet, groupTags []v3.AttributeKey, mq *v3.BuilderQuery) (string, error) {
metricName := mq.AggregateAttribute.Key
aggregateOperator := mq.AggregateOperator
var conditions []string
if mq.Temporality == v3.Delta {
conditions = append(conditions, fmt.Sprintf("metric_name = %s AND temporality = '%s' ", utils.ClickHouseFormattedValue(metricName), v3.Delta))
} else {
conditions = append(conditions, fmt.Sprintf("metric_name = %s AND temporality IN ['%s', '%s']", utils.ClickHouseFormattedValue(metricName), v3.Cumulative, v3.Unspecified))
}
if fs != nil && len(fs.Items) != 0 {
for _, item := range fs.Items {
toFormat := item.Value
op := v3.FilterOperator(strings.ToLower(strings.TrimSpace(string(item.Operator))))
// if the received value is an array for like/match op, just take the first value
// or should we throw an error?
if op == v3.FilterOperatorLike || op == v3.FilterOperatorRegex || op == v3.FilterOperatorNotLike || op == v3.FilterOperatorNotRegex {
x, ok := item.Value.([]interface{})
if ok {
if len(x) == 0 {
continue
}
toFormat = x[0]
}
}
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
toFormat = fmt.Sprintf("%%%s%%", toFormat)
}
fmtVal := utils.ClickHouseFormattedValue(toFormat)
switch op {
case v3.FilterOperatorEqual:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", item.Key.Key, fmtVal))
case v3.FilterOperatorNotEqual:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') != %s", item.Key.Key, fmtVal))
case v3.FilterOperatorIn:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') IN %s", item.Key.Key, fmtVal))
case v3.FilterOperatorNotIn:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') NOT IN %s", item.Key.Key, fmtVal))
case v3.FilterOperatorLike:
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorNotLike:
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorRegex:
conditions = append(conditions, fmt.Sprintf("match(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorNotRegex:
conditions = append(conditions, fmt.Sprintf("not match(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorGreaterThan:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') > %s", item.Key.Key, fmtVal))
case v3.FilterOperatorGreaterThanOrEq:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') >= %s", item.Key.Key, fmtVal))
case v3.FilterOperatorLessThan:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') < %s", item.Key.Key, fmtVal))
case v3.FilterOperatorLessThanOrEq:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') <= %s", item.Key.Key, fmtVal))
case v3.FilterOperatorContains:
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorNotContains:
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorExists:
conditions = append(conditions, fmt.Sprintf("has(JSONExtractKeys(labels), '%s')", item.Key.Key))
case v3.FilterOperatorNotExists:
conditions = append(conditions, fmt.Sprintf("not has(JSONExtractKeys(labels), '%s')", item.Key.Key))
default:
return "", fmt.Errorf("unsupported operation")
}
}
}
queryString := strings.Join(conditions, " AND ")
var selectLabels string
if aggregateOperator == v3.AggregateOperatorNoOp || aggregateOperator == v3.AggregateOperatorRate {
selectLabels = "labels,"
} else {
for _, tag := range groupTags {
selectLabels += fmt.Sprintf(" JSONExtractString(labels, '%s') as %s,", tag.Key, tag.Key)
}
}
filterSubQuery := fmt.Sprintf("SELECT %s fingerprint FROM %s.%s WHERE %s", selectLabels, constants.SIGNOZ_METRIC_DBNAME, constants.SIGNOZ_TIMESERIES_LOCAL_TABLENAME, queryString)
return filterSubQuery, nil
}
func buildMetricQuery(start, end, step int64, mq *v3.BuilderQuery, tableName string) (string, error) {
metricQueryGroupBy := mq.GroupBy
filterSubQuery, err := helpers.PrepareTimeseriesFilterQueryV3(start, end, mq)
// if the aggregate operator is a histogram quantile, and user has not forgotten
// the le tag in the group by then add the le tag to the group by
if mq.AggregateOperator == v3.AggregateOperatorHistQuant50 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant75 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant90 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant95 ||
mq.AggregateOperator == v3.AggregateOperatorHistQuant99 {
found := false
for _, tag := range mq.GroupBy {
if tag.Key == "le" {
found = true
break
}
}
if !found {
metricQueryGroupBy = append(
metricQueryGroupBy,
v3.AttributeKey{
Key: "le",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
IsColumn: false,
},
)
}
}
filterSubQuery, err := buildMetricsTimeSeriesFilterQuery(mq.Filters, metricQueryGroupBy, mq)
if err != nil {
return "", err
}
samplesTableTimeFilter := fmt.Sprintf("metric_name = %s AND unix_milli >= %d AND unix_milli < %d", utils.ClickHouseFormattedValue(mq.AggregateAttribute.Key), start, end)
samplesTableTimeFilter := fmt.Sprintf("metric_name = %s AND timestamp_ms >= %d AND timestamp_ms < %d", utils.ClickHouseFormattedValue(mq.AggregateAttribute.Key), start, end)
// Select the aggregate value for interval
queryTmpl :=
"SELECT %s" +
" toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL %d SECOND) as ts," +
" toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL %d SECOND) as ts," +
" %s as value" +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_V4_TABLENAME +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_TABLENAME +
" INNER JOIN" +
" (%s) as filtered_time_series" +
" USING fingerprint" +
@@ -197,9 +309,9 @@ func buildMetricQuery(start, end, step int64, mq *v3.BuilderQuery) (string, erro
case v3.AggregateOperatorNoOp:
queryTmpl :=
"SELECT fingerprint, labels as fullLabels," +
" toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL %d SECOND) as ts," +
" toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL %d SECOND) as ts," +
" any(value) as value" +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_V4_TABLENAME +
" FROM " + constants.SIGNOZ_METRIC_DBNAME + "." + constants.SIGNOZ_SAMPLES_TABLENAME +
" INNER JOIN" +
" (%s) as filtered_time_series" +
" USING fingerprint" +
@@ -349,15 +461,15 @@ func PrepareMetricQuery(start, end int64, queryType v3.QueryType, panelType v3.P
var err error
if mq.Temporality == v3.Delta {
if panelType == v3.PanelTypeTable {
query, err = buildDeltaMetricQueryForTable(start, end, mq.StepInterval, mq)
query, err = buildDeltaMetricQueryForTable(start, end, mq.StepInterval, mq, constants.SIGNOZ_TIMESERIES_TABLENAME)
} else {
query, err = buildDeltaMetricQuery(start, end, mq.StepInterval, mq)
query, err = buildDeltaMetricQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_TIMESERIES_TABLENAME)
}
} else {
if panelType == v3.PanelTypeTable {
query, err = buildMetricQueryForTable(start, end, mq.StepInterval, mq)
query, err = buildMetricQueryForTable(start, end, mq.StepInterval, mq, constants.SIGNOZ_TIMESERIES_TABLENAME)
} else {
query, err = buildMetricQuery(start, end, mq.StepInterval, mq)
query, err = buildMetricQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_TIMESERIES_TABLENAME)
}
}

View File

@@ -50,7 +50,6 @@ func TestBuildQueryWithFilters(t *testing.T) {
}},
AggregateOperator: v3.AggregateOperatorRateMax,
Expression: "A",
Temporality: v3.Cumulative,
},
},
},
@@ -58,7 +57,7 @@ func TestBuildQueryWithFilters(t *testing.T) {
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{PreferRPM: false})
require.NoError(t, err)
require.Contains(t, query, "WHERE metric_name = 'name' AND temporality = 'Cumulative' AND unix_milli >= 1650931200000 AND unix_milli < 1651078380000 AND JSONExtractString(labels, 'a') != 'b'")
require.Contains(t, query, "WHERE metric_name = 'name' AND temporality IN ['Cumulative', 'Unspecified'] AND JSONExtractString(labels, 'a') != 'b'")
require.Contains(t, query, rateWithoutNegative)
require.Contains(t, query, "not match(JSONExtractString(labels, 'code'), 'ERROR_*')")
})
@@ -79,7 +78,6 @@ func TestBuildQueryWithMultipleQueries(t *testing.T) {
{Key: v3.AttributeKey{Key: "in"}, Value: []interface{}{"a", "b", "c"}, Operator: v3.FilterOperatorIn},
}},
AggregateOperator: v3.AggregateOperatorRateAvg,
Temporality: v3.Cumulative,
Expression: "A",
},
"B": {
@@ -87,7 +85,6 @@ func TestBuildQueryWithMultipleQueries(t *testing.T) {
StepInterval: 60,
AggregateAttribute: v3.AttributeKey{Key: "name2"},
AggregateOperator: v3.AggregateOperatorRateMax,
Temporality: v3.Cumulative,
Expression: "B",
},
},
@@ -97,15 +94,158 @@ func TestBuildQueryWithMultipleQueries(t *testing.T) {
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{PreferRPM: false})
require.NoError(t, err)
require.Contains(t, query, "WHERE metric_name = 'name' AND temporality = 'Cumulative' AND unix_milli >= 1650931200000 AND unix_milli < 1651078380000 AND JSONExtractString(labels, 'in') IN ['a','b','c']")
require.Contains(t, query, "WHERE metric_name = 'name' AND temporality IN ['Cumulative', 'Unspecified'] AND JSONExtractString(labels, 'in') IN ['a','b','c']")
require.Contains(t, query, rateWithoutNegative)
})
}
func TestBuildQueryOperators(t *testing.T) {
testCases := []struct {
operator v3.FilterOperator
filterSet v3.FilterSet
expectedWhereClause string
}{
{
operator: v3.FilterOperatorEqual,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "service_name"}, Value: "route", Operator: v3.FilterOperatorEqual},
},
},
expectedWhereClause: "JSONExtractString(labels, 'service_name') = 'route'",
},
{
operator: v3.FilterOperatorNotEqual,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "service_name"}, Value: "route", Operator: v3.FilterOperatorNotEqual},
},
},
expectedWhereClause: "JSONExtractString(labels, 'service_name') != 'route'",
},
{
operator: v3.FilterOperatorRegex,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "service_name"}, Value: "out", Operator: v3.FilterOperatorRegex},
},
},
expectedWhereClause: "match(JSONExtractString(labels, 'service_name'), 'out')",
},
{
operator: v3.FilterOperatorNotRegex,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "service_name"}, Value: "out", Operator: v3.FilterOperatorNotRegex},
},
},
expectedWhereClause: "not match(JSONExtractString(labels, 'service_name'), 'out')",
},
{
operator: v3.FilterOperatorIn,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "service_name"}, Value: []interface{}{"route", "driver"}, Operator: v3.FilterOperatorIn},
},
},
expectedWhereClause: "JSONExtractString(labels, 'service_name') IN ['route','driver']",
},
{
operator: v3.FilterOperatorNotIn,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "service_name"}, Value: []interface{}{"route", "driver"}, Operator: v3.FilterOperatorNotIn},
},
},
expectedWhereClause: "JSONExtractString(labels, 'service_name') NOT IN ['route','driver']",
},
{
operator: v3.FilterOperatorExists,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "horn"}, Operator: v3.FilterOperatorExists},
},
},
expectedWhereClause: "has(JSONExtractKeys(labels), 'horn')",
},
{
operator: v3.FilterOperatorNotExists,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "horn"}, Operator: v3.FilterOperatorNotExists},
},
},
expectedWhereClause: "not has(JSONExtractKeys(labels), 'horn')",
},
{
operator: v3.FilterOperatorContains,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "service_name"}, Value: "out", Operator: v3.FilterOperatorContains},
},
},
expectedWhereClause: "like(JSONExtractString(labels, 'service_name'), '%out%')",
},
{
operator: v3.FilterOperatorNotContains,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "serice_name"}, Value: "out", Operator: v3.FilterOperatorNotContains},
},
},
expectedWhereClause: "notLike(JSONExtractString(labels, 'serice_name'), '%out%')",
},
{
operator: v3.FilterOperatorLike,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "service_name"}, Value: "dri", Operator: v3.FilterOperatorLike},
},
},
expectedWhereClause: "like(JSONExtractString(labels, 'service_name'), 'dri')",
},
{
operator: v3.FilterOperatorNotLike,
filterSet: v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "serice_name"}, Value: "dri", Operator: v3.FilterOperatorNotLike},
},
},
expectedWhereClause: "notLike(JSONExtractString(labels, 'serice_name'), 'dri')",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
mq := v3.BuilderQuery{
QueryName: "A",
StepInterval: 60,
AggregateAttribute: v3.AttributeKey{Key: "signoz_calls_total"},
AggregateOperator: v3.AggregateOperatorSum,
}
whereClause, err := buildMetricsTimeSeriesFilterQuery(&tc.filterSet, []v3.AttributeKey{}, &mq)
require.NoError(t, err)
require.Contains(t, whereClause, tc.expectedWhereClause)
})
}
}
func TestBuildQueryXRate(t *testing.T) {
t.Run("TestBuildQueryXRate", func(t *testing.T) {
tmpl := `SELECT ts, %s(rate_value) as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT fingerprint FROM signoz_metrics.time_series_v4_1day WHERE metric_name = 'name' AND temporality = '' AND unix_milli >= 1650931200000 AND unix_milli < 1651078380000) as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND unix_milli >= 1650991920000 AND unix_milli < 1651078380000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts) ) WHERE isNaN(rate_value) = 0 GROUP BY ts ORDER BY ts`
tmpl := `SELECT ts, %s(rate_value) as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'name' AND temporality IN ['Cumulative', 'Unspecified']) as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND timestamp_ms >= 1650991920000 AND timestamp_ms < 1651078380000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts) ) WHERE isNaN(rate_value) = 0 GROUP BY ts ORDER BY ts`
cases := []struct {
aggregateOperator v3.AggregateOperator
@@ -158,7 +298,7 @@ func TestBuildQueryXRate(t *testing.T) {
func TestBuildQueryRPM(t *testing.T) {
t.Run("TestBuildQueryXRate", func(t *testing.T) {
tmpl := `SELECT ts, ceil(value * 60) as value FROM (SELECT ts, %s(rate_value) as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT fingerprint FROM signoz_metrics.time_series_v4_1day WHERE metric_name = 'name' AND temporality = '' AND unix_milli >= 1650931200000 AND unix_milli < 1651078380000) as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND unix_milli >= 1650991920000 AND unix_milli < 1651078380000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts) ) WHERE isNaN(rate_value) = 0 GROUP BY ts ORDER BY ts)`
tmpl := `SELECT ts, ceil(value * 60) as value FROM (SELECT ts, %s(rate_value) as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'name' AND temporality IN ['Cumulative', 'Unspecified']) as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND timestamp_ms >= 1650991920000 AND timestamp_ms < 1651078380000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts) ) WHERE isNaN(rate_value) = 0 GROUP BY ts ORDER BY ts)`
cases := []struct {
aggregateOperator v3.AggregateOperator
@@ -237,7 +377,7 @@ func TestBuildQueryAdjustedTimes(t *testing.T) {
},
},
// 20:10:00 - 20:41:00
expected: "unix_milli >= 1686082200000 AND unix_milli < 1686084060000",
expected: "timestamp_ms >= 1686082200000 AND timestamp_ms < 1686084060000",
},
{
name: "TestBuildQueryAdjustedTimes start close to 50 seconds",
@@ -262,7 +402,7 @@ func TestBuildQueryAdjustedTimes(t *testing.T) {
},
},
// 20:10:00 - 20:41:00
expected: "unix_milli >= 1686082200000 AND unix_milli < 1686084060000",
expected: "timestamp_ms >= 1686082200000 AND timestamp_ms < 1686084060000",
},
{
name: "TestBuildQueryAdjustedTimes start close to 42 seconds with step 30 seconds",
@@ -287,7 +427,7 @@ func TestBuildQueryAdjustedTimes(t *testing.T) {
},
},
// 20:11:00 - 20:41:00
expected: "unix_milli >= 1686082260000 AND unix_milli < 1686084060000",
expected: "timestamp_ms >= 1686082260000 AND timestamp_ms < 1686084060000",
},
{
name: "TestBuildQueryAdjustedTimes start close to 42 seconds with step 30 seconds and end close to 30 seconds",
@@ -312,7 +452,7 @@ func TestBuildQueryAdjustedTimes(t *testing.T) {
},
},
// 20:11:00 - 20:41:00
expected: "unix_milli >= 1686082260000 AND unix_milli < 1686084060000",
expected: "timestamp_ms >= 1686082260000 AND timestamp_ms < 1686084060000",
},
{
name: "TestBuildQueryAdjustedTimes start close to 42 seconds with step 300 seconds and end close to 30 seconds",
@@ -339,7 +479,7 @@ func TestBuildQueryAdjustedTimes(t *testing.T) {
// 20:05:00 - 20:41:00
// 20:10:00 is the nearest 5 minute interval, but we round down to 20:05:00
// as this is a rate query and we want to include the previous value for the first interval
expected: "unix_milli >= 1686081900000 AND unix_milli < 1686084060000",
expected: "timestamp_ms >= 1686081900000 AND timestamp_ms < 1686084060000",
},
{
name: "TestBuildQueryAdjustedTimes start close to 42 seconds with step 180 seconds and end close to 30 seconds",
@@ -366,7 +506,7 @@ func TestBuildQueryAdjustedTimes(t *testing.T) {
// 20:06:00 - 20:39:00
// 20:09:00 is the nearest 3 minute interval, but we round down to 20:06:00
// as this is a rate query and we want to include the previous value for the first interval
expected: "unix_milli >= 1686081960000 AND unix_milli < 1686084060000",
expected: "timestamp_ms >= 1686081960000 AND timestamp_ms < 1686084060000",
},
}

View File

@@ -117,88 +117,3 @@ func PrepareTimeseriesFilterQuery(start, end int64, mq *v3.BuilderQuery) (string
return filterSubQuery, nil
}
// PrepareTimeseriesFilterQuery builds the sub-query to be used for filtering timeseries based on the search criteria
func PrepareTimeseriesFilterQueryV3(start, end int64, mq *v3.BuilderQuery) (string, error) {
var conditions []string
var fs *v3.FilterSet = mq.Filters
var groupTags []v3.AttributeKey = mq.GroupBy
conditions = append(conditions, fmt.Sprintf("metric_name = %s", utils.ClickHouseFormattedValue(mq.AggregateAttribute.Key)))
conditions = append(conditions, fmt.Sprintf("temporality = '%s'", mq.Temporality))
start, end, tableName := which(start, end)
conditions = append(conditions, fmt.Sprintf("unix_milli >= %d AND unix_milli < %d", start, end))
if fs != nil && len(fs.Items) != 0 {
for _, item := range fs.Items {
toFormat := item.Value
op := v3.FilterOperator(strings.ToLower(strings.TrimSpace(string(item.Operator))))
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
toFormat = fmt.Sprintf("%%%s%%", toFormat)
}
fmtVal := utils.ClickHouseFormattedValue(toFormat)
switch op {
case v3.FilterOperatorEqual:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", item.Key.Key, fmtVal))
case v3.FilterOperatorNotEqual:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') != %s", item.Key.Key, fmtVal))
case v3.FilterOperatorIn:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') IN %s", item.Key.Key, fmtVal))
case v3.FilterOperatorNotIn:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') NOT IN %s", item.Key.Key, fmtVal))
case v3.FilterOperatorLike:
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorNotLike:
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorRegex:
conditions = append(conditions, fmt.Sprintf("match(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorNotRegex:
conditions = append(conditions, fmt.Sprintf("not match(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorGreaterThan:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') > %s", item.Key.Key, fmtVal))
case v3.FilterOperatorGreaterThanOrEq:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') >= %s", item.Key.Key, fmtVal))
case v3.FilterOperatorLessThan:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') < %s", item.Key.Key, fmtVal))
case v3.FilterOperatorLessThanOrEq:
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') <= %s", item.Key.Key, fmtVal))
case v3.FilterOperatorContains:
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorNotContains:
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
case v3.FilterOperatorExists:
conditions = append(conditions, fmt.Sprintf("has(JSONExtractKeys(labels), '%s')", item.Key.Key))
case v3.FilterOperatorNotExists:
conditions = append(conditions, fmt.Sprintf("not has(JSONExtractKeys(labels), '%s')", item.Key.Key))
default:
return "", fmt.Errorf("unsupported filter operator")
}
}
}
whereClause := strings.Join(conditions, " AND ")
var selectLabels string
if mq.AggregateOperator == v3.AggregateOperatorNoOp || mq.AggregateOperator == v3.AggregateOperatorRate {
selectLabels += "labels, "
} else {
for _, tag := range groupTags {
selectLabels += fmt.Sprintf("JSONExtractString(labels, '%s') as %s, ", tag.Key, tag.Key)
}
}
// The table JOIN key always exists
selectLabels += "fingerprint"
filterSubQuery := fmt.Sprintf(
"SELECT DISTINCT %s FROM %s.%s WHERE %s",
selectLabels,
constants.SIGNOZ_METRIC_DBNAME,
tableName,
whereClause,
)
return filterSubQuery, nil
}

View File

@@ -23,8 +23,6 @@ func PrepareMetricQuery(start, end int64, queryType v3.QueryType, panelType v3.P
var quantile float64
percentileOperator := mq.SpaceAggregation
if v3.IsPercentileOperator(mq.SpaceAggregation) &&
mq.AggregateAttribute.Type != v3.AttributeKeyType(v3.MetricTypeExponentialHistogram) {
quantile = v3.GetPercentileFromOperator(mq.SpaceAggregation)
@@ -82,7 +80,6 @@ func PrepareMetricQuery(start, end int64, queryType v3.QueryType, panelType v3.P
// fixed-bucket histogram quantiles are calculated with UDF
if quantile != 0 && mq.AggregateAttribute.Type != v3.AttributeKeyType(v3.MetricTypeExponentialHistogram) {
query = fmt.Sprintf(`SELECT %s, histogramQuantile(arrayMap(x -> toFloat64(x), groupArray(le)), groupArray(value), %.3f) as value FROM (%s) GROUP BY %s ORDER BY %s`, groupBy, quantile, query, groupBy, orderBy)
mq.SpaceAggregation = percentileOperator
}
return query, nil

Some files were not shown because too many files have changed in this diff Show More