Compare commits
9 Commits
merge-json
...
v0.115.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae71f2608a | ||
|
|
f7140832d0 | ||
|
|
8c5ff10b42 | ||
|
|
12c0df8410 | ||
|
|
a139915f4e | ||
|
|
b69bcd63ba | ||
|
|
f576a86dd1 | ||
|
|
996c9a891f | ||
|
|
d1a872dadc |
@@ -190,7 +190,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.114.1
|
||||
image: signoz/signoz:v0.115.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
# - "6060:6060" # pprof port
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.114.1
|
||||
image: signoz/signoz:v0.115.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
volumes:
|
||||
|
||||
@@ -181,7 +181,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.114.1}
|
||||
image: signoz/signoz:${VERSION:-v0.115.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
|
||||
@@ -109,7 +109,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.114.1}
|
||||
image: signoz/signoz:${VERSION:-v0.115.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
|
||||
@@ -100,7 +100,7 @@ This command:
|
||||
|
||||
3. Create a `.env` file in this directory:
|
||||
```env
|
||||
FRONTEND_API_ENDPOINT=http://localhost:8080
|
||||
VITE_FRONTEND_API_ENDPOINT=http://localhost:8080
|
||||
```
|
||||
|
||||
4. Start the development server:
|
||||
|
||||
@@ -273,6 +273,7 @@ Options can be simple (direct link) or nested (with another question):
|
||||
- Place logo files in `public/Logos/`
|
||||
- Use SVG format
|
||||
- Reference as `"/Logos/your-logo.svg"`
|
||||
- **Optimize new SVGs**: Run any newly downloaded SVGs through an optimizer like [SVGOMG (svgo)](https://svgomg.net/) or use `npx svgo public/Logos/your-logo.svg` to minimise their size before committing.
|
||||
|
||||
### 4. Links
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
NODE_ENV="development"
|
||||
BUNDLE_ANALYSER="true"
|
||||
VITE_FRONTEND_API_ENDPOINT="http://localhost:8080/"
|
||||
VITE_FRONTEND_API_ENDPOINT="http://localhost:8080"
|
||||
VITE_PYLON_APP_ID="pylon-app-id"
|
||||
VITE_APPCUES_APP_ID="appcess-app-id"
|
||||
VITE_PYLON_IDENTITY_SECRET="pylon-identity-secret"
|
||||
|
||||
@@ -39,7 +39,7 @@ const config: Config.InitialOptions = {
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/sonner|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana)/)',
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/sonner|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
1
frontend/public/Logos/deno.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 24 24"><title>Deno</title><path d="M1.105 18.02A11.9 11.9 0 0 1 0 12.985q0-.698.078-1.376a12 12 0 0 1 .231-1.34A12 12 0 0 1 4.025 4.02a12 12 0 0 1 5.46-2.771 12 12 0 0 1 3.428-.23c1.452.112 2.825.477 4.077 1.05a12 12 0 0 1 2.78 1.774 12.02 12.02 0 0 1 4.053 7.078A12 12 0 0 1 24 12.985q0 .454-.036.914a12 12 0 0 1-.728 3.305 12 12 0 0 1-2.38 3.875c-1.33 1.357-3.02 1.962-4.43 1.936a4.4 4.4 0 0 1-2.724-1.024c-.99-.853-1.391-1.83-1.53-2.919a5 5 0 0 1 .128-1.518c.105-.38.37-1.116.76-1.437-.455-.197-1.04-.624-1.226-.829-.045-.05-.04-.13 0-.183a.155.155 0 0 1 .177-.053c.392.134.869.267 1.372.35.66.111 1.484.25 2.317.292 2.03.1 4.153-.813 4.812-2.627s.403-3.609-1.96-4.685-3.454-2.356-5.363-3.128c-1.247-.505-2.636-.205-4.06.582-3.838 2.121-7.277 8.822-5.69 15.032a.191.191 0 0 1-.315.19 12 12 0 0 1-1.25-1.634 12 12 0 0 1-.769-1.404M11.57 6.087c.649-.051 1.214.501 1.31 1.236.13.979-.228 1.99-1.41 2.013-1.01.02-1.315-.997-1.248-1.614.066-.616.574-1.575 1.35-1.635"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
frontend/public/Logos/haystack.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 24 24"><title>Haystack</title><path d="M2.008 0C.9 0 0 .9 0 2.008v19.984C0 23.1.9 24 2.008 24h19.984C23.1 24 24 23.1 24 21.992V2.008C24 .9 23.1 0 21.992 0Zm9.963 3.84c3.43 0 6.21 2.763 6.21 6.17v6.488a.27.27 0 0 1-.27.268 2.423 2.423 0 0 1-2.43-2.414V10.01c0-1.927-1.572-3.488-3.51-3.488S8.547 8.085 8.547 10.01v1.608a.263.263 0 0 0 .259.268h1.54a.27.27 0 0 0 .275-.263V9.945c0-.74.604-1.341 1.35-1.341s1.35.6 1.35 1.341V20.03a.275.275 0 0 1-.28.268 2.41 2.41 0 0 1-2.42-2.404v-3.23a.275.275 0 0 0-.276-.269H8.811a.264.264 0 0 0-.264.263v1.08c0 1.333-1.175 2.414-2.517 2.414a.27.27 0 0 1-.27-.268v-7.872c0-3.408 2.78-6.171 6.21-6.171"/></svg>
|
||||
|
After Width: | Height: | Size: 707 B |
1
frontend/public/Logos/huggingface.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95 88" fill="none"><path fill="#ffd21e" d="M47.21 76.5a34.75 34.75 0 1 0 0-69.5 34.75 34.75 0 0 0 0 69.5"/><path fill="#ff9d0b" d="M81.96 41.75a34.75 34.75 0 1 0-69.5 0 34.75 34.75 0 0 0 69.5 0m-73.5 0a38.75 38.75 0 1 1 77.5 0 38.75 38.75 0 0 1-77.5 0"/><path fill="#3a3b45" d="M58.5 32.3c1.28.44 1.78 3.06 3.07 2.38a5 5 0 1 0-6.76-2.07c.61 1.15 2.55-.72 3.7-.32Zm-23.55 0c-1.28.44-1.79 3.06-3.07 2.38a5 5 0 1 1 6.76-2.07c-.61 1.15-2.56-.72-3.7-.32Z"/><path fill="#ff323d" d="M46.96 56.29c9.83 0 13-8.76 13-13.26 0-2.34-1.57-1.6-4.09-.36-2.33 1.15-5.46 2.74-8.9 2.74-7.19 0-13-6.88-13-2.38s3.16 13.26 13 13.26Z"/><path fill="#3a3b45" fill-rule="evenodd" d="M39.43 54a8.7 8.7 0 0 1 5.3-4.49c.4-.12.81.57 1.24 1.28.4.68.82 1.37 1.24 1.37.45 0 .9-.68 1.33-1.35.45-.7.89-1.38 1.32-1.25a8.6 8.6 0 0 1 5 4.17c3.73-2.94 5.1-7.74 5.1-10.7 0-2.34-1.57-1.6-4.09-.36l-.14.07c-2.31 1.15-5.39 2.67-8.77 2.67s-6.45-1.52-8.77-2.67c-2.6-1.29-4.23-2.1-4.23.29 0 3.05 1.46 8.06 5.47 10.97" clip-rule="evenodd"/><path fill="#ff9d0b" d="M70.71 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5m-46.5 0a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5m-6.69 11c-1.62 0-3.06.66-4.07 1.87a5.97 5.97 0 0 0-1.33 3.76 7 7 0 0 0-1.94-.3c-1.55 0-2.95.59-3.94 1.66a5.8 5.8 0 0 0-.8 7 5.3 5.3 0 0 0-1.79 2.82c-.24.9-.48 2.8.8 4.74a5.22 5.22 0 0 0-.37 5.02c1.02 2.32 3.57 4.14 8.52 6.1 3.07 1.22 5.89 2 5.91 2.01a44.3 44.3 0 0 0 10.93 1.6c5.86 0 10.05-1.8 12.46-5.34 3.88-5.69 3.33-10.9-1.7-15.92-2.77-2.78-4.62-6.87-5-7.77-.78-2.66-2.84-5.62-6.25-5.62a5.7 5.7 0 0 0-4.6 2.46c-1-1.26-1.98-2.25-2.86-2.82A7.4 7.4 0 0 0 17.52 48m0 4c.51 0 1.14.22 1.82.65 2.14 1.36 6.25 8.43 7.76 11.18.5.92 1.37 1.31 2.14 1.31 1.55 0 2.75-1.53.15-3.48-3.92-2.93-2.55-7.72-.68-8.01.08-.02.17-.02.24-.02 1.7 0 2.45 2.93 2.45 2.93s2.2 5.52 5.98 9.3c3.77 3.77 3.97 6.8 1.22 10.83-1.88 2.75-5.47 3.58-9.16 3.58-3.81 0-7.73-.9-9.92-1.46-.11-.03-13.45-3.8-11.76-7 .28-.54.75-.76 1.34-.76 2.38 0 6.7 3.54 8.57 3.54.41 0 .7-.17.83-.6.79-2.85-12.06-4.05-10.98-8.17.2-.73.71-1.02 1.44-1.02 3.14 0 10.2 5.53 11.68 5.53.11 0 .2-.03.24-.1.74-1.2.33-2.04-4.9-5.2-5.21-3.16-8.88-5.06-6.8-7.33.24-.26.58-.38 1-.38 3.17 0 10.66 6.82 10.66 6.82s2.02 2.1 3.25 2.1c.28 0 .52-.1.68-.38.86-1.46-8.06-8.22-8.56-11.01-.34-1.9.24-2.85 1.31-2.85"/><path fill="#ffd21e" d="M38.6 76.69c2.75-4.04 2.55-7.07-1.22-10.84-3.78-3.77-5.98-9.3-5.98-9.3s-.82-3.2-2.69-2.9-3.24 5.08.68 8.01c3.91 2.93-.78 4.92-2.29 2.17-1.5-2.75-5.62-9.82-7.76-11.18-2.13-1.35-3.63-.6-3.13 2.2.5 2.79 9.43 9.55 8.56 11-.87 1.47-3.93-1.71-3.93-1.71s-9.57-8.71-11.66-6.44c-2.08 2.27 1.59 4.17 6.8 7.33 5.23 3.16 5.64 4 4.9 5.2-.75 1.2-12.28-8.53-13.36-4.4-1.08 4.11 11.77 5.3 10.98 8.15-.8 2.85-9.06-5.38-10.74-2.18-1.7 3.21 11.65 6.98 11.76 7.01 4.3 1.12 15.25 3.49 19.08-2.12"/><path fill="#ff9d0b" d="M77.4 48c1.62 0 3.07.66 4.07 1.87a5.97 5.97 0 0 1 1.33 3.76 7 7 0 0 1 1.95-.3c1.55 0 2.95.59 3.94 1.66a5.8 5.8 0 0 1 .8 7 5.3 5.3 0 0 1 1.78 2.82c.24.9.48 2.8-.8 4.74a5.22 5.22 0 0 1 .37 5.02c-1.02 2.32-3.57 4.14-8.51 6.1-3.08 1.22-5.9 2-5.92 2.01a44.3 44.3 0 0 1-10.93 1.6c-5.86 0-10.05-1.8-12.46-5.34-3.88-5.69-3.33-10.9 1.7-15.92 2.78-2.78 4.63-6.87 5.01-7.77.78-2.66 2.83-5.62 6.24-5.62a5.7 5.7 0 0 1 4.6 2.46c1-1.26 1.98-2.25 2.87-2.82A7.4 7.4 0 0 1 77.4 48m0 4c-.51 0-1.13.22-1.82.65-2.13 1.36-6.25 8.43-7.76 11.18a2.43 2.43 0 0 1-2.14 1.31c-1.54 0-2.75-1.53-.14-3.48 3.91-2.93 2.54-7.72.67-8.01a1.5 1.5 0 0 0-.24-.02c-1.7 0-2.45 2.93-2.45 2.93s-2.2 5.52-5.97 9.3c-3.78 3.77-3.98 6.8-1.22 10.83 1.87 2.75 5.47 3.58 9.15 3.58 3.82 0 7.73-.9 9.93-1.46.1-.03 13.45-3.8 11.76-7-.29-.54-.75-.76-1.34-.76-2.38 0-6.71 3.54-8.57 3.54-.42 0-.71-.17-.83-.6-.8-2.85 12.05-4.05 10.97-8.17-.19-.73-.7-1.02-1.44-1.02-3.14 0-10.2 5.53-11.68 5.53-.1 0-.19-.03-.23-.1-.74-1.2-.34-2.04 4.88-5.2 5.23-3.16 8.9-5.06 6.8-7.33-.23-.26-.57-.38-.98-.38-3.18 0-10.67 6.82-10.67 6.82s-2.02 2.1-3.24 2.1a.74.74 0 0 1-.68-.38c-.87-1.46 8.05-8.22 8.55-11.01.34-1.9-.24-2.85-1.31-2.85"/><path fill="#ffd21e" d="M56.33 76.69c-2.75-4.04-2.56-7.07 1.22-10.84 3.77-3.77 5.97-9.3 5.97-9.3s.82-3.2 2.7-2.9c1.86.3 3.23 5.08-.68 8.01-3.92 2.93.78 4.92 2.28 2.17 1.51-2.75 5.63-9.82 7.76-11.18 2.13-1.35 3.64-.6 3.13 2.2-.5 2.79-9.42 9.55-8.55 11 .86 1.47 3.92-1.71 3.92-1.71s9.58-8.71 11.66-6.44-1.58 4.17-6.8 7.33c-5.23 3.16-5.63 4-4.9 5.2.75 1.2 12.28-8.53 13.36-4.4 1.08 4.11-11.76 5.3-10.97 8.15.8 2.85 9.05-5.38 10.74-2.18 1.69 3.21-11.65 6.98-11.76 7.01-4.31 1.12-15.26 3.49-19.08-2.12"/></svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
1
frontend/public/Logos/nvidia-dcgm.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#76b900" viewBox="0 0 24 24"><title>NVIDIA</title><path d="M8.948 8.798v-1.43a7 7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851a3.7 3.7 0 0 1-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6 6 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964a6.5 6.5 0 0 1-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936"/></svg>
|
||||
|
After Width: | Height: | Size: 865 B |
1
frontend/public/Logos/ollama.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 24 24"><title>Ollama</title><path d="M16.361 10.26a.9.9 0 0 0-.558.47l-.072.148.001.207c0 .193.004.217.059.353.076.193.152.312.291.448.24.238.51.3.872.205a.86.86 0 0 0 .517-.436.75.75 0 0 0 .08-.498c-.064-.453-.33-.782-.724-.897a1.1 1.1 0 0 0-.466 0m-9.203.005c-.305.096-.533.32-.65.639a1.2 1.2 0 0 0-.06.52c.057.309.31.59.598.667.362.095.632.033.872-.205.14-.136.215-.255.291-.448.055-.136.059-.16.059-.353l.001-.207-.072-.148a.9.9 0 0 0-.565-.472 1 1 0 0 0-.474.007m4.184 2c-.131.071-.223.25-.195.383.031.143.157.288.353.407.105.063.112.072.117.136.004.038-.01.146-.029.243-.02.094-.036.194-.036.222.002.074.07.195.143.253.064.052.076.054.255.059.164.005.198.001.264-.03.169-.082.212-.234.15-.525-.052-.243-.042-.28.087-.355.137-.08.281-.219.324-.314a.365.365 0 0 0-.175-.48.4.4 0 0 0-.181-.033c-.126 0-.207.03-.355.124l-.085.053-.053-.032c-.219-.13-.259-.145-.391-.143a.4.4 0 0 0-.193.032m.39-2.195c-.373.036-.475.05-.654.086a4.5 4.5 0 0 0-.951.328c-.94.46-1.589 1.226-1.787 2.114-.04.176-.045.234-.045.53 0 .294.005.357.043.524.264 1.16 1.332 2.017 2.714 2.173.3.033 1.596.033 1.896 0 1.11-.125 2.064-.727 2.493-1.571.114-.226.169-.372.22-.602.039-.167.044-.23.044-.523 0-.297-.005-.355-.045-.531-.288-1.29-1.539-2.304-3.072-2.497a7 7 0 0 0-.855-.031zm.645.937a3.3 3.3 0 0 1 1.44.514c.223.148.537.458.671.662.166.251.26.508.303.82.02.143.01.251-.043.482-.08.345-.332.705-.672.957a3 3 0 0 1-.689.348c-.382.122-.632.144-1.525.138-.582-.006-.686-.01-.853-.042q-.856-.16-1.35-.68c-.264-.28-.385-.535-.45-.946-.03-.192.025-.509.137-.776.136-.326.488-.73.836-.963.403-.269.934-.46 1.422-.512.187-.02.586-.02.773-.002m-5.503-11a1.65 1.65 0 0 0-.683.298C5.617.74 5.173 1.666 4.985 2.819c-.07.436-.119 1.04-.119 1.503 0 .544.064 1.24.155 1.721.02.107.031.202.023.208l-.187.152a5.3 5.3 0 0 0-.949 1.02 5.5 5.5 0 0 0-.94 2.339 6.6 6.6 0 0 0-.023 1.357c.091.78.325 1.438.727 2.04l.13.195-.037.064c-.269.452-.498 1.105-.605 1.732-.084.496-.095.629-.095 1.294 0 .67.009.803.088 1.266.095.555.288 1.143.503 1.534.071.128.243.393.264.407.007.003-.014.067-.046.141a7.4 7.4 0 0 0-.548 1.873 5 5 0 0 0-.071.991c0 .56.031.832.148 1.279L3.42 24h1.478l-.05-.091c-.297-.552-.325-1.575-.068-2.597.117-.472.25-.819.498-1.296l.148-.29v-.177c0-.165-.003-.184-.057-.293a.9.9 0 0 0-.194-.25 1.7 1.7 0 0 1-.385-.543c-.424-.92-.506-2.286-.208-3.451.124-.486.329-.918.544-1.154a.8.8 0 0 0 .223-.531c0-.195-.07-.355-.224-.522a3.14 3.14 0 0 1-.817-1.729c-.14-.96.114-2.005.69-2.834.563-.814 1.353-1.336 2.237-1.475.199-.033.57-.028.776.01.226.04.367.028.512-.041.179-.085.268-.19.374-.431.093-.215.165-.333.36-.576.234-.29.46-.489.822-.729.413-.27.884-.467 1.352-.561.17-.035.25-.04.569-.04s.398.005.569.04a4.07 4.07 0 0 1 1.914.997c.117.109.398.457.488.602.034.057.095.177.132.267.105.241.195.346.374.43.14.068.286.082.503.045.343-.058.607-.053.943.016 1.144.23 2.14 1.173 2.581 2.437.385 1.108.276 2.267-.296 3.153-.097.15-.193.27-.333.419-.301.322-.301.722-.001 1.053.493.539.801 1.866.708 3.036-.062.772-.26 1.463-.533 1.854a2 2 0 0 1-.224.258.9.9 0 0 0-.194.25c-.054.109-.057.128-.057.293v.178l.148.29c.248.476.38.823.498 1.295.253 1.008.231 2.01-.059 2.581a1 1 0 0 0-.044.098c0 .006.329.009.732.009h.73l.02-.074.036-.134c.019-.076.057-.3.088-.516a9 9 0 0 0 0-1.258c-.11-.875-.295-1.57-.597-2.226-.032-.074-.053-.138-.046-.141a1.4 1.4 0 0 0 .108-.152c.376-.569.607-1.284.724-2.228.031-.26.031-1.378 0-1.628-.083-.645-.182-1.082-.348-1.525a6 6 0 0 0-.329-.7l-.038-.064.131-.194c.402-.604.636-1.262.727-2.04a6.6 6.6 0 0 0-.024-1.358 5.5 5.5 0 0 0-.939-2.339 5.3 5.3 0 0 0-.95-1.02l-.186-.152a.7.7 0 0 1 .023-.208c.208-1.087.201-2.443-.017-3.503-.19-.924-.535-1.658-.98-2.082-.354-.338-.716-.482-1.15-.455-.996.059-1.8 1.205-2.116 3.01a7 7 0 0 0-.097.726c0 .036-.007.066-.015.066a1 1 0 0 1-.149-.078A4.86 4.86 0 0 0 12 3.03c-.832 0-1.687.243-2.456.698a1 1 0 0 1-.148.078c-.008 0-.015-.03-.015-.066a7 7 0 0 0-.097-.725C8.997 1.392 8.337.319 7.46.048a2 2 0 0 0-.585-.041Zm.293 1.402c.248.197.523.759.682 1.388.03.113.06.244.069.292.007.047.026.152.041.233.067.365.098.76.102 1.24l.002.475-.12.175-.118.178h-.278c-.324 0-.646.041-.954.124l-.238.06c-.033.007-.038-.003-.057-.144a8.4 8.4 0 0 1 .016-2.323c.124-.788.413-1.501.696-1.711.067-.05.079-.049.157.013m9.825-.012c.17.126.358.46.498.888.28.854.36 2.028.212 3.145-.019.14-.024.151-.057.144l-.238-.06a3.7 3.7 0 0 0-.954-.124h-.278l-.119-.178-.119-.175.002-.474c.004-.669.066-1.19.214-1.772.157-.623.434-1.185.68-1.382.078-.062.09-.063.159-.012"/></svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
1
frontend/public/Logos/openrouter.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 24 24"><title>OpenRouter</title><path d="M16.778 1.844v1.919q-.569-.026-1.138-.032-.708-.008-1.415.037c-1.93.126-4.023.728-6.149 2.237-2.911 2.066-2.731 1.95-4.14 2.75-.396.223-1.342.574-2.185.798-.841.225-1.753.333-1.751.333v4.229s.768.108 1.61.333c.842.224 1.789.575 2.185.799 1.41.798 1.228.683 4.14 2.75 2.126 1.509 4.22 2.11 6.148 2.236.88.058 1.716.041 2.555.005v1.918l7.222-4.168-7.222-4.17v2.176c-.86.038-1.611.065-2.278.021-1.364-.09-2.417-.357-3.979-1.465-2.244-1.593-2.866-2.027-3.68-2.508.889-.518 1.449-.906 3.822-2.59 1.56-1.109 2.614-1.377 3.978-1.466.667-.044 1.418-.017 2.278.02v2.176L24 6.014Z"/></svg>
|
||||
|
After Width: | Height: | Size: 685 B |
1
frontend/public/Logos/prometheus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3.94 -1.44 438.62 432.87"><path fill="#e75225" d="M215.926 7.068c115.684.024 210.638 93.784 210.493 207.844-.148 115.793-94.713 208.252-212.912 208.169C97.95 423 4.52 329.143 4.601 213.221 4.68 99.867 99.833 7.044 215.926 7.068m-63.947 73.001c2.652 12.978.076 25.082-3.846 36.988-2.716 8.244-6.47 16.183-8.711 24.539-3.694 13.769-7.885 27.619-9.422 41.701-2.21 20.25 5.795 38.086 19.493 55.822L86.527 225.94c.11 1.978-.007 2.727.21 3.361 5.968 17.43 16.471 32.115 28.243 45.957 1.246 1.465 4.082 2.217 6.182 2.221 62.782.115 125.565.109 188.347.028 1.948-.003 4.546-.369 5.741-1.618 13.456-14.063 23.746-30.079 30.179-50.257l-66.658 12.976c4.397-8.567 9.417-16.1 12.302-24.377 9.869-28.315 5.779-55.69-8.387-81.509-11.368-20.72-21.854-41.349-16.183-66.32-12.005 11.786-16.615 26.79-19.541 42.253-2.882 15.23-4.58 30.684-6.811 46.136-.317-.467-.728-.811-.792-1.212-.258-1.621-.499-3.255-.587-4.893-1.355-25.31-6.328-49.696-16.823-72.987-6.178-13.71-12.99-27.727-6.622-44.081-4.31 2.259-8.205 4.505-10.997 7.711-8.333 9.569-11.779 21.062-12.666 33.645-.757 10.75-1.796 21.552-3.801 32.123-2.107 11.109-5.448 21.998-12.956 32.209-3.033-21.81-3.37-43.38-22.928-57.237m161.877 216.523H116.942v34.007h196.914zm-157.871 51.575c-.163 28.317 28.851 49.414 64.709 47.883 29.716-1.269 56.016-24.51 53.755-47.883z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
frontend/public/Logos/slurm.svg
Normal file
|
After Width: | Height: | Size: 11 KiB |
1
frontend/public/Logos/statsd.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 122.88 103.53"><path d="M5.47 0H117.4c3.01 0 5.47 2.46 5.47 5.47v92.58c0 3.01-2.46 5.47-5.47 5.47H5.47c-3.01 0-5.47-2.46-5.47-5.47V5.47C0 2.46 2.46 0 5.47 0m26.37 38.55 17.79 18.42 2.14 2.13-2.12 2.16-17.97 19.05-5.07-5 15.85-16.15L26.81 43.6zM94.1 79.41H54.69v-6.84H94.1zM38.19 9.83c3.19 0 5.78 2.59 5.78 5.78s-2.59 5.78-5.78 5.78-5.78-2.59-5.78-5.78S35 9.83 38.19 9.83m-19.24 0c3.19 0 5.78 2.59 5.78 5.78s-2.59 5.78-5.78 5.78-5.78-2.59-5.78-5.78 2.58-5.78 5.78-5.78M7.49 5.41H115.4c1.15 0 2.09.94 2.09 2.09v18.32H5.4V7.5c0-1.15.94-2.09 2.09-2.09" style="fill-rule:evenodd;clip-rule:evenodd;fill:#1668dc"/></svg>
|
||||
|
After Width: | Height: | Size: 687 B |
@@ -361,6 +361,58 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
expect(filtersForServiceName).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should match filter when query uses resource. prefix (resource.service.name matches service.name)', async () => {
|
||||
// Filter config uses unprefixed key (service.name)
|
||||
// Query has filter with resource. prefix (resource.service.name)
|
||||
// Checkbox should recognize the match and show checked state
|
||||
mockUseQueryBuilder.mockReturnValue({
|
||||
lastUsedQuery: 0,
|
||||
currentQuery: {
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
key: {
|
||||
key: 'resource.service.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
op: 'in',
|
||||
value: [OTEL_DEMO],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
} as any);
|
||||
|
||||
const mockFilter = createMockFilter({ defaultOpen: false });
|
||||
|
||||
render(
|
||||
<CheckboxFilter
|
||||
filter={mockFilter}
|
||||
source={QuickFiltersSource.LOGS_EXPLORER}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Filter should auto-open because it has active filters (key match via prefix stripping)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Filter values')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// otel-demo should be checked (filter uses resource.service.name IN [otel-demo])
|
||||
// Checked items are sorted to the top, so otel-demo is first
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
expect(checkboxes[0]).toBeChecked();
|
||||
expect(screen.getByText(OTEL_DEMO)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should extend an existing IN filter when checking an additional value', async () => {
|
||||
const redirectWithQueryBuilderData = jest.fn();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useGetAggregateValues } from 'hooks/queryBuilder/useGetAggregateValues'
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useGetQueryKeyValueSuggestions } from 'hooks/querySuggestions/useGetQueryKeyValueSuggestions';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { cloneDeep, isArray, isEqual, isFunction } from 'lodash-es';
|
||||
import { cloneDeep, isArray, isFunction } from 'lodash-es';
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@@ -26,6 +26,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import LogsQuickFilterEmptyState from './LogsQuickFilterEmptyState';
|
||||
import { isKeyMatch } from './utils';
|
||||
|
||||
import './Checkbox.styles.scss';
|
||||
|
||||
@@ -84,7 +85,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
currentQuery.builder.queryData?.[
|
||||
activeQueryIndex
|
||||
]?.filters?.items?.some((item) =>
|
||||
isEqual(item.key?.key, filter.attributeKey.key),
|
||||
isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
),
|
||||
[currentQuery.builder.queryData, activeQueryIndex, filter.attributeKey.key],
|
||||
);
|
||||
@@ -189,7 +190,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
const filterSync = currentQuery?.builder.queryData?.[
|
||||
activeQueryIndex
|
||||
]?.filters?.items.find((item) =>
|
||||
isEqual(item.key?.key, filter.attributeKey.key),
|
||||
isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
|
||||
if (filterSync) {
|
||||
@@ -236,7 +237,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
(currentQuery?.builder?.queryData?.[
|
||||
activeQueryIndex
|
||||
]?.filters?.items?.filter((item) =>
|
||||
isEqual(item.key?.key, filter.attributeKey.key),
|
||||
isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
)?.length || 0) > 1,
|
||||
|
||||
[currentQuery?.builder?.queryData, activeQueryIndex, filter.attributeKey],
|
||||
@@ -280,7 +281,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
items:
|
||||
idx === activeQueryIndex
|
||||
? item.filters?.items?.filter(
|
||||
(fil) => !isEqual(fil.key?.key, filter.attributeKey.key),
|
||||
(fil) => !isKeyMatch(fil.key?.key, filter.attributeKey.key),
|
||||
) || []
|
||||
: [...(item.filters?.items || [])],
|
||||
op: item.filters?.op || 'AND',
|
||||
@@ -313,7 +314,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
: 'Only'
|
||||
: 'Only';
|
||||
query.filters.items = query.filters.items.filter(
|
||||
(q) => !isEqual(q.key?.key, filter.attributeKey.key),
|
||||
(q) => !isKeyMatch(q.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
|
||||
if (query.filter?.expression) {
|
||||
@@ -335,13 +336,13 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
} else if (query?.filters?.items) {
|
||||
if (
|
||||
query.filters?.items?.some((item) =>
|
||||
isEqual(item.key?.key, filter.attributeKey.key),
|
||||
isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
)
|
||||
) {
|
||||
// if there is already a running filter for the current attribute key then
|
||||
// we split the cases by which particular operator is present right now!
|
||||
const currentFilter = query.filters?.items?.find((q) =>
|
||||
isEqual(q.key?.key, filter.attributeKey.key),
|
||||
isKeyMatch(q.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
if (currentFilter) {
|
||||
const runningOperator = currentFilter?.op;
|
||||
@@ -356,7 +357,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
value: [...currentFilter.value, value],
|
||||
};
|
||||
query.filters.items = query.filters.items.map((item) => {
|
||||
if (isEqual(item.key?.key, filter.attributeKey.key)) {
|
||||
if (isKeyMatch(item.key?.key, filter.attributeKey.key)) {
|
||||
return newFilter;
|
||||
}
|
||||
return item;
|
||||
@@ -368,7 +369,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
value: [currentFilter.value as string, value],
|
||||
};
|
||||
query.filters.items = query.filters.items.map((item) => {
|
||||
if (isEqual(item.key?.key, filter.attributeKey.key)) {
|
||||
if (isKeyMatch(item.key?.key, filter.attributeKey.key)) {
|
||||
return newFilter;
|
||||
}
|
||||
return item;
|
||||
@@ -385,11 +386,11 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
|
||||
if (newFilter.value.length === 0) {
|
||||
query.filters.items = query.filters.items.filter(
|
||||
(item) => !isEqual(item.key?.key, filter.attributeKey.key),
|
||||
(item) => !isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
} else {
|
||||
query.filters.items = query.filters.items.map((item) => {
|
||||
if (isEqual(item.key?.key, filter.attributeKey.key)) {
|
||||
if (isKeyMatch(item.key?.key, filter.attributeKey.key)) {
|
||||
return newFilter;
|
||||
}
|
||||
return item;
|
||||
@@ -398,7 +399,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
} else {
|
||||
// if not an array remove the whole thing altogether!
|
||||
query.filters.items = query.filters.items.filter(
|
||||
(item) => !isEqual(item.key?.key, filter.attributeKey.key),
|
||||
(item) => !isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -414,7 +415,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
value: [...currentFilter.value, value],
|
||||
};
|
||||
query.filters.items = query.filters.items.map((item) => {
|
||||
if (isEqual(item.key?.key, filter.attributeKey.key)) {
|
||||
if (isKeyMatch(item.key?.key, filter.attributeKey.key)) {
|
||||
return newFilter;
|
||||
}
|
||||
return item;
|
||||
@@ -426,7 +427,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
value: [currentFilter.value as string, value],
|
||||
};
|
||||
query.filters.items = query.filters.items.map((item) => {
|
||||
if (isEqual(item.key?.key, filter.attributeKey.key)) {
|
||||
if (isKeyMatch(item.key?.key, filter.attributeKey.key)) {
|
||||
return newFilter;
|
||||
}
|
||||
return item;
|
||||
@@ -441,7 +442,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
};
|
||||
if (newFilter.value.length === 0) {
|
||||
query.filters.items = query.filters.items.filter(
|
||||
(item) => !isEqual(item.key?.key, filter.attributeKey.key),
|
||||
(item) => !isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
if (query.filter?.expression) {
|
||||
query.filter.expression = removeKeysFromExpression(
|
||||
@@ -451,7 +452,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
}
|
||||
} else {
|
||||
query.filters.items = query.filters.items.map((item) => {
|
||||
if (isEqual(item.key?.key, filter.attributeKey.key)) {
|
||||
if (isKeyMatch(item.key?.key, filter.attributeKey.key)) {
|
||||
return newFilter;
|
||||
}
|
||||
return item;
|
||||
@@ -469,7 +470,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
query.filters.items = query.filters.items.filter(
|
||||
(item) => !isEqual(item.key?.key, filter.attributeKey.key),
|
||||
(item) => !isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -482,14 +483,14 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
value: [currentFilter.value as string, value],
|
||||
};
|
||||
query.filters.items = query.filters.items.map((item) => {
|
||||
if (isEqual(item.key?.key, filter.attributeKey.key)) {
|
||||
if (isKeyMatch(item.key?.key, filter.attributeKey.key)) {
|
||||
return newFilter;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
} else if (!checked) {
|
||||
query.filters.items = query.filters.items.filter(
|
||||
(item) => !isEqual(item.key?.key, filter.attributeKey.key),
|
||||
(item) => !isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
}
|
||||
break;
|
||||
@@ -501,14 +502,14 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
value: [currentFilter.value as string, value],
|
||||
};
|
||||
query.filters.items = query.filters.items.map((item) => {
|
||||
if (isEqual(item.key?.key, filter.attributeKey.key)) {
|
||||
if (isKeyMatch(item.key?.key, filter.attributeKey.key)) {
|
||||
return newFilter;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
} else if (checked) {
|
||||
query.filters.items = query.filters.items.filter(
|
||||
(item) => !isEqual(item.key?.key, filter.attributeKey.key),
|
||||
(item) => !isKeyMatch(item.key?.key, filter.attributeKey.key),
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* These prefixes are added to attribute keys based on their context.
|
||||
*/
|
||||
export const FIELD_CONTEXT_PREFIXES = [
|
||||
'metric',
|
||||
'log',
|
||||
'span',
|
||||
'trace',
|
||||
'resource',
|
||||
'scope',
|
||||
'attribute',
|
||||
'event',
|
||||
'body',
|
||||
];
|
||||
|
||||
/**
|
||||
* Removes the field context prefix from a key to get the base key name.
|
||||
* Example: 'resource.service.name' -> 'service.name'
|
||||
* Example: 'attribute.http.method' -> 'http.method'
|
||||
*/
|
||||
export function getKeyWithoutPrefix(key: string | undefined): string {
|
||||
if (!key) {
|
||||
return '';
|
||||
}
|
||||
const prefixPattern = new RegExp(
|
||||
`^(${FIELD_CONTEXT_PREFIXES.join('|')})\\.`,
|
||||
'i',
|
||||
);
|
||||
return key.replace(prefixPattern, '').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two keys by their base name (without prefix).
|
||||
* This ensures that 'service.name' matches 'resource.service.name'
|
||||
*/
|
||||
export function isKeyMatch(
|
||||
itemKey: string | undefined,
|
||||
filterKey: string | undefined,
|
||||
): boolean {
|
||||
return getKeyWithoutPrefix(itemKey) === getKeyWithoutPrefix(filterKey);
|
||||
}
|
||||
@@ -55,4 +55,5 @@ export enum QueryParams {
|
||||
source = 'source',
|
||||
showClassicCreateAlertsPage = 'showClassicCreateAlertsPage',
|
||||
isTestAlert = 'isTestAlert',
|
||||
yAxisUnit = 'yAxisUnit',
|
||||
}
|
||||
|
||||
@@ -193,7 +193,6 @@ describe('Dashboard landing page actions header tests', () => {
|
||||
handleDashboardLockToggle: jest.fn(),
|
||||
dashboardResponse: {} as IDashboardContext['dashboardResponse'],
|
||||
selectedDashboard: (getDashboardById.data as unknown) as Dashboard,
|
||||
dashboardId: '4',
|
||||
layouts: [],
|
||||
panelMap: {},
|
||||
setPanelMap: jest.fn(),
|
||||
@@ -205,8 +204,6 @@ describe('Dashboard landing page actions header tests', () => {
|
||||
updateLocalStorageDashboardVariables: jest.fn(),
|
||||
dashboardQueryRangeCalled: false,
|
||||
setDashboardQueryRangeCalled: jest.fn(),
|
||||
selectedRowWidgetId: null,
|
||||
setSelectedRowWidgetId: jest.fn(),
|
||||
isDashboardFetching: false,
|
||||
columnWidths: {},
|
||||
setColumnWidths: jest.fn(),
|
||||
|
||||
@@ -78,7 +78,6 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
isDashboardLocked,
|
||||
setSelectedDashboard,
|
||||
handleToggleDashboardSlider,
|
||||
setSelectedRowWidgetId,
|
||||
handleDashboardLockToggle,
|
||||
} = useDashboard();
|
||||
|
||||
@@ -146,7 +145,6 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
const [addPanelPermission] = useComponentPermission(permissions, userRole);
|
||||
|
||||
const onEmptyWidgetHandler = useCallback(() => {
|
||||
setSelectedRowWidgetId(null);
|
||||
handleToggleDashboardSlider(true);
|
||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||
dashboardId: selectedDashboard?.id,
|
||||
|
||||
@@ -67,17 +67,18 @@ export const useDashboardVariableUpdate = (): UseDashboardVariableUpdateReturn =
|
||||
const oldVariables = prev?.data.variables;
|
||||
// this is added to handle case where we have two different
|
||||
// schemas for variable response
|
||||
if (oldVariables?.[id]) {
|
||||
oldVariables[id] = {
|
||||
...oldVariables[id],
|
||||
const updatedVariables = { ...oldVariables };
|
||||
if (updatedVariables?.[id]) {
|
||||
updatedVariables[id] = {
|
||||
...updatedVariables[id],
|
||||
selectedValue: value,
|
||||
allSelected,
|
||||
haveCustomValuesSelected,
|
||||
};
|
||||
}
|
||||
if (oldVariables?.[name]) {
|
||||
oldVariables[name] = {
|
||||
...oldVariables[name],
|
||||
if (updatedVariables?.[name]) {
|
||||
updatedVariables[name] = {
|
||||
...updatedVariables[name],
|
||||
selectedValue: value,
|
||||
allSelected,
|
||||
haveCustomValuesSelected,
|
||||
@@ -87,9 +88,7 @@ export const useDashboardVariableUpdate = (): UseDashboardVariableUpdateReturn =
|
||||
...prev,
|
||||
data: {
|
||||
...prev?.data,
|
||||
variables: {
|
||||
...oldVariables,
|
||||
},
|
||||
variables: updatedVariables,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import ContextMenu from 'periscope/components/ContextMenu';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
@@ -62,7 +61,7 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
|
||||
currentQuery: widget.query,
|
||||
onClick: clickHandlerWithContextMenu,
|
||||
onDragSelect,
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
timezone,
|
||||
panelMode,
|
||||
minTimeScale: minTimeScale,
|
||||
|
||||
@@ -11,7 +11,6 @@ import { get } from 'lodash-es';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
import { AlignedData } from 'uplot';
|
||||
|
||||
import { PanelMode } from '../types';
|
||||
@@ -44,7 +43,7 @@ export function prepareBarPanelConfig({
|
||||
currentQuery: Query;
|
||||
onClick: OnClickPluginOpts['onClick'];
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
timezone: Timezone;
|
||||
panelMode: PanelMode;
|
||||
minTimeScale?: number;
|
||||
@@ -76,13 +75,17 @@ export function prepareBarPanelConfig({
|
||||
stepInterval: minStepInterval,
|
||||
});
|
||||
|
||||
if (!(apiResponse && apiResponse?.data?.result)) {
|
||||
// if no data, return the builder without adding any series
|
||||
return builder;
|
||||
}
|
||||
|
||||
if (widget.stackedBarChart) {
|
||||
const seriesCount = (apiResponse?.data?.result?.length ?? 0) + 1; // +1 for 1-based uPlot series indices
|
||||
const seriesCount = (apiResponse.data.result.length ?? 0) + 1; // +1 for 1-based uPlot series indices
|
||||
builder.setBands(getInitialStackedBands(seriesCount));
|
||||
}
|
||||
|
||||
const seriesList: QueryData[] = apiResponse?.data?.result || [];
|
||||
seriesList.forEach((series) => {
|
||||
apiResponse.data.result.forEach((series) => {
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
series.queryName || '', // query
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { DashboardCursorSync } from 'lib/uPlotV2/plugins/TooltipPlugin/types';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import Histogram from '../../charts/Histogram/Histogram';
|
||||
@@ -39,7 +38,7 @@ function HistogramPanel(props: PanelWrapperProps): JSX.Element {
|
||||
return prepareHistogramPanelConfig({
|
||||
widget,
|
||||
isDarkMode,
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
panelMode,
|
||||
});
|
||||
}, [widget, isDarkMode, queryResponse?.data?.payload, panelMode]);
|
||||
@@ -49,7 +48,7 @@ function HistogramPanel(props: PanelWrapperProps): JSX.Element {
|
||||
return [];
|
||||
}
|
||||
return prepareHistogramPanelData({
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
bucketWidth: widget?.bucketWidth,
|
||||
bucketCount: widget?.bucketCount,
|
||||
mergeAllActiveQueries: widget?.mergeAllActiveQueries,
|
||||
|
||||
@@ -149,7 +149,7 @@ export function prepareHistogramPanelConfig({
|
||||
isDarkMode,
|
||||
}: {
|
||||
widget: Widgets;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
panelMode: PanelMode;
|
||||
isDarkMode: boolean;
|
||||
}): UPlotConfigBuilder {
|
||||
@@ -204,7 +204,7 @@ export function prepareHistogramPanelConfig({
|
||||
fillColor: '#4E74F8',
|
||||
isDarkMode,
|
||||
});
|
||||
} else {
|
||||
} else if (apiResponse && apiResponse?.data?.result) {
|
||||
apiResponse.data.result.forEach((series) => {
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { ContextMenu } from 'periscope/components/ContextMenu';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
@@ -68,7 +67,7 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
|
||||
currentQuery: widget.query,
|
||||
onClick: clickHandlerWithContextMenu,
|
||||
onDragSelect,
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
timezone,
|
||||
panelMode,
|
||||
minTimeScale: minTimeScale,
|
||||
|
||||
@@ -68,11 +68,12 @@ export const prepareUPlotConfig = ({
|
||||
currentQuery: Query;
|
||||
onClick?: OnClickPluginOpts['onClick'];
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
timezone: Timezone;
|
||||
panelMode: PanelMode;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
}): UPlotConfigBuilder => {
|
||||
const stepIntervals: ExecStats['stepIntervals'] = get(
|
||||
apiResponse,
|
||||
@@ -100,7 +101,12 @@ export const prepareUPlotConfig = ({
|
||||
stepInterval: minStepInterval,
|
||||
});
|
||||
|
||||
apiResponse.data?.result?.forEach((series) => {
|
||||
if (!(apiResponse && apiResponse.data.result)) {
|
||||
// if no data, return the builder without adding any series
|
||||
return builder;
|
||||
}
|
||||
|
||||
apiResponse.data.result.forEach((series) => {
|
||||
const hasSingleValidPoint = hasSingleVisiblePointForSeries(series);
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
|
||||
@@ -18,7 +18,7 @@ import { PanelMode } from '../types';
|
||||
export interface BaseConfigBuilderProps {
|
||||
id: string;
|
||||
thresholds?: ThresholdProps[];
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
isDarkMode: boolean;
|
||||
onClick?: OnClickPluginOpts['onClick'];
|
||||
onDragSelect?: (startTime: number, endTime: number) => void;
|
||||
|
||||
@@ -19,7 +19,6 @@ export default function DashboardEmptyState(): JSX.Element {
|
||||
selectedDashboard,
|
||||
isDashboardLocked,
|
||||
handleToggleDashboardSlider,
|
||||
setSelectedRowWidgetId,
|
||||
} = useDashboard();
|
||||
|
||||
const variablesSettingsTabHandle = useRef<VariablesSettingsTab>(null);
|
||||
@@ -42,7 +41,6 @@ export default function DashboardEmptyState(): JSX.Element {
|
||||
const [addPanelPermission] = useComponentPermission(permissions, userRole);
|
||||
|
||||
const onEmptyWidgetHandler = useCallback(() => {
|
||||
setSelectedRowWidgetId(null);
|
||||
handleToggleDashboardSlider(true);
|
||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||
dashboardId: selectedDashboard?.id,
|
||||
|
||||
@@ -71,7 +71,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
isDashboardLocked,
|
||||
dashboardQueryRangeCalled,
|
||||
setDashboardQueryRangeCalled,
|
||||
setSelectedRowWidgetId,
|
||||
isDashboardFetching,
|
||||
columnWidths,
|
||||
} = useDashboard();
|
||||
@@ -195,7 +194,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
|
||||
updateDashboardMutation.mutate(updatedDashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
setSelectedRowWidgetId(null);
|
||||
if (updatedDashboard.data) {
|
||||
if (updatedDashboard.data.data.layout) {
|
||||
setLayouts(sortLayout(updatedDashboard.data.data.layout));
|
||||
|
||||
@@ -5,6 +5,7 @@ import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { EllipsisIcon, PenLine, Plus, X } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { setSelectedRowWidgetId } from 'providers/Dashboard/helpers/selectedRowWidgetIdHelper';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import { ComponentTypes } from 'utils/permission';
|
||||
|
||||
@@ -37,7 +38,6 @@ export function WidgetRowHeader(props: WidgetRowHeaderProps): JSX.Element {
|
||||
handleToggleDashboardSlider,
|
||||
selectedDashboard,
|
||||
isDashboardLocked,
|
||||
setSelectedRowWidgetId,
|
||||
} = useDashboard();
|
||||
|
||||
const permissions: ComponentTypes[] = ['add_panel'];
|
||||
@@ -81,7 +81,12 @@ export function WidgetRowHeader(props: WidgetRowHeaderProps): JSX.Element {
|
||||
disabled={!editWidget && addPanelPermission && !isDashboardLocked}
|
||||
icon={<Plus size={14} />}
|
||||
onClick={(): void => {
|
||||
setSelectedRowWidgetId(id);
|
||||
// TODO: @AshwinBhatkal Simplify this check in cleanup of https://github.com/SigNoz/engineering-pod/issues/3953
|
||||
if (!selectedDashboard?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedRowWidgetId(selectedDashboard.id, id);
|
||||
handleToggleDashboardSlider(true);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -39,6 +39,7 @@ import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQuery
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||
import useUrlYAxisUnit from 'hooks/useUrlYAxisUnit';
|
||||
import { isEmpty, isUndefined } from 'lodash-es';
|
||||
import LiveLogs from 'pages/LiveLogs';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
@@ -59,6 +60,7 @@ import LogsActionsContainer from './LogsActionsContainer';
|
||||
|
||||
import './LogsExplorerViews.styles.scss';
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function LogsExplorerViewsContainer({
|
||||
setIsLoadingQueries,
|
||||
listQueryKeyRef,
|
||||
@@ -109,7 +111,7 @@ function LogsExplorerViewsContainer({
|
||||
|
||||
const [orderBy, setOrderBy] = useState<string>('timestamp:desc');
|
||||
|
||||
const [yAxisUnit, setYAxisUnit] = useState<string>('');
|
||||
const { yAxisUnit, onUnitChange } = useUrlYAxisUnit('');
|
||||
|
||||
const listQuery = useMemo(() => getListQuery(stagedQuery) || null, [
|
||||
stagedQuery,
|
||||
@@ -367,10 +369,6 @@ function LogsExplorerViewsContainer({
|
||||
orderBy,
|
||||
]);
|
||||
|
||||
const onUnitChangeHandler = useCallback((value: string): void => {
|
||||
setYAxisUnit(value);
|
||||
}, []);
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
if (!stagedQuery) {
|
||||
return [];
|
||||
@@ -488,10 +486,7 @@ function LogsExplorerViewsContainer({
|
||||
{selectedPanelType === PANEL_TYPES.TIME_SERIES && !showLiveLogs && (
|
||||
<div className="time-series-view-container">
|
||||
<div className="time-series-view-container-header">
|
||||
<BuilderUnitsFilter
|
||||
onChange={onUnitChangeHandler}
|
||||
yAxisUnit={yAxisUnit}
|
||||
/>
|
||||
<BuilderUnitsFilter onChange={onUnitChange} yAxisUnit={yAxisUnit} />
|
||||
</div>
|
||||
<TimeSeriesView
|
||||
isLoading={isLoading || isFetching}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -11,6 +11,7 @@ import { BuilderUnitsFilter } from 'container/QueryBuilder/filters/BuilderUnitsF
|
||||
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||
import { convertDataValueToMs } from 'container/TimeSeriesView/utils';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import useUrlYAxisUnit from 'hooks/useUrlYAxisUnit';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -22,14 +23,13 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
function TimeSeries(): JSX.Element {
|
||||
const { stagedQuery, currentQuery } = useQueryBuilder();
|
||||
const { yAxisUnit, onUnitChange } = useUrlYAxisUnit('');
|
||||
|
||||
const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const [yAxisUnit, setYAxisUnit] = useState<string>('');
|
||||
|
||||
const isValidToConvertToMs = useMemo(() => {
|
||||
const isValid: boolean[] = [];
|
||||
|
||||
@@ -112,10 +112,6 @@ function TimeSeries(): JSX.Element {
|
||||
[data, isValidToConvertToMs],
|
||||
);
|
||||
|
||||
const onUnitChangeHandler = (value: string): void => {
|
||||
setYAxisUnit(value);
|
||||
};
|
||||
|
||||
const hasMetricSelected = useMemo(
|
||||
() => currentQuery.builder.queryData.some((q) => q.aggregateAttribute?.key),
|
||||
[currentQuery],
|
||||
@@ -123,7 +119,7 @@ function TimeSeries(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className="meter-time-series-container">
|
||||
<BuilderUnitsFilter onChange={onUnitChangeHandler} yAxisUnit={yAxisUnit} />
|
||||
<BuilderUnitsFilter onChange={onUnitChange} yAxisUnit={yAxisUnit} />
|
||||
<div className="time-series-container">
|
||||
{!hasMetricSelected && <EmptyMetricsSearch />}
|
||||
{hasMetricSelected &&
|
||||
|
||||
@@ -34,6 +34,10 @@ import { cloneDeep, defaultTo, isEmpty, isUndefined } from 'lodash-es';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import {
|
||||
clearSelectedRowWidgetId,
|
||||
getSelectedRowWidgetId,
|
||||
} from 'providers/Dashboard/helpers/selectedRowWidgetIdHelper';
|
||||
import {
|
||||
getNextWidgets,
|
||||
getPreviousWidgets,
|
||||
@@ -86,8 +90,6 @@ function NewWidget({
|
||||
selectedDashboard,
|
||||
setSelectedDashboard,
|
||||
setToScrollWidgetId,
|
||||
selectedRowWidgetId,
|
||||
setSelectedRowWidgetId,
|
||||
columnWidths,
|
||||
} = useDashboard();
|
||||
|
||||
@@ -450,6 +452,8 @@ function NewWidget({
|
||||
const widgetId = query.get('widgetId') || '';
|
||||
let updatedLayout = selectedDashboard.data.layout || [];
|
||||
|
||||
const selectedRowWidgetId = getSelectedRowWidgetId(dashboardId);
|
||||
|
||||
if (isNewDashboard && isEmpty(selectedRowWidgetId)) {
|
||||
const newLayoutItem = placeWidgetAtBottom(widgetId, updatedLayout);
|
||||
updatedLayout = [...updatedLayout, newLayoutItem];
|
||||
@@ -554,7 +558,6 @@ function NewWidget({
|
||||
|
||||
updateDashboardMutation.mutateAsync(dashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
setSelectedRowWidgetId(null);
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
setToScrollWidgetId(selectedWidget?.id || '');
|
||||
safeNavigate({
|
||||
@@ -566,7 +569,6 @@ function NewWidget({
|
||||
selectedDashboard,
|
||||
query,
|
||||
isNewDashboard,
|
||||
selectedRowWidgetId,
|
||||
afterWidgets,
|
||||
selectedWidget,
|
||||
selectedTime.enum,
|
||||
@@ -577,7 +579,6 @@ function NewWidget({
|
||||
widgets,
|
||||
setSelectedDashboard,
|
||||
setToScrollWidgetId,
|
||||
setSelectedRowWidgetId,
|
||||
safeNavigate,
|
||||
dashboardId,
|
||||
]);
|
||||
@@ -681,6 +682,10 @@ function NewWidget({
|
||||
* on mount here with the currentQuery in the begining itself
|
||||
*/
|
||||
setSupersetQuery(currentQuery);
|
||||
|
||||
return (): void => {
|
||||
clearSelectedRowWidgetId(dashboardId);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -619,6 +619,11 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
);
|
||||
};
|
||||
|
||||
const progressText = `Get Started (${Math.min(
|
||||
currentStep + 1,
|
||||
setupStepItems.length,
|
||||
)}/${setupStepItems.length})`;
|
||||
|
||||
return (
|
||||
<div className="onboarding-v2">
|
||||
<Layout>
|
||||
@@ -639,7 +644,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
history.push(ROUTES.HOME);
|
||||
}}
|
||||
/>
|
||||
<Typography.Text>Get Started (2/4)</Typography.Text>
|
||||
<Typography.Text>{progressText}</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div className="header-right-section">
|
||||
|
||||
@@ -611,6 +611,34 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataSource": "deno",
|
||||
"label": "Deno",
|
||||
"imgUrl": "/Logos/deno.svg",
|
||||
"tags": [
|
||||
"apm/traces",
|
||||
"logs",
|
||||
"metrics"
|
||||
],
|
||||
"module": "apm",
|
||||
"relatedSearchKeywords": [
|
||||
"deno",
|
||||
"deno monitoring",
|
||||
"deno observability",
|
||||
"javascript",
|
||||
"js",
|
||||
"ts",
|
||||
"typescript",
|
||||
"opentelemetry deno",
|
||||
"observability",
|
||||
"metrics",
|
||||
"logs",
|
||||
"traces",
|
||||
"tracing"
|
||||
],
|
||||
"id": "deno",
|
||||
"link": "/docs/instrumentation/opentelemetry-deno/"
|
||||
},
|
||||
{
|
||||
"dataSource": "javascript",
|
||||
"label": "JavaScript",
|
||||
@@ -1828,15 +1856,33 @@
|
||||
],
|
||||
"module": "logs",
|
||||
"relatedSearchKeywords": [
|
||||
"apache",
|
||||
"application log file collection",
|
||||
"c#",
|
||||
"c++",
|
||||
"collect",
|
||||
"collect logs from file",
|
||||
"collect logs from log file",
|
||||
"cpp",
|
||||
"csharp",
|
||||
"custom log file monitoring",
|
||||
"deno",
|
||||
"django",
|
||||
"dotnet",
|
||||
"elixir",
|
||||
"express",
|
||||
"file",
|
||||
"file based logging",
|
||||
"filebeat alternative",
|
||||
"flask",
|
||||
"from-log-file",
|
||||
"go",
|
||||
"golang",
|
||||
"java",
|
||||
"javascript",
|
||||
"js",
|
||||
"kotlin",
|
||||
"linux",
|
||||
"log",
|
||||
"log file ingestion",
|
||||
"log file to signoz",
|
||||
@@ -1844,7 +1890,29 @@
|
||||
"log tailing with otel",
|
||||
"logging",
|
||||
"logs",
|
||||
"otel file logs"
|
||||
"macos",
|
||||
"mongodb",
|
||||
"mysql",
|
||||
"nestjs",
|
||||
"nextjs",
|
||||
"nginx",
|
||||
"otel file logs",
|
||||
"php",
|
||||
"postgres",
|
||||
"postgresql",
|
||||
"python",
|
||||
"rails",
|
||||
"react",
|
||||
"redis",
|
||||
"ruby",
|
||||
"rust",
|
||||
"scala",
|
||||
"spring",
|
||||
"swift",
|
||||
"ts",
|
||||
"typescript",
|
||||
"vue",
|
||||
"windows"
|
||||
],
|
||||
"link": "/docs/userguide/collect_logs_from_file/"
|
||||
},
|
||||
@@ -5208,7 +5276,7 @@
|
||||
{
|
||||
"dataSource": "prometheus-metrics",
|
||||
"label": "Prometheus Metrics",
|
||||
"imgUrl": "/Logos/other-metrics.svg",
|
||||
"imgUrl": "/Logos/prometheus.svg",
|
||||
"tags": [
|
||||
"metrics",
|
||||
"infrastructure monitoring"
|
||||
@@ -5809,5 +5877,250 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataSource": "rust-metrics",
|
||||
"label": "Rust Metrics",
|
||||
"imgUrl": "/Logos/rust.svg",
|
||||
"tags": [
|
||||
"metrics"
|
||||
],
|
||||
"module": "metrics",
|
||||
"relatedSearchKeywords": [
|
||||
"metrics",
|
||||
"opentelemetry rust",
|
||||
"otel rust",
|
||||
"rust",
|
||||
"rust metrics",
|
||||
"rust monitoring",
|
||||
"rust observability"
|
||||
],
|
||||
"id": "rust-metrics",
|
||||
"link": "/docs/metrics-management/send-metrics/applications/opentelemetry-rust/",
|
||||
"question": {
|
||||
"desc": "What is your Environment?",
|
||||
"type": "select",
|
||||
"entityID": "environment",
|
||||
"options": [
|
||||
{
|
||||
"key": "vm",
|
||||
"label": "VM",
|
||||
"imgUrl": "/Logos/vm.svg",
|
||||
"link": "/docs/metrics-management/send-metrics/applications/opentelemetry-rust/"
|
||||
},
|
||||
{
|
||||
"key": "k8s",
|
||||
"label": "Kubernetes",
|
||||
"imgUrl": "/Logos/kubernetes.svg",
|
||||
"link": "/docs/metrics-management/send-metrics/applications/opentelemetry-rust/"
|
||||
},
|
||||
{
|
||||
"key": "windows",
|
||||
"label": "Windows",
|
||||
"imgUrl": "/Logos/windows.svg",
|
||||
"link": "/docs/metrics-management/send-metrics/applications/opentelemetry-rust/"
|
||||
},
|
||||
{
|
||||
"key": "docker",
|
||||
"label": "Docker",
|
||||
"imgUrl": "/Logos/docker.svg",
|
||||
"link": "/docs/metrics-management/send-metrics/applications/opentelemetry-rust/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataSource": "ruby-metrics",
|
||||
"label": "Ruby Metrics",
|
||||
"imgUrl": "/Logos/ruby-on-rails.svg",
|
||||
"tags": [
|
||||
"metrics"
|
||||
],
|
||||
"module": "metrics",
|
||||
"relatedSearchKeywords": [
|
||||
"metrics",
|
||||
"opentelemetry ruby",
|
||||
"otel ruby",
|
||||
"ruby",
|
||||
"ruby metrics",
|
||||
"ruby monitoring",
|
||||
"ruby observability",
|
||||
"ruby on rails metrics"
|
||||
],
|
||||
"id": "ruby-metrics",
|
||||
"link": "/docs/metrics-management/send-metrics/applications/opentelemetry-ruby/",
|
||||
"question": {
|
||||
"desc": "What is your Environment?",
|
||||
"type": "select",
|
||||
"entityID": "environment",
|
||||
"options": [
|
||||
{
|
||||
"key": "vm",
|
||||
"label": "VM",
|
||||
"imgUrl": "/Logos/vm.svg",
|
||||
"link": "/docs/metrics-management/send-metrics/applications/opentelemetry-ruby/"
|
||||
},
|
||||
{
|
||||
"key": "k8s",
|
||||
"label": "Kubernetes",
|
||||
"imgUrl": "/Logos/kubernetes.svg",
|
||||
"link": "/docs/metrics-management/send-metrics/applications/opentelemetry-ruby/"
|
||||
},
|
||||
{
|
||||
"key": "windows",
|
||||
"label": "Windows",
|
||||
"imgUrl": "/Logos/windows.svg",
|
||||
"link": "/docs/metrics-management/send-metrics/applications/opentelemetry-ruby/"
|
||||
},
|
||||
{
|
||||
"key": "docker",
|
||||
"label": "Docker",
|
||||
"imgUrl": "/Logos/docker.svg",
|
||||
"link": "/docs/metrics-management/send-metrics/applications/opentelemetry-ruby/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"dataSource": "statsd",
|
||||
"label": "StatsD",
|
||||
"imgUrl": "/Logos/statsd.svg",
|
||||
"tags": [
|
||||
"metrics"
|
||||
],
|
||||
"module": "metrics",
|
||||
"relatedSearchKeywords": [
|
||||
"metrics",
|
||||
"statsd",
|
||||
"statsd metrics",
|
||||
"statsd monitoring",
|
||||
"statsd observability"
|
||||
],
|
||||
"id": "statsd",
|
||||
"link": "/docs/userguide/opentelemetry-statsd/"
|
||||
},
|
||||
{
|
||||
"dataSource": "nvidia-dcgm",
|
||||
"label": "Nvidia DCGM",
|
||||
"imgUrl": "/Logos/nvidia-dcgm.svg",
|
||||
"tags": [
|
||||
"infrastructure monitoring"
|
||||
],
|
||||
"module": "infra-monitoring-hosts",
|
||||
"relatedSearchKeywords": [
|
||||
"dcgm",
|
||||
"gpu monitoring",
|
||||
"infrastructure monitoring",
|
||||
"nvidia",
|
||||
"nvidia dcgm",
|
||||
"nvidia metrics"
|
||||
],
|
||||
"id": "nvidia-dcgm",
|
||||
"link": "/docs/metrics-management/nvidia-dcgm-metrics/"
|
||||
},
|
||||
{
|
||||
"dataSource": "slurm",
|
||||
"label": "Slurm",
|
||||
"imgUrl": "/Logos/slurm.svg",
|
||||
"tags": [
|
||||
"infrastructure monitoring"
|
||||
],
|
||||
"module": "infra-monitoring-hosts",
|
||||
"relatedSearchKeywords": [
|
||||
"infrastructure monitoring",
|
||||
"slurm",
|
||||
"slurm cluster monitoring",
|
||||
"slurm metrics"
|
||||
],
|
||||
"id": "slurm",
|
||||
"link": "/docs/metrics-management/slurm-metrics/"
|
||||
},
|
||||
{
|
||||
"dataSource": "ollama-monitoring",
|
||||
"label": "Ollama",
|
||||
"imgUrl": "/Logos/ollama.svg",
|
||||
"tags": [
|
||||
"LLM Monitoring"
|
||||
],
|
||||
"module": "apm",
|
||||
"relatedSearchKeywords": [
|
||||
"llm",
|
||||
"llm monitoring",
|
||||
"monitoring",
|
||||
"observability",
|
||||
"ollama",
|
||||
"ollama monitoring",
|
||||
"otel ollama",
|
||||
"traces",
|
||||
"tracing"
|
||||
],
|
||||
"id": "ollama-monitoring",
|
||||
"link": "/docs/ollama-monitoring/"
|
||||
},
|
||||
{
|
||||
"dataSource": "haystack-monitoring",
|
||||
"label": "Haystack",
|
||||
"imgUrl": "/Logos/haystack.svg",
|
||||
"tags": [
|
||||
"LLM Monitoring"
|
||||
],
|
||||
"module": "apm",
|
||||
"relatedSearchKeywords": [
|
||||
"haystack",
|
||||
"haystack monitoring",
|
||||
"llm",
|
||||
"llm monitoring",
|
||||
"monitoring",
|
||||
"observability",
|
||||
"otel haystack",
|
||||
"traces",
|
||||
"tracing"
|
||||
],
|
||||
"id": "haystack-monitoring",
|
||||
"link": "/docs/haystack-monitoring/"
|
||||
},
|
||||
{
|
||||
"dataSource": "openrouter-observability",
|
||||
"label": "OpenRouter",
|
||||
"imgUrl": "/Logos/openrouter.svg",
|
||||
"tags": [
|
||||
"LLM Monitoring"
|
||||
],
|
||||
"module": "apm",
|
||||
"relatedSearchKeywords": [
|
||||
"llm",
|
||||
"llm monitoring",
|
||||
"monitoring",
|
||||
"observability",
|
||||
"openrouter",
|
||||
"openrouter monitoring",
|
||||
"otel openrouter",
|
||||
"traces",
|
||||
"tracing"
|
||||
],
|
||||
"id": "openrouter-observability",
|
||||
"link": "/docs/openrouter-observability/"
|
||||
},
|
||||
{
|
||||
"dataSource": "huggingface-observability",
|
||||
"label": "Hugging Face",
|
||||
"imgUrl": "/Logos/huggingface.svg",
|
||||
"tags": [
|
||||
"LLM Monitoring"
|
||||
],
|
||||
"module": "apm",
|
||||
"relatedSearchKeywords": [
|
||||
"hugging face monitoring",
|
||||
"huggingface",
|
||||
"llm",
|
||||
"llm monitoring",
|
||||
"monitoring",
|
||||
"observability",
|
||||
"otel huggingface",
|
||||
"traces",
|
||||
"tracing"
|
||||
],
|
||||
"id": "huggingface-observability",
|
||||
"link": "/docs/huggingface-observability/"
|
||||
}
|
||||
]
|
||||
|
||||
129
frontend/src/hooks/useGetQueryLabels.test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import {
|
||||
IBuilderFormula,
|
||||
IClickHouseQuery,
|
||||
IPromQLQuery,
|
||||
Query,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
import { useGetQueryLabels } from './useGetQueryLabels';
|
||||
|
||||
jest.mock('components/QueryBuilderV2/utils', () => ({
|
||||
getQueryLabelWithAggregation: jest.fn(() => []),
|
||||
}));
|
||||
|
||||
function buildQuery(overrides: Partial<Query> = {}): Query {
|
||||
return {
|
||||
id: 'test-id',
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
builder: {
|
||||
queryData: [],
|
||||
queryFormulas: [],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('useGetQueryLabels', () => {
|
||||
describe('QUERY_BUILDER type', () => {
|
||||
it('returns empty array when queryFormulas is undefined', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
builder: {
|
||||
queryData: [],
|
||||
queryFormulas: (undefined as unknown) as IBuilderFormula[],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns formula labels when queryFormulas is populated', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
builder: {
|
||||
queryData: [],
|
||||
queryFormulas: [
|
||||
({ queryName: 'F1' } as unknown) as IBuilderFormula,
|
||||
({ queryName: 'F2' } as unknown) as IBuilderFormula,
|
||||
],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([
|
||||
{ label: 'F1', value: 'F1' },
|
||||
{ label: 'F2', value: 'F2' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CLICKHOUSE type', () => {
|
||||
it('returns empty array when clickhouse_sql is undefined', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.CLICKHOUSE,
|
||||
clickhouse_sql: (undefined as unknown) as IClickHouseQuery[],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns labels from clickhouse_sql when populated', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.CLICKHOUSE,
|
||||
clickhouse_sql: [
|
||||
({ name: 'query_a' } as unknown) as IClickHouseQuery,
|
||||
({ name: 'query_b' } as unknown) as IClickHouseQuery,
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([
|
||||
{ label: 'query_a', value: 'query_a' },
|
||||
{ label: 'query_b', value: 'query_b' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PROM type (default)', () => {
|
||||
it('returns empty array when promql is undefined', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.PROM,
|
||||
promql: (undefined as unknown) as IPromQLQuery[],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns labels from promql when populated', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.PROM,
|
||||
promql: [
|
||||
({ name: 'prom_1' } as unknown) as IPromQLQuery,
|
||||
({ name: 'prom_2' } as unknown) as IPromQLQuery,
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([
|
||||
{ label: 'prom_1', value: 'prom_1' },
|
||||
{ label: 'prom_2', value: 'prom_2' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ export const useGetQueryLabels = (
|
||||
const queryLabels = getQueryLabelWithAggregation(
|
||||
currentQuery?.builder?.queryData || [],
|
||||
);
|
||||
const formulaLabels = currentQuery?.builder?.queryFormulas?.map(
|
||||
const formulaLabels = (currentQuery?.builder?.queryFormulas ?? []).map(
|
||||
(formula) => ({
|
||||
label: formula.queryName,
|
||||
value: formula.queryName,
|
||||
@@ -20,10 +20,13 @@ export const useGetQueryLabels = (
|
||||
return [...queryLabels, ...formulaLabels];
|
||||
}
|
||||
if (currentQuery?.queryType === EQueryType.CLICKHOUSE) {
|
||||
return currentQuery?.clickhouse_sql?.map((q) => ({
|
||||
return (currentQuery?.clickhouse_sql ?? []).map((q) => ({
|
||||
label: q.name,
|
||||
value: q.name,
|
||||
}));
|
||||
}
|
||||
return currentQuery?.promql?.map((q) => ({ label: q.name, value: q.name }));
|
||||
return (currentQuery?.promql ?? []).map((q) => ({
|
||||
label: q.name,
|
||||
value: q.name,
|
||||
}));
|
||||
}, [currentQuery]);
|
||||
|
||||
33
frontend/src/hooks/useUrlYAxisUnit.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useCallback } from 'react';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
|
||||
interface UseUrlYAxisUnitResult {
|
||||
yAxisUnit: string;
|
||||
onUnitChange: (value: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to manage y-axis unit synchronized with the URL query param.
|
||||
* It:
|
||||
* - Initializes from `QueryParams.yAxisUnit` or a provided default
|
||||
* - Keeps local state in sync when the URL changes (e.g. back/forward, shared links)
|
||||
* - Writes updates back to the URL while preserving existing query params
|
||||
*/
|
||||
function useUrlYAxisUnit(defaultUnit = ''): UseUrlYAxisUnitResult {
|
||||
const [yAxisUnit, setYAxisUnitInUrl] = useQueryState(
|
||||
QueryParams.yAxisUnit,
|
||||
parseAsString.withDefault(defaultUnit),
|
||||
);
|
||||
|
||||
const onUnitChange = useCallback(
|
||||
(value: string): void => {
|
||||
setYAxisUnitInUrl(value || null);
|
||||
},
|
||||
[setYAxisUnitInUrl],
|
||||
);
|
||||
|
||||
return { yAxisUnit, onUnitChange };
|
||||
}
|
||||
|
||||
export default useUrlYAxisUnit;
|
||||
@@ -6,6 +6,7 @@ import { Provider } from 'react-redux';
|
||||
import AppRoutes from 'AppRoutes';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ThemeProvider } from 'hooks/useDarkMode';
|
||||
import { NuqsAdapter } from 'nuqs/adapters/react';
|
||||
import { AppProvider } from 'providers/App/App';
|
||||
import TimezoneProvider from 'providers/Timezone';
|
||||
import store from 'store';
|
||||
@@ -45,17 +46,19 @@ if (container) {
|
||||
|
||||
root.render(
|
||||
<HelmetProvider>
|
||||
<ThemeProvider>
|
||||
<TimezoneProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<AppProvider>
|
||||
<AppRoutes />
|
||||
</AppProvider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</TimezoneProvider>
|
||||
</ThemeProvider>
|
||||
<NuqsAdapter>
|
||||
<ThemeProvider>
|
||||
<TimezoneProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<AppProvider>
|
||||
<AppRoutes />
|
||||
</AppProvider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</TimezoneProvider>
|
||||
</ThemeProvider>
|
||||
</NuqsAdapter>
|
||||
</HelmetProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -16,6 +15,7 @@ import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||
import { convertDataValueToMs } from 'container/TimeSeriesView/utils';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import useUrlYAxisUnit from 'hooks/useUrlYAxisUnit';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Warning } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
@@ -52,13 +52,8 @@ function TimeSeriesViewContainer({
|
||||
return isValid.every(Boolean);
|
||||
}, [currentQuery]);
|
||||
|
||||
const [yAxisUnit, setYAxisUnit] = useState<string>(
|
||||
isValidToConvertToMs ? 'ms' : 'short',
|
||||
);
|
||||
|
||||
const onUnitChangeHandler = (value: string): void => {
|
||||
setYAxisUnit(value);
|
||||
};
|
||||
const defaultUnit = isValidToConvertToMs ? 'ms' : 'short';
|
||||
const { yAxisUnit, onUnitChange } = useUrlYAxisUnit(defaultUnit);
|
||||
|
||||
const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
|
||||
AppState,
|
||||
@@ -121,7 +116,7 @@ function TimeSeriesViewContainer({
|
||||
return (
|
||||
<div className="trace-explorer-time-series-view-container">
|
||||
<div className="trace-explorer-time-series-view-container-header">
|
||||
<BuilderUnitsFilter onChange={onUnitChangeHandler} yAxisUnit={yAxisUnit} />
|
||||
<BuilderUnitsFilter onChange={onUnitChange} yAxisUnit={yAxisUnit} />
|
||||
</div>
|
||||
<TimeSeriesView
|
||||
isFilterApplied={isFilterApplied}
|
||||
|
||||
@@ -68,7 +68,6 @@ export const DashboardContext = createContext<IDashboardContext>({
|
||||
APIError
|
||||
>,
|
||||
selectedDashboard: {} as Dashboard,
|
||||
dashboardId: '',
|
||||
layouts: [],
|
||||
panelMap: {},
|
||||
setPanelMap: () => {},
|
||||
@@ -81,8 +80,6 @@ export const DashboardContext = createContext<IDashboardContext>({
|
||||
updateLocalStorageDashboardVariables: () => {},
|
||||
dashboardQueryRangeCalled: false,
|
||||
setDashboardQueryRangeCalled: () => {},
|
||||
selectedRowWidgetId: '',
|
||||
setSelectedRowWidgetId: () => {},
|
||||
isDashboardFetching: false,
|
||||
columnWidths: {},
|
||||
setColumnWidths: () => {},
|
||||
@@ -102,10 +99,6 @@ export function DashboardProvider({
|
||||
|
||||
const [isDashboardLocked, setIsDashboardLocked] = useState<boolean>(false);
|
||||
|
||||
const [selectedRowWidgetId, setSelectedRowWidgetId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const [
|
||||
dashboardQueryRangeCalled,
|
||||
setDashboardQueryRangeCalled,
|
||||
@@ -468,8 +461,6 @@ export function DashboardProvider({
|
||||
updateLocalStorageDashboardVariables,
|
||||
dashboardQueryRangeCalled,
|
||||
setDashboardQueryRangeCalled,
|
||||
selectedRowWidgetId,
|
||||
setSelectedRowWidgetId,
|
||||
isDashboardFetching,
|
||||
columnWidths,
|
||||
setColumnWidths,
|
||||
@@ -488,8 +479,6 @@ export function DashboardProvider({
|
||||
currentDashboard,
|
||||
dashboardQueryRangeCalled,
|
||||
setDashboardQueryRangeCalled,
|
||||
selectedRowWidgetId,
|
||||
setSelectedRowWidgetId,
|
||||
isDashboardFetching,
|
||||
columnWidths,
|
||||
setColumnWidths,
|
||||
|
||||
@@ -58,8 +58,9 @@ jest.mock('react-redux', () => ({
|
||||
jest.mock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid') }));
|
||||
|
||||
function TestComponent(): JSX.Element {
|
||||
const { dashboardResponse, dashboardId, selectedDashboard } = useDashboard();
|
||||
const { dashboardResponse, selectedDashboard } = useDashboard();
|
||||
const { dashboardVariables } = useDashboardVariables();
|
||||
const dashboardId = selectedDashboard?.id;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
const PREFIX = 'dashboard_row_widget_';
|
||||
|
||||
function getKey(dashboardId: string): string {
|
||||
return `${PREFIX}${dashboardId}`;
|
||||
}
|
||||
|
||||
export function setSelectedRowWidgetId(
|
||||
dashboardId: string,
|
||||
widgetId: string,
|
||||
): void {
|
||||
const key = getKey(dashboardId);
|
||||
|
||||
// remove all other selected widget ids for the dashboard before setting the new one
|
||||
// to ensure only one widget is selected at a time. Helps out in weird navigate and refresh scenarios
|
||||
Object.keys(sessionStorage)
|
||||
.filter((k) => k.startsWith(PREFIX) && k !== key)
|
||||
.forEach((k) => sessionStorage.removeItem(k));
|
||||
|
||||
sessionStorage.setItem(key, widgetId);
|
||||
}
|
||||
|
||||
export function getSelectedRowWidgetId(dashboardId: string): string | null {
|
||||
return sessionStorage.getItem(getKey(dashboardId));
|
||||
}
|
||||
|
||||
export function clearSelectedRowWidgetId(dashboardId: string): void {
|
||||
sessionStorage.removeItem(getKey(dashboardId));
|
||||
}
|
||||
@@ -15,7 +15,6 @@ export interface IDashboardContext {
|
||||
handleDashboardLockToggle: (value: boolean) => void;
|
||||
dashboardResponse: UseQueryResult<SuccessResponseV2<Dashboard>, unknown>;
|
||||
selectedDashboard: Dashboard | undefined;
|
||||
dashboardId: string;
|
||||
layouts: Layout[];
|
||||
panelMap: Record<string, { widgets: Layout[]; collapsed: boolean }>;
|
||||
setPanelMap: React.Dispatch<React.SetStateAction<Record<string, any>>>;
|
||||
@@ -40,8 +39,6 @@ export interface IDashboardContext {
|
||||
) => void;
|
||||
dashboardQueryRangeCalled: boolean;
|
||||
setDashboardQueryRangeCalled: (value: boolean) => void;
|
||||
selectedRowWidgetId: string | null;
|
||||
setSelectedRowWidgetId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
isDashboardFetching: boolean;
|
||||
columnWidths: WidgetColumnWidths;
|
||||
setColumnWidths: React.Dispatch<React.SetStateAction<WidgetColumnWidths>>;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { render, RenderOptions, RenderResult } from '@testing-library/react';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { ORG_PREFERENCES } from 'constants/orgPreferences';
|
||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||
import { NuqsAdapter } from 'nuqs/adapters/react';
|
||||
import { AppContext } from 'providers/App/App';
|
||||
import { IAppContext } from 'providers/App/types';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
@@ -280,23 +281,25 @@ export function AllTheProviders({
|
||||
|
||||
return (
|
||||
<MemoryRouter initialEntries={[initialRouteValue]}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={mockStored(roleValue)}>
|
||||
<AppContext.Provider
|
||||
value={getAppContextMock(roleValue, appContextOverridesValue)}
|
||||
>
|
||||
<ResourceProvider>
|
||||
<ErrorModalProvider>
|
||||
<TimezoneProvider>
|
||||
<PreferenceContextProvider>
|
||||
{queryBuilderContent}
|
||||
</PreferenceContextProvider>
|
||||
</TimezoneProvider>
|
||||
</ErrorModalProvider>
|
||||
</ResourceProvider>
|
||||
</AppContext.Provider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
<NuqsAdapter>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={mockStored(roleValue)}>
|
||||
<AppContext.Provider
|
||||
value={getAppContextMock(roleValue, appContextOverridesValue)}
|
||||
>
|
||||
<ResourceProvider>
|
||||
<ErrorModalProvider>
|
||||
<TimezoneProvider>
|
||||
<PreferenceContextProvider>
|
||||
{queryBuilderContent}
|
||||
</PreferenceContextProvider>
|
||||
</TimezoneProvider>
|
||||
</ErrorModalProvider>
|
||||
</ResourceProvider>
|
||||
</AppContext.Provider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</NuqsAdapter>
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
2
go.mod
@@ -11,6 +11,7 @@ require (
|
||||
github.com/SigNoz/signoz-otel-collector v0.144.2
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1
|
||||
github.com/antonmedv/expr v1.15.3
|
||||
github.com/bytedance/sonic v1.14.1
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/coreos/go-oidc/v3 v3.17.0
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0
|
||||
@@ -105,7 +106,6 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect
|
||||
github.com/aws/smithy-go v1.24.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
|
||||
@@ -78,7 +78,7 @@ func (m *module) ListPromotedAndIndexedPaths(ctx context.Context) ([]promotetype
|
||||
|
||||
// add the paths that are not promoted but have indexes
|
||||
for path, indexes := range aggr {
|
||||
path := strings.TrimPrefix(path, telemetrylogs.BodyV2ColumnPrefix)
|
||||
path := strings.TrimPrefix(path, telemetrylogs.BodyJSONColumnPrefix)
|
||||
path = telemetrytypes.BodyJSONStringSearchPrefix + path
|
||||
response = append(response, promotetypes.PromotePath{
|
||||
Path: path,
|
||||
@@ -163,7 +163,7 @@ func (m *module) PromoteAndIndexPaths(
|
||||
}
|
||||
}
|
||||
if len(it.Indexes) > 0 {
|
||||
parentColumn := telemetrylogs.LogsV2BodyV2Column
|
||||
parentColumn := telemetrylogs.LogsV2BodyJSONColumn
|
||||
// if the path is already promoted or is being promoted, add it to the promoted column
|
||||
if _, promoted := existingPromotedPaths[it.Path]; promoted || it.Promote {
|
||||
parentColumn = telemetrylogs.LogsV2BodyPromotedColumn
|
||||
|
||||
@@ -10,11 +10,13 @@ import (
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrylogs"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type builderQuery[T any] struct {
|
||||
@@ -260,6 +262,40 @@ func (q *builderQuery[T]) executeWithContext(ctx context.Context, query string,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// merge body_json and promoted into body
|
||||
if q.spec.Signal == telemetrytypes.SignalLogs {
|
||||
switch typedPayload := payload.(type) {
|
||||
case *qbtypes.RawData:
|
||||
for _, rr := range typedPayload.Rows {
|
||||
seeder := func() error {
|
||||
body, ok := rr.Data[telemetrylogs.LogsV2BodyJSONColumn].(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
promoted, ok := rr.Data[telemetrylogs.LogsV2BodyPromotedColumn].(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
seed(promoted, body)
|
||||
str, err := sonic.MarshalString(body)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to marshal body")
|
||||
}
|
||||
rr.Data["body"] = str
|
||||
return nil
|
||||
}
|
||||
err := seeder()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(rr.Data, telemetrylogs.LogsV2BodyJSONColumn)
|
||||
delete(rr.Data, telemetrylogs.LogsV2BodyPromotedColumn)
|
||||
}
|
||||
payload = typedPayload
|
||||
}
|
||||
}
|
||||
|
||||
return &qbtypes.Result{
|
||||
Type: q.kind,
|
||||
Value: payload,
|
||||
@@ -387,3 +423,18 @@ func decodeCursor(cur string) (int64, error) {
|
||||
}
|
||||
return strconv.ParseInt(string(b), 10, 64)
|
||||
}
|
||||
|
||||
func seed(promoted map[string]any, body map[string]any) {
|
||||
for key, fromValue := range promoted {
|
||||
if toValue, ok := body[key]; !ok {
|
||||
body[key] = fromValue
|
||||
} else {
|
||||
if fromValue, ok := fromValue.(map[string]any); ok {
|
||||
if toValue, ok := toValue.(map[string]any); ok {
|
||||
seed(fromValue, toValue)
|
||||
body[key] = toValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -393,11 +394,17 @@ func readAsRaw(rows driver.Rows, queryName string) (*qbtypes.RawData, error) {
|
||||
|
||||
// de-reference the typed pointer to any
|
||||
val := reflect.ValueOf(cellPtr).Elem().Interface()
|
||||
// Post-process JSON columns: normalize into String value
|
||||
|
||||
// Post-process JSON columns: normalize into structured values
|
||||
if strings.HasPrefix(strings.ToUpper(colTypes[i].DatabaseTypeName()), "JSON") {
|
||||
switch x := val.(type) {
|
||||
case []byte:
|
||||
val = string(x)
|
||||
if len(x) > 0 {
|
||||
var v any
|
||||
if err := sonic.Unmarshal(x, &v); err == nil {
|
||||
val = v
|
||||
}
|
||||
}
|
||||
default:
|
||||
// already a structured type (map[string]any, []any, etc.)
|
||||
}
|
||||
|
||||
@@ -204,10 +204,7 @@ func DataTypeCollisionHandledFieldName(key *telemetrytypes.TelemetryFieldKey, va
|
||||
// While we expect user not to send the mixed data types, it inevitably happens
|
||||
// So we handle the data type collisions here
|
||||
switch key.FieldDataType {
|
||||
case telemetrytypes.FieldDataTypeString, telemetrytypes.FieldDataTypeArrayString, telemetrytypes.FieldDataTypeJSON:
|
||||
if key.FieldDataType == telemetrytypes.FieldDataTypeJSON {
|
||||
tblFieldName = fmt.Sprintf("toString(%s)", tblFieldName)
|
||||
}
|
||||
case telemetrytypes.FieldDataTypeString, telemetrytypes.FieldDataTypeArrayString:
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
// try to convert the string value to to number
|
||||
@@ -222,6 +219,7 @@ func DataTypeCollisionHandledFieldName(key *telemetrytypes.TelemetryFieldKey, va
|
||||
// we don't have a toBoolOrNull in ClickHouse, so we need to convert the bool to a string
|
||||
value = fmt.Sprintf("%t", v)
|
||||
}
|
||||
|
||||
case telemetrytypes.FieldDataTypeInt64,
|
||||
telemetrytypes.FieldDataTypeArrayInt64,
|
||||
telemetrytypes.FieldDataTypeNumber,
|
||||
|
||||
@@ -313,30 +313,37 @@ func (v *filterExpressionVisitor) VisitPrimary(ctx *grammar.PrimaryContext) any
|
||||
return ""
|
||||
}
|
||||
child := ctx.GetChild(0)
|
||||
var searchText string
|
||||
if keyCtx, ok := child.(*grammar.KeyContext); ok {
|
||||
// create a full text search condition on the body field
|
||||
searchText = keyCtx.GetText()
|
||||
|
||||
keyText := keyCtx.GetText()
|
||||
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(keyText), v.builder, v.startNs, v.endNs)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
return cond
|
||||
} else if valCtx, ok := child.(*grammar.ValueContext); ok {
|
||||
var text string
|
||||
if valCtx.QUOTED_TEXT() != nil {
|
||||
searchText = trimQuotes(valCtx.QUOTED_TEXT().GetText())
|
||||
text = trimQuotes(valCtx.QUOTED_TEXT().GetText())
|
||||
} else if valCtx.NUMBER() != nil {
|
||||
searchText = valCtx.NUMBER().GetText()
|
||||
text = valCtx.NUMBER().GetText()
|
||||
} else if valCtx.BOOL() != nil {
|
||||
searchText = valCtx.BOOL().GetText()
|
||||
text = valCtx.BOOL().GetText()
|
||||
} else if valCtx.KEY() != nil {
|
||||
searchText = valCtx.KEY().GetText()
|
||||
text = valCtx.KEY().GetText()
|
||||
} else {
|
||||
v.errors = append(v.errors, fmt.Sprintf("unsupported value type: %s", valCtx.GetText()))
|
||||
return ""
|
||||
}
|
||||
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(text), v.builder, v.startNs, v.endNs)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
return cond
|
||||
}
|
||||
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(searchText), v.builder, v.startNs, v.endNs)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
return "" // Should not happen with valid input
|
||||
@@ -376,7 +383,6 @@ func (v *filterExpressionVisitor) VisitComparison(ctx *grammar.ComparisonContext
|
||||
for _, key := range keys {
|
||||
condition, err := v.conditionBuilder.ConditionFor(context.Background(), key, op, nil, v.builder, v.startNs, v.endNs)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to build condition: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
conds = append(conds, condition)
|
||||
@@ -642,6 +648,7 @@ func (v *filterExpressionVisitor) VisitValueList(ctx *grammar.ValueListContext)
|
||||
|
||||
// VisitFullText handles standalone quoted strings for full-text search
|
||||
func (v *filterExpressionVisitor) VisitFullText(ctx *grammar.FullTextContext) any {
|
||||
|
||||
if v.skipFullTextFilter {
|
||||
return ""
|
||||
}
|
||||
@@ -663,7 +670,6 @@ func (v *filterExpressionVisitor) VisitFullText(ctx *grammar.FullTextContext) an
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
return sb.ILike(tblFieldName, fmt.Sprintf("%%%s%%", value)), nil
|
||||
case qbtypes.FilterOperatorNotContains:
|
||||
return sb.NotILike(tblFieldName, fmt.Sprintf("%%%s%%", value)), nil
|
||||
|
||||
case qbtypes.FilterOperatorRegexp:
|
||||
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
||||
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
||||
@@ -175,16 +176,9 @@ func (c *conditionBuilder) conditionFor(
|
||||
var value any
|
||||
switch column.Type.GetType() {
|
||||
case schema.ColumnTypeEnumJSON:
|
||||
switch key.FieldDataType {
|
||||
case telemetrytypes.FieldDataTypeJSON:
|
||||
if operator == qbtypes.FilterOperatorExists {
|
||||
return sb.EQ(fmt.Sprintf("empty(%s)", tblFieldName), false), nil
|
||||
}
|
||||
return sb.EQ(fmt.Sprintf("empty(%s)", tblFieldName), true), nil
|
||||
default:
|
||||
if operator == qbtypes.FilterOperatorExists {
|
||||
return sb.IsNotNull(tblFieldName), nil
|
||||
}
|
||||
if operator == qbtypes.FilterOperatorExists {
|
||||
return sb.IsNotNull(tblFieldName), nil
|
||||
} else {
|
||||
return sb.IsNull(tblFieldName), nil
|
||||
}
|
||||
case schema.ColumnTypeEnumLowCardinality:
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package telemetrylogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/SigNoz/signoz-otel-collector/constants"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
@@ -20,7 +17,7 @@ const (
|
||||
LogsV2TimestampColumn = "timestamp"
|
||||
LogsV2ObservedTimestampColumn = "observed_timestamp"
|
||||
LogsV2BodyColumn = "body"
|
||||
LogsV2BodyV2Column = constants.BodyV2Column
|
||||
LogsV2BodyJSONColumn = constants.BodyV2Column
|
||||
LogsV2BodyPromotedColumn = constants.BodyPromotedColumn
|
||||
LogsV2TraceIDColumn = "trace_id"
|
||||
LogsV2SpanIDColumn = "span_id"
|
||||
@@ -37,22 +34,11 @@ const (
|
||||
LogsV2ResourcesStringColumn = "resources_string"
|
||||
LogsV2ScopeStringColumn = "scope_string"
|
||||
|
||||
BodyV2ColumnPrefix = constants.BodyV2ColumnPrefix
|
||||
BodyJSONColumnPrefix = constants.BodyV2ColumnPrefix
|
||||
BodyPromotedColumnPrefix = constants.BodyPromotedColumnPrefix
|
||||
MessageSubColumn = "message"
|
||||
bodySearchDefaultWarning = "body searches default to `body.message:string`. Use `body.<key>` to search a different field inside body"
|
||||
)
|
||||
|
||||
var (
|
||||
// mapping for body logical field to message sub column
|
||||
// TODO(Piyush): Add description for detailed explanation of remapping of body to message
|
||||
BodyLogicalFieldJSONMapping = &telemetrytypes.TelemetryFieldKey{
|
||||
Name: MessageSubColumn,
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextBody,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
JSONDataType: &telemetrytypes.String,
|
||||
}
|
||||
DefaultFullTextColumn = &telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
@@ -132,29 +118,3 @@ var (
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func bodyAliasExpression() string {
|
||||
if !querybuilder.BodyJSONQueryEnabled {
|
||||
return LogsV2BodyColumn
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s as body", LogsV2BodyV2Column)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// body logical field is mapped to message field in the body context that too only with String data type
|
||||
err := BodyLogicalFieldJSONMapping.SetJSONAccessPlan(telemetrytypes.JSONColumnMetadata{
|
||||
BaseColumn: LogsV2BodyV2Column,
|
||||
PromotedColumn: LogsV2BodyPromotedColumn,
|
||||
}, map[string][]telemetrytypes.JSONDataType{
|
||||
MessageSubColumn: {telemetrytypes.String},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
DefaultFullTextColumn = BodyLogicalFieldJSONMapping
|
||||
IntrinsicFields["body"] = *BodyLogicalFieldJSONMapping
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ var (
|
||||
"severity_text": {Name: "severity_text", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||
"severity_number": {Name: "severity_number", Type: schema.ColumnTypeUInt8},
|
||||
"body": {Name: "body", Type: schema.ColumnTypeString},
|
||||
LogsV2BodyV2Column: {Name: LogsV2BodyV2Column, Type: schema.JSONColumnType{
|
||||
LogsV2BodyJSONColumn: {Name: LogsV2BodyJSONColumn, Type: schema.JSONColumnType{
|
||||
MaxDynamicTypes: utils.ToPointer(uint(32)),
|
||||
MaxDynamicPaths: utils.ToPointer(uint(0)),
|
||||
}},
|
||||
@@ -89,9 +89,9 @@ func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.Telemetry
|
||||
}
|
||||
case telemetrytypes.FieldContextBody:
|
||||
// Body context is for JSON body fields
|
||||
// Use body_v2 if feature flag is enabled
|
||||
// Use body_json if feature flag is enabled
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
return logsV2Columns[LogsV2BodyV2Column], nil
|
||||
return logsV2Columns[LogsV2BodyJSONColumn], nil
|
||||
}
|
||||
// Fall back to legacy body column
|
||||
return logsV2Columns["body"], nil
|
||||
@@ -100,9 +100,9 @@ func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.Telemetry
|
||||
if !ok {
|
||||
// check if the key has body JSON search
|
||||
if strings.HasPrefix(key.Name, telemetrytypes.BodyJSONStringSearchPrefix) {
|
||||
// Use body_v2 if feature flag is enabled and we have a body condition builder
|
||||
// Use body_json if feature flag is enabled and we have a body condition builder
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
return logsV2Columns[LogsV2BodyV2Column], nil
|
||||
return logsV2Columns[LogsV2BodyJSONColumn], nil
|
||||
}
|
||||
// Fall back to legacy body column
|
||||
return logsV2Columns["body"], nil
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewJSONConditionBuilder(key *telemetrytypes.TelemetryFieldKey, valueType te
|
||||
return &jsonConditionBuilder{key: key, valueType: telemetrytypes.MappingFieldDataTypeToJSONDataType[valueType]}
|
||||
}
|
||||
|
||||
// BuildCondition builds the full WHERE condition for body_v2 JSON paths
|
||||
// BuildCondition builds the full WHERE condition for body_json JSON paths
|
||||
func (c *jsonConditionBuilder) buildJSONCondition(operator qbtypes.FilterOperator, value any, sb *sqlbuilder.SelectBuilder) (string, error) {
|
||||
conditions := []string{}
|
||||
for _, node := range c.key.JSONPlan {
|
||||
@@ -40,7 +40,6 @@ func (c *jsonConditionBuilder) buildJSONCondition(operator qbtypes.FilterOperato
|
||||
}
|
||||
conditions = append(conditions, condition)
|
||||
}
|
||||
|
||||
return sb.Or(conditions...), nil
|
||||
}
|
||||
|
||||
@@ -289,9 +288,9 @@ func (c *jsonConditionBuilder) applyOperator(sb *sqlbuilder.SelectBuilder, field
|
||||
}
|
||||
return sb.NotIn(fieldExpr, values...), nil
|
||||
case qbtypes.FilterOperatorExists:
|
||||
return sb.IsNotNull(fieldExpr), nil
|
||||
return fmt.Sprintf("%s IS NOT NULL", fieldExpr), nil
|
||||
case qbtypes.FilterOperatorNotExists:
|
||||
return sb.IsNull(fieldExpr), nil
|
||||
return fmt.Sprintf("%s IS NULL", fieldExpr), nil
|
||||
// between and not between
|
||||
case qbtypes.FilterOperatorBetween, qbtypes.FilterOperatorNotBetween:
|
||||
values, ok := value.([]any)
|
||||
|
||||
@@ -65,7 +65,7 @@ func (b *logQueryStatementBuilder) Build(
|
||||
start = querybuilder.ToNanoSecs(start)
|
||||
end = querybuilder.ToNanoSecs(end)
|
||||
|
||||
keySelectors, warnings := getKeySelectors(query)
|
||||
keySelectors := getKeySelectors(query)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, keySelectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -76,32 +76,20 @@ func (b *logQueryStatementBuilder) Build(
|
||||
// Create SQL builder
|
||||
q := sqlbuilder.NewSelectBuilder()
|
||||
|
||||
var stmt *qbtypes.Statement
|
||||
switch requestType {
|
||||
case qbtypes.RequestTypeRaw, qbtypes.RequestTypeRawStream:
|
||||
stmt, err = b.buildListQuery(ctx, q, query, start, end, keys, variables)
|
||||
return b.buildListQuery(ctx, q, query, start, end, keys, variables)
|
||||
case qbtypes.RequestTypeTimeSeries:
|
||||
stmt, err = b.buildTimeSeriesQuery(ctx, q, query, start, end, keys, variables)
|
||||
return b.buildTimeSeriesQuery(ctx, q, query, start, end, keys, variables)
|
||||
case qbtypes.RequestTypeScalar:
|
||||
stmt, err = b.buildScalarQuery(ctx, q, query, start, end, keys, false, variables)
|
||||
default:
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported request type: %s", requestType)
|
||||
return b.buildScalarQuery(ctx, q, query, start, end, keys, false, variables)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stmt != nil && len(warnings) > 0 {
|
||||
stmt.Warnings = append(stmt.Warnings, warnings...)
|
||||
}
|
||||
|
||||
return stmt, nil
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported request type: %s", requestType)
|
||||
}
|
||||
|
||||
func getKeySelectors(query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]) ([]*telemetrytypes.FieldKeySelector, []string) {
|
||||
func getKeySelectors(query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]) []*telemetrytypes.FieldKeySelector {
|
||||
var keySelectors []*telemetrytypes.FieldKeySelector
|
||||
var warnings []string
|
||||
|
||||
for idx := range query.Aggregations {
|
||||
aggExpr := query.Aggregations[idx]
|
||||
@@ -148,19 +136,7 @@ func getKeySelectors(query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]) ([
|
||||
keySelectors[idx].SelectorMatchType = telemetrytypes.FieldSelectorMatchTypeExact
|
||||
}
|
||||
|
||||
// When the new JSON body experience is enabled, warn the user if they use the bare
|
||||
// "body" key in the filter — queries on plain "body" default to body.message:string.
|
||||
// TODO(Piyush): Setup better for coming FTS support.
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
for _, sel := range keySelectors {
|
||||
if sel.Name == LogsV2BodyColumn {
|
||||
warnings = append(warnings, bodySearchDefaultWarning)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keySelectors, warnings
|
||||
return keySelectors
|
||||
}
|
||||
|
||||
func (b *logQueryStatementBuilder) adjustKeys(ctx context.Context, keys map[string][]*telemetrytypes.TelemetryFieldKey, query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation], requestType qbtypes.RequestType) qbtypes.QueryBuilderQuery[qbtypes.LogAggregation] {
|
||||
@@ -227,6 +203,7 @@ func (b *logQueryStatementBuilder) adjustKeys(ctx context.Context, keys map[stri
|
||||
}
|
||||
|
||||
func (b *logQueryStatementBuilder) adjustKey(key *telemetrytypes.TelemetryFieldKey, keys map[string][]*telemetrytypes.TelemetryFieldKey) []string {
|
||||
|
||||
// First check if it matches with any intrinsic fields
|
||||
var intrinsicOrCalculatedField telemetrytypes.TelemetryFieldKey
|
||||
if _, ok := IntrinsicFields[key.Name]; ok {
|
||||
@@ -235,6 +212,7 @@ func (b *logQueryStatementBuilder) adjustKey(key *telemetrytypes.TelemetryFieldK
|
||||
}
|
||||
|
||||
return querybuilder.AdjustKey(key, keys, nil)
|
||||
|
||||
}
|
||||
|
||||
// buildListQuery builds a query for list panel type
|
||||
@@ -271,7 +249,11 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
||||
sb.SelectMore(LogsV2SeverityNumberColumn)
|
||||
sb.SelectMore(LogsV2ScopeNameColumn)
|
||||
sb.SelectMore(LogsV2ScopeVersionColumn)
|
||||
sb.SelectMore(bodyAliasExpression())
|
||||
sb.SelectMore(LogsV2BodyColumn)
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
sb.SelectMore(LogsV2BodyJSONColumn)
|
||||
sb.SelectMore(LogsV2BodyPromotedColumn)
|
||||
}
|
||||
sb.SelectMore(LogsV2AttributesStringColumn)
|
||||
sb.SelectMore(LogsV2AttributesNumberColumn)
|
||||
sb.SelectMore(LogsV2AttributesBoolColumn)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder/resourcefilter"
|
||||
@@ -887,154 +886,3 @@ func TestAdjustKey(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStmtBuilderBodyField(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
requestType qbtypes.RequestType
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]
|
||||
enableBodyJSONQuery bool
|
||||
expected qbtypes.Statement
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "body_exists",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body Exists"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: true,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body_v2 as body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND (dynamicElement(body_v2.`message`, 'String') IS NOT NULL) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
Warnings: []string{bodySearchDefaultWarning},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_exists_disabled",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body Exists"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: false,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND body <> ? AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_empty",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body == ''"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: true,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body_v2 as body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND (dynamicElement(body_v2.`message`, 'String') = ?) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
Warnings: []string{bodySearchDefaultWarning},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_empty_disabled",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body == ''"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: false,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND body = ? AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_contains",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body CONTAINS 'error'"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: true,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body_v2 as body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND (LOWER(dynamicElement(body_v2.`message`, 'String')) LIKE LOWER(?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "%error%", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
Warnings: []string{bodySearchDefaultWarning},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_contains_disabled",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body CONTAINS 'error'"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: false,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND LOWER(body) LIKE LOWER(?) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "%error%", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
defer disable()
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if c.enableBodyJSONQuery {
|
||||
enable()
|
||||
} else {
|
||||
disable()
|
||||
}
|
||||
// build the key map after enabling/disabling body JSON query
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
mockMetadataStore.KeysMap = buildCompleteFieldKeyMap()
|
||||
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
resourceFilterStmtBuilder := resourceFilterStmtBuilder()
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
mockMetadataStore,
|
||||
fm,
|
||||
cb,
|
||||
resourceFilterStmtBuilder,
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
)
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.expectedErr.Error())
|
||||
} else {
|
||||
if err != nil {
|
||||
_, _, _, _, _, add := errors.Unwrapb(err)
|
||||
t.Logf("error additionals: %v", add)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.expected.Query, q.Query)
|
||||
require.Equal(t, c.expected.Args, q.Args)
|
||||
require.Equal(t, c.expected.Warnings, q.Warnings)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@ func buildCompleteFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey {
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
"body": {
|
||||
{
|
||||
Name: "body",
|
||||
FieldContext: telemetrytypes.FieldContextLog,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
"http.status_code": {
|
||||
{
|
||||
Name: "http.status_code",
|
||||
@@ -938,11 +945,6 @@ func buildCompleteFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey {
|
||||
key.Signal = telemetrytypes.SignalLogs
|
||||
}
|
||||
}
|
||||
|
||||
// add intrinsic fields to the map
|
||||
for fieldName, key := range IntrinsicFields {
|
||||
keysMap[fieldName] = append(keysMap[fieldName], &key)
|
||||
}
|
||||
return keysMap
|
||||
}
|
||||
|
||||
|
||||
@@ -319,7 +319,7 @@ func (t *telemetryMetaStore) ListJSONValues(ctx context.Context, path string, li
|
||||
if promoted {
|
||||
path = telemetrylogs.BodyPromotedColumnPrefix + path
|
||||
} else {
|
||||
path = telemetrylogs.BodyV2ColumnPrefix + path
|
||||
path = telemetrylogs.BodyJSONColumnPrefix + path
|
||||
}
|
||||
|
||||
from := fmt.Sprintf("%s.%s", telemetrylogs.DBName, telemetrylogs.LogsV2TableName)
|
||||
@@ -522,7 +522,7 @@ func (t *telemetryMetaStore) GetPromotedPaths(ctx context.Context, paths ...stri
|
||||
// TODO(Piyush): Remove this function
|
||||
func CleanPathPrefixes(path string) string {
|
||||
path = strings.TrimPrefix(path, telemetrytypes.BodyJSONStringSearchPrefix)
|
||||
path = strings.TrimPrefix(path, telemetrylogs.BodyV2ColumnPrefix)
|
||||
path = strings.TrimPrefix(path, telemetrylogs.BodyJSONColumnPrefix)
|
||||
path = strings.TrimPrefix(path, telemetrylogs.BodyPromotedColumnPrefix)
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ func NewTelemetryMetaStore(
|
||||
jsonColumnMetadata: map[telemetrytypes.Signal]map[telemetrytypes.FieldContext]telemetrytypes.JSONColumnMetadata{
|
||||
telemetrytypes.SignalLogs: {
|
||||
telemetrytypes.FieldContextBody: telemetrytypes.JSONColumnMetadata{
|
||||
BaseColumn: telemetrylogs.LogsV2BodyV2Column,
|
||||
BaseColumn: telemetrylogs.LogsV2BodyJSONColumn,
|
||||
PromotedColumn: telemetrylogs.LogsV2BodyPromotedColumn,
|
||||
},
|
||||
},
|
||||
@@ -351,7 +351,7 @@ func (t *telemetryMetaStore) logsTblStatementToFieldKeys(ctx context.Context) ([
|
||||
}
|
||||
|
||||
// getLogsKeys returns the keys from the spans that match the field selection criteria
|
||||
func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors []*telemetrytypes.FieldKeySelector) (map[string][]*telemetrytypes.TelemetryFieldKey, bool, error) {
|
||||
func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors []*telemetrytypes.FieldKeySelector) ([]*telemetrytypes.TelemetryFieldKey, bool, error) {
|
||||
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
|
||||
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
|
||||
instrumentationtypes.CodeNamespace: "metadata",
|
||||
@@ -367,10 +367,9 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// setOfKeys to reuse the same key object for qualified names
|
||||
setOfKeys := make(map[string]*telemetrytypes.TelemetryFieldKey)
|
||||
mapOfKeys := make(map[string]*telemetrytypes.TelemetryFieldKey)
|
||||
for _, key := range matKeys {
|
||||
setOfKeys[key.Text()] = key
|
||||
mapOfKeys[key.Name+";"+key.FieldContext.StringValue()+";"+key.FieldDataType.StringValue()] = key
|
||||
}
|
||||
|
||||
// queries for both attribute and resource keys tables
|
||||
@@ -471,7 +470,7 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
|
||||
if len(queries) == 0 {
|
||||
// No matching contexts, return empty result
|
||||
return nil, true, nil
|
||||
return []*telemetrytypes.TelemetryFieldKey{}, true, nil
|
||||
}
|
||||
|
||||
// Combine queries with UNION ALL
|
||||
@@ -499,7 +498,7 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
mapOfKeys := make(map[string][]*telemetrytypes.TelemetryFieldKey)
|
||||
keys := []*telemetrytypes.TelemetryFieldKey{}
|
||||
rowCount := 0
|
||||
searchTexts := []string{}
|
||||
dataTypes := []telemetrytypes.FieldDataType{}
|
||||
@@ -527,7 +526,7 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, errors.TypeInternal, errors.CodeInternal, ErrFailedToGetLogsKeys.Error())
|
||||
}
|
||||
key, ok := setOfKeys[name+";"+fieldContext.StringValue()+";"+fieldDataType.StringValue()]
|
||||
key, ok := mapOfKeys[name+";"+fieldContext.StringValue()+";"+fieldDataType.StringValue()]
|
||||
|
||||
// if there is no materialised column, create a key with the field context and data type
|
||||
if !ok {
|
||||
@@ -539,8 +538,8 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
}
|
||||
}
|
||||
|
||||
mapOfKeys[key.Name] = append(mapOfKeys[key.Name], key)
|
||||
setOfKeys[name+";"+fieldContext.StringValue()+";"+fieldDataType.StringValue()] = key
|
||||
keys = append(keys, key)
|
||||
mapOfKeys[name+";"+fieldContext.StringValue()+";"+fieldDataType.StringValue()] = key
|
||||
}
|
||||
|
||||
if rows.Err() != nil {
|
||||
@@ -566,13 +565,17 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
|
||||
if found {
|
||||
if field, exists := telemetrylogs.IntrinsicFields[key]; exists {
|
||||
if _, added := setOfKeys[field.Name+";"+field.FieldContext.StringValue()+";"+field.FieldDataType.StringValue()]; !added {
|
||||
mapOfKeys[field.Name] = append(mapOfKeys[field.Name], &field)
|
||||
// intrinsic field can be a logical field mapped to a different physical location
|
||||
mapOfKeys[key] = append(mapOfKeys[key], &field)
|
||||
if _, added := mapOfKeys[field.Name+";"+field.FieldContext.StringValue()+";"+field.FieldDataType.StringValue()]; !added {
|
||||
keys = append(keys, &field)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
keys = append(keys, &telemetrytypes.TelemetryFieldKey{
|
||||
Name: key,
|
||||
FieldContext: telemetrytypes.FieldContextLog,
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,13 +584,10 @@ func (t *telemetryMetaStore) getLogsKeys(ctx context.Context, fieldKeySelectors
|
||||
if err != nil {
|
||||
t.logger.ErrorContext(ctx, "failed to extract body JSON paths", "error", err)
|
||||
}
|
||||
for _, key := range bodyJSONPaths {
|
||||
mapOfKeys[key.Name] = append(mapOfKeys[key.Name], key)
|
||||
}
|
||||
keys = append(keys, bodyJSONPaths...)
|
||||
complete = complete && finished
|
||||
}
|
||||
|
||||
return mapOfKeys, complete, nil
|
||||
return keys, complete, nil
|
||||
}
|
||||
|
||||
func getPriorityForContext(ctx telemetrytypes.FieldContext) int {
|
||||
@@ -882,20 +882,12 @@ func (t *telemetryMetaStore) GetKeys(ctx context.Context, fieldKeySelector *tele
|
||||
if fieldKeySelector != nil {
|
||||
selectors = []*telemetrytypes.FieldKeySelector{fieldKeySelector}
|
||||
}
|
||||
mapOfKeys := make(map[string][]*telemetrytypes.TelemetryFieldKey)
|
||||
|
||||
switch fieldKeySelector.Signal {
|
||||
case telemetrytypes.SignalTraces:
|
||||
keys, complete, err = t.getTracesKeys(ctx, selectors)
|
||||
case telemetrytypes.SignalLogs:
|
||||
mapOfLogKeys, logsComplete, err := t.getLogsKeys(ctx, selectors)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
for keyName, keys := range mapOfLogKeys {
|
||||
mapOfKeys[keyName] = append(mapOfKeys[keyName], keys...)
|
||||
}
|
||||
complete = complete && logsComplete
|
||||
keys, complete, err = t.getLogsKeys(ctx, selectors)
|
||||
case telemetrytypes.SignalMetrics:
|
||||
if fieldKeySelector.Source == telemetrytypes.SourceMeter {
|
||||
keys, complete, err = t.getMeterSourceMetricKeys(ctx, selectors)
|
||||
@@ -911,13 +903,12 @@ func (t *telemetryMetaStore) GetKeys(ctx context.Context, fieldKeySelector *tele
|
||||
keys = append(keys, tracesKeys...)
|
||||
|
||||
// get logs keys
|
||||
mapOfLogKeys, logsComplete, err := t.getLogsKeys(ctx, selectors)
|
||||
logsKeys, logsComplete, err := t.getLogsKeys(ctx, selectors)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
for keyName, keys := range mapOfLogKeys {
|
||||
mapOfKeys[keyName] = append(mapOfKeys[keyName], keys...)
|
||||
}
|
||||
keys = append(keys, logsKeys...)
|
||||
|
||||
// get metrics keys
|
||||
metricsKeys, metricsComplete, err := t.getMetricsKeys(ctx, selectors)
|
||||
if err != nil {
|
||||
@@ -931,6 +922,7 @@ func (t *telemetryMetaStore) GetKeys(ctx context.Context, fieldKeySelector *tele
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
mapOfKeys := make(map[string][]*telemetrytypes.TelemetryFieldKey)
|
||||
for _, key := range keys {
|
||||
mapOfKeys[key.Name] = append(mapOfKeys[key.Name], key)
|
||||
}
|
||||
@@ -967,7 +959,7 @@ func (t *telemetryMetaStore) GetKeysMulti(ctx context.Context, fieldKeySelectors
|
||||
}
|
||||
}
|
||||
|
||||
mapOfLogKeys, logsComplete, err := t.getLogsKeys(ctx, logsSelectors)
|
||||
logsKeys, logsComplete, err := t.getLogsKeys(ctx, logsSelectors)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -988,8 +980,8 @@ func (t *telemetryMetaStore) GetKeysMulti(ctx context.Context, fieldKeySelectors
|
||||
complete := logsComplete && tracesComplete && metricsComplete
|
||||
|
||||
mapOfKeys := make(map[string][]*telemetrytypes.TelemetryFieldKey)
|
||||
for keyName, keys := range mapOfLogKeys {
|
||||
mapOfKeys[keyName] = append(mapOfKeys[keyName], keys...)
|
||||
for _, key := range logsKeys {
|
||||
mapOfKeys[key.Name] = append(mapOfKeys[key.Name], key)
|
||||
}
|
||||
for _, key := range tracesKeys {
|
||||
mapOfKeys[key.Name] = append(mapOfKeys[key.Name], key)
|
||||
|
||||
@@ -21,7 +21,6 @@ var (
|
||||
// int64 and number are synonyms for float64
|
||||
FieldDataTypeInt64 = FieldDataType{valuer.NewString("int64")}
|
||||
FieldDataTypeNumber = FieldDataType{valuer.NewString("number")}
|
||||
FieldDataTypeJSON = FieldDataType{valuer.NewString("json")}
|
||||
FieldDataTypeUnspecified = FieldDataType{valuer.NewString("")}
|
||||
|
||||
FieldDataTypeArrayString = FieldDataType{valuer.NewString("[]string")}
|
||||
|
||||
@@ -40,7 +40,7 @@ type JSONAccessNode struct {
|
||||
// Node information
|
||||
Name string
|
||||
IsTerminal bool
|
||||
isRoot bool // marked true for only body_v2 and body_promoted
|
||||
isRoot bool // marked true for only body_json and body_json_promoted
|
||||
|
||||
// Precomputed type information (single source of truth)
|
||||
AvailableTypes []JSONDataType
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
package telemetrytypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
otelconstants "github.com/SigNoz/signoz-otel-collector/constants"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
bodyV2Column = otelconstants.BodyV2Column
|
||||
bodyPromotedColumn = otelconstants.BodyPromotedColumn
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions for Test Data Creation
|
||||
// ============================================================================
|
||||
@@ -116,8 +109,8 @@ func TestNode_Alias(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Root node returns name as-is",
|
||||
node: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
expected: bodyV2Column,
|
||||
node: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
expected: "body_json",
|
||||
},
|
||||
{
|
||||
name: "Node without parent returns backticked name",
|
||||
@@ -131,9 +124,9 @@ func TestNode_Alias(t *testing.T) {
|
||||
name: "Node with root parent uses dot separator",
|
||||
node: &JSONAccessNode{
|
||||
Name: "age",
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
},
|
||||
expected: "`" + bodyV2Column + ".age`",
|
||||
expected: "`" + "body_json" + ".age`",
|
||||
},
|
||||
{
|
||||
name: "Node with non-root parent uses array separator",
|
||||
@@ -141,10 +134,10 @@ func TestNode_Alias(t *testing.T) {
|
||||
Name: "name",
|
||||
Parent: &JSONAccessNode{
|
||||
Name: "education",
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
},
|
||||
},
|
||||
expected: "`" + bodyV2Column + ".education[].name`",
|
||||
expected: "`" + "body_json" + ".education[].name`",
|
||||
},
|
||||
{
|
||||
name: "Nested array path with multiple levels",
|
||||
@@ -154,11 +147,11 @@ func TestNode_Alias(t *testing.T) {
|
||||
Name: "awards",
|
||||
Parent: &JSONAccessNode{
|
||||
Name: "education",
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "`" + bodyV2Column + ".education[].awards[].type`",
|
||||
expected: "`" + "body_json" + ".education[].awards[].type`",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -180,18 +173,18 @@ func TestNode_FieldPath(t *testing.T) {
|
||||
name: "Simple field path from root",
|
||||
node: &JSONAccessNode{
|
||||
Name: "user",
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
},
|
||||
// FieldPath() always wraps the field name in backticks
|
||||
expected: bodyV2Column + ".`user`",
|
||||
expected: "body_json" + ".`user`",
|
||||
},
|
||||
{
|
||||
name: "Field path with backtick-required key",
|
||||
node: &JSONAccessNode{
|
||||
Name: "user-name", // requires backtick
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
},
|
||||
expected: bodyV2Column + ".`user-name`",
|
||||
expected: "body_json" + ".`user-name`",
|
||||
},
|
||||
{
|
||||
name: "Nested field path",
|
||||
@@ -199,11 +192,11 @@ func TestNode_FieldPath(t *testing.T) {
|
||||
Name: "age",
|
||||
Parent: &JSONAccessNode{
|
||||
Name: "user",
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
},
|
||||
},
|
||||
// FieldPath() always wraps the field name in backticks
|
||||
expected: "`" + bodyV2Column + ".user`.`age`",
|
||||
expected: "`" + "body_json" + ".user`.`age`",
|
||||
},
|
||||
{
|
||||
name: "Array element field path",
|
||||
@@ -211,11 +204,11 @@ func TestNode_FieldPath(t *testing.T) {
|
||||
Name: "name",
|
||||
Parent: &JSONAccessNode{
|
||||
Name: "education",
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
},
|
||||
},
|
||||
// FieldPath() always wraps the field name in backticks
|
||||
expected: "`" + bodyV2Column + ".education`.`name`",
|
||||
expected: "`" + "body_json" + ".education`.`name`",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -243,36 +236,36 @@ func TestPlanJSON_BasicStructure(t *testing.T) {
|
||||
{
|
||||
name: "Simple path not promoted",
|
||||
key: makeKey("user.name", String, false),
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
expectedYAML: `
|
||||
- name: user.name
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- String
|
||||
maxDynamicTypes: 16
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column),
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Simple path promoted",
|
||||
key: makeKey("user.name", String, true),
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
expectedYAML: `
|
||||
- name: user.name
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- String
|
||||
maxDynamicTypes: 16
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
- name: user.name
|
||||
column: %s
|
||||
column: body_json_promoted
|
||||
availableTypes:
|
||||
- String
|
||||
maxDynamicTypes: 16
|
||||
maxDynamicPaths: 256
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column, bodyPromotedColumn),
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Empty path returns error",
|
||||
@@ -285,8 +278,8 @@ func TestPlanJSON_BasicStructure(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
}, types)
|
||||
if tt.expectErr {
|
||||
require.Error(t, err)
|
||||
@@ -311,9 +304,9 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
{
|
||||
name: "Single array level - JSON branch only",
|
||||
path: "education[].name",
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
expectedYAML: `
|
||||
- name: education
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -325,14 +318,14 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
maxDynamicTypes: 8
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column),
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Single array level - both JSON and Dynamic branches",
|
||||
path: "education[].awards[].type",
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
expectedYAML: `
|
||||
- name: education
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -359,14 +352,14 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
maxDynamicPaths: 256
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column),
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Deeply nested array path",
|
||||
path: "interests[].entities[].reviews[].entries[].metadata[].positions[].name",
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
expectedYAML: `
|
||||
- name: interests
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -406,14 +399,14 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
- String
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column),
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "ArrayAnyIndex replacement [*] to []",
|
||||
path: "education[*].name",
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
expectedYAML: `
|
||||
- name: education
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -425,7 +418,7 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
maxDynamicTypes: 8
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column),
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -433,8 +426,8 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
key := makeKey(tt.path, String, false)
|
||||
err := key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
}, types)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, key.JSONPlan)
|
||||
@@ -452,15 +445,15 @@ func TestPlanJSON_PromotedVsNonPromoted(t *testing.T) {
|
||||
t.Run("Non-promoted plan", func(t *testing.T) {
|
||||
key := makeKey(path, String, false)
|
||||
err := key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
}, types)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, key.JSONPlan, 1)
|
||||
|
||||
expectedYAML := fmt.Sprintf(`
|
||||
expectedYAML := `
|
||||
- name: education
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -487,7 +480,7 @@ func TestPlanJSON_PromotedVsNonPromoted(t *testing.T) {
|
||||
maxDynamicPaths: 256
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column)
|
||||
`
|
||||
got := plansToYAML(t, key.JSONPlan)
|
||||
require.YAMLEq(t, expectedYAML, got)
|
||||
})
|
||||
@@ -495,15 +488,15 @@ func TestPlanJSON_PromotedVsNonPromoted(t *testing.T) {
|
||||
t.Run("Promoted plan", func(t *testing.T) {
|
||||
key := makeKey(path, String, true)
|
||||
err := key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
}, types)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, key.JSONPlan, 2)
|
||||
|
||||
expectedYAML := fmt.Sprintf(`
|
||||
expectedYAML := `
|
||||
- name: education
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -531,7 +524,7 @@ func TestPlanJSON_PromotedVsNonPromoted(t *testing.T) {
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
- name: education
|
||||
column: %s
|
||||
column: body_json_promoted
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -561,7 +554,7 @@ func TestPlanJSON_PromotedVsNonPromoted(t *testing.T) {
|
||||
maxDynamicPaths: 256
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column, bodyPromotedColumn)
|
||||
`
|
||||
got := plansToYAML(t, key.JSONPlan)
|
||||
require.YAMLEq(t, expectedYAML, got)
|
||||
})
|
||||
@@ -582,11 +575,11 @@ func TestPlanJSON_EdgeCases(t *testing.T) {
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Very deep nesting - validates progression doesn't go negative",
|
||||
path: "interests[].entities[].reviews[].entries[].metadata[].positions[].name",
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
name: "Very deep nesting - validates progression doesn't go negative",
|
||||
path: "interests[].entities[].reviews[].entries[].metadata[].positions[].name",
|
||||
expectedYAML: `
|
||||
- name: interests
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -626,14 +619,14 @@ func TestPlanJSON_EdgeCases(t *testing.T) {
|
||||
- String
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column),
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Path with mixed scalar and array types",
|
||||
path: "education[].type",
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
name: "Path with mixed scalar and array types",
|
||||
path: "education[].type",
|
||||
expectedYAML: `
|
||||
- name: education
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -646,20 +639,20 @@ func TestPlanJSON_EdgeCases(t *testing.T) {
|
||||
maxDynamicTypes: 8
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column),
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Exists with only array types available",
|
||||
path: "education",
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
name: "Exists with only array types available",
|
||||
path: "education",
|
||||
expectedYAML: `
|
||||
- name: education
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
isTerminal: true
|
||||
elemType: Array(JSON)
|
||||
`, bodyV2Column),
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -675,8 +668,8 @@ func TestPlanJSON_EdgeCases(t *testing.T) {
|
||||
}
|
||||
key := makeKey(tt.path, keyType, false)
|
||||
err := key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
}, types)
|
||||
if tt.expectErr {
|
||||
require.Error(t, err)
|
||||
@@ -694,15 +687,15 @@ func TestPlanJSON_TreeStructure(t *testing.T) {
|
||||
path := "education[].awards[].participated[].team[].branch"
|
||||
key := makeKey(path, String, false)
|
||||
err := key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
}, types)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, key.JSONPlan, 1)
|
||||
|
||||
expectedYAML := fmt.Sprintf(`
|
||||
expectedYAML := `
|
||||
- name: education
|
||||
column: %s
|
||||
column: body_json
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -787,7 +780,7 @@ func TestPlanJSON_TreeStructure(t *testing.T) {
|
||||
maxDynamicPaths: 64
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`, bodyV2Column)
|
||||
`
|
||||
|
||||
got := plansToYAML(t, key.JSONPlan)
|
||||
require.YAMLEq(t, expectedYAML, got)
|
||||
|
||||
@@ -2,7 +2,6 @@ package telemetrytypestest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
schemamigrator "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
@@ -179,9 +178,8 @@ func matchesKey(selector *telemetrytypes.FieldKeySelector, key *telemetrytypes.T
|
||||
return true
|
||||
}
|
||||
|
||||
matchNameExceptions := []string{"body"}
|
||||
// Check name (already checked in matchesName, but double-check here)
|
||||
if selector.Name != "" && !matchesName(selector, key.Name) && slices.Contains(matchNameExceptions, key.Name) {
|
||||
if selector.Name != "" && !matchesName(selector, key.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||