mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-21 07:40:28 +01:00
Compare commits
3 Commits
infraM/rem
...
issue_1170
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4744f83cfe | ||
|
|
4147c5c4bd | ||
|
|
e1cb822091 |
7
.github/CODEOWNERS
vendored
7
.github/CODEOWNERS
vendored
@@ -189,6 +189,13 @@ go.mod @therealpandey
|
||||
/frontend/src/container/TriggeredAlerts/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/AnomalyAlertEvaluationView/ @SigNoz/pulse-frontend
|
||||
|
||||
## Notification Channels
|
||||
/frontend/src/pages/ChannelsEdit/ @SigNoz/pulse-frontend
|
||||
/frontend/src/pages/ChannelsNew/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/AllAlertChannels/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/CreateAlertChannels/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/EditAlertChannels/ @SigNoz/pulse-frontend
|
||||
|
||||
## OpenAPI Schema - Generated
|
||||
/frontend/src/api/generated/services/ @therealpandey @vikrantgupta25 @srikanthccv
|
||||
/docs/api/openapi.yml @therealpandey @vikrantgupta25 @srikanthccv
|
||||
|
||||
@@ -141,6 +141,10 @@ querier:
|
||||
flux_interval: 5m
|
||||
# The maximum number of concurrent queries for missing ranges.
|
||||
max_concurrent_queries: 4
|
||||
# When filtering logs by trace_id, clamp the query window to the trace time
|
||||
# range with padding to include slightly delayed log exports. Logs only; set
|
||||
# to 0 to disable.
|
||||
log_trace_id_window_padding: 5m
|
||||
|
||||
##################### TelemetryStore #####################
|
||||
telemetrystore:
|
||||
|
||||
@@ -48,13 +48,14 @@ const config: Config.InitialOptions = {
|
||||
],
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
// TODO: https://github.com/SigNoz/engineering-pod/issues/5334
|
||||
transformIgnorePatterns: [
|
||||
// @chenglou/pretext is ESM-only; @signozhq/ui pulls it in via text-ellipsis.
|
||||
// Pattern 1: allow .pnpm virtual store through (handled by pattern 2), plus root-level ESM packages.
|
||||
'node_modules/(?!(\\.pnpm|lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid|copy-text-to-clipboard)/)',
|
||||
'node_modules/(?!(\\.pnpm|lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid|copy-text-to-clipboard|react-markdown|vfile|vfile-message|unist-util-stringify-position|unified|bail|is-plain-obj|trough|remark-parse|mdast-util-from-markdown|mdast-util-to-string|micromark|micromark-core-commonmark|micromark-extension-gfm|micromark-extension-gfm-autolink-literal|micromark-extension-gfm-footnote|micromark-extension-gfm-strikethrough|micromark-extension-gfm-table|micromark-extension-gfm-tagfilter|micromark-extension-gfm-task-list-item|micromark-factory-destination|micromark-factory-label|micromark-factory-space|micromark-factory-title|micromark-factory-whitespace|micromark-util-character|micromark-util-chunked|micromark-util-classify-character|micromark-util-combine-extensions|micromark-util-decode-numeric-character-reference|micromark-util-decode-string|micromark-util-encode|micromark-util-html-tag-name|micromark-util-normalize-identifier|micromark-util-resolve-all|micromark-util-sanitize-uri|micromark-util-subtokenize|micromark-util-symbol|micromark-util-types|decode-named-character-reference|remark-rehype|mdast-util-to-hast|unist-util-position|trim-lines|unist-util-visit|unist-util-visit-parents|unist-util-is|unist-util-generated|mdast-util-definitions|property-information|hast-util-whitespace|space-separated-tokens|comma-separated-tokens|rehype-raw|hast-util-raw|hast-util-from-parse5|devlop|hastscript|hast-util-parse-selector|vfile-location|web-namespaces|hast-util-to-parse5|zwitch|html-void-elements)/)',
|
||||
// Pattern 2: pnpm virtual store — ignore everything except ESM-only packages.
|
||||
// pnpm encodes scoped packages as @scope+name@version, so match on scope prefix.
|
||||
'node_modules/\\.pnpm/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid|copy-text-to-clipboard)[^/]*/node_modules)',
|
||||
'node_modules/\\.pnpm/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid|copy-text-to-clipboard|react-markdown|vfile|vfile-message|unist-util-stringify-position|unified|bail|is-plain-obj|trough|remark-parse|mdast-util-from-markdown|mdast-util-to-string|micromark|decode-named-character-reference|remark-rehype|mdast-util-to-hast|unist-util-position|trim-lines|unist-util-visit|unist-util-visit-parents|unist-util-is|unist-util-generated|mdast-util-definitions|property-information|hast-util-whitespace|space-separated-tokens|comma-separated-tokens|rehype-raw|hast-util-raw|hast-util-from-parse5|devlop|hastscript|hast-util-parse-selector|vfile-location|web-namespaces|hast-util-to-parse5|zwitch|html-void-elements)[^/]*/node_modules)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"@dnd-kit/modifiers": "7.0.0",
|
||||
"@dnd-kit/sortable": "8.0.0",
|
||||
"@dnd-kit/utilities": "3.2.2",
|
||||
"@grafana/data": "^11.6.14",
|
||||
"@grafana/data": "^11.6.15",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@sentry/react": "10.57.0",
|
||||
"@sentry/vite-plugin": "5.3.0",
|
||||
@@ -79,7 +79,7 @@
|
||||
"event-source-polyfill": "1.0.31",
|
||||
"eventemitter3": "5.0.1",
|
||||
"history": "4.10.1",
|
||||
"http-proxy-middleware": "4.0.0",
|
||||
"http-proxy-middleware": "4.1.1",
|
||||
"http-status-codes": "2.3.0",
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
@@ -231,16 +231,17 @@
|
||||
"xml2js": "0.5.0",
|
||||
"phin": "^3.7.1",
|
||||
"body-parser": "1.20.3",
|
||||
"http-proxy-middleware": "4.0.0",
|
||||
"http-proxy-middleware": "4.1.1",
|
||||
"cross-spawn": "7.0.5",
|
||||
"cookie": "^0.7.1",
|
||||
"serialize-javascript": "6.0.2",
|
||||
"prismjs": "1.30.0",
|
||||
"got": "11.8.5",
|
||||
"form-data": "4.0.4",
|
||||
"form-data": "4.0.6",
|
||||
"brace-expansion": "^2.0.2",
|
||||
"on-headers": "^1.1.0",
|
||||
"tmp": "0.2.4",
|
||||
"js-cookie": "^3.0.7",
|
||||
"tmp": "0.2.7",
|
||||
"vite": "npm:rolldown-vite@7.3.1"
|
||||
}
|
||||
}
|
||||
85
frontend/pnpm-lock.yaml
generated
85
frontend/pnpm-lock.yaml
generated
@@ -12,16 +12,17 @@ overrides:
|
||||
xml2js: 0.5.0
|
||||
phin: ^3.7.1
|
||||
body-parser: 1.20.3
|
||||
http-proxy-middleware: 4.0.0
|
||||
http-proxy-middleware: 4.1.1
|
||||
cross-spawn: 7.0.5
|
||||
cookie: ^0.7.1
|
||||
serialize-javascript: 6.0.2
|
||||
prismjs: 1.30.0
|
||||
got: 11.8.5
|
||||
form-data: 4.0.4
|
||||
form-data: 4.0.6
|
||||
brace-expansion: ^2.0.2
|
||||
on-headers: ^1.1.0
|
||||
tmp: 0.2.4
|
||||
js-cookie: ^3.0.7
|
||||
tmp: 0.2.7
|
||||
vite: npm:rolldown-vite@7.3.1
|
||||
|
||||
importers:
|
||||
@@ -56,8 +57,8 @@ importers:
|
||||
specifier: 3.2.2
|
||||
version: 3.2.2(react@18.2.0)
|
||||
'@grafana/data':
|
||||
specifier: ^11.6.14
|
||||
version: 11.6.14(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
specifier: ^11.6.15
|
||||
version: 11.6.15(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@monaco-editor/react':
|
||||
specifier: ^4.7.0
|
||||
version: 4.7.0(monaco-editor@0.55.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -164,8 +165,8 @@ importers:
|
||||
specifier: 4.10.1
|
||||
version: 4.10.1
|
||||
http-proxy-middleware:
|
||||
specifier: 4.0.0
|
||||
version: 4.0.0
|
||||
specifier: 4.1.1
|
||||
version: 4.1.1
|
||||
http-status-codes:
|
||||
specifier: 2.3.0
|
||||
version: 2.3.0
|
||||
@@ -1636,14 +1637,14 @@ packages:
|
||||
'@gerrit0/mini-shiki@3.23.0':
|
||||
resolution: {integrity: sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==}
|
||||
|
||||
'@grafana/data@11.6.14':
|
||||
resolution: {integrity: sha512-Nsjq1A9m6LbsKsKvOgvAk9Wq7RGjy0V4N9d5YsSnzMwCiw/ov2wblR2bcDpy95uF8KaDTIR2Gf40nJaOYksPMA==}
|
||||
'@grafana/data@11.6.15':
|
||||
resolution: {integrity: sha512-q2Zbjr0N9iEGY/zKHm4Z4X5x64806E17W58y7mnvwc0MlbyGPPVulcp/rWA2Nd190mZeafZQPer9u+MaO+0HUQ==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
|
||||
'@grafana/schema@11.6.14':
|
||||
resolution: {integrity: sha512-YTqgYekb7kiu5NEoQxKF8czJ6QIARmMkCi9cNcynHqYpcDLOv5pg5Q0QtKgiiqHjlYoEeCV6iejdB4hXxzB+VA==}
|
||||
'@grafana/schema@11.6.15':
|
||||
resolution: {integrity: sha512-MPIvGAp9uzkswnH6e+Fmzu+WBTqWMgbv93/8iu56gb+sjCB2LciZLz4KvrPFdw32bWCGSMAGqsML9mgmeJZtGQ==}
|
||||
|
||||
'@humanfs/core@0.19.2':
|
||||
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
|
||||
@@ -5167,8 +5168,8 @@ packages:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
form-data@4.0.4:
|
||||
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
||||
form-data@4.0.6:
|
||||
resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
format@0.2.2:
|
||||
@@ -5381,6 +5382,10 @@ packages:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.4:
|
||||
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hast-util-from-parse5@8.0.1:
|
||||
resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==}
|
||||
|
||||
@@ -5456,8 +5461,8 @@ packages:
|
||||
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
http-proxy-middleware@4.0.0:
|
||||
resolution: {integrity: sha512-wuHwaUtmC0XzJNHqRp41zXtt5ojpHbusXGhq6781VvnjWUYPu7opmOF3eomGNujT07kEOnHWZyV9UZzKimVCKA==}
|
||||
http-proxy-middleware@4.1.1:
|
||||
resolution: {integrity: sha512-KX5ZofGXLFXqFAkQoOWZ+rTtaLTut7m0gyL+QzJrdejtIZ+F4bPPDoe7reISg2+v0CAz5OfVwEJEhty7X+e57g==}
|
||||
engines: {node: ^22.15.0 || ^24.0.0 || >=26.0.0}
|
||||
|
||||
http-status-codes@2.3.0:
|
||||
@@ -5467,8 +5472,8 @@ packages:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
httpxy@0.5.1:
|
||||
resolution: {integrity: sha512-JPhqYiixe1A1I+MXDewWDZqeudBGU8Q9jCHYN8ML+779RQzLjTi78HBvWz4jMxUD6h2/vUL12g4q/mFM0OUw1A==}
|
||||
httpxy@0.5.3:
|
||||
resolution: {integrity: sha512-SMS9V6Sn7VWaS11lYhoAr0ceoaiolTWf4jYdJn0NJhCdKMu9R2H9Fh0LBDWBHQF6HRLI1PmaePYsjanSpE5PEw==}
|
||||
|
||||
human-signals@2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
@@ -6041,8 +6046,8 @@ packages:
|
||||
js-base64@3.7.5:
|
||||
resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==}
|
||||
|
||||
js-cookie@2.2.1:
|
||||
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
|
||||
js-cookie@3.0.8:
|
||||
resolution: {integrity: sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==}
|
||||
|
||||
js-levenshtein@1.1.6:
|
||||
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
|
||||
@@ -8394,8 +8399,8 @@ packages:
|
||||
resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==}
|
||||
engines: {node: ^20.0.0 || >=22.0.0}
|
||||
|
||||
tmp@0.2.4:
|
||||
resolution: {integrity: sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==}
|
||||
tmp@0.2.7:
|
||||
resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==}
|
||||
engines: {node: '>=14.14'}
|
||||
|
||||
tmpl@1.0.5:
|
||||
@@ -10318,10 +10323,10 @@ snapshots:
|
||||
'@shikijs/types': 3.23.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@grafana/data@11.6.14(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
'@grafana/data@11.6.15(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@braintree/sanitize-url': 7.0.1
|
||||
'@grafana/schema': 11.6.14
|
||||
'@grafana/schema': 11.6.15
|
||||
'@types/d3-interpolate': 3.0.1
|
||||
'@types/string-hash': 1.1.3
|
||||
d3-interpolate: 3.0.1
|
||||
@@ -10347,7 +10352,7 @@ snapshots:
|
||||
uplot: 1.6.31
|
||||
xss: 1.0.14
|
||||
|
||||
'@grafana/schema@11.6.14':
|
||||
'@grafana/schema@11.6.15':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
@@ -12886,7 +12891,7 @@ snapshots:
|
||||
axios@1.16.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.4
|
||||
form-data: 4.0.6
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
@@ -13833,7 +13838,7 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.2
|
||||
hasown: 2.0.4
|
||||
|
||||
es-toolkit@1.46.1: {}
|
||||
|
||||
@@ -14031,7 +14036,7 @@ snapshots:
|
||||
dependencies:
|
||||
chardet: 0.7.0
|
||||
iconv-lite: 0.4.24
|
||||
tmp: 0.2.4
|
||||
tmp: 0.2.7
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
@@ -14164,12 +14169,12 @@ snapshots:
|
||||
cross-spawn: 7.0.5
|
||||
signal-exit: 4.1.0
|
||||
|
||||
form-data@4.0.4:
|
||||
form-data@4.0.6:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
hasown: 2.0.2
|
||||
hasown: 2.0.4
|
||||
mime-types: 2.1.35
|
||||
|
||||
format@0.2.2: {}
|
||||
@@ -14248,7 +14253,7 @@ snapshots:
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.2
|
||||
hasown: 2.0.4
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-nonce@1.0.1: {}
|
||||
@@ -14386,6 +14391,10 @@ snapshots:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hasown@2.0.4:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hast-util-from-parse5@8.0.1:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
@@ -14506,10 +14515,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
http-proxy-middleware@4.0.0:
|
||||
http-proxy-middleware@4.1.1:
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
httpxy: 0.5.1
|
||||
httpxy: 0.5.3
|
||||
is-glob: 4.0.3
|
||||
is-plain-obj: 4.1.0
|
||||
micromatch: 4.0.8
|
||||
@@ -14525,7 +14534,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
httpxy@0.5.1: {}
|
||||
httpxy@0.5.3: {}
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
@@ -15339,7 +15348,7 @@ snapshots:
|
||||
|
||||
js-base64@3.7.5: {}
|
||||
|
||||
js-cookie@2.2.1: {}
|
||||
js-cookie@3.0.8: {}
|
||||
|
||||
js-levenshtein@1.1.6: {}
|
||||
|
||||
@@ -15367,7 +15376,7 @@ snapshots:
|
||||
decimal.js: 10.6.0
|
||||
domexception: 4.0.0
|
||||
escodegen: 2.1.0
|
||||
form-data: 4.0.4
|
||||
form-data: 4.0.6
|
||||
html-encoding-sniffer: 3.0.0
|
||||
http-proxy-agent: 5.0.0
|
||||
https-proxy-agent: 5.0.1
|
||||
@@ -17336,7 +17345,7 @@ snapshots:
|
||||
copy-to-clipboard: 3.3.3
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-shallow-equal: 1.0.0
|
||||
js-cookie: 2.2.1
|
||||
js-cookie: 3.0.8
|
||||
nano-css: 5.6.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
@@ -17355,7 +17364,7 @@ snapshots:
|
||||
copy-to-clipboard: 3.3.3
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-shallow-equal: 1.0.0
|
||||
js-cookie: 2.2.1
|
||||
js-cookie: 3.0.8
|
||||
nano-css: 5.6.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
@@ -18103,7 +18112,7 @@ snapshots:
|
||||
|
||||
tinypool@2.1.0: {}
|
||||
|
||||
tmp@0.2.4: {}
|
||||
tmp@0.2.7: {}
|
||||
|
||||
tmpl@1.0.5: {}
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
),
|
||||
[pathname],
|
||||
);
|
||||
const isOldRoute = oldRoutes.indexOf(pathname) > -1;
|
||||
const currentRoute = mapRoutes.get('current');
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
@@ -83,12 +82,36 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
}, [usersData?.data]);
|
||||
|
||||
// Handle old routes - redirect to new routes
|
||||
const isOldRoute = oldRoutes.indexOf(pathname) > -1;
|
||||
if (isOldRoute) {
|
||||
const redirectUrl = oldNewRoutesMapping[pathname];
|
||||
// TODO(H4ad): Remove this after https://github.com/SigNoz/engineering-pod/issues/5322
|
||||
// A mapped target may itself carry a query string (e.g. `/alerts?tab=Channels`).
|
||||
// react-router does not re-parse a `?` embedded in the `pathname` field, so split
|
||||
// it out and merge with the incoming search params.
|
||||
const [redirectPath, redirectSearch = ''] = redirectUrl.split('?');
|
||||
const mergedParams = new URLSearchParams(location.search);
|
||||
new URLSearchParams(redirectSearch).forEach((value, name) => {
|
||||
mergedParams.set(name, value);
|
||||
});
|
||||
const search = mergedParams.toString();
|
||||
return (
|
||||
<Redirect
|
||||
to={{
|
||||
pathname: redirectUrl,
|
||||
pathname: redirectPath,
|
||||
search: search ? `?${search}` : '',
|
||||
hash: location.hash,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/settings/channels/edit/')) {
|
||||
const channelId = pathname.replace('/settings/channels/edit/', '');
|
||||
return (
|
||||
<Redirect
|
||||
to={{
|
||||
pathname: `/alerts/channels/edit/${channelId}`,
|
||||
search: location.search,
|
||||
hash: location.hash,
|
||||
}}
|
||||
|
||||
@@ -73,7 +73,13 @@ const queryClient = new QueryClient({
|
||||
// Component to capture current location for assertions
|
||||
function LocationDisplay(): ReactElement {
|
||||
const location = useLocation();
|
||||
return <div data-testid="location-display">{location.pathname}</div>;
|
||||
return (
|
||||
<>
|
||||
<div data-testid="location-display">{location.pathname}</div>
|
||||
<div data-testid="location-search">{location.search}</div>
|
||||
<div data-testid="location-hash">{location.hash}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper to create mock user
|
||||
@@ -1475,12 +1481,10 @@ describe('PrivateRoute', () => {
|
||||
await assertRedirectsTo(ROUTES.UN_AUTHORIZED);
|
||||
});
|
||||
|
||||
it('should not redirect VIEWER from /settings/channels/new due to route matching order (ALL_CHANNELS matches last)', () => {
|
||||
// Note: This tests the ACTUAL behavior of Private.tsx route matching
|
||||
// CHANNELS_NEW has path '/settings/channels/new' with permission ['ADMIN']
|
||||
// ALL_CHANNELS has path '/settings/channels' with permission ['ADMIN', 'EDITOR', 'VIEWER']
|
||||
// Due to non-exact matching and array order, ALL_CHANNELS matches LAST for '/settings/channels/new'
|
||||
// This is a known limitation - actual permission enforcement happens in the page component
|
||||
it('should redirect VIEWER from /alerts/channels/new (ADMIN only)', async () => {
|
||||
// After moving channels under /alerts, CHANNELS_NEW ('/alerts/channels/new')
|
||||
// is an exact, ADMIN-only route with no overlapping non-exact ALL_CHANNELS
|
||||
// route to match last, so a VIEWER is now correctly redirected.
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.CHANNELS_NEW,
|
||||
appContext: {
|
||||
@@ -1489,8 +1493,7 @@ describe('PrivateRoute', () => {
|
||||
},
|
||||
});
|
||||
|
||||
assertRendersChildren();
|
||||
assertStaysOnRoute(ROUTES.CHANNELS_NEW);
|
||||
await assertRedirectsTo(ROUTES.UN_AUTHORIZED);
|
||||
});
|
||||
|
||||
it('should allow EDITOR to access /get-started route', () => {
|
||||
@@ -1548,4 +1551,60 @@ describe('PrivateRoute', () => {
|
||||
await assertRedirectsTo(ROUTES.UN_AUTHORIZED);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Old channel route redirects', () => {
|
||||
it.each([
|
||||
['/settings/channels', '/alerts', 'tab=Channels'],
|
||||
['/settings/channels/new', '/alerts/channels/new', ''],
|
||||
])(
|
||||
'should redirect %s to %s',
|
||||
async (oldRoute, expectedPath, expectedSearch) => {
|
||||
renderPrivateRoute({
|
||||
initialRoute: oldRoute,
|
||||
appContext: { isLoggedIn: true },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('location-display')).toHaveTextContent(
|
||||
expectedPath,
|
||||
);
|
||||
});
|
||||
|
||||
if (expectedSearch) {
|
||||
const search = screen.getByTestId('location-search').textContent ?? '';
|
||||
const params = new URLSearchParams(search);
|
||||
new URLSearchParams(expectedSearch).forEach((value, name) => {
|
||||
expect(params.get(name)).toBe(value);
|
||||
});
|
||||
} else {
|
||||
expect(screen.getByTestId('location-search')).toHaveTextContent('');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it('should redirect dynamic channel edit route preserving the channel id', async () => {
|
||||
renderPrivateRoute({
|
||||
initialRoute: '/settings/channels/edit/abc123',
|
||||
appContext: { isLoggedIn: true },
|
||||
});
|
||||
|
||||
await assertRedirectsTo('/alerts/channels/edit/abc123');
|
||||
});
|
||||
|
||||
it('should merge incoming query params with the embedded query of the target', async () => {
|
||||
renderPrivateRoute({
|
||||
initialRoute: '/settings/channels?foo=bar',
|
||||
appContext: { isLoggedIn: true },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('location-display')).toHaveTextContent('/alerts');
|
||||
});
|
||||
|
||||
const search = screen.getByTestId('location-search').textContent ?? '';
|
||||
const params = new URLSearchParams(search);
|
||||
expect(params.get('tab')).toBe('Channels');
|
||||
expect(params.get('foo')).toBe('bar');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -142,12 +142,12 @@ export const AlertOverview = Loadable(
|
||||
() => import(/* webpackChunkName: "Alert Overview" */ 'pages/AlertList'),
|
||||
);
|
||||
|
||||
export const CreateAlertChannelAlerts = Loadable(
|
||||
() => import(/* webpackChunkName: "Create Channels" */ 'pages/Settings'),
|
||||
export const ChannelsNew = Loadable(
|
||||
() => import(/* webpackChunkName: "Create Channels" */ 'pages/AlertList'),
|
||||
);
|
||||
|
||||
export const AllAlertChannels = Loadable(
|
||||
() => import(/* webpackChunkName: "All Channels" */ 'pages/Settings'),
|
||||
export const ChannelsEdit = Loadable(
|
||||
() => import(/* webpackChunkName: "Edit Channels" */ 'pages/AlertList'),
|
||||
);
|
||||
|
||||
export const AllErrors = Loadable(
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
AIAssistantPage,
|
||||
AlertHistory,
|
||||
AlertOverview,
|
||||
AllAlertChannels,
|
||||
AllErrors,
|
||||
ApiMonitoring,
|
||||
CreateAlertChannelAlerts,
|
||||
ChannelsEdit,
|
||||
ChannelsNew,
|
||||
CreateNewAlerts,
|
||||
DashboardPage,
|
||||
DashboardsListPage,
|
||||
@@ -269,16 +269,16 @@ const routes: AppRoutes[] = [
|
||||
{
|
||||
path: ROUTES.CHANNELS_NEW,
|
||||
exact: true,
|
||||
component: CreateAlertChannelAlerts,
|
||||
component: ChannelsNew,
|
||||
isPrivate: true,
|
||||
key: 'CHANNELS_NEW',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALL_CHANNELS,
|
||||
path: ROUTES.CHANNELS_EDIT,
|
||||
exact: true,
|
||||
component: AllAlertChannels,
|
||||
component: ChannelsEdit,
|
||||
isPrivate: true,
|
||||
key: 'ALL_CHANNELS',
|
||||
key: 'CHANNELS_EDIT',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALL_ERROR,
|
||||
@@ -534,6 +534,9 @@ export const oldNewRoutesMapping: Record<string, string> = {
|
||||
'/messaging-queues': '/messaging-queues/overview',
|
||||
'/alerts/edit': '/alerts/overview',
|
||||
'/alerts/type-selection': '/alerts/new',
|
||||
// TODO(H4ad): Update this after https://github.com/SigNoz/engineering-pod/issues/5322
|
||||
'/settings/channels': '/alerts?tab=Channels',
|
||||
'/settings/channels/new': '/alerts/channels/new',
|
||||
};
|
||||
export const oldRoutes = Object.keys(oldNewRoutesMapping);
|
||||
|
||||
|
||||
@@ -29,9 +29,10 @@ const ROUTES = {
|
||||
ALERTS_NEW: '/alerts/new',
|
||||
ALERT_HISTORY: '/alerts/history',
|
||||
ALERT_OVERVIEW: '/alerts/overview',
|
||||
ALL_CHANNELS: '/settings/channels',
|
||||
CHANNELS_NEW: '/settings/channels/new',
|
||||
CHANNELS_EDIT: '/settings/channels/edit/:channelId',
|
||||
// TODO(H4ad): Add test to forbidden ? in this map after https://github.com/SigNoz/engineering-pod/issues/5322
|
||||
ALL_CHANNELS: '/alerts?tab=Channels',
|
||||
CHANNELS_NEW: '/alerts/channels/new',
|
||||
CHANNELS_EDIT: '/alerts/channels/edit/:channelId',
|
||||
ALL_ERROR: '/exceptions',
|
||||
ERROR_DETAIL: '/error-detail',
|
||||
VERSION: '/status',
|
||||
|
||||
@@ -94,7 +94,7 @@ describe('resourceRoute', () => {
|
||||
|
||||
it('routes channels to the edit page', () => {
|
||||
expect(resourceRoute(ResourceType.channel, 'channel-uuid-1')).toBe(
|
||||
'/settings/channels/edit/channel-uuid-1',
|
||||
'/alerts/channels/edit/channel-uuid-1',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.alert-channels-container {
|
||||
width: 90%;
|
||||
margin: 12px auto;
|
||||
width: 100%;
|
||||
padding: 0 var(--spacing-8);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
.create-alert-channels-container {
|
||||
width: 90%;
|
||||
margin: 12px auto;
|
||||
|
||||
width: 100%;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
border-radius: 3px;
|
||||
|
||||
@@ -80,7 +80,7 @@ import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
|
||||
|
||||
import { useCmdK } from '../../providers/cmdKProvider';
|
||||
import { routeConfig } from './config';
|
||||
import { getQueryString } from './helper';
|
||||
import { buildNavUrl, getQueryString } from './helper';
|
||||
import {
|
||||
defaultMoreMenuItems,
|
||||
getUserSettingsDropdownMenuItems,
|
||||
@@ -486,12 +486,13 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
const availableParams = routeConfig[key];
|
||||
|
||||
const queryString = getQueryString(availableParams || [], params);
|
||||
const url = buildNavUrl(key, queryString);
|
||||
|
||||
if (pathname !== key) {
|
||||
if (event && isModifierKeyPressed(event)) {
|
||||
openInNewTab(`${key}?${queryString.join('&')}`);
|
||||
openInNewTab(url);
|
||||
} else {
|
||||
history.push(`${key}?${queryString.join('&')}`, {
|
||||
history.push(url, {
|
||||
from: pathname,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,3 +8,14 @@ export const getQueryString = (
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated This should be removed after https://github.com/SigNoz/engineering-pod/issues/5322 is done
|
||||
*/
|
||||
export const buildNavUrl = (key: string, queryString: string[]): string => {
|
||||
if (key.includes('?')) {
|
||||
const extra = queryString.filter(Boolean).join('&');
|
||||
return extra ? `${key}&${extra}` : key;
|
||||
}
|
||||
return `${key}?${queryString.join('&')}`;
|
||||
};
|
||||
|
||||
@@ -337,6 +337,7 @@ export const settingsNavSections: SettingsNavSection[] = [
|
||||
isEnabled: true,
|
||||
itemKey: 'account',
|
||||
},
|
||||
// TODO(@SigNoz/pulse-frontend): https://github.com/SigNoz/engineering-pod/issues/5323
|
||||
{
|
||||
key: ROUTES.ALL_CHANNELS,
|
||||
label: 'Notification Channels',
|
||||
|
||||
@@ -4,14 +4,17 @@ import { Tabs, TabsProps } from 'antd';
|
||||
import ConfigureIcon from 'assets/AlertHistory/ConfigureIcon';
|
||||
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
|
||||
import ROUTES from 'constants/routes';
|
||||
import AllAlertChannels from 'container/AllAlertChannels';
|
||||
import AllAlertRules from 'container/ListAlertRules';
|
||||
import { PlannedDowntime } from 'container/PlannedDowntime/PlannedDowntime';
|
||||
import RoutingPolicies from 'container/RoutingPolicies';
|
||||
import TriggeredAlerts from 'container/TriggeredAlerts';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { GalleryVerticalEnd, Pyramid } from '@signozhq/icons';
|
||||
import { Cable, GalleryVerticalEnd, Pyramid } from '@signozhq/icons';
|
||||
import AlertDetails from 'pages/AlertDetails';
|
||||
import ChannelsEdit from 'pages/ChannelsEdit';
|
||||
import ChannelsNew from 'pages/ChannelsNew';
|
||||
|
||||
import { AlertListSubTabs, AlertListTabs } from './types';
|
||||
|
||||
@@ -26,6 +29,9 @@ function AllAlertList(): JSX.Element {
|
||||
const subTab = urlQuery.get('subTab');
|
||||
const isAlertHistory = location.pathname === ROUTES.ALERT_HISTORY;
|
||||
const isAlertOverview = location.pathname === ROUTES.ALERT_OVERVIEW;
|
||||
const isChannelsNew = location.pathname === ROUTES.CHANNELS_NEW;
|
||||
const isChannelsEdit = location.pathname.startsWith('/alerts/channels/edit/');
|
||||
const isChannelDetails = isChannelsNew || isChannelsEdit;
|
||||
|
||||
const handleConfigurationTabChange = useCallback(
|
||||
(subTab: string): void => {
|
||||
@@ -86,6 +92,22 @@ function AllAlertList(): JSX.Element {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<div className="periscope-tab top-level-tab">
|
||||
<Cable size={14} />
|
||||
Notification Channels
|
||||
</div>
|
||||
),
|
||||
key: AlertListTabs.CHANNELS,
|
||||
children: (
|
||||
<div className="alert-rules-container">
|
||||
{isChannelsNew && <ChannelsNew />}
|
||||
{isChannelsEdit && <ChannelsEdit />}
|
||||
{!isChannelDetails && <AllAlertChannels />}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<div className="periscope-tab top-level-tab">
|
||||
@@ -98,11 +120,21 @@ function AllAlertList(): JSX.Element {
|
||||
},
|
||||
];
|
||||
|
||||
const getActiveKey = (): string => {
|
||||
if (isAlertHistory || isAlertOverview) {
|
||||
return AlertListTabs.ALERT_RULES;
|
||||
}
|
||||
if (isChannelDetails) {
|
||||
return AlertListTabs.CHANNELS;
|
||||
}
|
||||
return tab || AlertListTabs.ALERT_RULES;
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
destroyInactiveTabPane
|
||||
items={items}
|
||||
activeKey={tab || AlertListTabs.ALERT_RULES}
|
||||
activeKey={getActiveKey()}
|
||||
onChange={(tab): void => {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
@@ -120,7 +152,9 @@ function AllAlertList(): JSX.Element {
|
||||
safeNavigate(`/alerts?${queryParams.toString()}`);
|
||||
}}
|
||||
className={`alerts-container ${
|
||||
isAlertHistory || isAlertOverview ? 'alert-details-tabs' : ''
|
||||
isAlertHistory || isAlertOverview || isChannelDetails
|
||||
? 'alert-details-tabs'
|
||||
: ''
|
||||
}`}
|
||||
tabBarExtraContent={
|
||||
<HeaderRightSection
|
||||
|
||||
@@ -7,4 +7,5 @@ export enum AlertListTabs {
|
||||
TRIGGERED_ALERTS = 'TriggeredAlerts',
|
||||
ALERT_RULES = 'AlertRules',
|
||||
CONFIGURATION = 'Configuration',
|
||||
CHANNELS = 'Channels',
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import get from 'api/channels/get';
|
||||
import AlertBreadcrumb from 'components/AlertBreadcrumb';
|
||||
import Spinner from 'components/Spinner';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
ChannelType,
|
||||
MsTeamsChannel,
|
||||
@@ -22,9 +24,9 @@ import './ChannelsEdit.styles.scss';
|
||||
function ChannelsEdit(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Extract channelId from URL pathname since useParams doesn't work in nested routing
|
||||
// Extract channelId from URL pathname
|
||||
const { pathname } = window.location;
|
||||
const channelIdMatch = pathname.match(/\/settings\/channels\/edit\/([^/]+)/);
|
||||
const channelIdMatch = pathname.match(/\/alerts\/channels\/edit\/([^/]+)/);
|
||||
const channelId = channelIdMatch ? channelIdMatch[1] : undefined;
|
||||
|
||||
const { isFetching, isError, data, error } = useQuery<
|
||||
@@ -135,17 +137,25 @@ function ChannelsEdit(): JSX.Element {
|
||||
const target = prepChannelConfig();
|
||||
|
||||
return (
|
||||
<div className="edit-alert-channels-container">
|
||||
<EditAlertChannels
|
||||
{...{
|
||||
initialValue: {
|
||||
...target.channel,
|
||||
type: target.type,
|
||||
name: value.name,
|
||||
},
|
||||
}}
|
||||
<>
|
||||
<AlertBreadcrumb
|
||||
items={[
|
||||
{ title: 'Channels', route: ROUTES.ALL_CHANNELS },
|
||||
{ title: value.name || 'Edit Channel', isLast: true },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="edit-alert-channels-container">
|
||||
<EditAlertChannels
|
||||
{...{
|
||||
initialValue: {
|
||||
...target.channel,
|
||||
type: target.type,
|
||||
name: value.name,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
23
frontend/src/pages/ChannelsNew/index.tsx
Normal file
23
frontend/src/pages/ChannelsNew/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import AlertBreadcrumb from 'components/AlertBreadcrumb';
|
||||
import ROUTES from 'constants/routes';
|
||||
import CreateAlertChannels from 'container/CreateAlertChannels';
|
||||
import { ChannelType } from 'container/CreateAlertChannels/config';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
function ChannelsNew(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<AlertBreadcrumb
|
||||
items={[
|
||||
{ title: 'Channels', route: ROUTES.ALL_CHANNELS },
|
||||
{ title: 'New Channel', isLast: true },
|
||||
]}
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
<CreateAlertChannels preType={ChannelType.Slack} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelsNew;
|
||||
4
frontend/src/pages/ChannelsNew/styles.module.scss
Normal file
4
frontend/src/pages/ChannelsNew/styles.module.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
.content {
|
||||
padding: var(--spacing-8);
|
||||
padding-top: 0px;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import RouteTab from 'components/RouteTab';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { routeConfig } from 'container/SideNav/config';
|
||||
import { getQueryString } from 'container/SideNav/helper';
|
||||
import { buildNavUrl, getQueryString } from 'container/SideNav/helper';
|
||||
import { settingsNavSections } from 'container/SideNav/menuItems';
|
||||
import NavItem from 'container/SideNav/NavItem/NavItem';
|
||||
import { SidebarItem } from 'container/SideNav/sideNav.types';
|
||||
@@ -240,12 +240,13 @@ function SettingsPage(): JSX.Element {
|
||||
const availableParams = routeConfig[key];
|
||||
|
||||
const queryString = getQueryString(availableParams || [], params);
|
||||
const url = buildNavUrl(key, queryString);
|
||||
|
||||
if (pathname !== key) {
|
||||
if (event && isModifierKeyPressed(event)) {
|
||||
openInNewTab(`${key}?${queryString.join('&')}`);
|
||||
openInNewTab(url);
|
||||
} else {
|
||||
history.push(`${key}?${queryString.join('&')}`, {
|
||||
history.push(url, {
|
||||
from: pathname,
|
||||
});
|
||||
}
|
||||
@@ -259,17 +260,6 @@ function SettingsPage(): JSX.Element {
|
||||
};
|
||||
|
||||
const isActiveNavItem = (key: string): boolean => {
|
||||
if (pathname.startsWith(ROUTES.ALL_CHANNELS) && key === ROUTES.ALL_CHANNELS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
pathname.startsWith(ROUTES.CHANNELS_EDIT) &&
|
||||
key === ROUTES.ALL_CHANNELS
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
pathname.startsWith(ROUTES.ROLES_SETTINGS) &&
|
||||
key === ROUTES.ROLES_SETTINGS
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { RouteTabProps } from 'components/RouteTab/types';
|
||||
import ROUTES from 'constants/routes';
|
||||
import AlertChannels from 'container/AllAlertChannels';
|
||||
import BillingContainer from 'container/BillingContainer/BillingContainer';
|
||||
import CreateAlertChannels from 'container/CreateAlertChannels';
|
||||
import { ChannelType } from 'container/CreateAlertChannels/config';
|
||||
import GeneralSettings from 'container/GeneralSettings';
|
||||
import GeneralSettingsCloud from 'container/GeneralSettingsCloud';
|
||||
import IngestionSettings from 'container/IngestionSettings/IngestionSettings';
|
||||
@@ -16,20 +13,16 @@ import RoleDetailsPage from 'container/RolesSettings/RoleDetails';
|
||||
import { TFunction } from 'i18next';
|
||||
import {
|
||||
Backpack,
|
||||
BellDot,
|
||||
Bot,
|
||||
Building,
|
||||
Cpu,
|
||||
CreditCard,
|
||||
Keyboard,
|
||||
Pencil,
|
||||
Plus,
|
||||
Shield,
|
||||
Sparkles,
|
||||
User,
|
||||
Users,
|
||||
} from '@signozhq/icons';
|
||||
import ChannelsEdit from 'pages/ChannelsEdit';
|
||||
import MembersSettings from 'pages/MembersSettings';
|
||||
import ServiceAccountsSettings from 'pages/ServiceAccountsSettings';
|
||||
import Shortcuts from 'pages/Shortcuts';
|
||||
@@ -47,19 +40,6 @@ export const organizationSettings = (t: TFunction): RouteTabProps['routes'] => [
|
||||
},
|
||||
];
|
||||
|
||||
export const alertChannels = (t: TFunction): RouteTabProps['routes'] => [
|
||||
{
|
||||
Component: AlertChannels,
|
||||
name: (
|
||||
<div className="periscope-tab">
|
||||
<BellDot size={16} /> {t('routes:alert_channels').toString()}
|
||||
</div>
|
||||
),
|
||||
route: ROUTES.ALL_CHANNELS,
|
||||
key: ROUTES.ALL_CHANNELS,
|
||||
},
|
||||
];
|
||||
|
||||
export const ingestionSettings = (t: TFunction): RouteTabProps['routes'] => [
|
||||
{
|
||||
Component: IngestionSettings,
|
||||
@@ -219,31 +199,3 @@ export const mcpServerSettings = (t: TFunction): RouteTabProps['routes'] => [
|
||||
key: ROUTES.MCP_SERVER,
|
||||
},
|
||||
];
|
||||
|
||||
export const createAlertChannels = (t: TFunction): RouteTabProps['routes'] => [
|
||||
{
|
||||
Component: (): JSX.Element => (
|
||||
<CreateAlertChannels preType={ChannelType.Slack} />
|
||||
),
|
||||
name: (
|
||||
<div className="periscope-tab">
|
||||
<Plus size={16} /> {t('routes:create_alert_channels').toString()}
|
||||
</div>
|
||||
),
|
||||
route: ROUTES.CHANNELS_NEW,
|
||||
key: ROUTES.CHANNELS_NEW,
|
||||
},
|
||||
];
|
||||
|
||||
export const editAlertChannels = (t: TFunction): RouteTabProps['routes'] => [
|
||||
{
|
||||
Component: ChannelsEdit,
|
||||
name: (
|
||||
<div className="periscope-tab">
|
||||
<Pencil size={16} /> {t('routes:edit_alert_channels').toString()}
|
||||
</div>
|
||||
),
|
||||
route: ROUTES.CHANNELS_EDIT,
|
||||
key: ROUTES.CHANNELS_EDIT,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -3,10 +3,7 @@ import { TFunction } from 'i18next';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
|
||||
import {
|
||||
alertChannels,
|
||||
billingSettings,
|
||||
createAlertChannels,
|
||||
editAlertChannels,
|
||||
generalSettings,
|
||||
ingestionSettings,
|
||||
keyboardShortcuts,
|
||||
@@ -60,8 +57,6 @@ export const getRoutes = (
|
||||
settings.push(...ingestionSettings(t));
|
||||
}
|
||||
|
||||
settings.push(...alertChannels(t));
|
||||
|
||||
// Visible to all authenticated users
|
||||
settings.push(
|
||||
...serviceAccountsSettings(t),
|
||||
@@ -80,8 +75,6 @@ export const getRoutes = (
|
||||
|
||||
settings.push(
|
||||
...mySettings(t),
|
||||
...createAlertChannels(t),
|
||||
...editAlertChannels(t),
|
||||
...keyboardShortcuts(t),
|
||||
...mcpServerSettings(t),
|
||||
);
|
||||
|
||||
@@ -31,6 +31,8 @@ type builderQuery[T any] struct {
|
||||
fromMS uint64
|
||||
toMS uint64
|
||||
kind qbtypes.RequestType
|
||||
|
||||
logTraceIDWindowPaddingMS uint64
|
||||
}
|
||||
|
||||
var _ qbtypes.Query = (*builderQuery[any])(nil)
|
||||
@@ -43,16 +45,18 @@ func newBuilderQuery[T any](
|
||||
tr qbtypes.TimeRange,
|
||||
kind qbtypes.RequestType,
|
||||
variables map[string]qbtypes.VariableItem,
|
||||
logTraceIDWindowPaddingMS uint64,
|
||||
) *builderQuery[T] {
|
||||
return &builderQuery[T]{
|
||||
logger: logger,
|
||||
telemetryStore: telemetryStore,
|
||||
stmtBuilder: stmtBuilder,
|
||||
spec: spec,
|
||||
variables: variables,
|
||||
fromMS: tr.From,
|
||||
toMS: tr.To,
|
||||
kind: kind,
|
||||
logger: logger,
|
||||
telemetryStore: telemetryStore,
|
||||
stmtBuilder: stmtBuilder,
|
||||
spec: spec,
|
||||
variables: variables,
|
||||
fromMS: tr.From,
|
||||
toMS: tr.To,
|
||||
kind: kind,
|
||||
logTraceIDWindowPaddingMS: logTraceIDWindowPaddingMS,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,9 +281,20 @@ func (q *builderQuery[T]) narrowWindowByTraceID(ctx context.Context, fromMS, toM
|
||||
return fromMS, toMS, true, ""
|
||||
}
|
||||
|
||||
// Logs can be flushed slightly after the span ends. The trace
|
||||
// time range comes from the spans table, so for logs we widen it by the
|
||||
// configured padding before clamping. Keep the actual recorded bounds for
|
||||
// the user-facing warning so it reports where the trace truly lies, not the
|
||||
// padded range.
|
||||
actualStartMS, actualEndMS := traceStartMS, traceEndMS
|
||||
if q.spec.Signal == telemetrytypes.SignalLogs {
|
||||
traceStartMS -= q.logTraceIDWindowPaddingMS
|
||||
traceEndMS += q.logTraceIDWindowPaddingMS
|
||||
}
|
||||
|
||||
if traceStartMS > toMS || traceEndMS < fromMS {
|
||||
traceStartUTC := time.UnixMilli(int64(traceStartMS)).UTC().Format(time.RFC3339)
|
||||
traceEndUTC := time.UnixMilli(int64(traceEndMS)).UTC().Format(time.RFC3339)
|
||||
traceStartUTC := time.UnixMilli(int64(actualStartMS)).UTC().Format(time.RFC3339)
|
||||
traceEndUTC := time.UnixMilli(int64(actualEndMS)).UTC().Format(time.RFC3339)
|
||||
return fromMS, toMS, false, fmt.Sprintf(traceOutsideRangeWarn, q.spec.Name, traceStartUTC, traceEndUTC)
|
||||
}
|
||||
if traceStartMS > fromMS {
|
||||
|
||||
@@ -23,6 +23,8 @@ type Config struct {
|
||||
MaxConcurrentQueries int `yaml:"max_concurrent_queries" mapstructure:"max_concurrent_queries"`
|
||||
// SkipResourceFingerprint configures when the resource fingerprint subquery is skipped in favor of main-table filtering.
|
||||
SkipResourceFingerprint SkipResourceFingerprint `yaml:"skip_resource_fingerprint" mapstructure:"skip_resource_fingerprint"`
|
||||
// LogTraceIDWindowPadding is the padding added to narrowed down timerange from trace summary to logs with trace_id filter.
|
||||
LogTraceIDWindowPadding time.Duration `yaml:"log_trace_id_window_padding" mapstructure:"log_trace_id_window_padding"`
|
||||
}
|
||||
|
||||
// NewConfigFactory creates a new config factory for querier.
|
||||
@@ -40,6 +42,7 @@ func newConfig() factory.Config {
|
||||
Enabled: false,
|
||||
Threshold: 100000,
|
||||
},
|
||||
LogTraceIDWindowPadding: 5 * time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +60,9 @@ func (c Config) Validate() error {
|
||||
if c.SkipResourceFingerprint.Enabled && c.SkipResourceFingerprint.Threshold == 0 {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "skip_resource_fingerprint.threshold must be > 0 when enabled")
|
||||
}
|
||||
if c.LogTraceIDWindowPadding < 0 {
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "log_trace_id_window_padding must not be negative, got %v", c.LogTraceIDWindowPadding)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -35,19 +35,20 @@ var (
|
||||
)
|
||||
|
||||
type querier struct {
|
||||
logger *slog.Logger
|
||||
fl flagger.Flagger
|
||||
telemetryStore telemetrystore.TelemetryStore
|
||||
metadataStore telemetrytypes.MetadataStore
|
||||
promEngine prometheus.Prometheus
|
||||
traceStmtBuilder qbtypes.StatementBuilder[qbtypes.TraceAggregation]
|
||||
logStmtBuilder qbtypes.StatementBuilder[qbtypes.LogAggregation]
|
||||
auditStmtBuilder qbtypes.StatementBuilder[qbtypes.LogAggregation]
|
||||
metricStmtBuilder qbtypes.StatementBuilder[qbtypes.MetricAggregation]
|
||||
meterStmtBuilder qbtypes.StatementBuilder[qbtypes.MetricAggregation]
|
||||
traceOperatorStmtBuilder qbtypes.TraceOperatorStatementBuilder
|
||||
bucketCache BucketCache
|
||||
liveDataRefresh time.Duration
|
||||
logger *slog.Logger
|
||||
fl flagger.Flagger
|
||||
telemetryStore telemetrystore.TelemetryStore
|
||||
metadataStore telemetrytypes.MetadataStore
|
||||
promEngine prometheus.Prometheus
|
||||
traceStmtBuilder qbtypes.StatementBuilder[qbtypes.TraceAggregation]
|
||||
logStmtBuilder qbtypes.StatementBuilder[qbtypes.LogAggregation]
|
||||
auditStmtBuilder qbtypes.StatementBuilder[qbtypes.LogAggregation]
|
||||
metricStmtBuilder qbtypes.StatementBuilder[qbtypes.MetricAggregation]
|
||||
meterStmtBuilder qbtypes.StatementBuilder[qbtypes.MetricAggregation]
|
||||
traceOperatorStmtBuilder qbtypes.TraceOperatorStatementBuilder
|
||||
bucketCache BucketCache
|
||||
liveDataRefresh time.Duration
|
||||
logTraceIDWindowPaddingMS uint64
|
||||
}
|
||||
|
||||
var _ Querier = (*querier)(nil)
|
||||
@@ -65,22 +66,24 @@ func New(
|
||||
traceOperatorStmtBuilder qbtypes.TraceOperatorStatementBuilder,
|
||||
bucketCache BucketCache,
|
||||
flagger flagger.Flagger,
|
||||
logTraceIDWindowPadding time.Duration,
|
||||
) *querier {
|
||||
querierSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/querier")
|
||||
return &querier{
|
||||
logger: querierSettings.Logger(),
|
||||
fl: flagger,
|
||||
telemetryStore: telemetryStore,
|
||||
metadataStore: metadataStore,
|
||||
promEngine: promEngine,
|
||||
traceStmtBuilder: traceStmtBuilder,
|
||||
logStmtBuilder: logStmtBuilder,
|
||||
auditStmtBuilder: auditStmtBuilder,
|
||||
metricStmtBuilder: metricStmtBuilder,
|
||||
meterStmtBuilder: meterStmtBuilder,
|
||||
traceOperatorStmtBuilder: traceOperatorStmtBuilder,
|
||||
bucketCache: bucketCache,
|
||||
liveDataRefresh: 5 * time.Second,
|
||||
logger: querierSettings.Logger(),
|
||||
fl: flagger,
|
||||
telemetryStore: telemetryStore,
|
||||
metadataStore: metadataStore,
|
||||
promEngine: promEngine,
|
||||
traceStmtBuilder: traceStmtBuilder,
|
||||
logStmtBuilder: logStmtBuilder,
|
||||
auditStmtBuilder: auditStmtBuilder,
|
||||
metricStmtBuilder: metricStmtBuilder,
|
||||
meterStmtBuilder: meterStmtBuilder,
|
||||
traceOperatorStmtBuilder: traceOperatorStmtBuilder,
|
||||
bucketCache: bucketCache,
|
||||
liveDataRefresh: 5 * time.Second,
|
||||
logTraceIDWindowPaddingMS: uint64(logTraceIDWindowPadding.Milliseconds()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +176,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
|
||||
case qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]:
|
||||
spec.ShiftBy = extractShiftFromBuilderQuery(spec)
|
||||
timeRange := adjustTimeRangeForShift(spec, qbtypes.TimeRange{From: req.Start, To: req.End}, req.RequestType)
|
||||
bq := newBuilderQuery(q.logger, q.telemetryStore, q.traceStmtBuilder, spec, timeRange, req.RequestType, tmplVars)
|
||||
bq := newBuilderQuery(q.logger, q.telemetryStore, q.traceStmtBuilder, spec, timeRange, req.RequestType, tmplVars, 0)
|
||||
queries[spec.Name] = bq
|
||||
steps[spec.Name] = spec.StepInterval
|
||||
case qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]:
|
||||
@@ -183,7 +186,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
|
||||
if spec.Source == telemetrytypes.SourceAudit {
|
||||
stmtBuilder = q.auditStmtBuilder
|
||||
}
|
||||
bq := newBuilderQuery(q.logger, q.telemetryStore, stmtBuilder, spec, timeRange, req.RequestType, tmplVars)
|
||||
bq := newBuilderQuery(q.logger, q.telemetryStore, stmtBuilder, spec, timeRange, req.RequestType, tmplVars, q.logTraceIDWindowPaddingMS)
|
||||
queries[spec.Name] = bq
|
||||
steps[spec.Name] = spec.StepInterval
|
||||
case qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]:
|
||||
@@ -200,9 +203,9 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
|
||||
|
||||
if spec.Source == telemetrytypes.SourceMeter {
|
||||
event.Source = telemetrytypes.SourceMeter.StringValue()
|
||||
bq = newBuilderQuery(q.logger, q.telemetryStore, q.meterStmtBuilder, spec, timeRange, req.RequestType, tmplVars)
|
||||
bq = newBuilderQuery(q.logger, q.telemetryStore, q.meterStmtBuilder, spec, timeRange, req.RequestType, tmplVars, 0)
|
||||
} else {
|
||||
bq = newBuilderQuery(q.logger, q.telemetryStore, q.metricStmtBuilder, spec, timeRange, req.RequestType, tmplVars)
|
||||
bq = newBuilderQuery(q.logger, q.telemetryStore, q.metricStmtBuilder, spec, timeRange, req.RequestType, tmplVars, 0)
|
||||
}
|
||||
|
||||
queries[spec.Name] = bq
|
||||
@@ -508,7 +511,7 @@ func (q *querier) QueryRawStream(ctx context.Context, orgID valuer.UUID, req *qb
|
||||
"id": {
|
||||
Value: updatedLogID,
|
||||
},
|
||||
})
|
||||
}, q.logTraceIDWindowPaddingMS)
|
||||
queries[spec.Name] = bq
|
||||
|
||||
qbResp, qbErr := q.run(ctx, orgID, queries, req, nil, event, nil)
|
||||
@@ -804,7 +807,7 @@ func (q *querier) createRangedQuery(originalQuery qbtypes.Query, timeRange qbtyp
|
||||
specCopy := qt.spec.Copy()
|
||||
specCopy.ShiftBy = extractShiftFromBuilderQuery(specCopy)
|
||||
adjustedTimeRange := adjustTimeRangeForShift(specCopy, timeRange, qt.kind)
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, q.traceStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables)
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, q.traceStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, 0)
|
||||
|
||||
case *builderQuery[qbtypes.LogAggregation]:
|
||||
specCopy := qt.spec.Copy()
|
||||
@@ -814,16 +817,16 @@ func (q *querier) createRangedQuery(originalQuery qbtypes.Query, timeRange qbtyp
|
||||
if qt.spec.Source == telemetrytypes.SourceAudit {
|
||||
shiftStmtBuilder = q.auditStmtBuilder
|
||||
}
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, shiftStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables)
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, shiftStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, q.logTraceIDWindowPaddingMS)
|
||||
|
||||
case *builderQuery[qbtypes.MetricAggregation]:
|
||||
specCopy := qt.spec.Copy()
|
||||
specCopy.ShiftBy = extractShiftFromBuilderQuery(specCopy)
|
||||
adjustedTimeRange := adjustTimeRangeForShift(specCopy, timeRange, qt.kind)
|
||||
if qt.spec.Source == telemetrytypes.SourceMeter {
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, q.meterStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables)
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, q.meterStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, 0)
|
||||
}
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, q.metricStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables)
|
||||
return newBuilderQuery(q.logger, q.telemetryStore, q.metricStmtBuilder, specCopy, adjustedTimeRange, qt.kind, qt.variables, 0)
|
||||
case *traceOperatorQuery:
|
||||
specCopy := qt.spec.Copy()
|
||||
return &traceOperatorQuery{
|
||||
|
||||
@@ -54,6 +54,7 @@ func TestQueryRange_MetricTypeMissing(t *testing.T) {
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
flaggertest.New(t), // flagger
|
||||
0,
|
||||
)
|
||||
|
||||
req := &qbtypes.QueryRangeRequest{
|
||||
@@ -124,6 +125,7 @@ func TestQueryRange_MetricTypeFromStore(t *testing.T) {
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
flaggertest.New(t), // flagger
|
||||
0,
|
||||
)
|
||||
|
||||
req := &qbtypes.QueryRangeRequest{
|
||||
|
||||
@@ -192,5 +192,6 @@ func newProvider(
|
||||
traceOperatorStmtBuilder,
|
||||
bucketCache,
|
||||
flagger,
|
||||
cfg.LogTraceIDWindowPadding,
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package rules
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
@@ -54,6 +55,7 @@ func prepareQuerierForMetrics(t *testing.T, telemetryStore telemetrystore.Teleme
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
flagger,
|
||||
0,
|
||||
), metadataStore
|
||||
}
|
||||
|
||||
@@ -107,6 +109,7 @@ func prepareQuerierForLogs(t *testing.T, telemetryStore telemetrystore.Telemetry
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
fl,
|
||||
5*time.Minute, // logTraceIDWindowPadding
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,5 +157,6 @@ func prepareQuerierForTraces(t *testing.T, telemetryStore telemetrystore.Telemet
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
fl,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2306,9 +2306,11 @@ def test_logs_list_filter_by_trace_id(
|
||||
"""
|
||||
Tests that filtering logs by trace_id uses the trace_summary lookup to
|
||||
narrow the query window before scanning the logs table:
|
||||
1. Returns the matching log (narrow window, single bucket).
|
||||
1. Returns the matching logs (narrow window, single bucket), including a log
|
||||
flushed shortly after the span ends — kept by the configured padding.
|
||||
2. Does not return duplicate logs when the query window should span multiple
|
||||
exponential buckets (>1 h). But is clamped to the timerange of trace.
|
||||
exponential buckets (>1 h). The window is clamped to the trace's recorded
|
||||
range widened by the padding, so the post-span log survives the clamp.
|
||||
3. Returns no results when the query window does not contain the trace.
|
||||
4. Logs carrying a trace_id whose trace is NOT in trace_summary (e.g.
|
||||
traces disabled) are still returned — the lookup miss must not
|
||||
@@ -2366,6 +2368,9 @@ def test_logs_list_filter_by_trace_id(
|
||||
# Insert logs:
|
||||
# - one with the target trace_id, at a timestamp within the trace's
|
||||
# recorded window (now-10s..now-5s, padded ±1s).
|
||||
# - one with the target trace_id flushed ~3s AFTER the span's recorded end
|
||||
# (now-2s). This is outside the ±1s base pad but inside the multi-minute
|
||||
# log_trace_id_window_padding, so it must still be returned.
|
||||
# - one with an orphan trace_id whose trace was never ingested — used to
|
||||
# verify the lookup miss does NOT short-circuit logs queries.
|
||||
insert_logs(
|
||||
@@ -2379,6 +2384,15 @@ def test_logs_list_filter_by_trace_id(
|
||||
trace_id=target_trace_id,
|
||||
span_id=target_root_span_id,
|
||||
),
|
||||
Logs(
|
||||
timestamp=now - timedelta(seconds=2),
|
||||
resources=common_resources,
|
||||
attributes={"http.method": "POST"},
|
||||
body="log flushed after the span ends, within padding window",
|
||||
severity_text="INFO",
|
||||
trace_id=target_trace_id,
|
||||
span_id=target_root_span_id,
|
||||
),
|
||||
Logs(
|
||||
timestamp=now - timedelta(seconds=2),
|
||||
resources=common_resources,
|
||||
@@ -2429,23 +2443,31 @@ def test_logs_list_filter_by_trace_id(
|
||||
|
||||
now_ms = int(now.timestamp() * 1000)
|
||||
|
||||
inside_window_body = "log inside the target trace window"
|
||||
post_span_body = "log flushed after the span ends, within padding window"
|
||||
|
||||
# --- Test 1: narrow window (single bucket, <1 h) ---
|
||||
narrow_start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
|
||||
narrow_rows, narrow_warnings = _query(narrow_start_ms, now_ms, target_trace_id)
|
||||
|
||||
assert len(narrow_rows) == 1, f"Expected 1 log for trace_id filter (narrow window), got {len(narrow_rows)}"
|
||||
assert narrow_rows[0]["data"]["trace_id"] == target_trace_id
|
||||
assert narrow_rows[0]["data"]["span_id"] == target_root_span_id
|
||||
assert len(narrow_rows) == 2, f"Expected 2 logs for trace_id filter (narrow window), got {len(narrow_rows)}"
|
||||
assert {r["data"]["trace_id"] for r in narrow_rows} == {target_trace_id}
|
||||
narrow_bodies = {r["data"]["body"] for r in narrow_rows}
|
||||
assert inside_window_body in narrow_bodies
|
||||
assert post_span_body in narrow_bodies, "post-span log should be returned within the padding window"
|
||||
assert not any(outside_range_msg in m for m in narrow_warnings), f"Did not expect outside-range warning, got {narrow_warnings}"
|
||||
|
||||
# --- Test 2: wide window (>1 h, clamp to the timerange from trace_summary) ---
|
||||
# Should still return exactly one log — no duplicates from multi-bucket scan.
|
||||
# --- Test 2: wide window (>1 h, clamp to the padded timerange from trace_summary) ---
|
||||
# Should return exactly the two target logs — no duplicates from multi-bucket
|
||||
# scan, and the post-span log survives the clamp only because of the padding.
|
||||
wide_start_ms = int((now - timedelta(hours=12)).timestamp() * 1000)
|
||||
wide_rows, wide_warnings = _query(wide_start_ms, now_ms, target_trace_id)
|
||||
|
||||
assert len(wide_rows) == 1, f"Expected 1 log for trace_id filter (wide window, multi-bucket), got {len(wide_rows)} — possible duplicate-log regression"
|
||||
assert wide_rows[0]["data"]["trace_id"] == target_trace_id
|
||||
assert wide_rows[0]["data"]["span_id"] == target_root_span_id
|
||||
assert len(wide_rows) == 2, f"Expected 2 logs for trace_id filter (wide window, multi-bucket), got {len(wide_rows)} — possible duplicate-log regression or padding not applied"
|
||||
assert {r["data"]["trace_id"] for r in wide_rows} == {target_trace_id}
|
||||
wide_bodies = {r["data"]["body"] for r in wide_rows}
|
||||
assert inside_window_body in wide_bodies
|
||||
assert post_span_body in wide_bodies, "post-span log should survive the clamp because of the padding"
|
||||
assert not any(outside_range_msg in m for m in wide_warnings), f"Did not expect outside-range warning, got {wide_warnings}"
|
||||
|
||||
# --- Test 3: window that does not contain the trace returns no results + warning ---
|
||||
|
||||
@@ -15,7 +15,6 @@ from fixtures.querier import (
|
||||
build_builder_query,
|
||||
find_named_result,
|
||||
get_all_warnings,
|
||||
get_error_message,
|
||||
index_series_by_label,
|
||||
make_query_request,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user