Merge branch 'develop' into SIG-5729

This commit is contained in:
rahulkeswani101 2024-10-04 16:10:54 +05:30 committed by GitHub
commit 36e2404814
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
73 changed files with 766 additions and 485 deletions

View File

@ -146,11 +146,11 @@ services:
condition: on-failure
query-service:
image: signoz/query-service:0.49.1
image: signoz/query-service:0.55.0
command:
[
"-config=/root/config/prometheus.yml",
# "--prefer-delta=true"
"--use-logs-new-schema=true"
]
# ports:
# - "6060:6060" # pprof port
@ -186,7 +186,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:0.48.0
image: signoz/frontend:0.55.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.102.2
image: signoz/signoz-otel-collector:0.102.10
command:
[
"--config=/etc/otel-collector-config.yaml",
@ -238,7 +238,7 @@ services:
- query-service
otel-collector-migrator:
image: signoz/signoz-schema-migrator:0.102.2
image: signoz/signoz-schema-migrator:0.102.10
deploy:
restart_policy:
condition: on-failure

View File

@ -144,6 +144,7 @@ exporters:
dsn: tcp://clickhouse:9000/signoz_logs
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
timeout: 10s
use_new_schema: true
extensions:
health_check:
endpoint: 0.0.0.0:13133

View File

@ -66,7 +66,7 @@ services:
- --storage.path=/data
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.10}
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.102.2
image: signoz/signoz-otel-collector:0.102.10
command:
[
"--config=/etc/otel-collector-config.yaml",

View File

@ -25,7 +25,7 @@ services:
command:
[
"-config=/root/config/prometheus.yml",
# "--prefer-delta=true"
"--use-logs-new-schema=true"
]
ports:
- "6060:6060"

View File

@ -164,13 +164,13 @@ 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.49.1}
image: signoz/query-service:${DOCKER_TAG:-0.55.0}
container_name: signoz-query-service
command:
[
"-config=/root/config/prometheus.yml",
"-gateway-url=https://api.staging.signoz.cloud"
# "--prefer-delta=true"
"-gateway-url=https://api.staging.signoz.cloud",
"--use-logs-new-schema=true"
]
# ports:
# - "6060:6060" # pprof port
@ -204,7 +204,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.49.1}
image: signoz/frontend:${DOCKER_TAG:-0.55.0}
container_name: signoz-frontend
restart: on-failure
depends_on:
@ -216,7 +216,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.10}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@ -230,7 +230,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.2}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.10}
container_name: signoz-otel-collector
command:
[

View File

@ -164,12 +164,12 @@ 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.49.1}
image: signoz/query-service:${DOCKER_TAG:-0.55.0}
container_name: signoz-query-service
command:
[
"-config=/root/config/prometheus.yml"
# "--prefer-delta=true"
"-config=/root/config/prometheus.yml",
"--use-logs-new-schema=true"
]
# ports:
# - "6060:6060" # pprof port
@ -203,7 +203,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.49.1}
image: signoz/frontend:${DOCKER_TAG:-0.55.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.102.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.10}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@ -229,7 +229,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.2}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.10}
container_name: signoz-otel-collector
command:
[

View File

@ -154,6 +154,7 @@ exporters:
dsn: tcp://clickhouse:9000/signoz_logs
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
timeout: 10s
use_new_schema: true
# logging: {}
service:

View File

@ -757,7 +757,7 @@ func makeRulesManager(
RepoURL: ruleRepoURL,
DBConn: db,
Context: context.Background(),
Logger: nil,
Logger: zap.L(),
DisableRules: disableRules,
FeatureFlags: fm,
Reader: ch,

View File

@ -250,7 +250,7 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (interface{}, erro
}
lb := labels.NewBuilder(smpl.Metric).Del(labels.MetricNameLabel).Del(labels.TemporalityLabel)
resultLabels := labels.NewBuilder(smpl.MetricOrig).Del(labels.MetricNameLabel).Del(labels.TemporalityLabel).Labels()
resultLabels := labels.NewBuilder(smpl.Metric).Del(labels.MetricNameLabel).Del(labels.TemporalityLabel).Labels()
for name, value := range r.Labels().Map() {
lb.Set(name, expand(value))
@ -262,7 +262,7 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (interface{}, erro
annotations := make(labels.Labels, 0, len(r.Annotations().Map()))
for name, value := range r.Annotations().Map() {
annotations = append(annotations, labels.Label{Name: common.NormalizeLabelName(name), Value: expand(value)})
annotations = append(annotations, labels.Label{Name: name, Value: expand(value)})
}
if smpl.IsMissing {
lb.Set(labels.AlertNameLabel, "[No data] "+r.Name())

View File

@ -73,7 +73,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(opts.Rule.Frequency), rules, opts.ManagerOpts, opts.NotifyFunc, opts.RuleDB)
} else {
return nil, fmt.Errorf("unsupported rule type. Supported types: %s, %s", baserules.RuleTypeProm, baserules.RuleTypeThreshold)
return nil, fmt.Errorf("unsupported rule type %s. Supported types: %s, %s", opts.Rule.RuleType, baserules.RuleTypeProm, baserules.RuleTypeThreshold)
}
return task, nil

View File

@ -1,11 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2048_2251)">
<path opacity="0.9" d="M8.02226 15.9866C3.56539 15.9866 -6.10352e-05 12.4896 -6.10352e-05 8.11832C-6.10352e-05 3.79075 3.56539 0.25 8.02226 0.25H13.0584C14.7075 0.25 15.9999 1.56139 15.9999 3.13506V8.11832C15.9999 12.4896 12.4345 15.9866 8.02226 15.9866Z" fill="#F25733"/>
<path d="M7.95919 4.71207C4.63025 4.71207 2.75514 7.46868 2.67693 7.58603C2.48413 7.87508 2.48413 8.24888 2.67707 8.53816C2.75514 8.65528 4.63025 11.4119 7.95919 11.4119C11.2881 11.4119 13.1633 8.65528 13.2414 8.53792C13.4342 8.24888 13.4342 7.87508 13.2413 7.58582C13.1632 7.46868 11.2881 4.71207 7.95919 4.71207ZM3.13771 8.23088C3.06925 8.12832 3.06925 7.99571 3.13771 7.89307C3.20059 7.79867 4.53564 5.83764 6.92256 5.36723C5.84092 5.78476 5.07127 6.83485 5.07127 8.062C5.07127 9.28912 5.84092 10.3392 6.92256 10.7567C4.53564 10.2863 3.20059 8.32528 3.13771 8.23088ZM6.62838 8.062C6.62838 8.21488 6.50443 8.3388 6.35151 8.3388C6.19859 8.3388 6.07465 8.21488 6.07465 8.062C6.07465 7.02287 6.92003 6.17748 7.95916 6.17748C8.11207 6.17748 8.23599 6.30141 8.23599 6.45434C8.23599 6.60727 8.11207 6.73119 7.95916 6.73119C7.22535 6.73119 6.62838 7.32815 6.62838 8.062ZM7.95919 8.73504C7.58803 8.73504 7.2861 8.43312 7.2861 8.062C7.2861 7.69085 7.58803 7.3889 7.95919 7.3889C8.33039 7.3889 8.63231 7.69083 8.63231 8.062C8.63231 8.43312 8.33039 8.73504 7.95919 8.73504ZM12.7806 8.23088C12.7178 8.32528 11.3827 10.2863 8.99583 10.7567C10.0775 10.3392 10.8471 9.28912 10.8471 8.062C10.8471 6.83487 10.0775 5.78477 8.99583 5.36724C11.3827 5.83768 12.7178 7.7987 12.7806 7.89307C12.8491 7.99571 12.8491 8.12832 12.7806 8.23088Z" fill="#F9F2F9"/>
</g>
<defs>
<clipPath id="clip0_2048_2251">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="none"><rect width="100" height="100" fill="url(#a)" rx="20"/><g fill="#fff" fill-rule="evenodd" clip-rule="evenodd" filter="url(#b)"><path d="M11 49.941v-.003l.002-.005.003-.014.007-.035a8.37 8.37 0 0 1 .105-.42c.073-.263.184-.624.348-1.072.328-.896.866-2.135 1.73-3.617 1.732-2.97 4.753-6.883 9.95-10.955 5.223-4.092 10.295-6.293 14.08-7.471a35.328 35.328 0 0 1 4.585-1.114 23.628 23.628 0 0 1 1.687-.223 9.17 9.17 0 0 1 .108-.009l.034-.002h.011l.007-.001s.002 0 .133 2.217c.13 2.218.132 2.218.132 2.218h-.002l-.053.004a19.098 19.098 0 0 0-1.326.178c-.809.136-1.937.37-3.302.763l-.127.043c-2.745.94-6.666 2.775-11.249 6.362-4.572 3.577-7.142 6.95-8.563 9.393-.711 1.222-1.137 2.215-1.383 2.889a9.995 9.995 0 0 0-.29.933c.008.037.022.095.044.173.046.166.123.423.246.76.246.674.672 1.667 1.383 2.89 1.421 2.441 3.991 5.815 8.563 9.392 4.584 3.587 8.504 5.423 11.25 6.362l.126.043c1.365.393 2.493.627 3.302.763a19.098 19.098 0 0 0 1.326.178l.053.004h.002s-.002 0-.133 2.218C43.66 75 43.657 75 43.657 75h-.007l-.011-.001-.034-.002a9.17 9.17 0 0 1-.478-.046 23.628 23.628 0 0 1-1.317-.186 35.328 35.328 0 0 1-4.584-1.114c-3.786-1.178-8.858-3.38-14.081-7.471-5.197-4.072-8.218-7.985-9.95-10.955-.864-1.482-1.402-2.72-1.73-3.617-.164-.448-.275-.81-.348-1.072a8.37 8.37 0 0 1-.105-.42l-.007-.035-.003-.014-.002-.005v-.121Zm78 0v-.003l-.002-.005-.002-.014-.008-.035a8.532 8.532 0 0 0-.105-.42 14.049 14.049 0 0 0-.348-1.072c-.328-.896-.866-2.135-1.73-3.617-1.732-2.97-4.753-6.883-9.95-10.955-5.223-4.092-10.295-6.293-14.08-7.471a35.328 35.328 0 0 0-4.585-1.114 23.628 23.628 0 0 0-1.687-.223 9.17 9.17 0 0 0-.108-.009l-.034-.002h-.011L56.343 25s-.002 0-.133 2.217c-.13 2.218-.132 2.218-.132 2.218h.002l.053.004a19.098 19.098 0 0 1 1.326.178c.809.136 1.937.37 3.302.763l.127.043c2.745.94 6.666 2.775 11.249 6.362 4.572 3.577 7.141 6.95 8.563 9.393.711 1.222 1.137 2.215 1.383 2.889a9.995 9.995 0 0 1 .29.933 9.995 9.995 0 0 1-.29.934c-.246.673-.672 1.666-1.383 2.888-1.422 2.442-3.991 5.816-8.563 9.393-4.584 3.587-8.504 5.423-11.25 6.362l-.126.043a30.108 30.108 0 0 1-3.302.763 19.098 19.098 0 0 1-1.326.178l-.053.004h-.002s.002 0 .133 2.218C56.34 75 56.343 75 56.343 75h.007l.011-.001.034-.002a9.17 9.17 0 0 0 .478-.046c.314-.034.758-.092 1.317-.186a35.328 35.328 0 0 0 4.584-1.114c3.786-1.178 8.858-3.38 14.081-7.471 5.197-4.072 8.218-7.985 9.95-10.955.864-1.482 1.402-2.72 1.73-3.617.164-.448.275-.81.348-1.072a8.532 8.532 0 0 0 .105-.42l.008-.035.002-.014.001-.005.001-.003v-.118Z"/><path d="M68.342 49.998c0 9.846-7.924 17.827-17.7 17.827-9.775 0-17.7-7.981-17.7-17.827 0-9.846 7.925-17.827 17.7-17.827 9.776 0 17.7 7.981 17.7 17.827ZM46.218 39.97s-2.127 2.508-2.766 4.457c-.412 1.257-.553 3.343-.553 3.343h-5.531s0-1.672 1.106-4.457c1.106-2.786 2.212-3.343 2.212-3.343h5.532Zm-2.766 15.6c.639 1.949 2.766 4.457 2.766 4.457h-5.532s-1.106-.557-2.212-3.343c-1.106-2.785-1.106-4.457-1.106-4.457h5.53s.142 2.086.554 3.343Z"/></g><defs><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="matrix(40.99997 42 -42 40.99997 50 50)" gradientUnits="userSpaceOnUse"><stop offset=".33" stop-color="#F76526"/><stop offset="1" stop-color="#F43030"/></radialGradient><filter id="b" width="90" height="62" x="5" y="23" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="4"/><feGaussianBlur stdDeviation="3"/><feComposite in2="hardAlpha" operator="out"/><feColorMatrix values="0 0 0 0 0.368384 0 0 0 0 0.0623777 0 0 0 0 0.0623777 0 0 0 0.25 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_3909_18731"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_3909_18731" result="shape"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="4"/><feGaussianBlur stdDeviation="3"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/><feBlend in2="shape" result="effect2_innerShadow_3909_18731"/></filter></defs></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -118,6 +118,8 @@
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
"field_unit": "Threshold unit",
"text_alert_on_absent": "Send a notification if data is missing for",
"text_require_min_points": "Run alert evaluation only when there are minimum of",
"text_num_points": "data points in each result group",
"text_alert_frequency": "Run alert every",
"text_for": "minutes",
"selected_query_placeholder": "Select query"

View File

@ -118,6 +118,8 @@
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
"field_unit": "Threshold unit",
"text_alert_on_absent": "Send a notification if data is missing for",
"text_require_min_points": "Run alert evaluation only when there are minimum of",
"text_num_points": "data points in each result group",
"text_alert_frequency": "Run alert every",
"text_for": "minutes",
"selected_query_placeholder": "Select query"

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -12,6 +12,7 @@ import useAnalytics from 'hooks/analytics/useAnalytics';
import { KeyboardHotkeysProvider } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useIsDarkMode, useThemeConfig } from 'hooks/useDarkMode';
import { THEME_MODE } from 'hooks/useDarkMode/constant';
import useFeatureFlags from 'hooks/useFeatureFlag';
import useGetFeatureFlag from 'hooks/useGetFeatureFlag';
import useLicense, { LICENSE_PLAN_KEY } from 'hooks/useLicense';
import { NotificationProvider } from 'hooks/useNotifications';
@ -58,23 +59,13 @@ function App(): JSX.Element {
const isDarkMode = useIsDarkMode();
const isChatSupportEnabled =
useFeatureFlags(FeatureKeys.CHAT_SUPPORT)?.active || false;
const isPremiumSupportEnabled =
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
const featureResponse = useGetFeatureFlag((allFlags) => {
const isOnboardingEnabled =
allFlags.find((flag) => flag.name === FeatureKeys.ONBOARDING)?.active ||
false;
const isChatSupportEnabled =
allFlags.find((flag) => flag.name === FeatureKeys.CHAT_SUPPORT)?.active ||
false;
const isPremiumSupportEnabled =
allFlags.find((flag) => flag.name === FeatureKeys.PREMIUM_SUPPORT)?.active ||
false;
const showAddCreditCardModal =
!isPremiumSupportEnabled &&
!licenseData?.payload?.trialConvertedToSubscription;
dispatch({
type: UPDATE_FEATURE_FLAG_RESPONSE,
payload: {
@ -83,6 +74,10 @@ function App(): JSX.Element {
},
});
const isOnboardingEnabled =
allFlags.find((flag) => flag.name === FeatureKeys.ONBOARDING)?.active ||
false;
if (!isOnboardingEnabled || !isCloudUserVal) {
const newRoutes = routes.filter(
(route) => route?.path !== ROUTES.GET_STARTED,
@ -90,16 +85,6 @@ function App(): JSX.Element {
setRoutes(newRoutes);
}
if (isLoggedInState && isChatSupportEnabled && !showAddCreditCardModal) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.Intercom('boot', {
app_id: process.env.INTERCOM_APP_ID,
email: user?.email || '',
name: user?.name || '',
});
}
});
const isOnBasicPlan =
@ -201,6 +186,26 @@ function App(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pathname]);
useEffect(() => {
const showAddCreditCardModal =
!isPremiumSupportEnabled &&
!licenseData?.payload?.trialConvertedToSubscription;
if (isLoggedInState && isChatSupportEnabled && !showAddCreditCardModal) {
window.Intercom('boot', {
app_id: process.env.INTERCOM_APP_ID,
email: user?.email || '',
name: user?.name || '',
});
}
}, [
isLoggedInState,
isChatSupportEnabled,
user,
licenseData,
isPremiumSupportEnabled,
]);
useEffect(() => {
if (user && user?.email && user?.userId && user?.name) {
try {

View File

@ -22,7 +22,13 @@ export type GetViewDetailsUsingViewKey = (
viewKey: string,
data: ViewProps[] | undefined,
) =>
| { query: Query; name: string; uuid: string; panelType: PANEL_TYPES }
| {
query: Query;
name: string;
uuid: string;
panelType: PANEL_TYPES;
extraData?: string;
}
| undefined;
export interface IsQueryUpdatedInViewProps {

View File

@ -29,9 +29,9 @@ export const getViewDetailsUsingViewKey: GetViewDetailsUsingViewKey = (
) => {
const selectedView = data?.find((view) => view.uuid === viewKey);
if (selectedView) {
const { compositeQuery, name, uuid } = selectedView;
const { compositeQuery, name, uuid, extraData } = selectedView;
const query = mapQueryDataFromApi(compositeQuery);
return { query, name, uuid, panelType: compositeQuery.panelType };
return { query, name, uuid, panelType: compositeQuery.panelType, extraData };
}
return undefined;
};

View File

@ -16,7 +16,7 @@ function WelcomeLeftContainer({
<Container>
<LeftContainer direction="vertical">
<Space align="center">
<Logo src="signoz-signup.svg" alt="logo" />
<Logo src="/Logos/signoz-brand-logo.svg" alt="logo" />
<Title style={{ fontSize: '46px', margin: 0 }}>SigNoz</Title>
</Space>
<Typography>{t('monitor_signup')}</Typography>

View File

@ -6,7 +6,6 @@ export const AUTH0_REDIRECT_PATH = '/redirect';
export const DEFAULT_AUTH0_APP_REDIRECTION_PATH = ROUTES.APPLICATION;
export const IS_SIDEBAR_COLLAPSED = 'isSideBarCollapsed';
export const INVITE_MEMBERS_HASH = '#invite-team-members';
export const SIGNOZ_UPGRADE_PLAN_URL =

View File

@ -1,8 +1,4 @@
import { getUserOperatingSystem, UserOperatingSystem } from 'utils/getUserOS';
const userOS = getUserOperatingSystem();
export const GlobalShortcuts = {
SidebarCollapse: '\\+meta',
NavigateToServices: 's+shift',
NavigateToTraces: 't+shift',
NavigateToLogs: 'l+shift',
@ -13,7 +9,6 @@ export const GlobalShortcuts = {
};
export const GlobalShortcutsName = {
SidebarCollapse: `${userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'}+\\`,
NavigateToServices: 'shift+s',
NavigateToTraces: 'shift+t',
NavigateToLogs: 'shift+l',
@ -24,7 +19,6 @@ export const GlobalShortcutsName = {
};
export const GlobalShortcutsDescription = {
SidebarCollapse: 'Collpase the sidebar',
NavigateToServices: 'Navigate to Services page',
NavigateToTraces: 'Navigate to Traces page',
NavigateToLogs: 'Navigate to logs page',

View File

@ -1,10 +1,15 @@
import { Color } from '@signozhq/design-tokens';
import Uplot from 'components/Uplot';
import { QueryParams } from 'constants/query';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import heatmapPlugin from 'lib/uPlotLib/plugins/heatmapPlugin';
import timelinePlugin from 'lib/uPlotLib/plugins/timelinePlugin';
import { useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { UpdateTimeInterval } from 'store/actions';
import { AlertRuleTimelineGraphResponse } from 'types/api/alerts/def';
import uPlot, { AlignedData } from 'uplot';
@ -41,11 +46,13 @@ function HorizontalTimelineGraph({
return [timestamps, states];
}, [data]);
const urlQuery = useUrlQuery();
const dispatch = useDispatch();
const options: uPlot.Options = useMemo(
() => ({
width,
height: 85,
cursor: { show: false },
axes: [
{
@ -66,6 +73,40 @@ function HorizontalTimelineGraph({
label: 'States',
},
],
hooks: {
setSelect: [
(self): void => {
const selection = self.select;
if (selection) {
const startTime = self.posToVal(selection.left, 'x');
const endTime = self.posToVal(selection.left + selection.width, 'x');
const diff = endTime - startTime;
if (diff > 0) {
if (urlQuery.has(QueryParams.relativeTime)) {
urlQuery.delete(QueryParams.relativeTime);
}
const startTimestamp = Math.floor(startTime * 1000);
const endTimestamp = Math.floor(endTime * 1000);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
history.push({
search: urlQuery.toString(),
});
}
}
},
],
},
plugins:
transformedData?.length > 1
? [
@ -76,7 +117,7 @@ function HorizontalTimelineGraph({
]
: [],
}),
[width, isDarkMode, transformedData],
[width, isDarkMode, transformedData.length, urlQuery, dispatch],
);
return <Uplot data={transformedData} options={options} />;
}

View File

@ -16,12 +16,6 @@
width: 100%;
}
}
&.docked {
.app-content {
width: calc(100% - 240px);
}
}
}
.chat-support-gateway {

View File

@ -5,13 +5,11 @@ import './AppLayout.styles.scss';
import * as Sentry from '@sentry/react';
import { Flex } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import getUserLatestVersion from 'api/user/getLatestVersion';
import getUserVersion from 'api/user/getVersion';
import cx from 'classnames';
import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes';
import SideNav from 'container/SideNav';
@ -22,22 +20,13 @@ import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import {
ReactNode,
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { Dispatch } from 'redux';
import { sideBarCollapse } from 'store/actions';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import {
@ -59,10 +48,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
(state) => state.app,
);
const [collapsed, setCollapsed] = useState<boolean>(
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
);
const { notifications } = useNotifications();
const isDarkMode = useIsDarkMode();
@ -117,14 +102,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const latestCurrentCounter = useRef(0);
const latestVersionCounter = useRef(0);
const onCollapse = useCallback(() => {
setCollapsed((collapsed) => !collapsed);
}, []);
useLayoutEffect(() => {
dispatch(sideBarCollapse(collapsed));
}, [collapsed, dispatch]);
useEffect(() => {
if (
getUserLatestVersionResponse.isFetched &&
@ -255,19 +232,16 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD';
const isAlertHistory = (): boolean => routeKey === 'ALERT_HISTORY';
const isAlertOverview = (): boolean => routeKey === 'ALERT_OVERVIEW';
const isDashboardView = (): boolean => {
/**
* need to match using regex here as the getRoute function will not work for
* routes with id
*/
const regex = /^\/dashboard\/[a-zA-Z0-9_-]+$/;
return regex.test(pathname);
};
const isPathMatch = (regex: RegExp): boolean => regex.test(pathname);
const isDashboardWidgetView = (): boolean => {
const regex = /^\/dashboard\/[a-zA-Z0-9_-]+\/new$/;
return regex.test(pathname);
};
const isDashboardView = (): boolean =>
isPathMatch(/^\/dashboard\/[a-zA-Z0-9_-]+$/);
const isDashboardWidgetView = (): boolean =>
isPathMatch(/^\/dashboard\/[a-zA-Z0-9_-]+\/new$/);
const isTraceDetailsView = (): boolean =>
isPathMatch(/^\/trace\/[a-zA-Z0-9]+(\?.*)?$/);
useEffect(() => {
if (isDarkMode) {
@ -279,23 +253,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
}
}, [isDarkMode]);
const isSideNavCollapsed = getLocalStorageKey(IS_SIDEBAR_COLLAPSED);
/**
* Note: Right now we don't have a page-level method to pass the sidebar collapse state.
* Since the use case for overriding is not widely needed, we are setting it here
* so that the workspace locked page will have an expanded sidebar regardless of how users
* have set it or what is stored in localStorage. This will not affect the localStorage config.
*/
const isWorkspaceLocked = pathname === ROUTES.WORKSPACE_LOCKED;
return (
<Layout
className={cx(
isDarkMode ? 'darkMode' : 'lightMode',
isSideNavCollapsed ? 'sidebarCollapsed' : '',
)}
>
<Layout className={cx(isDarkMode ? 'darkMode' : 'lightMode')}>
<Helmet>
<title>{pageTitle}</title>
</Helmet>
@ -321,25 +280,11 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
</div>
)}
<Flex
className={cx(
'app-layout',
isDarkMode ? 'darkMode' : 'lightMode',
!collapsed && !renderFullScreen ? 'docked' : '',
)}
>
<Flex className={cx('app-layout', isDarkMode ? 'darkMode' : 'lightMode')}>
{isToDisplayLayout && !renderFullScreen && (
<SideNav
licenseData={licenseData}
isFetching={isFetching}
onCollapse={onCollapse}
collapsed={isWorkspaceLocked ? false : collapsed}
/>
<SideNav licenseData={licenseData} isFetching={isFetching} />
)}
<div
className={cx('app-content', collapsed ? 'collapsed' : '')}
data-overlayscrollbars-initialize
>
<div className="app-content" data-overlayscrollbars-initialize>
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<LayoutContent data-overlayscrollbars-initialize>
<OverlayScrollbar>
@ -356,6 +301,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
isMessagingQueues()
? 0
: '0 1rem',
...(isTraceDetailsView() ? { marginRight: 0 } : {}),
}}
>
{isToDisplayLayout && !renderFullScreen && <TopNav />}

View File

@ -58,6 +58,21 @@ const calculateStartEndTime = (
export function BillingUsageGraph(props: BillingUsageGraphProps): JSX.Element {
const { data, billAmount } = props;
// Added this to fix the issue where breakdown with one day data are causing the bars to spread across multiple days
data?.details?.breakdown?.forEach((breakdown: any) => {
if (breakdown?.dayWiseBreakdown?.breakdown?.length === 1) {
const currentDay = breakdown.dayWiseBreakdown.breakdown[0];
const nextDay = {
...currentDay,
timestamp: currentDay.timestamp + 86400,
count: 0,
size: 0,
quantity: 0,
total: 0,
};
breakdown.dayWiseBreakdown.breakdown.push(nextDay);
}
});
const graphCompatibleData = useMemo(
() => convertDataToMetricRangePayload(data),
[data],

View File

@ -24,6 +24,9 @@ import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import ExportPanelContainer from 'container/ExportPanel/ExportPanelContainer';
import { useOptionsMenu } from 'container/OptionsMenu';
import { defaultTraceSelectedColumns } from 'container/OptionsMenu/constants';
import { OptionsQuery } from 'container/OptionsMenu/types';
import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useGetAllViews } from 'hooks/saveViews/useGetAllViews';
@ -34,7 +37,7 @@ import useErrorNotification from 'hooks/useErrorNotification';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
import { useNotifications } from 'hooks/useNotifications';
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
import { cloneDeep } from 'lodash-es';
import { cloneDeep, isEqual } from 'lodash-es';
import {
Check,
ConciergeBell,
@ -58,7 +61,9 @@ import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { Dashboard } from 'types/api/dashboard/getAll';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { ViewProps } from 'types/api/saveViews/types';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
@ -252,6 +257,46 @@ function ExplorerOptions({
const { handleExplorerTabChange } = useHandleExplorerTabChange();
const { options, handleOptionsChange } = useOptionsMenu({
storageKey: LOCALSTORAGE.TRACES_LIST_OPTIONS,
dataSource: DataSource.TRACES,
aggregateOperator: StringOperators.NOOP,
});
type ExtraData = {
selectColumns?: BaseAutocompleteData[];
};
const updateOrRestoreSelectColumns = (
key: string,
allViewsData: ViewProps[] | undefined,
options: OptionsQuery,
handleOptionsChange: (newQueryData: OptionsQuery) => void,
): void => {
const currentViewDetails = getViewDetailsUsingViewKey(key, allViewsData);
if (!currentViewDetails) {
return;
}
let extraData: ExtraData = {};
try {
extraData = JSON.parse(currentViewDetails?.extraData ?? '{}') as ExtraData;
} catch (error) {
console.error('Error parsing extraData:', error);
}
if (extraData.selectColumns?.length) {
handleOptionsChange({
...options,
selectColumns: extraData.selectColumns,
});
} else if (!isEqual(defaultTraceSelectedColumns, options.selectColumns)) {
handleOptionsChange({
...options,
selectColumns: defaultTraceSelectedColumns,
});
}
};
const onMenuItemSelectHandler = useCallback(
({ key }: { key: string }): void => {
const currentViewDetails = getViewDetailsUsingViewKey(
@ -321,6 +366,13 @@ function ExplorerOptions({
updatePreservedViewInLocalStorage(option);
updateOrRestoreSelectColumns(
option.key,
viewsData?.data?.data,
options,
handleOptionsChange,
);
if (ref.current) {
ref.current.blur();
}
@ -360,14 +412,20 @@ function ExplorerOptions({
viewName: newViewName || '',
compositeQuery,
sourcePage: sourcepage,
extraData: JSON.stringify({ color }),
extraData: JSON.stringify({
color,
selectColumns: options.selectColumns,
}),
});
const onSaveHandler = (): void => {
saveNewViewHandler({
compositeQuery,
handlePopOverClose: hideSaveViewModal,
extraData: JSON.stringify({ color }),
extraData: JSON.stringify({
color,
selectColumns: options.selectColumns,
}),
notifications,
panelType: panelType || PANEL_TYPES.LIST,
redirectWithQueryBuilderData,

View File

@ -323,6 +323,45 @@ function RuleOptions({
<Typography.Text>{t('text_for')}</Typography.Text>
</Space>
</VerticalLine>
<VerticalLine>
<Space direction="horizontal" align="center">
<Form.Item noStyle name={['condition', 'requireMinPoints']}>
<Checkbox
checked={alertDef?.condition?.requireMinPoints}
onChange={(e): void => {
setAlertDef({
...alertDef,
condition: {
...alertDef.condition,
requireMinPoints: e.target.checked,
},
});
}}
/>
</Form.Item>
<Typography.Text>{t('text_require_min_points')}</Typography.Text>
<Form.Item noStyle name={['condition', 'requiredNumPoints']}>
<InputNumber
min={1}
value={alertDef?.condition?.requiredNumPoints}
onChange={(value): void => {
setAlertDef({
...alertDef,
condition: {
...alertDef.condition,
requiredNumPoints: Number(value) || 0,
},
});
}}
type="number"
onWheel={(e): void => e.currentTarget.blur()}
/>
</Form.Item>
<Typography.Text>{t('text_num_points')}</Typography.Text>
</Space>
</VerticalLine>
</Space>
</Collapse.Panel>
</Collapse>

View File

@ -0,0 +1,5 @@
.long-text-tooltip {
max-width: 500px;
max-height: 500px;
overflow-y: auto;
}

View File

@ -1,3 +1,5 @@
import './GridTableComponent.styles.scss';
import { ExclamationCircleFilled } from '@ant-design/icons';
import { Space, Tooltip } from 'antd';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
@ -5,6 +7,7 @@ import { Events } from 'constants/events';
import { QueryTable } from 'container/QueryTable';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { cloneDeep, get, isEmpty } from 'lodash-es';
import LineClampedText from 'periscope/components/LineClampedText/LineClampedText';
import { memo, ReactNode, useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { eventEmitter } from 'utils/getEventEmitter';
@ -116,7 +119,16 @@ function GridTableComponent({
}
>
<Space>
{text}
<LineClampedText
text={text}
lines={3}
tooltipProps={{
placement: 'right',
autoAdjustOverflow: true,
overlayClassName: 'long-text-tooltip',
}}
/>
{hasMultipleMatches && (
<Tooltip title={t('this_value_satisfies_multiple_thresholds')}>
<ExclamationCircleFilled className="value-graph-icon" />
@ -127,7 +139,19 @@ function GridTableComponent({
);
}
}
return <div>{text}</div>;
return (
<div>
<LineClampedText
text={text}
lines={3}
tooltipProps={{
placement: 'right',
autoAdjustOverflow: true,
overlayClassName: 'long-text-tooltip',
}}
/>
</div>
);
},
}));

View File

@ -63,8 +63,7 @@
height: 40px;
justify-content: end;
padding: 0 8px;
margin-top: 12px;
margin-bottom: 2px;
margin: 12px 0 2px;
}
}

View File

@ -266,14 +266,22 @@ exports[`Table panel wrappper tests table should render fine with the query resp
class="ant-table-cell"
>
<div>
demo-app
<div
class="line-clamped-text"
>
demo-app
</div>
</div>
</td>
<td
class="ant-table-cell"
>
<div>
4.35 s
<div
class="line-clamped-text"
>
4.35 s
</div>
</div>
</td>
</tr>
@ -284,14 +292,22 @@ exports[`Table panel wrappper tests table should render fine with the query resp
class="ant-table-cell"
>
<div>
customer
<div
class="line-clamped-text"
>
customer
</div>
</div>
</td>
<td
class="ant-table-cell"
>
<div>
431 ms
<div
class="line-clamped-text"
>
431 ms
</div>
</div>
</td>
</tr>
@ -302,14 +318,22 @@ exports[`Table panel wrappper tests table should render fine with the query resp
class="ant-table-cell"
>
<div>
mysql
<div
class="line-clamped-text"
>
mysql
</div>
</div>
</td>
<td
class="ant-table-cell"
>
<div>
431 ms
<div
class="line-clamped-text"
>
431 ms
</div>
</div>
</td>
</tr>
@ -320,14 +344,22 @@ exports[`Table panel wrappper tests table should render fine with the query resp
class="ant-table-cell"
>
<div>
frontend
<div
class="line-clamped-text"
>
frontend
</div>
</div>
</td>
<td
class="ant-table-cell"
>
<div>
287 ms
<div
class="line-clamped-text"
>
287 ms
</div>
</div>
</td>
</tr>
@ -338,14 +370,22 @@ exports[`Table panel wrappper tests table should render fine with the query resp
class="ant-table-cell"
>
<div>
driver
<div
class="line-clamped-text"
>
driver
</div>
</div>
</td>
<td
class="ant-table-cell"
>
<div>
230 ms
<div
class="line-clamped-text"
>
230 ms
</div>
</div>
</td>
</tr>
@ -356,14 +396,22 @@ exports[`Table panel wrappper tests table should render fine with the query resp
class="ant-table-cell"
>
<div>
route
<div
class="line-clamped-text"
>
route
</div>
</div>
</td>
<td
class="ant-table-cell"
>
<div>
66.4 ms
<div
class="line-clamped-text"
>
66.4 ms
</div>
</div>
</td>
</tr>
@ -374,14 +422,22 @@ exports[`Table panel wrappper tests table should render fine with the query resp
class="ant-table-cell"
>
<div>
redis
<div
class="line-clamped-text"
>
redis
</div>
</div>
</td>
<td
class="ant-table-cell"
>
<div>
31.3 ms
<div
class="line-clamped-text"
>
31.3 ms
</div>
</div>
</td>
</tr>

View File

@ -3,10 +3,6 @@
height: 100%;
position: relative;
z-index: 1;
&.docked {
width: 240px;
}
}
.sideNav {
@ -229,39 +225,6 @@
display: block;
}
}
&.docked {
flex: 0 0 240px;
max-width: 240px;
min-width: 240px;
width: 240px;
.secondary-nav-items {
width: 240px;
}
.brand {
justify-content: space-between;
}
.get-started-nav-items {
.get-started-btn {
justify-content: flex-start;
}
}
.collapse-expand-handlers {
display: block;
}
.nav-item-label {
display: block;
}
.nav-item-beta {
display: block;
}
}
}
.lightMode {

View File

@ -3,7 +3,7 @@
import './SideNav.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, Tooltip } from 'antd';
import { Button } from 'antd';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import { FeatureKeys } from 'constants/features';
@ -16,9 +16,6 @@ import history from 'lib/history';
import {
AlertTriangle,
CheckSquare,
ChevronLeftCircle,
ChevronRightCircle,
PanelRight,
RocketIcon,
UserCircle,
} from 'lucide-react';
@ -55,13 +52,9 @@ interface UserManagementMenuItems {
function SideNav({
licenseData,
isFetching,
onCollapse,
collapsed,
}: {
licenseData: any;
isFetching: boolean;
onCollapse: () => void;
collapsed: boolean;
}): JSX.Element {
const [menuItems, setMenuItems] = useState(defaultMenuItems);
@ -330,8 +323,6 @@ function SideNav({
};
useEffect(() => {
registerShortcut(GlobalShortcuts.SidebarCollapse, onCollapse);
registerShortcut(GlobalShortcuts.NavigateToServices, () =>
onClickHandler(ROUTES.APPLICATION, null),
);
@ -359,7 +350,6 @@ function SideNav({
);
return (): void => {
deregisterShortcut(GlobalShortcuts.SidebarCollapse);
deregisterShortcut(GlobalShortcuts.NavigateToServices);
deregisterShortcut(GlobalShortcuts.NavigateToTraces);
deregisterShortcut(GlobalShortcuts.NavigateToLogs);
@ -368,11 +358,11 @@ function SideNav({
deregisterShortcut(GlobalShortcuts.NavigateToExceptions);
deregisterShortcut(GlobalShortcuts.NavigateToMessagingQueues);
};
}, [deregisterShortcut, onClickHandler, onCollapse, registerShortcut]);
}, [deregisterShortcut, onClickHandler, registerShortcut]);
return (
<div className={cx('sidenav-container', !collapsed ? 'docked' : '')}>
<div className={cx('sideNav', !collapsed ? 'docked' : '')}>
<div className={cx('sidenav-container')}>
<div className={cx('sideNav')}>
<div className="brand">
<div className="brand-company-meta">
<div
@ -392,17 +382,6 @@ function SideNav({
<div className="license tag nav-item-label">{licenseTag}</div>
)}
</div>
<Tooltip
title={collapsed ? 'Dock Sidebar' : 'Undock Sidebar'}
placement="right"
>
<Button
className="periscope-btn nav-item-label dockBtn"
icon={<PanelRight size={16} />}
onClick={onCollapse}
/>
</Tooltip>
</div>
{isCloudUserVal && (
@ -504,14 +483,6 @@ function SideNav({
}}
/>
)}
<div className="collapse-expand-handlers" onClick={onCollapse}>
{collapsed ? (
<ChevronRightCircle size={18} />
) : (
<ChevronLeftCircle size={18} />
)}
</div>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { Button, Modal, Tabs, Tooltip, Typography } from 'antd';
import { Button, Modal, Row, Tabs, Tooltip, Typography } from 'antd';
import Editor from 'components/Editor';
import { StyledSpace } from 'components/Styled';
import { QueryParams } from 'constants/query';
@ -6,7 +6,8 @@ import ROUTES from 'constants/routes';
import { useIsDarkMode } from 'hooks/useDarkMode';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { useState } from 'react';
import { PanelRight } from 'lucide-react';
import { Dispatch, SetStateAction, useState } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
@ -28,6 +29,7 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
firstSpanStartTime,
traceStartTime = minTime,
traceEndTime = maxTime,
setCollapsed,
} = props;
const { id: traceId } = useParams<Params>();
@ -96,14 +98,14 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
styledclass={[styles.selectedSpanDetailsContainer, styles.overflow]}
direction="vertical"
>
<Typography.Text
strong
style={{
marginTop: '16px',
}}
>
Details for selected Span
</Typography.Text>
<Row align="middle" justify="space-between">
<Typography.Text strong>Details for selected Span</Typography.Text>
<Button
className="periscope-btn nav-item-label expand-collapse-btn"
icon={<PanelRight size={16} />}
onClick={(): void => setCollapsed((prev) => !prev)}
/>
</Row>
<Typography.Text style={{ fontWeight: 700 }}>Service</Typography.Text>
@ -170,6 +172,7 @@ interface SelectedSpanDetailsProps {
firstSpanStartTime: number;
traceStartTime?: number;
traceEndTime?: number;
setCollapsed: Dispatch<SetStateAction<boolean>>;
}
SelectedSpanDetails.defaultProps = {

View File

@ -20,4 +20,10 @@
background-color: white !important;
}
}
.expand-collapse-btn {
padding: 4px;
height: auto;
width: auto;
}
}

View File

@ -22,6 +22,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
import { spanServiceNameToColorMapping } from 'lib/getRandomColor';
import history from 'lib/history';
import { map } from 'lodash-es';
import { PanelRight } from 'lucide-react';
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
import { useEffect, useMemo, useState } from 'react';
import { ITraceForest, PayloadProps } from 'types/api/trace/getTraceItem';
@ -267,14 +268,21 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
collapsed={collapsed}
reverseArrow
width={300}
collapsedWidth={40}
collapsedWidth={48}
defaultCollapsed
onCollapse={(value): void => setCollapsed(value)}
trigger={null}
data-testid="span-details-sider"
>
{!collapsed && (
<StyledCol styledclass={[styles.selectedSpanDetailContainer]}>
<StyledCol styledclass={[styles.selectedSpanDetailContainer]}>
{collapsed ? (
<Button
className="periscope-btn nav-item-label expand-collapse-btn"
icon={<PanelRight size={16} />}
onClick={(): void => setCollapsed((prev) => !prev)}
/>
) : (
<SelectedSpanDetails
setCollapsed={setCollapsed}
firstSpanStartTime={firstSpanStartTime}
traceStartTime={traceStartTime}
traceEndTime={traceEndTime}
@ -287,8 +295,8 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
.filter(Boolean)
.find((tree) => tree)}
/>
</StyledCol>
)}
)}
</StyledCol>
</Sider>
</StyledRow>
);

View File

@ -59,6 +59,8 @@ export const selectedSpanDetailContainer = css`
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 12px;
`;
/**

View File

@ -14,9 +14,8 @@ import { Pagination } from 'hooks/queryPagination';
import useDragColumns from 'hooks/useDragColumns';
import { getDraggedColumns } from 'hooks/useDragColumns/utils';
import useUrlQueryData from 'hooks/useUrlQueryData';
import history from 'lib/history';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { HTMLAttributes, memo, useCallback, useMemo } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { DataSource } from 'types/common/queryBuilder';
@ -25,7 +24,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import { TracesLoading } from '../TraceLoading/TraceLoading';
import { defaultSelectedColumns, PER_PAGE_OPTIONS } from './configs';
import { Container, ErrorText, tableStyles } from './styles';
import { getListColumns, getTraceLink, transformDataWithDate } from './utils';
import { getListColumns, transformDataWithDate } from './utils';
interface ListViewProps {
isFilterApplied: boolean;
@ -108,21 +107,6 @@ function ListView({ isFilterApplied }: ListViewProps): JSX.Element {
[queryTableData],
);
const handleRow = useCallback(
(record: RowData): HTMLAttributes<RowData> => ({
onClick: (event): void => {
event.preventDefault();
event.stopPropagation();
if (event.metaKey || event.ctrlKey) {
window.open(getTraceLink(record), '_blank');
} else {
history.push(getTraceLink(record));
}
},
}),
[],
);
const handleDragColumn = useCallback(
(fromIndex: number, toIndex: number) =>
onDragColumns(columns, fromIndex, toIndex),
@ -169,7 +153,6 @@ function ListView({ isFilterApplied }: ListViewProps): JSX.Element {
style={tableStyles}
dataSource={transformedQueryTableData}
columns={columns}
onRow={handleRow}
onDragColumn={handleDragColumn}
/>
)}

View File

@ -47,11 +47,11 @@ export const getListColumns = (
key: 'date',
title: 'Timestamp',
width: 145,
render: (item): JSX.Element => {
render: (value, item): JSX.Element => {
const date =
typeof item === 'string'
? dayjs(item).format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(item / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
typeof value === 'string'
? dayjs(value).format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(value / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
return (
<BlockLink to={getTraceLink(item)}>
<Typography.Text>{date}</Typography.Text>
@ -67,10 +67,10 @@ export const getListColumns = (
dataIndex: key,
key: `${key}-${dataType}-${type}`,
width: 145,
render: (value): JSX.Element => {
render: (value, item): JSX.Element => {
if (value === '') {
return (
<BlockLink to={getTraceLink(value)}>
<BlockLink to={getTraceLink(item)}>
<Typography data-testid={key}>N/A</Typography>
</BlockLink>
);
@ -78,7 +78,7 @@ export const getListColumns = (
if (key === 'httpMethod' || key === 'responseStatusCode') {
return (
<BlockLink to={getTraceLink(value)}>
<BlockLink to={getTraceLink(item)}>
<Tag data-testid={key} color="magenta">
{value}
</Tag>
@ -88,14 +88,14 @@ export const getListColumns = (
if (key === 'durationNano') {
return (
<BlockLink to={getTraceLink(value)}>
<BlockLink to={getTraceLink(item)}>
<Typography data-testid={key}>{getMs(value)}ms</Typography>
</BlockLink>
);
}
return (
<BlockLink to={getTraceLink(value)}>
<BlockLink to={getTraceLink(item)}>
<Typography data-testid={key}>{value}</Typography>
</BlockLink>
);

View File

@ -52,8 +52,7 @@
height: 40px;
justify-content: end;
padding: 0 8px;
margin-top: 12px;
margin-bottom: 2px;
margin: 12px 0 2px;
}
}

View File

@ -49,6 +49,7 @@
/>
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
<meta name="robots" content="noindex">
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

View File

@ -17,6 +17,7 @@ import AlertHistory from 'container/AlertHistory';
import { TIMELINE_TABLE_PAGE_SIZE } from 'container/AlertHistory/constants';
import { AlertDetailsTab, TimelineFilter } from 'container/AlertHistory/types';
import { urlKey } from 'container/AllError/utils';
import { RelativeTimeMap } from 'container/TopNav/DateTimeSelection/config';
import useAxiosError from 'hooks/useAxiosError';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
@ -31,9 +32,7 @@ import PaginationInfoText from 'periscope/components/PaginationInfoText/Paginati
import { useAlertRule } from 'providers/Alert';
import { useCallback, useMemo } from 'react';
import { useMutation, useQuery, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
import { generatePath, useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
AlertDef,
@ -44,7 +43,6 @@ import {
AlertRuleTopContributorsPayload,
} from 'types/api/alerts/def';
import { PayloadProps } from 'types/api/alerts/get';
import { GlobalReducer } from 'types/reducer/globalTime';
import { nanoToMilli } from 'utils/timeUtils';
export const useAlertHistoryQueryParams = (): {
@ -56,11 +54,10 @@ export const useAlertHistoryQueryParams = (): {
} => {
const params = useUrlQuery();
const globalTime = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const startTime = params.get(QueryParams.startTime);
const endTime = params.get(QueryParams.endTime);
const relativeTime =
params.get(QueryParams.relativeTime) ?? RelativeTimeMap['6hr'];
const intStartTime = parseInt(startTime || '0', 10);
const intEndTime = parseInt(endTime || '0', 10);
@ -69,8 +66,8 @@ export const useAlertHistoryQueryParams = (): {
const { maxTime, minTime } = useMemo(() => {
if (hasStartAndEndParams)
return GetMinMax('custom', [intStartTime, intEndTime]);
return GetMinMax(globalTime.selectedTime);
}, [hasStartAndEndParams, intStartTime, intEndTime, globalTime.selectedTime]);
return GetMinMax(relativeTime);
}, [hasStartAndEndParams, intStartTime, intEndTime, relativeTime]);
const ruleId = params.get(QueryParams.ruleId);

View File

@ -10,8 +10,6 @@ import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
import {
Book,
Cable,
Calendar,
CreditCard,
Github,
MessageSquare,
@ -78,22 +76,6 @@ const supportChannels = [
url: '',
btnText: 'Launch chat',
},
{
key: 'schedule_call',
name: 'Schedule a call',
icon: <Calendar />,
title: 'Schedule a call with the founders.',
url: 'https://calendly.com/vishal-signoz/30min',
btnText: 'Schedule call',
},
{
key: 'slack_connect',
name: 'Slack Connect',
icon: <Cable />,
title: 'Get a dedicated support channel for your team.',
url: '',
btnText: 'Request Slack connect',
},
];
export default function Support(): JSX.Element {
@ -122,20 +104,6 @@ export default function Support(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleSlackConnectRequest = (): void => {
const recipient = 'support@signoz.io';
const subject = 'Slack Connect Request';
const body = `I'd like to request a dedicated Slack Connect channel for me and my team. Users (emails) to include besides mine:`;
// Create the mailto link
const mailtoLink = `mailto:${recipient}?subject=${encodeURIComponent(
subject,
)}&body=${encodeURIComponent(body)}`;
// Open the default email client
window.location.href = mailtoLink;
};
const isPremiumChatSupportEnabled =
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
@ -214,15 +182,11 @@ export default function Support(): JSX.Element {
case channelsMap.documentation:
case channelsMap.github:
case channelsMap.slack_community:
case channelsMap.schedule_call:
handleChannelWithRedirects(channel.url);
break;
case channelsMap.chat:
handleChat();
break;
case channelsMap.slack_connect:
handleSlackConnectRequest();
break;
default:
handleChannelWithRedirects('https://signoz.io/slack');
break;

View File

@ -139,9 +139,7 @@ describe('TraceDetail', () => {
const slider = await findByTestId('span-details-sider');
expect(slider).toBeInTheDocument();
fireEvent.click(
slider.querySelector('.ant-layout-sider-trigger') as HTMLElement,
);
fireEvent.click(slider.querySelector('.expand-collapse-btn') as HTMLElement);
expect(queryByText('Details for selected Span')).not.toBeInTheDocument();
});

View File

@ -1,6 +1,7 @@
.trace-explorer-header {
display: flex;
justify-content: space-between;
align-items: center;
.trace-explorer-run-query {
display: flex;

View File

@ -32,4 +32,5 @@ function CustomerStoryCard({
</a>
);
}
export default CustomerStoryCard;

View File

@ -1,4 +1,5 @@
$light-theme: 'lightMode';
$dark-theme: 'darkMode';
@keyframes gradientFlow {
0% {
@ -147,6 +148,34 @@ $light-theme: 'lightMode';
animation: gradientFlow 24s ease infinite;
margin-bottom: 18px;
}
&__faq-container {
width: 100%;
.ant-collapse,
.ant-collapse-item,
.ant-collapse-content-active {
.#{$dark-theme} & {
border-color: var(--bg-slate-400);
}
}
}
&__customer-stories {
&__left-container,
&__right-container {
display: flex;
flex-direction: column;
}
&__left-container {
align-items: flex-end;
}
&__right-container {
align-items: flex-start;
}
}
}
.contact-us {

View File

@ -54,6 +54,25 @@ export default function WorkspaceBlocked(): JSX.Element {
data: licensesData,
} = useLicense();
useEffect((): void => {
logEvent('Workspace Blocked: Screen Viewed', {});
}, []);
const handleContactUsClick = (): void => {
logEvent('Workspace Blocked: Contact Us Clicked', {});
};
const handleTabClick = (key: string): void => {
logEvent('Workspace Blocked: Screen Tabs Clicked', { tabKey: key });
};
const handleCollapseChange = (key: string | string[]): void => {
const lastKey = Array.isArray(key) ? key.slice(-1)[0] : key;
logEvent('Workspace Blocked: Screen Tab FAQ Item Clicked', {
panelKey: lastKey,
});
};
useEffect(() => {
if (!isFetchingLicenseData) {
const shouldBlockWorkspace = licensesData?.payload?.workSpaceBlock;
@ -135,7 +154,7 @@ export default function WorkspaceBlocked(): JSX.Element {
const tabItems: TabsProps['items'] = [
{
key: '1',
key: 'whyChooseSignoz',
label: t('whyChooseSignoz'),
children: (
<Row align="middle" justify="center">
@ -182,13 +201,23 @@ export default function WorkspaceBlocked(): JSX.Element {
),
},
{
key: '2',
key: 'youAreInGoodCompany',
label: t('youAreInGoodCompany'),
children: (
<Row gutter={[24, 16]} justify="center">
{/* #FIXME: please suggest if there is any better way to loop in different columns to get the masonry layout */}
<Col span={10}>{renderCustomerStories((index) => index % 2 === 0)}</Col>
<Col span={10}>{renderCustomerStories((index) => index % 2 !== 0)}</Col>
<Col
span={10}
className="workspace-locked__customer-stories__left-container"
>
{renderCustomerStories((index) => index % 2 === 0)}
</Col>
<Col
span={10}
className="workspace-locked__customer-stories__right-container"
>
{renderCustomerStories((index) => index % 2 !== 0)}
</Col>
{isAdmin && (
<Col span={24}>
<Flex justify="center">
@ -214,13 +243,21 @@ export default function WorkspaceBlocked(): JSX.Element {
// children: 'Our Pricing',
// },
{
key: '4',
key: 'faqs',
label: t('faqs'),
children: (
<Row align="middle" justify="center">
<Col span={18}>
<Space size="large" direction="vertical">
<Collapse items={faqData} defaultActiveKey={['1']} />
<Col span={12}>
<Space
size="large"
direction="vertical"
className="workspace-locked__faq-container"
>
<Collapse
items={faqData}
defaultActiveKey={['signoz-cloud-vs-community']}
onChange={handleCollapseChange}
/>
{isAdmin && (
<Button
type="primary"
@ -258,6 +295,7 @@ export default function WorkspaceBlocked(): JSX.Element {
size="middle"
href="mailto:cloud-support@signoz.io"
role="button"
onClick={handleContactUsClick}
>
Contact Us
</Button>
@ -324,7 +362,7 @@ export default function WorkspaceBlocked(): JSX.Element {
loading={isLoading}
onClick={handleUpdateCreditCard}
>
continue my journey
Continue my Journey
</Button>
</Col>
<Col>
@ -340,9 +378,13 @@ export default function WorkspaceBlocked(): JSX.Element {
</Row>
)}
<Flex justify="center" className="workspace-locked__tabs">
<Tabs items={tabItems} defaultActiveKey="2" />
</Flex>
<div className="workspace-locked__tabs">
<Tabs
items={tabItems}
defaultActiveKey="youAreInGoodCompany"
onTabClick={handleTabClick}
/>
</div>
</>
)}
</div>

View File

@ -42,7 +42,7 @@ export const enterpriseGradeValuesData = [
export const customerStoriesData = [
{
key: 'c-story-1',
key: 'story-subomi-oluwalana',
avatar: 'https://signoz.io/img/users/subomi-oluwalana.webp',
personName: 'Subomi Oluwalana',
role: 'Founder & CEO at Convoy',
@ -53,7 +53,7 @@ export const customerStoriesData = [
'https://www.linkedin.com/feed/update/urn:li:activity:7212117589068591105/',
},
{
key: 'c-story-2',
key: 'story-dhruv-garg',
avatar: 'https://signoz.io/img/users/dhruv-garg.webp',
personName: 'Dhruv Garg',
role: 'Tech Lead at Nudge',
@ -64,7 +64,7 @@ export const customerStoriesData = [
'https://www.linkedin.com/posts/dhruv-garg79_signoz-docker-kubernetes-activity-7205163679028240384-Otlb/',
},
{
key: 'c-story-3',
key: 'story-vivek-bhakta',
avatar: 'https://signoz.io/img/users/vivek-bhakta.webp',
personName: 'Vivek Bhakta',
role: 'CTO at Wombo AI',
@ -74,7 +74,7 @@ export const customerStoriesData = [
link: 'https://x.com/notorious_VB/status/1701773119696904242',
},
{
key: 'c-story-4',
key: 'story-pranay-narang',
avatar: 'https://signoz.io/img/users/pranay-narang.webp',
personName: 'Pranay Narang',
role: 'Engineering at Azodha',
@ -84,7 +84,7 @@ export const customerStoriesData = [
link: 'https://x.com/PranayNarang/status/1676247073396752387',
},
{
key: 'c-story-4',
key: 'story-Sheheryar-Sewani',
avatar: 'https://signoz.io/img/users/shey.webp',
personName: 'Sheheryar Sewani',
role: 'Seasoned Rails Dev & Founder',
@ -95,7 +95,7 @@ export const customerStoriesData = [
'https://www.linkedin.com/feed/update/urn:li:activity:7181011853915926528/',
},
{
key: 'c-story-5',
key: 'story-daniel-schell',
avatar: 'https://signoz.io/img/users/daniel.webp',
personName: 'Daniel Schell',
role: 'Founder & CTO at Airlockdigital',
@ -115,7 +115,7 @@ export const customerStoriesData = [
link: 'https://x.com/gofrendiasgard/status/1680139003658641408',
},
{
key: 'c-story-7',
key: 'story-anselm-eickhoff',
avatar: 'https://signoz.io/img/users/anselm.jpg',
personName: 'Anselm Eickhoff',
role: 'Software Architect',
@ -129,26 +129,26 @@ export const customerStoriesData = [
export const faqData = [
{
key: '1',
key: 'signoz-cloud-vs-community',
label:
'What is the difference between SigNoz Cloud(Teams) and Community Edition?',
children:
'You can self-host and manage the community edition yourself. You should choose SigNoz Cloud if you dont want to worry about managing the SigNoz cluster. There are some exclusive features like SSO & SAML support, which come with SigNoz cloud offering. Our team also offers support on the initial configuration of dashboards & alerts and advises on best practices for setting up your observability stack in the SigNoz cloud offering.',
},
{
key: '2',
key: 'calc-for-metrics',
label: 'How are number of samples calculated for metrics pricing?',
children:
"If a timeseries sends data every 30s, then it will generate 2 samples per min. So, if you have 10,000 time series sending data every 30s then you will be sending 20,000 samples per min to SigNoz. This will be around 864 mn samples per month and would cost 86.4 USD/month. Here's an explainer video on how metrics pricing is calculated - Link: https://vimeo.com/973012522",
},
{
key: '3',
key: 'enterprise-support-plans',
label: 'Do you offer enterprise support plans?',
children:
'Yes, feel free to reach out to us on hello@signoz.io if you need a dedicated support plan or paid support for setting up your initial SigNoz setup.',
},
{
key: '4',
key: 'who-should-use-enterprise-plans',
label: 'Who should use Enterprise plans?',
children:
'Teams which need enterprise support or features like SSO, Audit logs, etc. may find our enterprise plans valuable.',

View File

@ -1,14 +1,16 @@
import './LineClampedText.styles.scss';
import { Tooltip } from 'antd';
import { Tooltip, TooltipProps } from 'antd';
import { useEffect, useRef, useState } from 'react';
function LineClampedText({
text,
lines,
tooltipProps,
}: {
text: string;
lines?: number;
tooltipProps?: TooltipProps;
}): JSX.Element {
const [isOverflowing, setIsOverflowing] = useState(false);
const textRef = useRef<HTMLDivElement>(null);
@ -42,11 +44,22 @@ function LineClampedText({
</div>
);
return isOverflowing ? <Tooltip title={text}>{content}</Tooltip> : content;
return isOverflowing ? (
<Tooltip
title={text}
// eslint-disable-next-line react/jsx-props-no-spreading
{...tooltipProps}
>
{content}
</Tooltip>
) : (
content
);
}
LineClampedText.defaultProps = {
lines: 1,
tooltipProps: {},
};
export default LineClampedText;

View File

@ -1 +0,0 @@
export * from './sideBarCollapse';

View File

@ -1,16 +0,0 @@
import setLocalStorageKey from 'api/browser/localstorage/set';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
export const sideBarCollapse = (
collapseState: boolean,
): ((dispatch: Dispatch<AppActions>) => void) => {
setLocalStorageKey(IS_SIDEBAR_COLLAPSED, `${collapseState}`);
return (dispatch: Dispatch<AppActions>): void => {
dispatch({
type: 'SIDEBAR_COLLAPSE',
payload: collapseState,
});
};
};

View File

@ -1,4 +1,3 @@
export * from './app';
export * from './global';
export * from './metrics';
export * from './serviceMap';

View File

@ -1,11 +1,9 @@
import getLocalStorageKey from 'api/browser/localstorage/get';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import { LOCALSTORAGE } from 'constants/localStorage';
import { getInitialUserTokenRefreshToken } from 'store/utils';
import {
AppAction,
LOGGED_IN,
SIDEBAR_COLLAPSE,
UPDATE_CONFIGS,
UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION,
@ -44,7 +42,6 @@ const getInitialUser = (): User | null => {
const InitialValue: InitialValueTypes = {
isLoggedIn: getLocalStorageKey(LOCALSTORAGE.IS_LOGGED_IN) === 'true',
isSideBarCollapsed: getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
currentVersion: '',
latestVersion: '',
featureResponse: {
@ -76,13 +73,6 @@ const appReducer = (
};
}
case SIDEBAR_COLLAPSE: {
return {
...state,
isSideBarCollapsed: action.payload,
};
}
case UPDATE_FEATURE_FLAG_RESPONSE: {
return {
...state,

View File

@ -34,11 +34,6 @@ export interface LoggedInUser {
};
}
export interface SideBarCollapse {
type: typeof SIDEBAR_COLLAPSE;
payload: boolean;
}
export interface UpdateAppVersion {
type: typeof UPDATE_CURRENT_VERSION;
payload: {
@ -137,7 +132,6 @@ export interface UpdateFeatureFlag {
export type AppAction =
| LoggedInUser
| SideBarCollapse
| UpdateAppVersion
| UpdateLatestVersion
| UpdateVersionError

View File

@ -37,6 +37,8 @@ export interface RuleCondition {
selectedQueryName?: string;
alertOnAbsent?: boolean | undefined;
absentFor?: number | undefined;
requireMinPoints?: boolean | undefined;
requiredNumPoints?: number | undefined;
}
export interface Labels {
[key: string]: string;

View File

@ -17,7 +17,6 @@ export interface User {
export default interface AppReducer {
isLoggedIn: boolean;
isSideBarCollapsed: boolean;
currentVersion: string;
latestVersion: string;
isCurrentVersionError: boolean;

View File

@ -1,38 +1,34 @@
{
"cookies": [],
"origins": [
{
"origin": "http://localhost:3301",
"localStorage": [
{
"name": "isSideBarCollapsed",
"value": "false"
},
{
"name": "metricsTimeDurations",
"value": "{}"
},
{
"name": "i18nextLng",
"value": "en-US"
},
{
"name": "reactQueryDevtoolsSortFn",
"value": "\"Status > Last Updated\""
},
{
"name": "AUTH_TOKEN",
"value": "authtoken"
},
{
"name": "IS_LOGGED_IN",
"value": "true"
},
{
"name": "REFRESH_AUTH_TOKEN",
"value": "refreshJwt"
}
]
}
]
}
"cookies": [],
"origins": [
{
"origin": "http://localhost:3301",
"localStorage": [
{
"name": "metricsTimeDurations",
"value": "{}"
},
{
"name": "i18nextLng",
"value": "en-US"
},
{
"name": "reactQueryDevtoolsSortFn",
"value": "\"Status > Last Updated\""
},
{
"name": "AUTH_TOKEN",
"value": "authtoken"
},
{
"name": "IS_LOGGED_IN",
"value": "true"
},
{
"name": "REFRESH_AUTH_TOKEN",
"value": "refreshJwt"
}
]
}
]
}

2
go.mod
View File

@ -30,7 +30,6 @@ require (
github.com/mailru/easyjson v0.7.7
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/minio/minio-go/v6 v6.0.57
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c
github.com/oklog/oklog v0.3.2
github.com/open-telemetry/opamp-go v0.5.0
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.102.0
@ -141,6 +140,7 @@ require (
github.com/minio/sha256-simd v0.1.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect

4
go.sum
View File

@ -64,8 +64,6 @@ 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.1 h1:roM8ugYf4UxaeKKujEeBvoX7ybq3IrS+TB26KiRtIJg=
github.com/SigNoz/prometheus v1.11.1/go.mod h1:uv4mQwZQtx7y4GQ6EdHOi8Wsk07uHNn2XHd1zM85m6I=
github.com/SigNoz/signoz-otel-collector v0.102.2 h1:SmjsBZjMjTVVpuOlfJXlsDJQbdefQP/9Wz3CyzSuZuU=
github.com/SigNoz/signoz-otel-collector v0.102.2/go.mod h1:ISAXYhZenojCWg6CdDJtPMpfS6Zwc08+uoxH25tc6Y0=
github.com/SigNoz/signoz-otel-collector v0.102.10 h1:1zjU31OcRZL6fS0IIag8LA8bdhP4S28dzovDwuOg7Lg=
github.com/SigNoz/signoz-otel-collector v0.102.10/go.mod h1:APoBVD4aRu9vIny1vdzZSi2wPY3elyjHA/I/rh1hKfs=
github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc=
@ -716,8 +714,6 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/srikanthccv/ClickHouse-go-mock v0.8.0 h1:DeeM8XLbTFl6sjYPPwazPEXx7kmRV8TgPFVkt1SqT0Y=
github.com/srikanthccv/ClickHouse-go-mock v0.8.0/go.mod h1:pgJm+apjvi7FHxEdgw1Bt4MRbUYpVxyhKQ/59Wkig24=
github.com/srikanthccv/ClickHouse-go-mock v0.9.0 h1:XKr1Tb7GL1HlifKH874QGR3R6l0e6takXasROUiZawU=
github.com/srikanthccv/ClickHouse-go-mock v0.9.0/go.mod h1:pgJm+apjvi7FHxEdgw1Bt4MRbUYpVxyhKQ/59Wkig24=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@ -742,7 +742,7 @@ func makeRulesManager(
RepoURL: ruleRepoURL,
DBConn: db,
Context: context.Background(),
Logger: nil,
Logger: zap.L(),
DisableRules: disableRules,
FeatureFlags: fm,
Reader: ch,

View File

@ -106,16 +106,18 @@ const (
)
type RuleCondition struct {
CompositeQuery *v3.CompositeQuery `json:"compositeQuery,omitempty" yaml:"compositeQuery,omitempty"`
CompareOp CompareOp `yaml:"op,omitempty" json:"op,omitempty"`
Target *float64 `yaml:"target,omitempty" json:"target,omitempty"`
AlertOnAbsent bool `yaml:"alertOnAbsent,omitempty" json:"alertOnAbsent,omitempty"`
AbsentFor uint64 `yaml:"absentFor,omitempty" json:"absentFor,omitempty"`
MatchType MatchType `json:"matchType,omitempty"`
TargetUnit string `json:"targetUnit,omitempty"`
Algorithm string `json:"algorithm,omitempty"`
Seasonality string `json:"seasonality,omitempty"`
SelectedQuery string `json:"selectedQueryName,omitempty"`
CompositeQuery *v3.CompositeQuery `json:"compositeQuery,omitempty" yaml:"compositeQuery,omitempty"`
CompareOp CompareOp `yaml:"op,omitempty" json:"op,omitempty"`
Target *float64 `yaml:"target,omitempty" json:"target,omitempty"`
AlertOnAbsent bool `yaml:"alertOnAbsent,omitempty" json:"alertOnAbsent,omitempty"`
AbsentFor uint64 `yaml:"absentFor,omitempty" json:"absentFor,omitempty"`
MatchType MatchType `json:"matchType,omitempty"`
TargetUnit string `json:"targetUnit,omitempty"`
Algorithm string `json:"algorithm,omitempty"`
Seasonality string `json:"seasonality,omitempty"`
SelectedQuery string `json:"selectedQueryName,omitempty"`
RequireMinPoints bool `yaml:"requireMinPoints,omitempty" json:"requireMinPoints,omitempty"`
RequiredNumPoints int `yaml:"requiredNumPoints,omitempty" json:"requiredNumPoints,omitempty"`
}
func (rc *RuleCondition) GetSelectedQueryName() string {

View File

@ -8,7 +8,6 @@ import (
"sync"
"time"
"go.signoz.io/signoz/pkg/query-service/common"
"go.signoz.io/signoz/pkg/query-service/converter"
"go.signoz.io/signoz/pkg/query-service/interfaces"
"go.signoz.io/signoz/pkg/query-service/model"
@ -339,11 +338,9 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
var alertSmpl Sample
var shouldAlert bool
var lbls qslabels.Labels
var lblsNormalized qslabels.Labels
for name, value := range series.Labels {
lbls = append(lbls, qslabels.Label{Name: name, Value: value})
lblsNormalized = append(lblsNormalized, qslabels.Label{Name: common.NormalizeLabelName(name), Value: value})
}
series.Points = removeGroupinSetPoints(series)
@ -353,13 +350,20 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
return alertSmpl, false
}
if r.ruleCondition.RequireMinPoints {
if len(series.Points) < r.ruleCondition.RequiredNumPoints {
zap.L().Info("not enough data points to evaluate series, skipping", zap.String("ruleid", r.ID()), zap.Int("numPoints", len(series.Points)), zap.Int("requiredPoints", r.ruleCondition.RequiredNumPoints))
return alertSmpl, false
}
}
switch r.matchType() {
case AtleastOnce:
// If any sample matches the condition, the rule is firing.
if r.compareOp() == ValueIsAbove {
for _, smpl := range series.Points {
if smpl.Value > r.targetVal() {
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lbls}
shouldAlert = true
break
}
@ -367,7 +371,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
} else if r.compareOp() == ValueIsBelow {
for _, smpl := range series.Points {
if smpl.Value < r.targetVal() {
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lbls}
shouldAlert = true
break
}
@ -375,7 +379,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
} else if r.compareOp() == ValueIsEq {
for _, smpl := range series.Points {
if smpl.Value == r.targetVal() {
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lbls}
shouldAlert = true
break
}
@ -383,7 +387,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
} else if r.compareOp() == ValueIsNotEq {
for _, smpl := range series.Points {
if smpl.Value != r.targetVal() {
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lbls}
shouldAlert = true
break
}
@ -391,7 +395,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
} else if r.compareOp() == ValueOutsideBounds {
for _, smpl := range series.Points {
if math.Abs(smpl.Value) >= r.targetVal() {
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lbls}
shouldAlert = true
break
}
@ -400,7 +404,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
case AllTheTimes:
// If all samples match the condition, the rule is firing.
shouldAlert = true
alertSmpl = Sample{Point: Point{V: r.targetVal()}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: r.targetVal()}, Metric: lbls}
if r.compareOp() == ValueIsAbove {
for _, smpl := range series.Points {
if smpl.Value <= r.targetVal() {
@ -416,7 +420,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
minValue = smpl.Value
}
}
alertSmpl = Sample{Point: Point{V: minValue}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: minValue}, Metric: lbls}
}
} else if r.compareOp() == ValueIsBelow {
for _, smpl := range series.Points {
@ -432,7 +436,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
maxValue = smpl.Value
}
}
alertSmpl = Sample{Point: Point{V: maxValue}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: maxValue}, Metric: lbls}
}
} else if r.compareOp() == ValueIsEq {
for _, smpl := range series.Points {
@ -452,7 +456,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
if shouldAlert {
for _, smpl := range series.Points {
if !math.IsInf(smpl.Value, 0) && !math.IsNaN(smpl.Value) {
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lbls}
break
}
}
@ -460,7 +464,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
} else if r.compareOp() == ValueOutsideBounds {
for _, smpl := range series.Points {
if math.Abs(smpl.Value) >= r.targetVal() {
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lbls}
shouldAlert = true
break
}
@ -477,7 +481,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
count++
}
avg := sum / count
alertSmpl = Sample{Point: Point{V: avg}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: avg}, Metric: lbls}
if r.compareOp() == ValueIsAbove {
if avg > r.targetVal() {
shouldAlert = true
@ -509,7 +513,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
}
sum += smpl.Value
}
alertSmpl = Sample{Point: Point{V: sum}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: sum}, Metric: lbls}
if r.compareOp() == ValueIsAbove {
if sum > r.targetVal() {
shouldAlert = true
@ -534,7 +538,7 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
case Last:
// If the last sample matches the condition, the rule is firing.
shouldAlert = false
alertSmpl = Sample{Point: Point{V: series.Points[len(series.Points)-1].Value}, Metric: lblsNormalized, MetricOrig: lbls}
alertSmpl = Sample{Point: Point{V: series.Points[len(series.Points)-1].Value}, Metric: lbls}
if r.compareOp() == ValueIsAbove {
if series.Points[len(series.Points)-1].Value > r.targetVal() {
shouldAlert = true

View File

@ -0,0 +1,64 @@
package rules
import (
"testing"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
)
func TestBaseRule_RequireMinPoints(t *testing.T) {
threshold := 1.0
tests := []struct {
name string
rule *BaseRule
shouldAlert bool
series *v3.Series
}{
{
name: "test should skip if less than min points",
rule: &BaseRule{
ruleCondition: &RuleCondition{
RequireMinPoints: true,
RequiredNumPoints: 4,
},
},
series: &v3.Series{
Points: []v3.Point{
{Value: 1},
{Value: 2},
},
},
shouldAlert: false,
},
{
name: "test should alert if more than min points",
rule: &BaseRule{
ruleCondition: &RuleCondition{
RequireMinPoints: true,
RequiredNumPoints: 4,
CompareOp: ValueIsAbove,
MatchType: AtleastOnce,
Target: &threshold,
},
},
series: &v3.Series{
Points: []v3.Point{
{Value: 1},
{Value: 2},
{Value: 3},
{Value: 4},
},
},
shouldAlert: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, shouldAlert := test.rule.ShouldAlert(*test.series)
if shouldAlert != test.shouldAlert {
t.Errorf("expected shouldAlert to be %v, got %v", test.shouldAlert, shouldAlert)
}
})
}
}

View File

@ -173,7 +173,7 @@ func defaultPrepareTaskFunc(opts PrepareTaskOptions) (Task, error) {
task = newTask(TaskTypeProm, opts.TaskName, taskNamesuffix, time.Duration(opts.Rule.Frequency), rules, opts.ManagerOpts, opts.NotifyFunc, opts.RuleDB)
} else {
return nil, fmt.Errorf("unsupported rule type. Supported types: %s, %s", RuleTypeProm, RuleTypeThreshold)
return nil, fmt.Errorf("unsupported rule type %s. Supported types: %s, %s", opts.Rule.RuleType, RuleTypeProm, RuleTypeThreshold)
}
return task, nil

View File

@ -43,6 +43,7 @@ func NewPromRule(
BaseRule: baseRule,
pqlEngine: pqlEngine,
}
p.logger = logger
query, err := p.getPqlQuery()

View File

@ -17,10 +17,6 @@ type Sample struct {
Metric labels.Labels
// Label keys as-is from the result query.
// The original labels are used to prepare the related{logs, traces} link in alert notification
MetricOrig labels.Labels
IsMissing bool
}

View File

@ -16,6 +16,7 @@ import (
"golang.org/x/text/cases"
"go.signoz.io/signoz/pkg/query-service/common"
"go.signoz.io/signoz/pkg/query-service/utils/times"
)
@ -204,17 +205,47 @@ func NewTemplateExpander(
// AlertTemplateData returns the interface to be used in expanding the template.
func AlertTemplateData(labels map[string]string, value string, threshold string) interface{} {
// This exists here for backwards compatibility.
// The labels map passed in no longer contains the normalized labels.
// To continue supporting the old way of referencing labels, we need to
// add the normalized labels just for the template expander.
// This is done by creating a new map and adding the normalized labels to it.
newLabels := make(map[string]string)
for k, v := range labels {
newLabels[k] = v
newLabels[common.NormalizeLabelName(k)] = v
}
return struct {
Labels map[string]string
Value string
Threshold string
}{
Labels: labels,
Labels: newLabels,
Value: value,
Threshold: threshold,
}
}
// preprocessTemplate preprocesses the template to replace our custom $variable syntax with the correct Go template syntax.
// example, $service.name in the template is replaced with {{index $labels "service.name"}}
// While we could use go template functions to do this, we need to keep the syntax
// consistent across the platform.
// If there is a go template block, it won't be replaced.
// The example for existing go template block is: {{$threshold}} or {{$value}} or any other valid go template syntax.
func (te *TemplateExpander) preprocessTemplate() {
re := regexp.MustCompile(`({{.*?}})|(\$(\w+(?:\.\w+)*))`)
te.text = re.ReplaceAllStringFunc(te.text, func(match string) string {
if strings.HasPrefix(match, "{{") {
// If it's a Go template block, leave it unchanged
return match
}
// Otherwise, it's our custom $variable syntax
path := strings.Split(match[1:], ".")
return "{{index $labels \"" + strings.Join(path, ".") + "\"}}"
})
}
// Funcs adds the functions in fm to the Expander's function map.
// Existing functions will be overwritten in case of conflict.
func (te TemplateExpander) Funcs(fm text_template.FuncMap) {
@ -237,6 +268,8 @@ func (te TemplateExpander) Expand() (result string, resultErr error) {
}
}()
te.preprocessTemplate()
tmpl, err := text_template.New(te.name).Funcs(te.funcMap).Option("missingkey=zero").Parse(te.text)
if err != nil {
return "", fmt.Errorf("error parsing template %v: %v", te.name, err)

View File

@ -0,0 +1,65 @@
package rules
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/query-service/utils/times"
)
func TestTemplateExpander(t *testing.T) {
defs := "{{$labels := .Labels}}{{$value := .Value}}{{$threshold := .Threshold}}"
data := AlertTemplateData(map[string]string{"service.name": "my-service"}, "100", "200")
expander := NewTemplateExpander(context.Background(), defs+"test $service.name", "test", data, times.Time(time.Now().Unix()), nil)
result, err := expander.Expand()
if err != nil {
t.Fatal(err)
}
require.Equal(t, "test my-service", result)
}
func TestTemplateExpander_WithThreshold(t *testing.T) {
defs := "{{$labels := .Labels}}{{$value := .Value}}{{$threshold := .Threshold}}"
data := AlertTemplateData(map[string]string{"service.name": "my-service"}, "200", "100")
expander := NewTemplateExpander(context.Background(), defs+"test $service.name exceeds {{$threshold}} and observed at {{$value}}", "test", data, times.Time(time.Now().Unix()), nil)
result, err := expander.Expand()
if err != nil {
t.Fatal(err)
}
require.Equal(t, "test my-service exceeds 100 and observed at 200", result)
}
func TestTemplateExpanderOldVariableSyntax(t *testing.T) {
defs := "{{$labels := .Labels}}{{$value := .Value}}{{$threshold := .Threshold}}"
data := AlertTemplateData(map[string]string{"service.name": "my-service"}, "200", "100")
expander := NewTemplateExpander(context.Background(), defs+"test {{.Labels.service_name}} exceeds {{$threshold}} and observed at {{$value}}", "test", data, times.Time(time.Now().Unix()), nil)
result, err := expander.Expand()
if err != nil {
t.Fatal(err)
}
require.Equal(t, "test my-service exceeds 100 and observed at 200", result)
}
func TestTemplateExpander_WithAlreadyNormalizedKey(t *testing.T) {
defs := "{{$labels := .Labels}}{{$value := .Value}}{{$threshold := .Threshold}}"
data := AlertTemplateData(map[string]string{"service_name": "my-service"}, "200", "100")
expander := NewTemplateExpander(context.Background(), defs+"test {{.Labels.service_name}} exceeds {{$threshold}} and observed at {{$value}}", "test", data, times.Time(time.Now().Unix()), nil)
result, err := expander.Expand()
if err != nil {
t.Fatal(err)
}
require.Equal(t, "test my-service exceeds 100 and observed at 200", result)
}
func TestTemplateExpander_WithMissingKey(t *testing.T) {
defs := "{{$labels := .Labels}}{{$value := .Value}}{{$threshold := .Threshold}}"
data := AlertTemplateData(map[string]string{"service_name": "my-service"}, "200", "100")
expander := NewTemplateExpander(context.Background(), defs+"test {{.Labels.missing_key}} exceeds {{$threshold}} and observed at {{$value}}", "test", data, times.Time(time.Now().Unix()), nil)
result, err := expander.Expand()
if err != nil {
t.Fatal(err)
}
require.Equal(t, "test exceeds 100 and observed at 200", result)
}

View File

@ -401,7 +401,7 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time) (interface{}, er
}
lb := labels.NewBuilder(smpl.Metric).Del(labels.MetricNameLabel).Del(labels.TemporalityLabel)
resultLabels := labels.NewBuilder(smpl.MetricOrig).Del(labels.MetricNameLabel).Del(labels.TemporalityLabel).Labels()
resultLabels := labels.NewBuilder(smpl.Metric).Del(labels.MetricNameLabel).Del(labels.TemporalityLabel).Labels()
for name, value := range r.labels.Map() {
lb.Set(name, expand(value))
@ -413,7 +413,7 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time) (interface{}, er
annotations := make(labels.Labels, 0, len(r.annotations.Map()))
for name, value := range r.annotations.Map() {
annotations = append(annotations, labels.Label{Name: common.NormalizeLabelName(name), Value: expand(value)})
annotations = append(annotations, labels.Label{Name: name, Value: expand(value)})
}
if smpl.IsMissing {
lb.Set(labels.AlertNameLabel, "[No data] "+r.Name())
@ -423,13 +423,13 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time) (interface{}, er
// is used alert grouping, and we want to group alerts with the same
// label set, but different timestamps, together.
if r.typ == AlertTypeTraces {
link := r.prepareLinksToTraces(ts, smpl.MetricOrig)
link := r.prepareLinksToTraces(ts, smpl.Metric)
if link != "" && r.hostFromSource() != "" {
zap.L().Info("adding traces link to annotations", zap.String("link", fmt.Sprintf("%s/traces-explorer?%s", r.hostFromSource(), link)))
annotations = append(annotations, labels.Label{Name: "related_traces", Value: fmt.Sprintf("%s/traces-explorer?%s", r.hostFromSource(), link)})
}
} else if r.typ == AlertTypeLogs {
link := r.prepareLinksToLogs(ts, smpl.MetricOrig)
link := r.prepareLinksToLogs(ts, smpl.Metric)
if link != "" && r.hostFromSource() != "" {
zap.L().Info("adding logs link to annotations", zap.String("link", fmt.Sprintf("%s/logs/logs-explorer?%s", r.hostFromSource(), link)))
annotations = append(annotations, labels.Label{Name: "related_logs", Value: fmt.Sprintf("%s/logs/logs-explorer?%s", r.hostFromSource(), link)})

View File

@ -1010,7 +1010,7 @@ func TestThresholdRuleLabelNormalization(t *testing.T) {
sample, shoulAlert := rule.ShouldAlert(c.values)
for name, value := range c.values.Labels {
assert.Equal(t, value, sample.Metric.Get(common.NormalizeLabelName(name)))
assert.Equal(t, value, sample.Metric.Get(name))
}
assert.Equal(t, c.expectAlert, shoulAlert, "Test case %d", idx)

View File

@ -158,7 +158,7 @@ services:
command:
[
"-config=/root/config/prometheus.yml",
# "--prefer-delta=true"
"--use-logs-new-schema=true"
]
# ports:
# - "6060:6060" # pprof port
@ -192,7 +192,7 @@ services:
<<: *db-depend
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.10}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@ -205,7 +205,7 @@ services:
# condition: service_healthy
otel-collector:
image: signoz/signoz-otel-collector:0.102.2
image: signoz/signoz-otel-collector:0.102.10
container_name: signoz-otel-collector
command:
[

View File

@ -115,6 +115,7 @@ exporters:
dsn: tcp://clickhouse:9000/signoz_logs
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
timeout: 10s
use_new_schema: true
# logging: {}
service: