mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-20 15:20:31 +01:00
Compare commits
11 Commits
feat/enabl
...
feat/deplo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cee4a3e2e2 | ||
|
|
c0336001f5 | ||
|
|
d05d394f57 | ||
|
|
b4e5085a5a | ||
|
|
88f7502a15 | ||
|
|
b0442761ac | ||
|
|
d539ca9bab | ||
|
|
c8194e9abb | ||
|
|
c919102fee | ||
|
|
141380f1c7 | ||
|
|
7ba6a56115 |
7
.github/workflows/build-community.yaml
vendored
7
.github/workflows/build-community.yaml
vendored
@@ -3,8 +3,8 @@ name: build-community
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+'
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
- "v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -69,14 +69,13 @@ jobs:
|
||||
GO_BUILD_CONTEXT: ./cmd/community
|
||||
GO_BUILD_FLAGS: >-
|
||||
-tags timetzdata
|
||||
-ldflags='-linkmode external -extldflags \"-static\" -s -w
|
||||
-ldflags='-s -w
|
||||
-X github.com/SigNoz/signoz/pkg/version.version=${{ needs.prepare.outputs.version }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.variant=community
|
||||
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.time=${{ needs.prepare.outputs.time }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}
|
||||
-X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr'
|
||||
GO_CGO_ENABLED: 1
|
||||
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
|
||||
DOCKER_DOCKERFILE_PATH: ./cmd/community/Dockerfile.multi-arch
|
||||
DOCKER_MANIFEST: true
|
||||
|
||||
5
.github/workflows/build-enterprise.yaml
vendored
5
.github/workflows/build-enterprise.yaml
vendored
@@ -84,7 +84,7 @@ jobs:
|
||||
JS_INPUT_ARTIFACT_CACHE_KEY: enterprise-dotenv-${{ github.sha }}
|
||||
JS_INPUT_ARTIFACT_PATH: frontend/.env
|
||||
JS_OUTPUT_ARTIFACT_CACHE_KEY: enterprise-jsbuild-${{ github.sha }}
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
DOCKER_BUILD: false
|
||||
DOCKER_MANIFEST: false
|
||||
go-build:
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
GO_BUILD_CONTEXT: ./cmd/enterprise
|
||||
GO_BUILD_FLAGS: >-
|
||||
-tags timetzdata
|
||||
-ldflags='-linkmode external -extldflags \"-static\" -s -w
|
||||
-ldflags='-s -w
|
||||
-X github.com/SigNoz/signoz/pkg/version.version=${{ needs.prepare.outputs.version }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.variant=enterprise
|
||||
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
|
||||
@@ -110,7 +110,6 @@ jobs:
|
||||
-X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.signoz.cloud
|
||||
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1
|
||||
-X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr'
|
||||
GO_CGO_ENABLED: 1
|
||||
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
|
||||
DOCKER_DOCKERFILE_PATH: ./cmd/enterprise/Dockerfile.multi-arch
|
||||
DOCKER_MANIFEST: true
|
||||
|
||||
5
.github/workflows/build-staging.yaml
vendored
5
.github/workflows/build-staging.yaml
vendored
@@ -98,7 +98,7 @@ jobs:
|
||||
GO_BUILD_CONTEXT: ./cmd/enterprise
|
||||
GO_BUILD_FLAGS: >-
|
||||
-tags timetzdata
|
||||
-ldflags='-linkmode external -extldflags \"-static\" -s -w
|
||||
-ldflags='-s -w
|
||||
-X github.com/SigNoz/signoz/pkg/version.version=${{ needs.prepare.outputs.version }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.variant=enterprise
|
||||
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
|
||||
@@ -109,7 +109,6 @@ jobs:
|
||||
-X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.staging.signoz.cloud
|
||||
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.staging.signoz.cloud/api/v1
|
||||
-X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr'
|
||||
GO_CGO_ENABLED: 1
|
||||
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
|
||||
DOCKER_DOCKERFILE_PATH: ./cmd/enterprise/Dockerfile.multi-arch
|
||||
DOCKER_MANIFEST: true
|
||||
@@ -125,4 +124,4 @@ jobs:
|
||||
GITHUB_SILENT: true
|
||||
GITHUB_REPOSITORY_NAME: charts-saas-v3-staging
|
||||
GITHUB_EVENT_NAME: releaser
|
||||
GITHUB_EVENT_PAYLOAD: "{\"deployment\": \"${{ needs.prepare.outputs.deployment }}\", \"signoz_version\": \"${{ needs.prepare.outputs.version }}\"}"
|
||||
GITHUB_EVENT_PAYLOAD: '{"deployment": "${{ needs.prepare.outputs.deployment }}", "signoz_version": "${{ needs.prepare.outputs.version }}"}'
|
||||
|
||||
12
Makefile
12
Makefile
@@ -114,9 +114,9 @@ $(GO_BUILD_ARCHS_COMMUNITY): go-build-community-%: $(TARGET_DIR)
|
||||
@mkdir -p $(TARGET_DIR)/$(OS)-$*
|
||||
@echo ">> building binary $(TARGET_DIR)/$(OS)-$*/$(NAME)-community"
|
||||
@if [ $* = "arm64" ]; then \
|
||||
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_COMMUNITY) -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME)-community -ldflags "-linkmode external -extldflags '-static' -s -w $(GO_BUILD_LDFLAGS_COMMUNITY)"; \
|
||||
GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_COMMUNITY) -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME)-community -ldflags "-s -w $(GO_BUILD_LDFLAGS_COMMUNITY)"; \
|
||||
else \
|
||||
CGO_ENABLED=1 GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_COMMUNITY) -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME)-community -ldflags "-linkmode external -extldflags '-static' -s -w $(GO_BUILD_LDFLAGS_COMMUNITY)"; \
|
||||
GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_COMMUNITY) -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME)-community -ldflags "-s -w $(GO_BUILD_LDFLAGS_COMMUNITY)"; \
|
||||
fi
|
||||
|
||||
|
||||
@@ -127,9 +127,9 @@ $(GO_BUILD_ARCHS_ENTERPRISE): go-build-enterprise-%: $(TARGET_DIR)
|
||||
@mkdir -p $(TARGET_DIR)/$(OS)-$*
|
||||
@echo ">> building binary $(TARGET_DIR)/$(OS)-$*/$(NAME)"
|
||||
@if [ $* = "arm64" ]; then \
|
||||
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_ENTERPRISE) -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME) -ldflags "-linkmode external -extldflags '-static' -s -w $(GO_BUILD_LDFLAGS_ENTERPRISE)"; \
|
||||
GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_ENTERPRISE) -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME) -ldflags "-s -w $(GO_BUILD_LDFLAGS_ENTERPRISE)"; \
|
||||
else \
|
||||
CGO_ENABLED=1 GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_ENTERPRISE) -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME) -ldflags "-linkmode external -extldflags '-static' -s -w $(GO_BUILD_LDFLAGS_ENTERPRISE)"; \
|
||||
GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_ENTERPRISE) -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME) -ldflags "-s -w $(GO_BUILD_LDFLAGS_ENTERPRISE)"; \
|
||||
fi
|
||||
|
||||
.PHONY: go-build-enterprise-race $(GO_BUILD_ARCHS_ENTERPRISE_RACE)
|
||||
@@ -139,9 +139,9 @@ $(GO_BUILD_ARCHS_ENTERPRISE_RACE): go-build-enterprise-race-%: $(TARGET_DIR)
|
||||
@mkdir -p $(TARGET_DIR)/$(OS)-$*
|
||||
@echo ">> building binary $(TARGET_DIR)/$(OS)-$*/$(NAME)"
|
||||
@if [ $* = "arm64" ]; then \
|
||||
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_ENTERPRISE) -race -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME) -ldflags "-linkmode external -extldflags '-static' -s -w $(GO_BUILD_LDFLAGS_ENTERPRISE)"; \
|
||||
GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_ENTERPRISE) -race -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME) -ldflags "-s -w $(GO_BUILD_LDFLAGS_ENTERPRISE)"; \
|
||||
else \
|
||||
CGO_ENABLED=1 GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_ENTERPRISE) -race -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME) -ldflags "-linkmode external -extldflags '-static' -s -w $(GO_BUILD_LDFLAGS_ENTERPRISE)"; \
|
||||
GOARCH=$* GOOS=$(OS) go build -C $(GO_BUILD_CONTEXT_ENTERPRISE) -race -tags timetzdata -o $(TARGET_DIR)/$(OS)-$*/$(NAME) -ldflags "-s -w $(GO_BUILD_LDFLAGS_ENTERPRISE)"; \
|
||||
fi
|
||||
|
||||
##############################################################
|
||||
|
||||
@@ -12,12 +12,6 @@ builds:
|
||||
- id: signoz
|
||||
binary: bin/signoz
|
||||
main: ./cmd/community
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- >-
|
||||
{{- if eq .Os "linux" }}
|
||||
{{- if eq .Arch "arm64" }}CC=aarch64-linux-gnu-gcc{{- end }}
|
||||
{{- end }}
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
@@ -36,8 +30,6 @@ builds:
|
||||
- -X github.com/SigNoz/signoz/pkg/version.time={{ .CommitTimestamp }}
|
||||
- -X github.com/SigNoz/signoz/pkg/version.branch={{ .Branch }}
|
||||
- -X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr
|
||||
- >-
|
||||
{{- if eq .Os "linux" }}-linkmode external -extldflags '-static'{{- end }}
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
tags:
|
||||
- timetzdata
|
||||
|
||||
@@ -12,12 +12,6 @@ builds:
|
||||
- id: signoz
|
||||
binary: bin/signoz
|
||||
main: ./cmd/enterprise
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- >-
|
||||
{{- if eq .Os "linux" }}
|
||||
{{- if eq .Arch "arm64" }}CC=aarch64-linux-gnu-gcc{{- end }}
|
||||
{{- end }}
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
@@ -40,8 +34,6 @@ builds:
|
||||
- -X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.signoz.cloud
|
||||
- -X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1
|
||||
- -X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr
|
||||
- >-
|
||||
{{- if eq .Os "linux" }}-linkmode external -extldflags '-static'{{- end }}
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
tags:
|
||||
- timetzdata
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
##################### SigNoz Configuration Example #####################
|
||||
#
|
||||
#
|
||||
# Do not modify this file
|
||||
#
|
||||
|
||||
@@ -58,7 +58,7 @@ cache:
|
||||
# The port on which the Redis server is running. Default is usually 6379.
|
||||
port: 6379
|
||||
# The password for authenticating with the Redis server, if required.
|
||||
password:
|
||||
password:
|
||||
# The Redis database number to use
|
||||
db: 0
|
||||
|
||||
@@ -71,6 +71,10 @@ sqlstore:
|
||||
sqlite:
|
||||
# The path to the SQLite database file.
|
||||
path: /var/lib/signoz/signoz.db
|
||||
# Mode is the mode to use for the sqlite database.
|
||||
mode: delete
|
||||
# BusyTimeout is the timeout for the sqlite database to wait for a lock.
|
||||
busy_timeout: 10s
|
||||
|
||||
##################### APIServer #####################
|
||||
apiserver:
|
||||
@@ -238,7 +242,6 @@ statsreporter:
|
||||
# Whether to collect identities and traits (emails).
|
||||
identities: true
|
||||
|
||||
|
||||
##################### Gateway (License only) #####################
|
||||
gateway:
|
||||
# The URL of the gateway's api.
|
||||
|
||||
@@ -176,7 +176,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.97.0
|
||||
image: signoz/signoz:v0.98.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
ports:
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.97.0
|
||||
image: signoz/signoz:v0.98.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
ports:
|
||||
|
||||
@@ -179,7 +179,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.97.0}
|
||||
image: signoz/signoz:${VERSION:-v0.98.0}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
|
||||
@@ -111,7 +111,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.97.0}
|
||||
image: signoz/signoz:${VERSION:-v0.98.0}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
|
||||
@@ -13,8 +13,6 @@ Before diving in, make sure you have these tools installed:
|
||||
- Download from [go.dev/dl](https://go.dev/dl/)
|
||||
- Check [go.mod](../../go.mod#L3) for the minimum version
|
||||
|
||||
- **GCC** - Required for CGO dependencies
|
||||
- Download from [gcc.gnu.org](https://gcc.gnu.org/)
|
||||
|
||||
- **Node** - Powers our frontend
|
||||
- Download from [nodejs.org](https://nodejs.org)
|
||||
|
||||
@@ -2,6 +2,7 @@ package postgressqlschema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
@@ -47,50 +48,45 @@ func (provider *provider) Operator() sqlschema.SQLOperator {
|
||||
}
|
||||
|
||||
func (provider *provider) GetTable(ctx context.Context, tableName sqlschema.TableName) (*sqlschema.Table, []*sqlschema.UniqueConstraint, error) {
|
||||
rows, err := provider.
|
||||
columns := []struct {
|
||||
ColumnName string `bun:"column_name"`
|
||||
Nullable bool `bun:"nullable"`
|
||||
SQLDataType string `bun:"udt_name"`
|
||||
DefaultVal *string `bun:"column_default"`
|
||||
}{}
|
||||
|
||||
err := provider.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
QueryContext(ctx, `
|
||||
NewRaw(`
|
||||
SELECT
|
||||
c.column_name,
|
||||
c.is_nullable = 'YES',
|
||||
c.is_nullable = 'YES' as nullable,
|
||||
c.udt_name,
|
||||
c.column_default
|
||||
FROM
|
||||
information_schema.columns AS c
|
||||
WHERE
|
||||
c.table_name = ?`, string(tableName))
|
||||
c.table_name = ?`, string(tableName)).
|
||||
Scan(ctx, &columns)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(columns) == 0 {
|
||||
return nil, nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "error closing rows", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
columns := make([]*sqlschema.Column, 0)
|
||||
for rows.Next() {
|
||||
var (
|
||||
name string
|
||||
sqlDataType string
|
||||
nullable bool
|
||||
defaultVal *string
|
||||
)
|
||||
if err := rows.Scan(&name, &nullable, &sqlDataType, &defaultVal); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sqlschemaColumns := make([]*sqlschema.Column, 0)
|
||||
for _, column := range columns {
|
||||
columnDefault := ""
|
||||
if defaultVal != nil {
|
||||
columnDefault = *defaultVal
|
||||
if column.DefaultVal != nil {
|
||||
columnDefault = *column.DefaultVal
|
||||
}
|
||||
|
||||
columns = append(columns, &sqlschema.Column{
|
||||
Name: sqlschema.ColumnName(name),
|
||||
Nullable: nullable,
|
||||
DataType: provider.fmter.DataTypeOf(sqlDataType),
|
||||
sqlschemaColumns = append(sqlschemaColumns, &sqlschema.Column{
|
||||
Name: sqlschema.ColumnName(column.ColumnName),
|
||||
Nullable: column.Nullable,
|
||||
DataType: provider.fmter.DataTypeOf(column.SQLDataType),
|
||||
Default: columnDefault,
|
||||
})
|
||||
}
|
||||
@@ -208,7 +204,7 @@ WHERE
|
||||
|
||||
return &sqlschema.Table{
|
||||
Name: tableName,
|
||||
Columns: columns,
|
||||
Columns: sqlschemaColumns,
|
||||
PrimaryKeyConstraint: primaryKeyConstraint,
|
||||
ForeignKeyConstraints: foreignKeyConstraints,
|
||||
}, uniqueConstraints, nil
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"prismjs": "1.30.0",
|
||||
"got": "11.8.5",
|
||||
"form-data": "4.0.4",
|
||||
"brace-expansion": "^2.0.2"
|
||||
"brace-expansion": "^2.0.2",
|
||||
"on-headers": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import { IUser } from 'providers/App/types';
|
||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
import { KBarCommandPaletteProvider } from 'providers/KBarCommandPaletteProvider';
|
||||
import { MarkersProvider } from 'providers/Markers/Markers';
|
||||
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||
@@ -379,30 +380,34 @@ function App(): JSX.Element {
|
||||
<PrivateRoute>
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
<DashboardProvider>
|
||||
<KeyboardHotkeysProvider>
|
||||
<AlertRuleProvider>
|
||||
<AppLayout>
|
||||
<PreferenceContextProvider>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</PreferenceContextProvider>
|
||||
</AppLayout>
|
||||
</AlertRuleProvider>
|
||||
</KeyboardHotkeysProvider>
|
||||
</DashboardProvider>
|
||||
<MarkersProvider>
|
||||
<DashboardProvider>
|
||||
<KeyboardHotkeysProvider>
|
||||
<AlertRuleProvider>
|
||||
<AppLayout>
|
||||
<PreferenceContextProvider>
|
||||
<Suspense
|
||||
fallback={<Spinner size="large" tip="Loading..." />}
|
||||
>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</PreferenceContextProvider>
|
||||
</AppLayout>
|
||||
</AlertRuleProvider>
|
||||
</KeyboardHotkeysProvider>
|
||||
</DashboardProvider>
|
||||
</MarkersProvider>
|
||||
</QueryBuilderProvider>
|
||||
</ResourceProvider>
|
||||
</PrivateRoute>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
.panel-markers-control {
|
||||
padding: 16px;
|
||||
.panel-markers-view-controller {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markers-control-skeleton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-top: 8px;
|
||||
.ant-skeleton-input{
|
||||
width: 230px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-markers-inputs-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-top: 8px;
|
||||
.panel-markers-select-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
.custom-multiselect-wrapper {
|
||||
width: fit-content;
|
||||
min-width: 230px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.variable-name {
|
||||
display: flex;
|
||||
min-width: 56px;
|
||||
height: 32px;
|
||||
padding: 6px 6px 6px 8px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-300);
|
||||
color: var(--bg-robin-300);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
|
||||
.info-icon {
|
||||
margin-left: 4px;
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
3
frontend/src/components/PanelMarkersControl/constants.ts
Normal file
3
frontend/src/components/PanelMarkersControl/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const MARKER_TYPES = {
|
||||
DEPLOYMENT: 'deployment',
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { Dispatch } from 'react';
|
||||
import { useReducer } from 'react';
|
||||
|
||||
import type { MarkerControlState, MarkerQueryState } from '../types';
|
||||
|
||||
export const MARKER_ACTIONS = {
|
||||
TOGGLE_SHOW_MARKERS: 'toggleShowMarkers',
|
||||
SET_MARKER_SERVICES: 'setMarkerServices',
|
||||
SET_MARKER_TYPES: 'setMarkerTypes',
|
||||
SET_DEFAULTS_ON: 'setDefaultsOn',
|
||||
RESET: 'reset',
|
||||
} as const;
|
||||
|
||||
export type MarkerActionType = typeof MARKER_ACTIONS[keyof typeof MARKER_ACTIONS];
|
||||
|
||||
export type MarkerControlAction =
|
||||
| { type: typeof MARKER_ACTIONS.TOGGLE_SHOW_MARKERS; payload: boolean }
|
||||
| { type: typeof MARKER_ACTIONS.SET_MARKER_SERVICES; payload: string[] }
|
||||
| { type: typeof MARKER_ACTIONS.SET_MARKER_TYPES; payload: string[] }
|
||||
| {
|
||||
type: typeof MARKER_ACTIONS.SET_DEFAULTS_ON;
|
||||
payload: { markerServices: string[]; markerTypes: string[] };
|
||||
}
|
||||
| { type: typeof MARKER_ACTIONS.RESET };
|
||||
|
||||
function normalizeInitialState(
|
||||
state: MarkerQueryState | null,
|
||||
): MarkerControlState {
|
||||
return {
|
||||
showMarkers: state?.showMarkers ? 1 : 0,
|
||||
markerServices: state?.markerServices || [],
|
||||
markerTypes: state?.markerTypes || [],
|
||||
};
|
||||
}
|
||||
|
||||
function reducer(
|
||||
state: MarkerControlState,
|
||||
action: MarkerControlAction,
|
||||
): MarkerControlState {
|
||||
switch (action.type) {
|
||||
case MARKER_ACTIONS.TOGGLE_SHOW_MARKERS:
|
||||
return { ...state, showMarkers: action.payload ? 1 : 0 };
|
||||
case MARKER_ACTIONS.SET_MARKER_SERVICES:
|
||||
return { ...state, markerServices: action.payload };
|
||||
case MARKER_ACTIONS.SET_MARKER_TYPES:
|
||||
return { ...state, markerTypes: action.payload };
|
||||
case MARKER_ACTIONS.SET_DEFAULTS_ON:
|
||||
return {
|
||||
...state,
|
||||
showMarkers: 1,
|
||||
markerServices: action.payload.markerServices,
|
||||
markerTypes: action.payload.markerTypes,
|
||||
};
|
||||
case MARKER_ACTIONS.RESET:
|
||||
return { showMarkers: 0, markerServices: [], markerTypes: [] };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default function useMarkerControlState(
|
||||
initialQueryState: MarkerQueryState | null,
|
||||
): {
|
||||
store: MarkerControlState;
|
||||
dispatch: Dispatch<MarkerControlAction>;
|
||||
} {
|
||||
const initial = normalizeInitialState(initialQueryState);
|
||||
const [store, dispatch] = useReducer(reducer, initial);
|
||||
return { store, dispatch };
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
clearLocalStorageState,
|
||||
getLocalStorageState,
|
||||
getMarkerStateFromQuery,
|
||||
getQueryParamsFromState,
|
||||
setLocalStorageState,
|
||||
} from 'components/PanelMarkersControl/utils';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
|
||||
type MarkerHandlers = {
|
||||
onMarkerToggleOn: () => void;
|
||||
onMarkerToggleOff: () => void;
|
||||
};
|
||||
|
||||
const useMarkerHandlers = ({ key }: { key: string }): MarkerHandlers => {
|
||||
const urlQuery = useUrlQuery();
|
||||
const { search } = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
// useEffect to sync url query with local storage
|
||||
useEffect(() => {
|
||||
const queryState = getMarkerStateFromQuery(urlQuery);
|
||||
const localStorageState = getLocalStorageState(key);
|
||||
|
||||
if (queryState === null && localStorageState?.showMarkers) {
|
||||
const params = new URLSearchParams(search);
|
||||
const queryParams = getQueryParamsFromState(params, localStorageState);
|
||||
history.replace({ search: queryParams.toString() });
|
||||
} else {
|
||||
setLocalStorageState(key, queryState);
|
||||
}
|
||||
}, [urlQuery, key, search, history]);
|
||||
|
||||
const onMarkerToggleOn = useCallback(() => {
|
||||
// set defaults for service and marker type
|
||||
const params = new URLSearchParams(search);
|
||||
params.set('showMarkers', '1');
|
||||
history.replace({ search: params.toString() });
|
||||
}, [search, history]);
|
||||
|
||||
const onMarkerToggleOff = useCallback(() => {
|
||||
// important to clear both url query and local storage here. Else url local storage sync useEffect will not work as expected.
|
||||
clearLocalStorageState(key);
|
||||
|
||||
const params = new URLSearchParams(search);
|
||||
params.delete('showMarkers');
|
||||
params.delete('markerServices');
|
||||
params.delete('markerTypes');
|
||||
history.replace({ search: params.toString() });
|
||||
}, [key, search, history]);
|
||||
|
||||
return { onMarkerToggleOn, onMarkerToggleOff };
|
||||
};
|
||||
|
||||
export default useMarkerHandlers;
|
||||
234
frontend/src/components/PanelMarkersControl/index.tsx
Normal file
234
frontend/src/components/PanelMarkersControl/index.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
import './PanelMarkersControl.scss';
|
||||
|
||||
import { Skeleton, Switch, Typography } from 'antd';
|
||||
import CustomMultiSelect from 'components/NewSelect/CustomMultiSelect';
|
||||
import { MARKER_TYPES } from 'components/PanelMarkersControl/constants';
|
||||
import useMarkerControlState, {
|
||||
MARKER_ACTIONS,
|
||||
} from 'components/PanelMarkersControl/hooks/useMarkerControlState';
|
||||
import useMarkerHandlers from 'components/PanelMarkersControl/hooks/useMarkerHandlers';
|
||||
import {
|
||||
getInitialStateForControls,
|
||||
getQueryParamsFromState,
|
||||
} from 'components/PanelMarkersControl/utils';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { useFetchMarkersData, useMarkers } from 'providers/Markers/Markers';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
|
||||
function PanelMarkersControl(): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
const { search } = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
const { selectedDashboard } = useDashboard();
|
||||
|
||||
const { store: markerControlState, dispatch } = useMarkerControlState(
|
||||
getInitialStateForControls(selectedDashboard?.id || '', urlQuery),
|
||||
);
|
||||
|
||||
const { markersData, setMarkersData } = useMarkers();
|
||||
|
||||
const { loadingMarkers } = useFetchMarkersData({
|
||||
isFetchEnabled: markerControlState.showMarkers === 1,
|
||||
});
|
||||
|
||||
const { onMarkerToggleOn, onMarkerToggleOff } = useMarkerHandlers({
|
||||
key: selectedDashboard?.id || '',
|
||||
});
|
||||
|
||||
// API integration: check if this is correct
|
||||
const markerTypeOptions = useMemo(() => {
|
||||
const uniqueTypes = Array.from(
|
||||
new Set((markersData || []).map((m: any) => m?.type).filter(Boolean)),
|
||||
);
|
||||
return uniqueTypes.map((t: string) => ({
|
||||
label: t.charAt(0).toUpperCase() + t.slice(1),
|
||||
value: t,
|
||||
}));
|
||||
}, [markersData]);
|
||||
|
||||
// API integration: check if this is correct
|
||||
const serviceNameOptions = useMemo(() => {
|
||||
const uniqueServices = Array.from(
|
||||
new Set(
|
||||
(markersData || [])
|
||||
.map((m: any) => m?.attr?.['service.name'])
|
||||
.filter(Boolean),
|
||||
),
|
||||
);
|
||||
return uniqueServices.map((s: string) => ({ label: s, value: s }));
|
||||
}, [markersData]);
|
||||
|
||||
const handleServiceChange = useCallback(
|
||||
(serviceOrServices: string | string[] | undefined): void => {
|
||||
let servicesArray: string[] = [];
|
||||
if (Array.isArray(serviceOrServices)) {
|
||||
servicesArray = serviceOrServices;
|
||||
} else if (serviceOrServices) {
|
||||
servicesArray = [serviceOrServices];
|
||||
}
|
||||
dispatch({
|
||||
type: MARKER_ACTIONS.SET_MARKER_SERVICES,
|
||||
payload: servicesArray,
|
||||
});
|
||||
|
||||
// sync URL param
|
||||
const params = new URLSearchParams(search);
|
||||
if (servicesArray.length > 0) {
|
||||
params.set('markerServices', servicesArray.join(','));
|
||||
} else {
|
||||
params.delete('markerServices');
|
||||
}
|
||||
history.replace({ search: params.toString() });
|
||||
},
|
||||
[history, search, dispatch],
|
||||
);
|
||||
|
||||
const handleMarkerTypesChange = useCallback(
|
||||
(typesOrArray: string | string[] | undefined): void => {
|
||||
let typesArray: string[] = [];
|
||||
if (Array.isArray(typesOrArray)) {
|
||||
typesArray = typesOrArray;
|
||||
} else if (typesOrArray) {
|
||||
typesArray = [typesOrArray];
|
||||
}
|
||||
dispatch({ type: MARKER_ACTIONS.SET_MARKER_TYPES, payload: typesArray });
|
||||
|
||||
const params = new URLSearchParams(search);
|
||||
if (typesArray.length > 0) {
|
||||
params.set('markerTypes', typesArray.join(','));
|
||||
} else {
|
||||
params.delete('markerTypes');
|
||||
}
|
||||
history.replace({ search: params.toString() });
|
||||
},
|
||||
[history, search, dispatch],
|
||||
);
|
||||
|
||||
const handleToggleShowMarkers = useCallback(
|
||||
(checked: boolean): void => {
|
||||
dispatch({ type: MARKER_ACTIONS.TOGGLE_SHOW_MARKERS, payload: checked });
|
||||
if (checked) {
|
||||
// get default services and marker types from markersData
|
||||
onMarkerToggleOn();
|
||||
} else {
|
||||
// consider using useReducer to reset the state
|
||||
setMarkersData([]);
|
||||
dispatch({ type: MARKER_ACTIONS.RESET });
|
||||
onMarkerToggleOff();
|
||||
}
|
||||
},
|
||||
[onMarkerToggleOn, onMarkerToggleOff, dispatch, setMarkersData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (markersData.length < 1) return;
|
||||
/**
|
||||
* On markers data change, derive defaults (or use query selections if present)
|
||||
* and set them in a single reducer dispatch.
|
||||
*/
|
||||
const queryMarkerServicesRaw = urlQuery.get('markerServices') || '';
|
||||
const queryMarkerTypesRaw = urlQuery.get('markerTypes') || '';
|
||||
|
||||
const defMarkerServices = ['cart-service'];
|
||||
const defMarkerTypes = [MARKER_TYPES.DEPLOYMENT];
|
||||
|
||||
const servicesArray = queryMarkerServicesRaw
|
||||
? queryMarkerServicesRaw
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0)
|
||||
: defMarkerServices;
|
||||
const typesArray = queryMarkerTypesRaw
|
||||
? queryMarkerTypesRaw
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t.length > 0)
|
||||
: defMarkerTypes;
|
||||
|
||||
dispatch({
|
||||
type: MARKER_ACTIONS.SET_DEFAULTS_ON,
|
||||
payload: { markerServices: servicesArray, markerTypes: typesArray },
|
||||
});
|
||||
|
||||
// reflect in URL params as well
|
||||
const params = new URLSearchParams(search);
|
||||
const queryParams = getQueryParamsFromState(params, {
|
||||
showMarkers: 1,
|
||||
markerServices: servicesArray,
|
||||
markerTypes: typesArray,
|
||||
});
|
||||
history.replace({ search: queryParams.toString() });
|
||||
|
||||
console.log('>>> markersData', markersData);
|
||||
// urlQuery removed from dependencies as not able to unset markerTypes. But works with markerServices. [CHECK THIS]
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [markersData]);
|
||||
|
||||
// ADD INITIAL STATE FOR SELECTED SERVICES AND MARKER TYPES
|
||||
|
||||
return (
|
||||
<div className="panel-markers-control">
|
||||
<div className="panel-markers-view-controller">
|
||||
<Typography>Show Markers</Typography>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={markerControlState.showMarkers === 1}
|
||||
onChange={handleToggleShowMarkers}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{markerControlState.showMarkers === 1 && (
|
||||
<div className="panel-markers-inputs-section">
|
||||
{loadingMarkers ? (
|
||||
<div className="markers-control-skeleton">
|
||||
<Skeleton.Input active size="small" />
|
||||
<Skeleton.Input active size="small" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="panel-markers-select-container">
|
||||
<Typography.Text className="variable-name" ellipsis>
|
||||
Marker type
|
||||
</Typography.Text>
|
||||
<CustomMultiSelect
|
||||
className="panel-markers-select"
|
||||
placeholder="Select one or more marker types"
|
||||
enableAllSelection={false}
|
||||
options={markerTypeOptions}
|
||||
maxTagCount={3}
|
||||
value={markerControlState.markerTypes}
|
||||
onChange={handleMarkerTypesChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="panel-markers-select-container">
|
||||
<Typography.Text className="variable-name" ellipsis>
|
||||
Service name
|
||||
</Typography.Text>
|
||||
<CustomMultiSelect
|
||||
className="panel-markers-select"
|
||||
placeholder="Select one or more service names"
|
||||
maxTagCount={3}
|
||||
enableAllSelection={false}
|
||||
options={serviceNameOptions}
|
||||
value={markerControlState.markerServices}
|
||||
onChange={handleServiceChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// select bright color for the markers
|
||||
// convert panel marker state to useReducer
|
||||
// removed urlQuery from dependencies.
|
||||
// filters on markersData should work properly. If no markers selected. Show no markers.
|
||||
|
||||
export default PanelMarkersControl;
|
||||
7
frontend/src/components/PanelMarkersControl/types.ts
Normal file
7
frontend/src/components/PanelMarkersControl/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface MarkerControlState {
|
||||
showMarkers: number;
|
||||
markerServices: string[];
|
||||
markerTypes: string[];
|
||||
}
|
||||
|
||||
export type MarkerQueryState = MarkerControlState | null;
|
||||
108
frontend/src/components/PanelMarkersControl/utils.ts
Normal file
108
frontend/src/components/PanelMarkersControl/utils.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import type { MarkerQueryState } from 'components/PanelMarkersControl/types';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
|
||||
// CHECK LOGIC AND CREATE UTIL
|
||||
export function getMarkerStateFromQuery(
|
||||
urlQuery: URLSearchParams,
|
||||
): MarkerQueryState | null {
|
||||
const showMarkers = urlQuery.get('showMarkers') === '1';
|
||||
const servicesRaw = urlQuery.get('markerServices') || '';
|
||||
|
||||
if (!showMarkers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const markerTypesParam = urlQuery.get('markerTypes') || '';
|
||||
|
||||
const markerTypes = markerTypesParam
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t.length > 0);
|
||||
|
||||
const markerServices = servicesRaw
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
|
||||
return {
|
||||
showMarkers: 1,
|
||||
markerServices,
|
||||
markerTypes,
|
||||
};
|
||||
}
|
||||
|
||||
export const getLocalStorageState = (key: string): MarkerQueryState | null => {
|
||||
const raw = localStorage.getItem(LOCALSTORAGE.MARKERS_OVERLAY_STATE);
|
||||
try {
|
||||
const parsed = raw ? JSON.parse(raw) : null;
|
||||
return parsed?.[key] ?? null;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getQueryParamsFromState = (
|
||||
params: URLSearchParams,
|
||||
state: MarkerQueryState,
|
||||
): URLSearchParams => {
|
||||
if (!state) {
|
||||
return params;
|
||||
}
|
||||
if (state.showMarkers) {
|
||||
params.set('showMarkers', String(state.showMarkers) || '0');
|
||||
}
|
||||
if (Array.isArray(state.markerServices) && state.markerServices.length > 0) {
|
||||
params.set('markerServices', state.markerServices.join(','));
|
||||
}
|
||||
if (Array.isArray(state.markerTypes) && state.markerTypes.length > 0) {
|
||||
params.set('markerTypes', state.markerTypes.join(','));
|
||||
}
|
||||
return params;
|
||||
};
|
||||
|
||||
export const setLocalStorageState = (
|
||||
key: string,
|
||||
state: MarkerQueryState | null,
|
||||
): void => {
|
||||
if (!key || key.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = localStorage.getItem(LOCALSTORAGE.MARKERS_OVERLAY_STATE);
|
||||
let obj: Record<string, unknown> = {};
|
||||
try {
|
||||
obj = raw ? JSON.parse(raw) : {};
|
||||
} catch (_) {
|
||||
obj = {};
|
||||
}
|
||||
obj[key] = state;
|
||||
localStorage.setItem(LOCALSTORAGE.MARKERS_OVERLAY_STATE, JSON.stringify(obj));
|
||||
} catch (_) {
|
||||
// ignore storage errors
|
||||
}
|
||||
};
|
||||
|
||||
export const getInitialStateForControls = (
|
||||
key: string,
|
||||
urlQuery: URLSearchParams,
|
||||
): MarkerQueryState | null => {
|
||||
const queryState = getMarkerStateFromQuery(urlQuery);
|
||||
const localStorageState = getLocalStorageState(key);
|
||||
return queryState ?? localStorageState;
|
||||
};
|
||||
|
||||
export const clearLocalStorageState = (key: string): void => {
|
||||
if (!key || key.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
const raw = localStorage.getItem(LOCALSTORAGE.MARKERS_OVERLAY_STATE);
|
||||
let obj: Record<string, unknown> = {};
|
||||
try {
|
||||
obj = raw ? JSON.parse(raw) : {};
|
||||
} catch (_) {
|
||||
obj = {};
|
||||
}
|
||||
delete obj[key];
|
||||
localStorage.setItem(LOCALSTORAGE.MARKERS_OVERLAY_STATE, JSON.stringify(obj));
|
||||
};
|
||||
@@ -18,11 +18,6 @@ import UPlot from 'uplot';
|
||||
|
||||
import { dataMatch, optionsUpdateState } from './utils';
|
||||
|
||||
// Extended uPlot interface with custom properties
|
||||
interface ExtendedUPlot extends uPlot {
|
||||
_legendScrollCleanup?: () => void;
|
||||
}
|
||||
|
||||
export interface UplotProps {
|
||||
options: uPlot.Options;
|
||||
data: uPlot.AlignedData;
|
||||
@@ -71,12 +66,6 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
|
||||
|
||||
const destroy = useCallback((chart: uPlot | null) => {
|
||||
if (chart) {
|
||||
// Clean up legend scroll event listener
|
||||
const extendedChart = chart as ExtendedUPlot;
|
||||
if (extendedChart._legendScrollCleanup) {
|
||||
extendedChart._legendScrollCleanup();
|
||||
}
|
||||
|
||||
onDeleteRef.current?.(chart);
|
||||
chart.destroy();
|
||||
chartRef.current = null;
|
||||
|
||||
177
frontend/src/components/Uplot/plugins/verticalMarkersPlugin.ts
Normal file
177
frontend/src/components/Uplot/plugins/verticalMarkersPlugin.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
|
||||
import uPlot from 'uplot';
|
||||
|
||||
type MarkersData = { id: string | number; val: number; stroke?: string };
|
||||
|
||||
export function verticalMarkersPlugin({
|
||||
markersData = [],
|
||||
lineType = [5, 3],
|
||||
width = 1,
|
||||
}: {
|
||||
markersData?: MarkersData[];
|
||||
lineType?: number[];
|
||||
width?: number;
|
||||
} = {}): uPlot.Plugin {
|
||||
const DEFAULT_STROKE = 'rgba(0, 102, 255, 0.95)';
|
||||
let removeListeners: (() => void) | null = null;
|
||||
let tooltipEl: HTMLDivElement | null = null;
|
||||
const renderAxisMarkers = (uu: uPlot): void => {
|
||||
const axes = uu.root.querySelectorAll('.u-axis');
|
||||
const xAxis = (axes && (axes[0] as HTMLElement)) || null;
|
||||
if (!xAxis) return;
|
||||
|
||||
// attach delegated hover/mouseout listeners once on the x-axis container
|
||||
if (!(xAxis as HTMLElement).dataset?.vlineHoverAttached) {
|
||||
const onMouseOver = (e: MouseEvent): void => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!target?.classList?.contains('vline-triangle-marker')) return;
|
||||
|
||||
const { id } = target.dataset;
|
||||
const valStr = target.dataset.val;
|
||||
const val = valStr ? Number(valStr) : undefined;
|
||||
// const mData = markersData.find((d) => String(d.id) === String(id));
|
||||
// create tooltip
|
||||
if (!tooltipEl) {
|
||||
tooltipEl = document.createElement('div');
|
||||
tooltipEl.className = 'vline-marker-tooltip';
|
||||
Object.assign(tooltipEl.style, {
|
||||
position: 'fixed',
|
||||
padding: '6px 8px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px',
|
||||
zIndex: '10000',
|
||||
pointerEvents: 'none',
|
||||
background: '#111827',
|
||||
color: '#e5e7eb',
|
||||
border: '1px solid #374151',
|
||||
});
|
||||
document.body.appendChild(tooltipEl);
|
||||
}
|
||||
tooltipEl.textContent = `id: ${id ?? ''} • ts: ${val ?? ''}`;
|
||||
// position near cursor
|
||||
tooltipEl.style.left = `${e.clientX + 10}px`;
|
||||
tooltipEl.style.top = `${e.clientY - 28}px`;
|
||||
};
|
||||
const onMouseOut = (e: MouseEvent): void => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!target?.classList?.contains('vline-triangle-marker')) return;
|
||||
if (tooltipEl) {
|
||||
tooltipEl.remove();
|
||||
tooltipEl = null;
|
||||
}
|
||||
};
|
||||
xAxis.addEventListener('mouseover', onMouseOver);
|
||||
xAxis.addEventListener('mouseout', onMouseOut);
|
||||
removeListeners = (): void => {
|
||||
xAxis.removeEventListener('mouseover', onMouseOver);
|
||||
xAxis.removeEventListener('mouseout', onMouseOut);
|
||||
};
|
||||
(xAxis as HTMLElement).dataset.vlineHoverAttached = '1';
|
||||
}
|
||||
|
||||
// cleanup markers to avoid duplicates on rerender/resize
|
||||
xAxis.querySelectorAll('.vline-triangle-marker').forEach((el) => el.remove());
|
||||
|
||||
const plotLeft = uu.bbox.left;
|
||||
const plotRight = plotLeft + uu.bbox.width;
|
||||
|
||||
for (let i = 0; i < markersData.length; i++) {
|
||||
const mData = markersData[i];
|
||||
const xAbs = uu.valToPos(mData.val, 'x', true);
|
||||
if (xAbs >= plotLeft && xAbs <= plotRight) {
|
||||
const xPx = (xAbs - plotLeft) / window.devicePixelRatio;
|
||||
|
||||
const marker = document.createElement('div');
|
||||
marker.className = 'vline-triangle-marker';
|
||||
marker.dataset.id = String(mData.id); // may change later after BE discussion
|
||||
marker.dataset.val = String(mData.val); // TODO: remove this later
|
||||
Object.assign(marker.style, {
|
||||
position: 'absolute',
|
||||
width: '0px',
|
||||
height: '0px',
|
||||
borderLeft: '5px solid transparent',
|
||||
borderRight: '5px solid transparent',
|
||||
borderBottomWidth: '5px',
|
||||
borderBottomStyle: 'solid',
|
||||
borderBottomColor: mData.stroke || DEFAULT_STROKE,
|
||||
transform: 'translateX(-50%)',
|
||||
cursor: 'pointer',
|
||||
zIndex: '1',
|
||||
left: `${xPx}px`,
|
||||
});
|
||||
|
||||
xAxis.appendChild(marker);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
hooks: {
|
||||
destroy: [
|
||||
(): void => {
|
||||
if (tooltipEl) {
|
||||
tooltipEl.remove();
|
||||
tooltipEl = null;
|
||||
}
|
||||
if (removeListeners) {
|
||||
removeListeners();
|
||||
removeListeners = null;
|
||||
}
|
||||
},
|
||||
],
|
||||
drawAxes: [
|
||||
(uu: uPlot): void => {
|
||||
renderAxisMarkers(uu);
|
||||
},
|
||||
],
|
||||
draw: [
|
||||
(uu: uPlot): void => {
|
||||
const { ctx } = uu;
|
||||
const { top } = uu.bbox;
|
||||
const bottom = top + uu.bbox.height;
|
||||
const plotLeft = uu.bbox.left;
|
||||
const plotRight = plotLeft + uu.bbox.width;
|
||||
|
||||
ctx.save();
|
||||
for (let i = 0; i < markersData.length; i++) {
|
||||
const mData = markersData[i];
|
||||
const x = uu.valToPos(mData.val, 'x', true);
|
||||
// only draw if within plot bounds
|
||||
if (x >= plotLeft && x <= plotRight) {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = mData.stroke || DEFAULT_STROKE;
|
||||
ctx.lineWidth = width;
|
||||
ctx.setLineDash(lineType || []);
|
||||
ctx.moveTo(x, top);
|
||||
ctx.lineTo(x, bottom);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// MOVE TO REACT. or use portal for tooltip.
|
||||
// correct the format should work with expected data from BE
|
||||
// Remove cognitive complexity rule.
|
||||
|
||||
// logic to get marker plugin to be added to context
|
||||
// depending on type of marker parse the data to choose color. example deployment should be red etc.
|
||||
// pass such data with multple colors to check render.
|
||||
// PERF CHECK.
|
||||
|
||||
// drive data passing from the context for markers. data in this is enriched only when we
|
||||
// use <ShowMarkers /> component. else it will be empty.
|
||||
// plugins. and pass marker hooks using this
|
||||
// should only pass marker hook if data is present(shouldRenderMarker memo)
|
||||
// depending on type of marker parse the data to choose color. example deployment should be red etc.
|
||||
// pass such data with multple colors to check render.
|
||||
// PERF CHECK.
|
||||
|
||||
// drive data passing from the context for markers. data in this is enriched only when we
|
||||
// use <ShowMarkers /> component. else it will be empty.
|
||||
@@ -36,4 +36,5 @@ export enum LOCALSTORAGE {
|
||||
LAST_USED_CUSTOM_TIME_RANGES = 'LAST_USED_CUSTOM_TIME_RANGES',
|
||||
SHOW_FREQUENCY_CHART = 'SHOW_FREQUENCY_CHART',
|
||||
DISSMISSED_COST_METER_INFO = 'DISMISSED_COST_METER_INFO',
|
||||
MARKERS_OVERLAY_STATE = 'MARKERS_OVERLAY_STATE',
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
|
||||
import PanelMarkersControl from 'components/PanelMarkersControl';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
@@ -488,6 +489,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
<DashboardVariableSelection />
|
||||
</section>
|
||||
)}
|
||||
<PanelMarkersControl />
|
||||
<DashboardGraphSlider />
|
||||
|
||||
<Modal
|
||||
|
||||
@@ -28,7 +28,15 @@ function ConfigureGoogleAuthAuthnProvider({
|
||||
</Typography.Paragraph>
|
||||
</section>
|
||||
|
||||
<Form.Item label="Domain" name="name" className="field">
|
||||
<Form.Item
|
||||
label="Domain"
|
||||
name="name"
|
||||
className="field"
|
||||
tooltip={{
|
||||
title:
|
||||
'The email domain for users who should use SSO (e.g., `example.com` for users with `@example.com` emails)',
|
||||
}}
|
||||
>
|
||||
<Input disabled={!isCreate} />
|
||||
</Form.Item>
|
||||
|
||||
|
||||
@@ -16,7 +16,14 @@ function ConfigureOIDCAuthnProvider({
|
||||
</Typography.Text>
|
||||
</section>
|
||||
|
||||
<Form.Item label="Domain" name="name">
|
||||
<Form.Item
|
||||
label="Domain"
|
||||
name="name"
|
||||
tooltip={{
|
||||
title:
|
||||
'The email domain for users who should use SSO (e.g., `example.com` for users with `@example.com` emails)',
|
||||
}}
|
||||
>
|
||||
<Input disabled={!isCreate} />
|
||||
</Form.Item>
|
||||
|
||||
|
||||
@@ -16,7 +16,14 @@ function ConfigureSAMLAuthnProvider({
|
||||
</Typography.Text>
|
||||
</section>
|
||||
|
||||
<Form.Item label="Domain" name="name">
|
||||
<Form.Item
|
||||
label="Domain"
|
||||
name="name"
|
||||
tooltip={{
|
||||
title:
|
||||
'The email domain for users who should use SSO (e.g., `example.com` for users with `@example.com` emails)',
|
||||
}}
|
||||
>
|
||||
<Input disabled={!isCreate} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -24,7 +31,7 @@ function ConfigureSAMLAuthnProvider({
|
||||
label="SAML ACS URL"
|
||||
name={['samlConfig', 'samlIdp']}
|
||||
tooltip={{
|
||||
title: `The entityID of the SAML identity provider. It can typically be found in the EntityID attribute of the EntityDescriptor element in the SAML metadata of the identity provider. Example: <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="{samlEntity}">`,
|
||||
title: `The SSO endpoint of the SAML identity provider. It can typically be found in the SingleSignOnService element in the SAML metadata of the identity provider. Example: <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="{samlIdp}"/>`,
|
||||
}}
|
||||
>
|
||||
<Input />
|
||||
@@ -34,7 +41,7 @@ function ConfigureSAMLAuthnProvider({
|
||||
label="SAML Entity ID"
|
||||
name={['samlConfig', 'samlEntity']}
|
||||
tooltip={{
|
||||
title: `The SSO endpoint of the SAML identity provider. It can typically be found in the SingleSignOnService element in the SAML metadata of the identity provider. Example: <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="{samlIdp}"/>`,
|
||||
title: `The entityID of the SAML identity provider. It can typically be found in the EntityID attribute of the EntityDescriptor element in the SAML metadata of the identity provider. Example: <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="{samlEntity}">`,
|
||||
}}
|
||||
>
|
||||
<Input />
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
|
||||
import './UplotPanelWrapper.styles.scss';
|
||||
|
||||
import { Alert } from 'antd';
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import Uplot from 'components/Uplot';
|
||||
import { verticalMarkersPlugin } from 'components/Uplot/plugins/verticalMarkersPlugin';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import GraphManager from 'container/GridCardLayout/GridCard/FullView/GraphManager';
|
||||
import { getLocalStorageGraphVisibilityState } from 'container/GridCardLayout/GridCard/utils';
|
||||
@@ -17,6 +20,7 @@ import { cloneDeep, isEqual, isUndefined } from 'lodash-es';
|
||||
import _noop from 'lodash-es/noop';
|
||||
import { ContextMenu, useCoordinates } from 'periscope/components/ContextMenu';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { useMarkers } from 'providers/Markers/Markers';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@@ -55,6 +59,32 @@ function UplotPanelWrapper({
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { filteredMarkersData, shouldShowMarkers } = useMarkers();
|
||||
|
||||
const markersPlugin: uPlot.Plugin | null = useMemo(() => {
|
||||
if (shouldShowMarkers) {
|
||||
console.log('*** filteredMarkersData', filteredMarkersData);
|
||||
console.log('*** shouldShowMarkers', {
|
||||
markersData: [
|
||||
{ id: 'm1', val: 1760625000, stroke: 'rgba(96, 255, 128, 0.95)' },
|
||||
{ id: 'm2', val: 1760630000, stroke: 'rgba(255, 96, 96, 0.95)' },
|
||||
{ id: 'm3', val: 1760640000, stroke: 'rgba(255, 96, 96, 0.95)' },
|
||||
],
|
||||
lineType: [6, 4],
|
||||
width: 1,
|
||||
});
|
||||
return verticalMarkersPlugin({
|
||||
markersData: [
|
||||
{ id: 'm1', val: 1760625000, stroke: 'rgba(96, 255, 128, 0.95)' },
|
||||
{ id: 'm2', val: 1760630000, stroke: 'rgba(255, 96, 96, 0.95)' },
|
||||
{ id: 'm3', val: 1760640000, stroke: 'rgba(255, 96, 96, 0.95)' },
|
||||
],
|
||||
lineType: [6, 4],
|
||||
width: 1,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}, [shouldShowMarkers, filteredMarkersData]);
|
||||
|
||||
const [hiddenGraph, setHiddenGraph] = useState<{ [key: string]: boolean }>();
|
||||
|
||||
@@ -249,6 +279,7 @@ function UplotPanelWrapper({
|
||||
}) => {
|
||||
legendScrollPositionRef.current = position;
|
||||
},
|
||||
customPlugins: [...(markersPlugin ? [markersPlugin] : [])],
|
||||
}),
|
||||
[
|
||||
queryResponse.data?.payload,
|
||||
@@ -270,6 +301,7 @@ function UplotPanelWrapper({
|
||||
onClickHandler,
|
||||
widget,
|
||||
stackedBarChart,
|
||||
markersPlugin,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import { getYAxisScale } from './utils/getYAxisScale';
|
||||
interface ExtendedUPlot extends uPlot {
|
||||
_legendScrollCleanup?: () => void;
|
||||
_tooltipCleanup?: () => void;
|
||||
_legendElementCleanup?: Array<() => void>;
|
||||
}
|
||||
|
||||
export interface GetUPlotChartOptions {
|
||||
@@ -86,6 +87,7 @@ export interface GetUPlotChartOptions {
|
||||
scrollTop: number;
|
||||
scrollLeft: number;
|
||||
}) => void;
|
||||
customPlugins?: uPlot.Plugin[];
|
||||
}
|
||||
|
||||
/** the function converts series A , series B , series C to
|
||||
@@ -217,6 +219,7 @@ export const getUPlotChartOptions = ({
|
||||
query,
|
||||
legendScrollPosition,
|
||||
setLegendScrollPosition,
|
||||
customPlugins = [],
|
||||
}: GetUPlotChartOptions): uPlot.Options => {
|
||||
const timeScaleProps = getXAxisScale(minTimeScale, maxTimeScale);
|
||||
|
||||
@@ -386,6 +389,7 @@ export const getUPlotChartOptions = ({
|
||||
],
|
||||
},
|
||||
},
|
||||
...customPlugins,
|
||||
],
|
||||
hooks: {
|
||||
draw: [
|
||||
@@ -473,6 +477,9 @@ export const getUPlotChartOptions = ({
|
||||
if (legend) {
|
||||
const legendElement = legend as HTMLElement;
|
||||
|
||||
// Initialize cleanup array for legend element listeners
|
||||
(self as ExtendedUPlot)._legendElementCleanup = [];
|
||||
|
||||
// Apply enhanced legend styling
|
||||
if (enhancedLegend) {
|
||||
applyEnhancedLegendStyling(
|
||||
@@ -639,6 +646,17 @@ export const getUPlotChartOptions = ({
|
||||
thElement.addEventListener('mouseenter', showTooltip);
|
||||
thElement.addEventListener('mouseleave', hideTooltip);
|
||||
|
||||
// Store cleanup function for tooltip listeners
|
||||
(self as ExtendedUPlot)._legendElementCleanup?.push(() => {
|
||||
thElement.removeEventListener('mouseenter', showTooltip);
|
||||
thElement.removeEventListener('mouseleave', hideTooltip);
|
||||
// Cleanup any lingering tooltip
|
||||
if (tooltipElement) {
|
||||
tooltipElement.remove();
|
||||
tooltipElement = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Add click handlers for marker and text separately
|
||||
const currentMarker = thElement.querySelector('.u-marker');
|
||||
const textElement = thElement.querySelector('.legend-text');
|
||||
@@ -658,7 +676,7 @@ export const getUPlotChartOptions = ({
|
||||
|
||||
// Marker click handler - checkbox behavior (toggle individual series)
|
||||
if (currentMarker) {
|
||||
currentMarker.addEventListener('click', (e) => {
|
||||
const markerClickHandler = (e: Event): void => {
|
||||
e.stopPropagation?.(); // Prevent event bubbling to text handler
|
||||
|
||||
if (stackChart) {
|
||||
@@ -680,12 +698,19 @@ export const getUPlotChartOptions = ({
|
||||
return newGraphVisibilityStates;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
currentMarker.addEventListener('click', markerClickHandler);
|
||||
|
||||
// Store cleanup function for marker click listener
|
||||
(self as ExtendedUPlot)._legendElementCleanup?.push(() => {
|
||||
currentMarker.removeEventListener('click', markerClickHandler);
|
||||
});
|
||||
}
|
||||
|
||||
// Text click handler - show only/show all behavior (existing behavior)
|
||||
if (textElement) {
|
||||
textElement.addEventListener('click', (e) => {
|
||||
const textClickHandler = (e: Event): void => {
|
||||
e.stopPropagation?.(); // Prevent event bubbling
|
||||
|
||||
if (stackChart) {
|
||||
@@ -716,6 +741,13 @@ export const getUPlotChartOptions = ({
|
||||
return newGraphVisibilityStates;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
textElement.addEventListener('click', textClickHandler);
|
||||
|
||||
// Store cleanup function for text click listener
|
||||
(self as ExtendedUPlot)._legendElementCleanup?.push(() => {
|
||||
textElement.removeEventListener('click', textClickHandler);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -723,6 +755,33 @@ export const getUPlotChartOptions = ({
|
||||
}
|
||||
},
|
||||
],
|
||||
destroy: [
|
||||
(self): void => {
|
||||
// Clean up legend scroll listener
|
||||
if ((self as ExtendedUPlot)._legendScrollCleanup) {
|
||||
(self as ExtendedUPlot)._legendScrollCleanup?.();
|
||||
(self as ExtendedUPlot)._legendScrollCleanup = undefined;
|
||||
}
|
||||
|
||||
// Clean up tooltip global listener
|
||||
if ((self as ExtendedUPlot)._tooltipCleanup) {
|
||||
(self as ExtendedUPlot)._tooltipCleanup?.();
|
||||
(self as ExtendedUPlot)._tooltipCleanup = undefined;
|
||||
}
|
||||
|
||||
// Clean up all legend element listeners
|
||||
if ((self as ExtendedUPlot)._legendElementCleanup) {
|
||||
(self as ExtendedUPlot)._legendElementCleanup?.forEach((cleanup) => {
|
||||
cleanup();
|
||||
});
|
||||
(self as ExtendedUPlot)._legendElementCleanup = [];
|
||||
}
|
||||
|
||||
// Clean up any remaining tooltips in DOM
|
||||
const existingTooltips = document.querySelectorAll('.legend-tooltip');
|
||||
existingTooltips.forEach((tooltip) => tooltip.remove());
|
||||
},
|
||||
],
|
||||
},
|
||||
series: customSeries
|
||||
? customSeries(apiResponse?.data?.result || [])
|
||||
|
||||
@@ -700,24 +700,23 @@ describe('TracesExplorer - ', () => {
|
||||
});
|
||||
|
||||
it('select a view options - assert and save this view', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const { container } = renderWithTracesExplorerRouter(<TracesExplorer />, [
|
||||
'/traces-explorer/?panelType=list&selectedExplorerView=list',
|
||||
]);
|
||||
await screen.findByText(FILTER_SERVICE_NAME);
|
||||
await act(async () => {
|
||||
fireEvent.mouseDown(
|
||||
container.querySelector(
|
||||
'.view-options .ant-select-selection-search-input',
|
||||
) as HTMLElement,
|
||||
);
|
||||
});
|
||||
|
||||
const viewListOptions = await screen.findByRole('listbox');
|
||||
expect(viewListOptions).toBeInTheDocument();
|
||||
const viewSearchInput = container.querySelector(
|
||||
'.view-options .ant-select-selection-search-input',
|
||||
) as HTMLElement;
|
||||
|
||||
expect(within(viewListOptions).getByText('R-test panel')).toBeInTheDocument();
|
||||
expect(viewSearchInput).toBeInTheDocument();
|
||||
|
||||
expect(within(viewListOptions).getByText('Table View')).toBeInTheDocument();
|
||||
fireEvent.mouseDown(viewSearchInput);
|
||||
|
||||
expect(
|
||||
await screen.findByRole('option', { name: 'R-test panel' }),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// save this view
|
||||
fireEvent.click(screen.getByText('Save this view'));
|
||||
|
||||
184
frontend/src/providers/Markers/Markers.tsx
Normal file
184
frontend/src/providers/Markers/Markers.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
type Marker = {
|
||||
type: string;
|
||||
env: string;
|
||||
source: string;
|
||||
attr: {
|
||||
version: string;
|
||||
firstSeen: number;
|
||||
'service.name': string;
|
||||
region: string;
|
||||
spanCount: number;
|
||||
};
|
||||
};
|
||||
|
||||
function generateMockMarkers(
|
||||
minTime: number,
|
||||
maxTime: number,
|
||||
count = 10,
|
||||
): { markers: Marker[] } {
|
||||
const step = (maxTime - minTime) / count;
|
||||
|
||||
const services = [
|
||||
'cart-service',
|
||||
'checkout-service',
|
||||
'inventory-service',
|
||||
'auth-service',
|
||||
'payment-service',
|
||||
'recommendation-service',
|
||||
'search-service',
|
||||
'user-service',
|
||||
'notification-service',
|
||||
'analytics-service',
|
||||
];
|
||||
|
||||
const regions = [
|
||||
'us-east-1',
|
||||
'us-west-1',
|
||||
'us-east-2',
|
||||
'eu-central-1',
|
||||
'ap-southeast-1',
|
||||
'eu-west-1',
|
||||
];
|
||||
const envs = ['prod-us', 'prod-eu', 'staging', 'prod-ap'];
|
||||
const sources = ['traces', 'logs'];
|
||||
|
||||
const markers: Marker[] = Array.from({ length: count }).map((_, i) => {
|
||||
const firstSeen = Math.round(minTime + i * step);
|
||||
const service = services[i % services.length];
|
||||
const env = envs[i % envs.length];
|
||||
const region = regions[i % regions.length];
|
||||
const source = sources[i % sources.length];
|
||||
|
||||
return {
|
||||
type: 'deployment',
|
||||
env,
|
||||
source,
|
||||
attr: {
|
||||
version: `1.${160 + i}.0-${env.includes('prod') ? 'prod' : 'stg'}`,
|
||||
firstSeen,
|
||||
'service.name': service,
|
||||
region,
|
||||
spanCount: 100 + Math.floor(Math.random() * 150),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return { markers };
|
||||
}
|
||||
|
||||
type MarkersContextValue = {
|
||||
markersData: Marker[];
|
||||
filteredMarkersData: Marker[];
|
||||
setMarkersData: (markersData: Marker[]) => void;
|
||||
shouldShowMarkers: boolean;
|
||||
};
|
||||
|
||||
const MarkersContext = createContext<MarkersContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export function MarkersProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const [markersData, setMarkersData] = useState<Marker[]>([]);
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
// CHECK LOGIC AND CREATE UTIL
|
||||
const filteredMarkersData = useMemo(() => {
|
||||
const servicesRaw = urlQuery.get('markerServices') || '';
|
||||
const typesRaw = urlQuery.get('markerTypes') || '';
|
||||
const selectedServices = servicesRaw
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
const selectedTypes = typesRaw
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t.length > 0);
|
||||
|
||||
if (selectedServices.length === 0 && selectedTypes.length === 0) {
|
||||
return markersData;
|
||||
}
|
||||
|
||||
return (markersData || []).filter((m: Marker) => {
|
||||
const typeOk = selectedTypes.includes(m?.type);
|
||||
const serviceOk = selectedServices.includes(m?.attr?.['service.name']);
|
||||
return typeOk && serviceOk;
|
||||
});
|
||||
}, [urlQuery, markersData]);
|
||||
|
||||
const shouldShowMarkers = useMemo(() => !!urlQuery.get('showMarkers'), [
|
||||
urlQuery,
|
||||
]);
|
||||
|
||||
console.log('*** filteredMarkersData', filteredMarkersData);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
markersData,
|
||||
filteredMarkersData,
|
||||
setMarkersData,
|
||||
shouldShowMarkers,
|
||||
}),
|
||||
[markersData, filteredMarkersData, setMarkersData, shouldShowMarkers],
|
||||
);
|
||||
|
||||
return (
|
||||
<MarkersContext.Provider value={value}>{children}</MarkersContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useMarkers(): MarkersContextValue {
|
||||
const ctx = useContext(MarkersContext);
|
||||
if (!ctx) {
|
||||
throw new Error('useMarkers must be used within a MarkersProvider');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function useFetchMarkersData({
|
||||
isFetchEnabled,
|
||||
}: {
|
||||
isFetchEnabled: boolean;
|
||||
}): { loadingMarkers: boolean } {
|
||||
const { setMarkersData } = useMarkers();
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { data, isLoading, isFetching } = useQuery<{ markers: Marker[] }>(
|
||||
['mock-markers', minTime, maxTime],
|
||||
async () => {
|
||||
// simulate network latency without returning from the executor
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, 2000);
|
||||
});
|
||||
return generateMockMarkers(minTime, maxTime, 10);
|
||||
},
|
||||
{
|
||||
enabled: isFetchEnabled,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
console.log('*** setting markers data', data.markers);
|
||||
setMarkersData(data.markers);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data]);
|
||||
|
||||
return {
|
||||
loadingMarkers: isLoading || isFetching,
|
||||
};
|
||||
}
|
||||
|
||||
export default MarkersProvider;
|
||||
@@ -13752,10 +13752,10 @@ on-finished@2.4.1, on-finished@^2.4.1:
|
||||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
on-headers@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz"
|
||||
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
|
||||
on-headers@^1.1.0, on-headers@~1.0.2:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65"
|
||||
integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==
|
||||
|
||||
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
|
||||
7
go.mod
7
go.mod
@@ -32,7 +32,6 @@ require (
|
||||
github.com/knadh/koanf v1.5.0
|
||||
github.com/knadh/koanf/v2 v2.2.0
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/mattn/go-sqlite3 v1.14.24
|
||||
github.com/open-telemetry/opamp-go v0.19.0
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.128.0
|
||||
github.com/openfga/api/proto v0.0.0-20250909172242-b4b2a12f5c67
|
||||
@@ -84,6 +83,7 @@ require (
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/apimachinery v0.34.0
|
||||
modernc.org/sqlite v1.39.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -93,10 +93,9 @@ require (
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
modernc.org/libc v1.66.3 // indirect
|
||||
modernc.org/libc v1.66.10 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.39.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -330,7 +329,7 @@ require (
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
gonum.org/v1/gonum v0.16.0 // indirect
|
||||
|
||||
26
go.sum
26
go.sum
@@ -680,8 +680,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
@@ -1461,8 +1459,8 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
@@ -1785,18 +1783,18 @@ k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOP
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
|
||||
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
|
||||
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
|
||||
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
|
||||
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
|
||||
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
|
||||
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
|
||||
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -1805,8 +1803,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
|
||||
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4=
|
||||
modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -38,7 +38,7 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/websocket"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/contextlinks"
|
||||
traceFunnelsModule "github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
)
|
||||
|
||||
@@ -23,6 +25,12 @@ type PostgresConfig struct {
|
||||
type SqliteConfig struct {
|
||||
// Path is the path to the sqlite database.
|
||||
Path string `mapstructure:"path"`
|
||||
|
||||
// Mode is the mode to use for the sqlite database.
|
||||
Mode string `mapstructure:"mode"`
|
||||
|
||||
// BusyTimeout is the timeout for the sqlite database to wait for a lock.
|
||||
BusyTimeout time.Duration `mapstructure:"busy_timeout"`
|
||||
}
|
||||
|
||||
type ConnectionConfig struct {
|
||||
@@ -41,7 +49,9 @@ func newConfig() factory.Config {
|
||||
MaxOpenConns: 100,
|
||||
},
|
||||
Sqlite: SqliteConfig{
|
||||
Path: "/var/lib/signoz/signoz.db",
|
||||
Path: "/var/lib/signoz/signoz.db",
|
||||
Mode: "delete",
|
||||
BusyTimeout: 10000 * time.Millisecond, // increasing the defaults from https://github.com/mattn/go-sqlite3/blob/master/sqlite3.go#L1098 because of transpilation from C to GO
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,17 @@ package sqlitesqlstore
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
sqlite3 "github.com/mattn/go-sqlite3"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/sqlitedialect"
|
||||
|
||||
"modernc.org/sqlite"
|
||||
sqlite3 "modernc.org/sqlite/lib"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
@@ -38,7 +42,12 @@ func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook,
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config, hooks ...sqlstore.SQLStoreHook) (sqlstore.SQLStore, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/sqlitesqlstore")
|
||||
|
||||
sqldb, err := sql.Open("sqlite3", "file:"+config.Sqlite.Path+"?_foreign_keys=true")
|
||||
connectionParams := url.Values{}
|
||||
// do not update the order of the connection params as busy_timeout doesn't work if it's not the first parameter
|
||||
connectionParams.Add("_pragma", fmt.Sprintf("busy_timeout(%d)", config.Sqlite.BusyTimeout.Milliseconds()))
|
||||
connectionParams.Add("_pragma", fmt.Sprintf("journal_mode(%s)", config.Sqlite.Mode))
|
||||
connectionParams.Add("_pragma", "foreign_keys(1)")
|
||||
sqldb, err := sql.Open("sqlite", "file:"+config.Sqlite.Path+"?"+connectionParams.Encode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -82,8 +91,8 @@ func (provider *provider) WrapNotFoundErrf(err error, code errors.Code, format s
|
||||
}
|
||||
|
||||
func (provider *provider) WrapAlreadyExistsErrf(err error, code errors.Code, format string, args ...any) error {
|
||||
if sqlite3Err, ok := err.(sqlite3.Error); ok {
|
||||
if sqlite3Err.ExtendedCode == sqlite3.ErrConstraintUnique {
|
||||
if sqlite3Err, ok := err.(*sqlite.Error); ok {
|
||||
if sqlite3Err.Code() == sqlite3.SQLITE_CONSTRAINT_UNIQUE || sqlite3Err.Code() == sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY {
|
||||
return errors.Wrapf(err, errors.TypeAlreadyExists, code, format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ builds:
|
||||
- id: signoz
|
||||
binary: bin/histogram-quantile
|
||||
main: scripts/clickhouse/histogramquantile/main.go
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from os import path
|
||||
import platform
|
||||
import time
|
||||
from http import HTTPStatus
|
||||
@@ -68,9 +69,10 @@ def signoz( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
||||
|
||||
provider = request.config.getoption("--sqlstore-provider")
|
||||
if provider == "sqlite":
|
||||
dir_path = path.dirname(sqlstore.env["SIGNOZ_SQLSTORE_SQLITE_PATH"])
|
||||
container.with_volume_mapping(
|
||||
sqlstore.env["SIGNOZ_SQLSTORE_SQLITE_PATH"],
|
||||
sqlstore.env["SIGNOZ_SQLSTORE_SQLITE_PATH"],
|
||||
dir_path,
|
||||
dir_path,
|
||||
"rw",
|
||||
)
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ def sqlite(
|
||||
with engine.connect() as conn:
|
||||
result = conn.execute(sql.text("SELECT 1"))
|
||||
assert result.fetchone()[0] == 1
|
||||
|
||||
|
||||
return types.TestContainerSQL(
|
||||
container=types.TestContainerDocker(
|
||||
@@ -52,13 +53,14 @@ def sqlite(
|
||||
result = conn.execute(sql.text("SELECT 1"))
|
||||
assert result.fetchone()[0] == 1
|
||||
|
||||
|
||||
return types.TestContainerSQL(
|
||||
container=types.TestContainerDocker(
|
||||
id="",
|
||||
host_configs={},
|
||||
container_configs={},
|
||||
),
|
||||
conn=conn,
|
||||
conn=engine,
|
||||
env=cache["env"],
|
||||
)
|
||||
|
||||
|
||||
@@ -131,14 +131,14 @@ def test_refresh_license(
|
||||
|
||||
assert response.status_code == http.HTTPStatus.NO_CONTENT
|
||||
|
||||
with signoz.sqlstore.conn.connect() as conn:
|
||||
result = conn.execute(
|
||||
sql.text("SELECT data FROM license WHERE id=:id"),
|
||||
{"id": "0196360e-90cd-7a74-8313-1aa815ce2a67"},
|
||||
)
|
||||
record = result.fetchone()[0]
|
||||
assert json.loads(record)["valid_from"] == 1732146922
|
||||
|
||||
response = requests.get(
|
||||
url=signoz.self.host_configs["8080"].get("/api/v3/licenses/active"),
|
||||
headers={"Authorization": "Bearer " + access_token},
|
||||
timeout=5,
|
||||
)
|
||||
assert response.status_code == http.HTTPStatus.OK
|
||||
assert response.json()["data"]["valid_from"] == 1732146922
|
||||
|
||||
response = requests.post(
|
||||
url=signoz.zeus.host_configs["8080"].get("/__admin/requests/count"),
|
||||
json={"method": "GET", "url": "/v2/licenses/me"},
|
||||
|
||||
@@ -185,6 +185,7 @@ def test_reset_password(
|
||||
assert token is not None
|
||||
|
||||
|
||||
|
||||
def test_reset_password_with_no_password(
|
||||
signoz: types.SigNoz, get_token: Callable[[str, str], str]
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user