Compare commits
23 Commits
v0.68.0
...
fix/range-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7ae68f35 | ||
|
|
c565c2b865 | ||
|
|
4ec1e66c7e | ||
|
|
89541862cc | ||
|
|
610f4d43e7 | ||
|
|
044a124cc1 | ||
|
|
0cf9003e3a | ||
|
|
644135a933 | ||
|
|
b465f74e4a | ||
|
|
e00e365964 | ||
|
|
5c45e1f7b3 | ||
|
|
16e61b45ac | ||
|
|
fdcdbf021a | ||
|
|
c92ef53e9c | ||
|
|
268f283785 | ||
|
|
c574adc634 | ||
|
|
939ab5270e | ||
|
|
42525b6067 | ||
|
|
c66cd3ce4e | ||
|
|
e9618d64bc | ||
|
|
8e11a988be | ||
|
|
92299e1b08 | ||
|
|
bab8c8274c |
@@ -1,32 +0,0 @@
|
|||||||
##################### SigNoz Configuration Defaults #####################
|
|
||||||
#
|
|
||||||
# Do not modify this file
|
|
||||||
#
|
|
||||||
|
|
||||||
##################### Web #####################
|
|
||||||
web:
|
|
||||||
# The prefix to serve web on
|
|
||||||
prefix: /
|
|
||||||
# The directory containing the static build files.
|
|
||||||
directory: /etc/signoz/web
|
|
||||||
|
|
||||||
##################### Cache #####################
|
|
||||||
cache:
|
|
||||||
# specifies the caching provider to use.
|
|
||||||
provider: memory
|
|
||||||
# memory: Uses in-memory caching.
|
|
||||||
memory:
|
|
||||||
# Time-to-live for cache entries in memory. Specify the duration in ns
|
|
||||||
ttl: 60000000000
|
|
||||||
# The interval at which the cache will be cleaned up
|
|
||||||
cleanupInterval:
|
|
||||||
# redis: Uses Redis as the caching backend.
|
|
||||||
redis:
|
|
||||||
# The hostname or IP address of the Redis server.
|
|
||||||
host: localhost
|
|
||||||
# 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:
|
|
||||||
# The Redis database number to use
|
|
||||||
db: 0
|
|
||||||
70
conf/example.yaml
Normal file
70
conf/example.yaml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
##################### SigNoz Configuration Example #####################
|
||||||
|
#
|
||||||
|
# Do not modify this file
|
||||||
|
#
|
||||||
|
|
||||||
|
##################### Instrumentation #####################
|
||||||
|
instrumentation:
|
||||||
|
logs:
|
||||||
|
level: info
|
||||||
|
enabled: false
|
||||||
|
processors:
|
||||||
|
batch:
|
||||||
|
exporter:
|
||||||
|
otlp:
|
||||||
|
endpoint: localhost:4317
|
||||||
|
traces:
|
||||||
|
enabled: false
|
||||||
|
processors:
|
||||||
|
batch:
|
||||||
|
exporter:
|
||||||
|
otlp:
|
||||||
|
endpoint: localhost:4317
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
readers:
|
||||||
|
pull:
|
||||||
|
exporter:
|
||||||
|
prometheus:
|
||||||
|
host: "0.0.0.0"
|
||||||
|
port: 9090
|
||||||
|
|
||||||
|
##################### Web #####################
|
||||||
|
web:
|
||||||
|
# Whether to enable the web frontend
|
||||||
|
enabled: true
|
||||||
|
# The prefix to serve web on
|
||||||
|
prefix: /
|
||||||
|
# The directory containing the static build files.
|
||||||
|
directory: /etc/signoz/web
|
||||||
|
|
||||||
|
##################### Cache #####################
|
||||||
|
cache:
|
||||||
|
# specifies the caching provider to use.
|
||||||
|
provider: memory
|
||||||
|
# memory: Uses in-memory caching.
|
||||||
|
memory:
|
||||||
|
# Time-to-live for cache entries in memory. Specify the duration in ns
|
||||||
|
ttl: 60000000000
|
||||||
|
# The interval at which the cache will be cleaned up
|
||||||
|
cleanupInterval: 1m
|
||||||
|
# redis: Uses Redis as the caching backend.
|
||||||
|
redis:
|
||||||
|
# The hostname or IP address of the Redis server.
|
||||||
|
host: localhost
|
||||||
|
# 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:
|
||||||
|
# The Redis database number to use
|
||||||
|
db: 0
|
||||||
|
|
||||||
|
##################### SQLStore #####################
|
||||||
|
sqlstore:
|
||||||
|
# specifies the SQLStore provider to use.
|
||||||
|
provider: sqlite
|
||||||
|
# The maximum number of open connections to the database.
|
||||||
|
max_open_conns: 100
|
||||||
|
sqlite:
|
||||||
|
# The path to the SQLite database file.
|
||||||
|
path: /var/lib/signoz/signoz.db
|
||||||
@@ -32,6 +32,11 @@ has_cmd() {
|
|||||||
command -v "$1" > /dev/null 2>&1
|
command -v "$1" > /dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check if docker compose plugin is present
|
||||||
|
has_docker_compose_plugin() {
|
||||||
|
docker compose version > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
is_mac() {
|
is_mac() {
|
||||||
[[ $OSTYPE == darwin* ]]
|
[[ $OSTYPE == darwin* ]]
|
||||||
}
|
}
|
||||||
@@ -183,9 +188,7 @@ install_docker() {
|
|||||||
$sudo_cmd yum-config-manager --add-repo https://download.docker.com/linux/$os/docker-ce.repo
|
$sudo_cmd yum-config-manager --add-repo https://download.docker.com/linux/$os/docker-ce.repo
|
||||||
echo "Installing docker"
|
echo "Installing docker"
|
||||||
$yum_cmd install docker-ce docker-ce-cli containerd.io
|
$yum_cmd install docker-ce docker-ce-cli containerd.io
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compose_version () {
|
compose_version () {
|
||||||
@@ -227,12 +230,6 @@ start_docker() {
|
|||||||
echo "Starting docker service"
|
echo "Starting docker service"
|
||||||
$sudo_cmd systemctl start docker.service
|
$sudo_cmd systemctl start docker.service
|
||||||
fi
|
fi
|
||||||
# if [[ -z $sudo_cmd ]]; then
|
|
||||||
# docker ps > /dev/null && true
|
|
||||||
# if [[ $? -ne 0 ]]; then
|
|
||||||
# request_sudo
|
|
||||||
# fi
|
|
||||||
# fi
|
|
||||||
if [[ -z $sudo_cmd ]]; then
|
if [[ -z $sudo_cmd ]]; then
|
||||||
if ! docker ps > /dev/null && true; then
|
if ! docker ps > /dev/null && true; then
|
||||||
request_sudo
|
request_sudo
|
||||||
@@ -265,7 +262,7 @@ bye() { # Prints a friendly good bye message and exits the script.
|
|||||||
|
|
||||||
echo "🔴 The containers didn't seem to start correctly. Please run the following command to check containers that may have errored out:"
|
echo "🔴 The containers didn't seem to start correctly. Please run the following command to check containers that may have errored out:"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
|
echo -e "$sudo_cmd $docker_compose_cmd -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
|
||||||
|
|
||||||
echo "Please read our troubleshooting guide https://signoz.io/docs/install/troubleshooting/"
|
echo "Please read our troubleshooting guide https://signoz.io/docs/install/troubleshooting/"
|
||||||
echo "or reach us for support in #help channel in our Slack Community https://signoz.io/slack"
|
echo "or reach us for support in #help channel in our Slack Community https://signoz.io/slack"
|
||||||
@@ -296,11 +293,6 @@ request_sudo() {
|
|||||||
if (( $EUID != 0 )); then
|
if (( $EUID != 0 )); then
|
||||||
sudo_cmd="sudo"
|
sudo_cmd="sudo"
|
||||||
echo -e "Please enter your sudo password, if prompted."
|
echo -e "Please enter your sudo password, if prompted."
|
||||||
# $sudo_cmd -l | grep -e "NOPASSWD: ALL" > /dev/null
|
|
||||||
# if [[ $? -ne 0 ]] && ! $sudo_cmd -v; then
|
|
||||||
# echo "Need sudo privileges to proceed with the installation."
|
|
||||||
# exit 1;
|
|
||||||
# fi
|
|
||||||
if ! $sudo_cmd -l | grep -e "NOPASSWD: ALL" > /dev/null && ! $sudo_cmd -v; then
|
if ! $sudo_cmd -l | grep -e "NOPASSWD: ALL" > /dev/null && ! $sudo_cmd -v; then
|
||||||
echo "Need sudo privileges to proceed with the installation."
|
echo "Need sudo privileges to proceed with the installation."
|
||||||
exit 1;
|
exit 1;
|
||||||
@@ -317,6 +309,7 @@ echo -e "👋 Thank you for trying out SigNoz! "
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
sudo_cmd=""
|
sudo_cmd=""
|
||||||
|
docker_compose_cmd=""
|
||||||
|
|
||||||
# Check sudo permissions
|
# Check sudo permissions
|
||||||
if (( $EUID != 0 )); then
|
if (( $EUID != 0 )); then
|
||||||
@@ -362,28 +355,8 @@ else
|
|||||||
SIGNOZ_INSTALLATION_ID=$(echo "$sysinfo" | $digest_cmd | grep -E -o '[a-zA-Z0-9]{64}')
|
SIGNOZ_INSTALLATION_ID=$(echo "$sysinfo" | $digest_cmd | grep -E -o '[a-zA-Z0-9]{64}')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# echo ""
|
|
||||||
|
|
||||||
# echo -e "👉 ${RED}Two ways to go forward\n"
|
|
||||||
# echo -e "${RED}1) ClickHouse as database (default)\n"
|
|
||||||
# read -p "⚙️ Enter your preference (1/2):" choice_setup
|
|
||||||
|
|
||||||
# while [[ $choice_setup != "1" && $choice_setup != "2" && $choice_setup != "" ]]
|
|
||||||
# do
|
|
||||||
# # echo $choice_setup
|
|
||||||
# echo -e "\n❌ ${CYAN}Please enter either 1 or 2"
|
|
||||||
# read -p "⚙️ Enter your preference (1/2): " choice_setup
|
|
||||||
# # echo $choice_setup
|
|
||||||
# done
|
|
||||||
|
|
||||||
# if [[ $choice_setup == "1" || $choice_setup == "" ]];then
|
|
||||||
# setup_type='clickhouse'
|
|
||||||
# fi
|
|
||||||
|
|
||||||
setup_type='clickhouse'
|
setup_type='clickhouse'
|
||||||
|
|
||||||
# echo -e "\n✅ ${CYAN}You have chosen: ${setup_type} setup\n"
|
|
||||||
|
|
||||||
# Run bye if failure happens
|
# Run bye if failure happens
|
||||||
trap bye EXIT
|
trap bye EXIT
|
||||||
|
|
||||||
@@ -455,8 +428,6 @@ if [[ $desired_os -eq 0 ]]; then
|
|||||||
send_event "os_not_supported"
|
send_event "os_not_supported"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# check_ports_occupied
|
|
||||||
|
|
||||||
# Check is Docker daemon is installed and available. If not, the install & start Docker for Linux machines. We cannot automatically install Docker Desktop on Mac OS
|
# Check is Docker daemon is installed and available. If not, the install & start Docker for Linux machines. We cannot automatically install Docker Desktop on Mac OS
|
||||||
if ! is_command_present docker; then
|
if ! is_command_present docker; then
|
||||||
|
|
||||||
@@ -486,27 +457,39 @@ if ! is_command_present docker; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if has_docker_compose_plugin; then
|
||||||
|
echo "docker compose plugin is present, using it"
|
||||||
|
docker_compose_cmd="docker compose"
|
||||||
# Install docker-compose
|
# Install docker-compose
|
||||||
if ! is_command_present docker-compose; then
|
else
|
||||||
request_sudo
|
docker_compose_cmd="docker-compose"
|
||||||
install_docker_compose
|
if ! is_command_present docker-compose; then
|
||||||
|
request_sudo
|
||||||
|
install_docker_compose
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
start_docker
|
start_docker
|
||||||
|
|
||||||
# $sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml up -d --remove-orphans || true
|
# check for open ports, if signoz is not installed
|
||||||
|
if is_command_present docker-compose; then
|
||||||
|
if $sudo_cmd $docker_compose_cmd -f ./docker/clickhouse-setup/docker-compose.yaml ps | grep "signoz-query-service" | grep -q "healthy" > /dev/null 2>&1; then
|
||||||
|
echo "SigNoz already installed, skipping the occupied ports check"
|
||||||
|
else
|
||||||
|
check_ports_occupied
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "\n🟡 Pulling the latest container images for SigNoz.\n"
|
echo -e "\n🟡 Pulling the latest container images for SigNoz.\n"
|
||||||
$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml pull
|
$sudo_cmd $docker_compose_cmd -f ./docker/clickhouse-setup/docker-compose.yaml pull
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "🟡 Starting the SigNoz containers. It may take a few minutes ..."
|
echo "🟡 Starting the SigNoz containers. It may take a few minutes ..."
|
||||||
echo
|
echo
|
||||||
# The docker-compose command does some nasty stuff for the `--detach` functionality. So we add a `|| true` so that the
|
# The $docker_compose_cmd command does some nasty stuff for the `--detach` functionality. So we add a `|| true` so that the
|
||||||
# script doesn't exit because this command looks like it failed to do it's thing.
|
# script doesn't exit because this command looks like it failed to do it's thing.
|
||||||
$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml up --detach --remove-orphans || true
|
$sudo_cmd $docker_compose_cmd -f ./docker/clickhouse-setup/docker-compose.yaml up --detach --remove-orphans || true
|
||||||
|
|
||||||
wait_for_containers_start 60
|
wait_for_containers_start 60
|
||||||
echo ""
|
echo ""
|
||||||
@@ -516,7 +499,7 @@ if [[ $status_code -ne 200 ]]; then
|
|||||||
echo "🔴 The containers didn't seem to start correctly. Please run the following command to check containers that may have errored out:"
|
echo "🔴 The containers didn't seem to start correctly. Please run the following command to check containers that may have errored out:"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
|
echo -e "$sudo_cmd $docker_compose_cmd -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
|
||||||
|
|
||||||
echo "Please read our troubleshooting guide https://signoz.io/docs/install/troubleshooting/"
|
echo "Please read our troubleshooting guide https://signoz.io/docs/install/troubleshooting/"
|
||||||
echo "or reach us on SigNoz for support https://signoz.io/slack"
|
echo "or reach us on SigNoz for support https://signoz.io/slack"
|
||||||
@@ -537,7 +520,7 @@ else
|
|||||||
echo "ℹ️ By default, retention period is set to 15 days for logs and traces, and 30 days for metrics."
|
echo "ℹ️ By default, retention period is set to 15 days for logs and traces, and 30 days for metrics."
|
||||||
echo -e "To change this, navigate to the General tab on the Settings page of SigNoz UI. For more details, refer to https://signoz.io/docs/userguide/retention-period \n"
|
echo -e "To change this, navigate to the General tab on the Settings page of SigNoz UI. For more details, refer to https://signoz.io/docs/userguide/retention-period \n"
|
||||||
|
|
||||||
echo "ℹ️ To bring down SigNoz and clean volumes : $sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml down -v"
|
echo "ℹ️ To bring down SigNoz and clean volumes : $sudo_cmd $docker_compose_cmd -f ./docker/clickhouse-setup/docker-compose.yaml down -v"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "+++++++++++++++++++++++++++++++++++++++++++++++++"
|
echo "+++++++++++++++++++++++++++++++++++++++++++++++++"
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import (
|
|||||||
"go.signoz.io/signoz/ee/query-service/interfaces"
|
"go.signoz.io/signoz/ee/query-service/interfaces"
|
||||||
"go.signoz.io/signoz/ee/query-service/rules"
|
"go.signoz.io/signoz/ee/query-service/rules"
|
||||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
"go.signoz.io/signoz/pkg/query-service/migrate"
|
|
||||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
"go.signoz.io/signoz/pkg/signoz"
|
"go.signoz.io/signoz/pkg/signoz"
|
||||||
"go.signoz.io/signoz/pkg/web"
|
"go.signoz.io/signoz/pkg/web"
|
||||||
@@ -82,7 +81,6 @@ type ServerOptions struct {
|
|||||||
GatewayUrl string
|
GatewayUrl string
|
||||||
UseLogsNewSchema bool
|
UseLogsNewSchema bool
|
||||||
UseTraceNewSchema bool
|
UseTraceNewSchema bool
|
||||||
SkipWebFrontend bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server runs HTTP api service
|
// Server runs HTTP api service
|
||||||
@@ -202,13 +200,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
|
||||||
err = migrate.ClickHouseMigrate(reader.GetConn(), serverOptions.Cluster)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("error while running clickhouse migrations", zap.Error(err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// initiate opamp
|
// initiate opamp
|
||||||
_, err = opAmpModel.InitDB(localDB)
|
_, err = opAmpModel.InitDB(localDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -351,7 +342,7 @@ func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) createPublicServer(apiHandler *api.APIHandler, web *web.Web) (*http.Server, error) {
|
func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*http.Server, error) {
|
||||||
|
|
||||||
r := baseapp.NewRouter()
|
r := baseapp.NewRouter()
|
||||||
|
|
||||||
@@ -396,11 +387,9 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web *web.Web) (*
|
|||||||
|
|
||||||
handler = handlers.CompressHandler(handler)
|
handler = handlers.CompressHandler(handler)
|
||||||
|
|
||||||
if !s.serverOptions.SkipWebFrontend {
|
err := web.AddToRouter(r)
|
||||||
err := web.AddToRouter(r)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &http.Server{
|
return &http.Server{
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.opentelemetry.io/collector/confmap"
|
|
||||||
"go.opentelemetry.io/otel/sdk/resource"
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||||
"go.signoz.io/signoz/ee/query-service/app"
|
"go.signoz.io/signoz/ee/query-service/app"
|
||||||
signozconfig "go.signoz.io/signoz/pkg/config"
|
"go.signoz.io/signoz/pkg/config"
|
||||||
"go.signoz.io/signoz/pkg/confmap/provider/signozenvprovider"
|
"go.signoz.io/signoz/pkg/config/envprovider"
|
||||||
|
"go.signoz.io/signoz/pkg/config/fileprovider"
|
||||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/signoz/pkg/query-service/migrate"
|
"go.signoz.io/signoz/pkg/query-service/migrate"
|
||||||
@@ -108,7 +108,6 @@ func main() {
|
|||||||
var dialTimeout time.Duration
|
var dialTimeout time.Duration
|
||||||
var gatewayUrl string
|
var gatewayUrl string
|
||||||
var useLicensesV3 bool
|
var useLicensesV3 bool
|
||||||
var skipWebFrontend bool
|
|
||||||
|
|
||||||
flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs")
|
flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs")
|
||||||
flag.BoolVar(&useTraceNewSchema, "use-trace-new-schema", false, "use new schema for traces")
|
flag.BoolVar(&useTraceNewSchema, "use-trace-new-schema", false, "use new schema for traces")
|
||||||
@@ -126,7 +125,6 @@ func main() {
|
|||||||
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
|
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
|
||||||
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
|
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
|
||||||
flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses")
|
flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses")
|
||||||
flag.BoolVar(&skipWebFrontend, "skip-web-frontend", false, "skip web frontend")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
loggerMgr := initZapLog(enableQueryServiceLogOTLPExport)
|
loggerMgr := initZapLog(enableQueryServiceLogOTLPExport)
|
||||||
@@ -136,19 +134,18 @@ func main() {
|
|||||||
|
|
||||||
version.PrintVersion()
|
version.PrintVersion()
|
||||||
|
|
||||||
config, err := signozconfig.New(context.Background(), signozconfig.ProviderSettings{
|
config, err := signoz.NewConfig(context.Background(), config.ResolverConfig{
|
||||||
ResolverSettings: confmap.ResolverSettings{
|
Uris: []string{"env:"},
|
||||||
URIs: []string{"signozenv:"},
|
ProviderFactories: []config.ProviderFactory{
|
||||||
ProviderFactories: []confmap.ProviderFactory{
|
envprovider.NewFactory(),
|
||||||
signozenvprovider.NewFactory(),
|
fileprovider.NewFactory(),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Fatal("Failed to create config", zap.Error(err))
|
zap.L().Fatal("Failed to create config", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
signoz, err := signoz.New(config, skipWebFrontend)
|
signoz, err := signoz.New(context.Background(), config, signoz.NewProviderConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
|
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
|
||||||
}
|
}
|
||||||
@@ -171,7 +168,6 @@ func main() {
|
|||||||
GatewayUrl: gatewayUrl,
|
GatewayUrl: gatewayUrl,
|
||||||
UseLogsNewSchema: useLogsNewSchema,
|
UseLogsNewSchema: useLogsNewSchema,
|
||||||
UseTraceNewSchema: useTraceNewSchema,
|
UseTraceNewSchema: useTraceNewSchema,
|
||||||
SkipWebFrontend: skipWebFrontend,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the jwt secret key
|
// Read the jwt secret key
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiBaseInstance } from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
@@ -59,7 +59,7 @@ export const getHostLists = async (
|
|||||||
headers?: Record<string, string>,
|
headers?: Record<string, string>,
|
||||||
): Promise<SuccessResponse<HostListResponse> | ErrorResponse> => {
|
): Promise<SuccessResponse<HostListResponse> | ErrorResponse> => {
|
||||||
try {
|
try {
|
||||||
const response = await ApiBaseInstance.post('/hosts/list', props, {
|
const response = await axios.post('/hosts/list', props, {
|
||||||
signal,
|
signal,
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiBaseInstance } from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
@@ -47,7 +47,7 @@ export const getK8sNodesList = async (
|
|||||||
headers?: Record<string, string>,
|
headers?: Record<string, string>,
|
||||||
): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => {
|
): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => {
|
||||||
try {
|
try {
|
||||||
const response = await ApiBaseInstance.post('/nodes/list', props, {
|
const response = await axios.post('/nodes/list', props, {
|
||||||
signal,
|
signal,
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiBaseInstance } from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
@@ -75,7 +75,7 @@ export const getK8sPodsList = async (
|
|||||||
headers?: Record<string, string>,
|
headers?: Record<string, string>,
|
||||||
): Promise<SuccessResponse<K8sPodsListResponse> | ErrorResponse> => {
|
): Promise<SuccessResponse<K8sPodsListResponse> | ErrorResponse> => {
|
||||||
try {
|
try {
|
||||||
const response = await ApiBaseInstance.post('/pods/list', props, {
|
const response = await axios.post('/pods/list', props, {
|
||||||
signal,
|
signal,
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
|||||||
showTime={{
|
showTime={{
|
||||||
use12Hours: true,
|
use12Hours: true,
|
||||||
format: 'hh:mm A',
|
format: 'hh:mm A',
|
||||||
|
defaultValue: [dayjs().tz(timezone.value), dayjs().tz(timezone.value)],
|
||||||
}}
|
}}
|
||||||
format={(date: Dayjs): string =>
|
format={(date: Dayjs): string =>
|
||||||
date.tz(timezone.value).format('YYYY-MM-DD hh:mm A')
|
date.tz(timezone.value).format('YYYY-MM-DD hh:mm A')
|
||||||
|
|||||||
@@ -219,12 +219,14 @@ function ListLogView({
|
|||||||
<LogStateIndicator type={logType} fontSize={fontSize} />
|
<LogStateIndicator type={logType} fontSize={fontSize} />
|
||||||
<div>
|
<div>
|
||||||
<LogContainer fontSize={fontSize}>
|
<LogContainer fontSize={fontSize}>
|
||||||
<LogGeneralField
|
{updatedSelecedFields.some((field) => field.name === 'body') && (
|
||||||
fieldKey="Log"
|
<LogGeneralField
|
||||||
fieldValue={flattenLogData.body}
|
fieldKey="Log"
|
||||||
linesPerRow={linesPerRow}
|
fieldValue={flattenLogData.body}
|
||||||
fontSize={fontSize}
|
linesPerRow={linesPerRow}
|
||||||
/>
|
fontSize={fontSize}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{flattenLogData.stream && (
|
{flattenLogData.stream && (
|
||||||
<LogGeneralField
|
<LogGeneralField
|
||||||
fieldKey="Stream"
|
fieldKey="Stream"
|
||||||
@@ -232,23 +234,27 @@ function ListLogView({
|
|||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<LogGeneralField
|
{updatedSelecedFields.some((field) => field.name === 'timestamp') && (
|
||||||
fieldKey="Timestamp"
|
<LogGeneralField
|
||||||
fieldValue={timestampValue}
|
fieldKey="Timestamp"
|
||||||
fontSize={fontSize}
|
fieldValue={timestampValue}
|
||||||
/>
|
fontSize={fontSize}
|
||||||
|
/>
|
||||||
{updatedSelecedFields.map((field) =>
|
|
||||||
isValidLogField(flattenLogData[field.name] as never) ? (
|
|
||||||
<LogSelectedField
|
|
||||||
key={field.name}
|
|
||||||
fieldKey={field.name}
|
|
||||||
fieldValue={flattenLogData[field.name] as never}
|
|
||||||
onAddToQuery={onAddToQuery}
|
|
||||||
fontSize={fontSize}
|
|
||||||
/>
|
|
||||||
) : null,
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{updatedSelecedFields
|
||||||
|
.filter((field) => !['timestamp', 'body'].includes(field.name))
|
||||||
|
.map((field) =>
|
||||||
|
isValidLogField(flattenLogData[field.name] as never) ? (
|
||||||
|
<LogSelectedField
|
||||||
|
key={field.name}
|
||||||
|
fieldKey={field.name}
|
||||||
|
fieldValue={flattenLogData[field.name] as never}
|
||||||
|
onAddToQuery={onAddToQuery}
|
||||||
|
fontSize={fontSize}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
|
)}
|
||||||
</LogContainer>
|
</LogContainer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ function RawLogView({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const attributesValues = updatedSelecedFields
|
const attributesValues = updatedSelecedFields
|
||||||
|
.filter((field) => !['timestamp', 'body'].includes(field.name))
|
||||||
.map((field) => flattenLogData[field.name])
|
.map((field) => flattenLogData[field.name])
|
||||||
.filter((attribute) => {
|
.filter((attribute) => {
|
||||||
// loadash isEmpty doesnot work with numbers
|
// loadash isEmpty doesnot work with numbers
|
||||||
@@ -92,19 +93,40 @@ function RawLogView({
|
|||||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||||
|
|
||||||
const text = useMemo(() => {
|
const text = useMemo(() => {
|
||||||
const date =
|
const parts = [];
|
||||||
typeof data.timestamp === 'string'
|
|
||||||
? formatTimezoneAdjustedTimestamp(data.timestamp, 'YYYY-MM-DD HH:mm:ss.SSS')
|
|
||||||
: formatTimezoneAdjustedTimestamp(
|
|
||||||
data.timestamp / 1e6,
|
|
||||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
|
||||||
);
|
|
||||||
|
|
||||||
return `${date} | ${attributesText} ${data.body}`;
|
// Check if timestamp is selected
|
||||||
|
const showTimestamp = selectedFields.some(
|
||||||
|
(field) => field.name === 'timestamp',
|
||||||
|
);
|
||||||
|
if (showTimestamp) {
|
||||||
|
const date =
|
||||||
|
typeof data.timestamp === 'string'
|
||||||
|
? formatTimezoneAdjustedTimestamp(
|
||||||
|
data.timestamp,
|
||||||
|
'YYYY-MM-DD HH:mm:ss.SSS',
|
||||||
|
)
|
||||||
|
: formatTimezoneAdjustedTimestamp(
|
||||||
|
data.timestamp / 1e6,
|
||||||
|
'YYYY-MM-DD HH:mm:ss.SSS',
|
||||||
|
);
|
||||||
|
parts.push(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if body is selected
|
||||||
|
const showBody = selectedFields.some((field) => field.name === 'body');
|
||||||
|
if (showBody) {
|
||||||
|
parts.push(`${attributesText} ${data.body}`);
|
||||||
|
} else {
|
||||||
|
parts.push(attributesText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join(' | ');
|
||||||
}, [
|
}, [
|
||||||
|
selectedFields,
|
||||||
|
attributesText,
|
||||||
data.timestamp,
|
data.timestamp,
|
||||||
data.body,
|
data.body,
|
||||||
attributesText,
|
|
||||||
formatTimezoneAdjustedTimestamp,
|
formatTimezoneAdjustedTimestamp,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
|
|
||||||
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
||||||
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
|
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
|
||||||
.filter((e) => e.name !== 'id')
|
.filter((e) => !['id', 'body', 'timestamp'].includes(e.name))
|
||||||
.map(({ name }) => ({
|
.map(({ name }) => ({
|
||||||
title: name,
|
title: name,
|
||||||
dataIndex: name,
|
dataIndex: name,
|
||||||
@@ -91,55 +91,67 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
...(fields.some((field) => field.name === 'timestamp')
|
||||||
title: 'timestamp',
|
? [
|
||||||
dataIndex: 'timestamp',
|
{
|
||||||
key: 'timestamp',
|
title: 'timestamp',
|
||||||
// https://github.com/ant-design/ant-design/discussions/36886
|
dataIndex: 'timestamp',
|
||||||
render: (field): ColumnTypeRender<Record<string, unknown>> => {
|
key: 'timestamp',
|
||||||
const date =
|
// https://github.com/ant-design/ant-design/discussions/36886
|
||||||
typeof field === 'string'
|
render: (
|
||||||
? formatTimezoneAdjustedTimestamp(field, 'YYYY-MM-DD HH:mm:ss.SSS')
|
field: string | number,
|
||||||
: formatTimezoneAdjustedTimestamp(
|
): ColumnTypeRender<Record<string, unknown>> => {
|
||||||
field / 1e6,
|
const date =
|
||||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
typeof field === 'string'
|
||||||
);
|
? formatTimezoneAdjustedTimestamp(field, 'YYYY-MM-DD HH:mm:ss.SSS')
|
||||||
return {
|
: formatTimezoneAdjustedTimestamp(
|
||||||
children: (
|
field / 1e6,
|
||||||
<div className="table-timestamp">
|
'YYYY-MM-DD HH:mm:ss.SSS',
|
||||||
<Typography.Paragraph ellipsis className={cx('text', fontSize)}>
|
);
|
||||||
{date}
|
return {
|
||||||
</Typography.Paragraph>
|
children: (
|
||||||
</div>
|
<div className="table-timestamp">
|
||||||
),
|
<Typography.Paragraph ellipsis className={cx('text', fontSize)}>
|
||||||
};
|
{date}
|
||||||
},
|
</Typography.Paragraph>
|
||||||
},
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
...(appendTo === 'center' ? fieldColumns : []),
|
...(appendTo === 'center' ? fieldColumns : []),
|
||||||
{
|
...(fields.some((field) => field.name === 'body')
|
||||||
title: 'body',
|
? [
|
||||||
dataIndex: 'body',
|
{
|
||||||
key: 'body',
|
title: 'body',
|
||||||
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
|
dataIndex: 'body',
|
||||||
props: {
|
key: 'body',
|
||||||
style: defaultTableStyle,
|
render: (
|
||||||
},
|
field: string | number,
|
||||||
children: (
|
): ColumnTypeRender<Record<string, unknown>> => ({
|
||||||
<TableBodyContent
|
props: {
|
||||||
dangerouslySetInnerHTML={{
|
style: defaultTableStyle,
|
||||||
__html: convert.toHtml(
|
},
|
||||||
dompurify.sanitize(unescapeString(field), {
|
children: (
|
||||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
<TableBodyContent
|
||||||
}),
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: convert.toHtml(
|
||||||
|
dompurify.sanitize(unescapeString(field as string), {
|
||||||
|
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
fontSize={fontSize}
|
||||||
|
linesPerRow={linesPerRow}
|
||||||
|
isDarkMode={isDarkMode}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
}}
|
}),
|
||||||
fontSize={fontSize}
|
},
|
||||||
linesPerRow={linesPerRow}
|
]
|
||||||
isDarkMode={isDarkMode}
|
: []),
|
||||||
/>
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
...(appendTo === 'end' ? fieldColumns : []),
|
...(appendTo === 'end' ? fieldColumns : []),
|
||||||
];
|
];
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import 'uplot/dist/uPlot.min.css';
|
import 'uplot/dist/uPlot.min.css';
|
||||||
import './AnomalyAlertEvaluationView.styles.scss';
|
import './AnomalyAlertEvaluationView.styles.scss';
|
||||||
|
|
||||||
import { Checkbox, Typography } from 'antd';
|
import { Checkbox, Input, Typography } from 'antd';
|
||||||
import Search from 'antd/es/input/Search';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||||
import { useResizeObserver } from 'hooks/useDimensions';
|
import { useResizeObserver } from 'hooks/useDimensions';
|
||||||
@@ -16,6 +15,8 @@ import uPlot from 'uplot';
|
|||||||
|
|
||||||
import tooltipPlugin from './tooltipPlugin';
|
import tooltipPlugin from './tooltipPlugin';
|
||||||
|
|
||||||
|
const { Search } = Input;
|
||||||
|
|
||||||
function UplotChart({
|
function UplotChart({
|
||||||
data,
|
data,
|
||||||
options,
|
options,
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import CreateAlertPage from 'pages/CreateAlert';
|
||||||
|
import { MemoryRouter, Route } from 'react-router-dom';
|
||||||
|
import { act, fireEvent, render } from 'tests/test-utils';
|
||||||
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
|
|
||||||
|
import { ALERT_TYPE_TO_TITLE, ALERT_TYPE_URL_MAP } from './constants';
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
useLocation: (): { pathname: string } => ({
|
||||||
|
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.ALERTS_NEW}`,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('uplot', () => {
|
||||||
|
const paths = {
|
||||||
|
spline: jest.fn(),
|
||||||
|
bars: jest.fn(),
|
||||||
|
};
|
||||||
|
const uplotMock = jest.fn(() => ({
|
||||||
|
paths,
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
paths,
|
||||||
|
default: uplotMock,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let mockWindowOpen: jest.Mock;
|
||||||
|
|
||||||
|
window.ResizeObserver =
|
||||||
|
window.ResizeObserver ||
|
||||||
|
jest.fn().mockImplementation(() => ({
|
||||||
|
disconnect: jest.fn(),
|
||||||
|
observe: jest.fn(),
|
||||||
|
unobserve: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
function findLinkForAlertType(
|
||||||
|
links: HTMLElement[],
|
||||||
|
alertType: AlertTypes,
|
||||||
|
): HTMLElement {
|
||||||
|
const link = links.find(
|
||||||
|
(el) =>
|
||||||
|
el.closest('[data-testid]')?.getAttribute('data-testid') ===
|
||||||
|
`alert-type-card-${alertType}`,
|
||||||
|
);
|
||||||
|
expect(link).toBeTruthy();
|
||||||
|
return link as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickLinkAndVerifyRedirect(
|
||||||
|
link: HTMLElement,
|
||||||
|
expectedUrl: string,
|
||||||
|
): void {
|
||||||
|
fireEvent.click(link);
|
||||||
|
expect(mockWindowOpen).toHaveBeenCalledWith(expectedUrl, '_blank');
|
||||||
|
}
|
||||||
|
describe('Alert rule documentation redirection', () => {
|
||||||
|
let renderResult: ReturnType<typeof render>;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
mockWindowOpen = jest.fn();
|
||||||
|
window.open = mockWindowOpen;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
act(() => {
|
||||||
|
renderResult = render(
|
||||||
|
<MemoryRouter initialEntries={['/alerts/new']}>
|
||||||
|
<Route path={ROUTES.ALERTS_NEW}>
|
||||||
|
<CreateAlertPage />
|
||||||
|
</Route>
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render alert type cards', () => {
|
||||||
|
const { getByText, getAllByText } = renderResult;
|
||||||
|
|
||||||
|
// Check for the heading
|
||||||
|
expect(getByText('choose_alert_type')).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Check for alert type titles and descriptions
|
||||||
|
Object.values(AlertTypes).forEach((alertType) => {
|
||||||
|
const title = ALERT_TYPE_TO_TITLE[alertType];
|
||||||
|
expect(getByText(title)).toBeInTheDocument();
|
||||||
|
expect(getByText(`${title}_desc`)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const clickHereLinks = getAllByText(
|
||||||
|
'Click here to see how to create a sample alert.',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(clickHereLinks).toHaveLength(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to correct documentation for each alert type', () => {
|
||||||
|
const { getAllByText } = renderResult;
|
||||||
|
|
||||||
|
const clickHereLinks = getAllByText(
|
||||||
|
'Click here to see how to create a sample alert.',
|
||||||
|
);
|
||||||
|
const alertTypeCount = Object.keys(AlertTypes).length;
|
||||||
|
|
||||||
|
expect(clickHereLinks).toHaveLength(alertTypeCount);
|
||||||
|
|
||||||
|
Object.values(AlertTypes).forEach((alertType) => {
|
||||||
|
const linkForAlertType = findLinkForAlertType(clickHereLinks, alertType);
|
||||||
|
const expectedUrl = ALERT_TYPE_URL_MAP[alertType];
|
||||||
|
|
||||||
|
clickLinkAndVerifyRedirect(linkForAlertType, expectedUrl.selection);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockWindowOpen).toHaveBeenCalledTimes(alertTypeCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.values(AlertTypes)
|
||||||
|
.filter((type) => type !== AlertTypes.ANOMALY_BASED_ALERT)
|
||||||
|
.forEach((alertType) => {
|
||||||
|
it(`should redirect to create alert page for ${alertType} and "Check an example alert" should redirect to the correct documentation`, () => {
|
||||||
|
const { getByTestId, getByRole } = renderResult;
|
||||||
|
|
||||||
|
const alertTypeLink = getByTestId(`alert-type-card-${alertType}`);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(alertTypeLink);
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(
|
||||||
|
getByRole('button', {
|
||||||
|
name: /alert setup guide/i,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||||
|
ALERT_TYPE_URL_MAP[alertType].creation,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import CreateAlertPage from 'pages/CreateAlert';
|
||||||
|
import { MemoryRouter, Route } from 'react-router-dom';
|
||||||
|
import { act, fireEvent, render } from 'tests/test-utils';
|
||||||
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
|
|
||||||
|
import { ALERT_TYPE_URL_MAP } from './constants';
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
useLocation: (): { pathname: string; search: string } => ({
|
||||||
|
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.ALERTS_NEW}`,
|
||||||
|
search: 'ruleType=anomaly_rule',
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('uplot', () => {
|
||||||
|
const paths = {
|
||||||
|
spline: jest.fn(),
|
||||||
|
bars: jest.fn(),
|
||||||
|
};
|
||||||
|
const uplotMock = jest.fn(() => ({
|
||||||
|
paths,
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
paths,
|
||||||
|
default: uplotMock,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
window.ResizeObserver =
|
||||||
|
window.ResizeObserver ||
|
||||||
|
jest.fn().mockImplementation(() => ({
|
||||||
|
disconnect: jest.fn(),
|
||||||
|
observe: jest.fn(),
|
||||||
|
unobserve: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Anomaly Alert Documentation Redirection', () => {
|
||||||
|
let mockWindowOpen: jest.Mock;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
mockWindowOpen = jest.fn();
|
||||||
|
window.open = mockWindowOpen;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle anomaly alert documentation redirection correctly', () => {
|
||||||
|
const { getByRole } = render(
|
||||||
|
<MemoryRouter initialEntries={['/alerts/new']}>
|
||||||
|
<Route path={ROUTES.ALERTS_NEW}>
|
||||||
|
<CreateAlertPage />
|
||||||
|
</Route>
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const alertType = AlertTypes.ANOMALY_BASED_ALERT;
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(
|
||||||
|
getByRole('button', {
|
||||||
|
name: /alert setup guide/i,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||||
|
ALERT_TYPE_URL_MAP[alertType].creation,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
47
frontend/src/container/CreateAlertRule/constants.ts
Normal file
47
frontend/src/container/CreateAlertRule/constants.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
|
|
||||||
|
// since we don't have a card in alert creation for anomaly based alert
|
||||||
|
|
||||||
|
export const ALERT_TYPE_URL_MAP: Record<
|
||||||
|
AlertTypes,
|
||||||
|
{ selection: string; creation: string }
|
||||||
|
> = {
|
||||||
|
[AlertTypes.METRICS_BASED_ALERT]: {
|
||||||
|
selection:
|
||||||
|
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples',
|
||||||
|
creation:
|
||||||
|
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||||
|
},
|
||||||
|
[AlertTypes.LOGS_BASED_ALERT]: {
|
||||||
|
selection:
|
||||||
|
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples',
|
||||||
|
creation:
|
||||||
|
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||||
|
},
|
||||||
|
[AlertTypes.TRACES_BASED_ALERT]: {
|
||||||
|
selection:
|
||||||
|
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples',
|
||||||
|
creation:
|
||||||
|
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||||
|
},
|
||||||
|
[AlertTypes.EXCEPTIONS_BASED_ALERT]: {
|
||||||
|
selection:
|
||||||
|
'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples',
|
||||||
|
creation:
|
||||||
|
'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||||
|
},
|
||||||
|
[AlertTypes.ANOMALY_BASED_ALERT]: {
|
||||||
|
selection:
|
||||||
|
'https://signoz.io/docs/alerts-management/anomaly-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples',
|
||||||
|
creation:
|
||||||
|
'https://signoz.io/docs/alerts-management/anomaly-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ALERT_TYPE_TO_TITLE: Record<AlertTypes, string> = {
|
||||||
|
[AlertTypes.METRICS_BASED_ALERT]: 'metric_based_alert',
|
||||||
|
[AlertTypes.LOGS_BASED_ALERT]: 'log_based_alert',
|
||||||
|
[AlertTypes.TRACES_BASED_ALERT]: 'traces_based_alert',
|
||||||
|
[AlertTypes.EXCEPTIONS_BASED_ALERT]: 'exceptions_based_alert',
|
||||||
|
[AlertTypes.ANOMALY_BASED_ALERT]: 'anomaly_based_alert',
|
||||||
|
};
|
||||||
@@ -39,10 +39,6 @@
|
|||||||
.ant-collapse-header {
|
.ant-collapse-header {
|
||||||
border-bottom: 1px solid var(--bg-slate-400);
|
border-bottom: 1px solid var(--bg-slate-400);
|
||||||
padding: 12px 8px;
|
padding: 12px 8px;
|
||||||
|
|
||||||
&[aria-expanded='true'] {
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-collapse-content-box {
|
.ant-collapse-content-box {
|
||||||
@@ -271,8 +267,6 @@
|
|||||||
|
|
||||||
.group-by-label {
|
.group-by-label {
|
||||||
min-width: max-content;
|
min-width: max-content;
|
||||||
|
|
||||||
color: var(--bg-vanilla-100, #c0c1c3);
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -282,7 +276,6 @@
|
|||||||
border-radius: 2px 0px 0px 2px;
|
border-radius: 2px 0px 0px 2px;
|
||||||
border: 1px solid var(--bg-slate-400, #1d212d);
|
border: 1px solid var(--bg-slate-400, #1d212d);
|
||||||
border-right: none;
|
border-right: none;
|
||||||
background: var(--bg-ink-100, #16181d);
|
|
||||||
border-top-right-radius: 0px;
|
border-top-right-radius: 0px;
|
||||||
border-bottom-right-radius: 0px;
|
border-bottom-right-radius: 0px;
|
||||||
|
|
||||||
@@ -488,7 +481,7 @@
|
|||||||
.expanded-table-container {
|
.expanded-table-container {
|
||||||
border: 1px solid var(--bg-ink-400);
|
border: 1px solid var(--bg-ink-400);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
padding-left: 16px;
|
padding-left: 48px;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 0.1rem;
|
width: 0.1rem;
|
||||||
@@ -710,8 +703,34 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-cell {
|
.ant-table-cell {
|
||||||
min-width: 170px !important;
|
min-width: 140px !important;
|
||||||
max-width: 170px !important;
|
max-width: 140px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
&:has(.pod-name-header) {
|
||||||
|
min-width: 250px !important;
|
||||||
|
max-width: 250px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
&:has(.med-col) {
|
||||||
|
min-width: 180px !important;
|
||||||
|
max-width: 180px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded-k8s-list-table {
|
||||||
|
.ant-table-cell {
|
||||||
|
min-width: 180px !important;
|
||||||
|
max-width: 180px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-row-expand-icon-cell {
|
||||||
|
min-width: 30px !important;
|
||||||
|
max-width: 30px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-row-expand-icon-cell {
|
.ant-table-row-expand-icon-cell {
|
||||||
@@ -808,6 +827,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
|
.infra-monitoring-container {
|
||||||
|
.k8s-list-table {
|
||||||
|
.ant-table-expanded-row {
|
||||||
|
&:hover {
|
||||||
|
background: var(--bg-vanilla-100) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
background: var(--bg-vanilla-100) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table .ant-table-thead > tr > th {
|
||||||
|
padding: 4px 16px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.event-content-container {
|
.event-content-container {
|
||||||
.ant-table {
|
.ant-table {
|
||||||
background: var(--bg-vanilla-100);
|
background: var(--bg-vanilla-100);
|
||||||
@@ -831,4 +868,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entity-group-header {
|
||||||
|
.ant-tag {
|
||||||
|
background-color: var(--bg-vanilla-300) !important;
|
||||||
|
color: var(--bg-slate-400) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|||||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||||
import { Container, Workflow } from 'lucide-react';
|
import { Container, Workflow } from 'lucide-react';
|
||||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||||
import { useCallback, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -24,6 +24,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
|||||||
const [showFilters, setShowFilters] = useState(true);
|
const [showFilters, setShowFilters] = useState(true);
|
||||||
|
|
||||||
const [selectedCategory, setSelectedCategory] = useState(K8sCategories.PODS);
|
const [selectedCategory, setSelectedCategory] = useState(K8sCategories.PODS);
|
||||||
|
const [quickFiltersLastUpdated, setQuickFiltersLastUpdated] = useState(-1);
|
||||||
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
const { currentQuery } = useQueryBuilder();
|
||||||
|
|
||||||
@@ -37,14 +38,12 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
|||||||
entityVersion: '',
|
entityVersion: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleFilterChange = useCallback(
|
const handleFilterChange = (query: Query): void => {
|
||||||
(query: Query): void => {
|
// update the current query with the new filters
|
||||||
// update the current query with the new filters
|
// in infra monitoring k8s, we are using only one query, hence updating the 0th index of queryData
|
||||||
// in infra monitoring k8s, we are using only one query, hence updating the 0th index of queryData
|
handleChangeQueryData('filters', query.builder.queryData[0].filters);
|
||||||
handleChangeQueryData('filters', query.builder.queryData[0].filters);
|
setQuickFiltersLastUpdated(Date.now());
|
||||||
},
|
};
|
||||||
[handleChangeQueryData],
|
|
||||||
);
|
|
||||||
|
|
||||||
const items: CollapseProps['items'] = [
|
const items: CollapseProps['items'] = [
|
||||||
{
|
{
|
||||||
@@ -262,6 +261,8 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
|||||||
const handleCategoryChange = (key: string | string[]): void => {
|
const handleCategoryChange = (key: string | string[]): void => {
|
||||||
if (Array.isArray(key) && key.length > 0) {
|
if (Array.isArray(key) && key.length > 0) {
|
||||||
setSelectedCategory(key[0] as string);
|
setSelectedCategory(key[0] as string);
|
||||||
|
// Reset filters
|
||||||
|
handleChangeQueryData('filters', { items: [], op: 'and' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -302,6 +303,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
|||||||
<K8sPodLists
|
<K8sPodLists
|
||||||
isFiltersVisible={showFilters}
|
isFiltersVisible={showFilters}
|
||||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
|
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -309,6 +311,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
|||||||
<K8sNodesList
|
<K8sNodesList
|
||||||
isFiltersVisible={showFilters}
|
isFiltersVisible={showFilters}
|
||||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
|
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Button, Input } from 'antd';
|
|||||||
import { GripVertical, TableColumnsSplit, X } from 'lucide-react';
|
import { GripVertical, TableColumnsSplit, X } from 'lucide-react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { IPodColumn } from '../utils';
|
import { IEntityColumn } from '../utils';
|
||||||
|
|
||||||
function K8sFiltersSidePanel({
|
function K8sFiltersSidePanel({
|
||||||
defaultAddedColumns,
|
defaultAddedColumns,
|
||||||
@@ -17,12 +17,12 @@ function K8sFiltersSidePanel({
|
|||||||
onAddColumn = () => {},
|
onAddColumn = () => {},
|
||||||
onRemoveColumn = () => {},
|
onRemoveColumn = () => {},
|
||||||
}: {
|
}: {
|
||||||
defaultAddedColumns: IPodColumn[];
|
defaultAddedColumns: IEntityColumn[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
addedColumns?: IPodColumn[];
|
addedColumns?: IEntityColumn[];
|
||||||
availableColumns?: IPodColumn[];
|
availableColumns?: IEntityColumn[];
|
||||||
onAddColumn?: (column: IPodColumn) => void;
|
onAddColumn?: (column: IEntityColumn) => void;
|
||||||
onRemoveColumn?: (column: IPodColumn) => void;
|
onRemoveColumn?: (column: IEntityColumn) => void;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
const sidePanelRef = useRef<HTMLDivElement>(null);
|
const sidePanelRef = useRef<HTMLDivElement>(null);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|||||||
|
|
||||||
import { K8sCategory } from './constants';
|
import { K8sCategory } from './constants';
|
||||||
import K8sFiltersSidePanel from './K8sFiltersSidePanel/K8sFiltersSidePanel';
|
import K8sFiltersSidePanel from './K8sFiltersSidePanel/K8sFiltersSidePanel';
|
||||||
import { IPodColumn } from './utils';
|
import { IEntityColumn } from './utils';
|
||||||
|
|
||||||
interface K8sHeaderProps {
|
interface K8sHeaderProps {
|
||||||
selectedGroupBy: BaseAutocompleteData[];
|
selectedGroupBy: BaseAutocompleteData[];
|
||||||
@@ -20,11 +20,11 @@ interface K8sHeaderProps {
|
|||||||
isLoadingGroupByFilters: boolean;
|
isLoadingGroupByFilters: boolean;
|
||||||
handleFiltersChange: (value: IBuilderQuery['filters']) => void;
|
handleFiltersChange: (value: IBuilderQuery['filters']) => void;
|
||||||
handleGroupByChange: (value: IBuilderQuery['groupBy']) => void;
|
handleGroupByChange: (value: IBuilderQuery['groupBy']) => void;
|
||||||
defaultAddedColumns: IPodColumn[];
|
defaultAddedColumns: IEntityColumn[];
|
||||||
addedColumns?: IPodColumn[];
|
addedColumns?: IEntityColumn[];
|
||||||
availableColumns?: IPodColumn[];
|
availableColumns?: IEntityColumn[];
|
||||||
onAddColumn?: (column: IPodColumn) => void;
|
onAddColumn?: (column: IEntityColumn) => void;
|
||||||
onRemoveColumn?: (column: IPodColumn) => void;
|
onRemoveColumn?: (column: IEntityColumn) => void;
|
||||||
handleFilterVisibilityChange: () => void;
|
handleFilterVisibilityChange: () => void;
|
||||||
isFiltersVisible: boolean;
|
isFiltersVisible: boolean;
|
||||||
entity: K8sCategory;
|
entity: K8sCategory;
|
||||||
|
|||||||
@@ -45,9 +45,11 @@ import {
|
|||||||
function K8sNodesList({
|
function K8sNodesList({
|
||||||
isFiltersVisible,
|
isFiltersVisible,
|
||||||
handleFilterVisibilityChange,
|
handleFilterVisibilityChange,
|
||||||
|
quickFiltersLastUpdated,
|
||||||
}: {
|
}: {
|
||||||
isFiltersVisible: boolean;
|
isFiltersVisible: boolean;
|
||||||
handleFilterVisibilityChange: () => void;
|
handleFilterVisibilityChange: () => void;
|
||||||
|
quickFiltersLastUpdated: number;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
@@ -60,7 +62,7 @@ function K8sNodesList({
|
|||||||
const [orderBy, setOrderBy] = useState<{
|
const [orderBy, setOrderBy] = useState<{
|
||||||
columnName: string;
|
columnName: string;
|
||||||
order: 'asc' | 'desc';
|
order: 'asc' | 'desc';
|
||||||
} | null>(null);
|
} | null>({ columnName: 'cpu', order: 'desc' });
|
||||||
|
|
||||||
const [selectedNodeUID, setselectedNodeUID] = useState<string | null>(null);
|
const [selectedNodeUID, setselectedNodeUID] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -76,12 +78,28 @@ function K8sNodesList({
|
|||||||
{ value: string; label: string }[]
|
{ value: string; label: string }[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
|
const { currentQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const queryFilters = useMemo(
|
||||||
|
() =>
|
||||||
|
currentQuery?.builder?.queryData[0]?.filters || {
|
||||||
|
items: [],
|
||||||
|
op: 'and',
|
||||||
|
},
|
||||||
|
[currentQuery?.builder?.queryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset pagination every time quick filters are changed
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
const createFiltersForSelectedRowData = (
|
const createFiltersForSelectedRowData = (
|
||||||
selectedRowData: K8sNodesRowData,
|
selectedRowData: K8sNodesRowData,
|
||||||
groupBy: IBuilderQuery['groupBy'],
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
): IBuilderQuery['filters'] => {
|
): IBuilderQuery['filters'] => {
|
||||||
const baseFilters: IBuilderQuery['filters'] = {
|
const baseFilters: IBuilderQuery['filters'] = {
|
||||||
items: [],
|
items: [...queryFilters.items],
|
||||||
op: 'and',
|
op: 'and',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -120,6 +138,7 @@ function K8sNodesList({
|
|||||||
end: Math.floor(maxTime / 1000000),
|
end: Math.floor(maxTime / 1000000),
|
||||||
orderBy,
|
orderBy,
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -133,8 +152,6 @@ function K8sNodesList({
|
|||||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: groupByFiltersData,
|
data: groupByFiltersData,
|
||||||
isLoading: isLoadingGroupByFilters,
|
isLoading: isLoadingGroupByFilters,
|
||||||
@@ -153,15 +170,6 @@ function K8sNodesList({
|
|||||||
K8sCategory.NODES,
|
K8sCategory.NODES,
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryFilters = useMemo(
|
|
||||||
() =>
|
|
||||||
currentQuery?.builder?.queryData[0]?.filters || {
|
|
||||||
items: [],
|
|
||||||
op: 'and',
|
|
||||||
},
|
|
||||||
[currentQuery?.builder?.queryData],
|
|
||||||
);
|
|
||||||
|
|
||||||
const query = useMemo(() => {
|
const query = useMemo(() => {
|
||||||
const baseQuery = getK8sNodesListQuery();
|
const baseQuery = getK8sNodesListQuery();
|
||||||
const queryPayload = {
|
const queryPayload = {
|
||||||
@@ -308,6 +316,7 @@ function K8sNodesList({
|
|||||||
) : (
|
) : (
|
||||||
<div className="expanded-table">
|
<div className="expanded-table">
|
||||||
<Table
|
<Table
|
||||||
|
className="expanded-table-view"
|
||||||
columns={nestedColumns as ColumnType<K8sNodesRowData>[]}
|
columns={nestedColumns as ColumnType<K8sNodesRowData>[]}
|
||||||
dataSource={formattedGroupedByNodesData}
|
dataSource={formattedGroupedByNodesData}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
@@ -382,18 +391,6 @@ function K8sNodesList({
|
|||||||
setselectedNodeUID(null);
|
setselectedNodeUID(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showsNodesTable =
|
|
||||||
!isError &&
|
|
||||||
!isLoading &&
|
|
||||||
!isFetching &&
|
|
||||||
!(formattedNodesData.length === 0 && queryFilters.items.length > 0);
|
|
||||||
|
|
||||||
const showNoFilteredNodesMessage =
|
|
||||||
!isFetching &&
|
|
||||||
!isLoading &&
|
|
||||||
formattedNodesData.length === 0 &&
|
|
||||||
queryFilters.items.length > 0;
|
|
||||||
|
|
||||||
const handleGroupByChange = useCallback(
|
const handleGroupByChange = useCallback(
|
||||||
(value: IBuilderQuery['groupBy']) => {
|
(value: IBuilderQuery['groupBy']) => {
|
||||||
const groupBy = [];
|
const groupBy = [];
|
||||||
@@ -442,54 +439,53 @@ function K8sNodesList({
|
|||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
{showNoFilteredNodesMessage && (
|
<Table
|
||||||
<div className="no-filtered-hosts-message-container">
|
className="k8s-list-table nodes-list-table"
|
||||||
<div className="no-filtered-hosts-message-content">
|
dataSource={isFetching || isLoading ? [] : formattedNodesData}
|
||||||
<img
|
columns={columns}
|
||||||
src="/Icons/emptyState.svg"
|
pagination={{
|
||||||
alt="thinking-emoji"
|
current: currentPage,
|
||||||
className="empty-state-svg"
|
pageSize,
|
||||||
/>
|
total: totalCount,
|
||||||
|
showSizeChanger: false,
|
||||||
|
hideOnSinglePage: true,
|
||||||
|
}}
|
||||||
|
scroll={{ x: true }}
|
||||||
|
loading={{
|
||||||
|
spinning: isFetching || isLoading,
|
||||||
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
|
}}
|
||||||
|
locale={{
|
||||||
|
emptyText:
|
||||||
|
isFetching || isLoading ? null : (
|
||||||
|
<div className="no-filtered-hosts-message-container">
|
||||||
|
<div className="no-filtered-hosts-message-content">
|
||||||
|
<img
|
||||||
|
src="/Icons/emptyState.svg"
|
||||||
|
alt="thinking-emoji"
|
||||||
|
className="empty-state-svg"
|
||||||
|
/>
|
||||||
|
|
||||||
<Typography.Text className="no-filtered-hosts-message">
|
<Typography.Text className="no-filtered-hosts-message">
|
||||||
This query had no results. Edit your query and try again!
|
This query had no results. Edit your query and try again!
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
),
|
||||||
|
}}
|
||||||
|
tableLayout="fixed"
|
||||||
|
onChange={handleTableChange}
|
||||||
|
onRow={(record): { onClick: () => void; className: string } => ({
|
||||||
|
onClick: (): void => handleRowClick(record),
|
||||||
|
className: 'clickable-row',
|
||||||
|
})}
|
||||||
|
expandable={{
|
||||||
|
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||||
|
expandIcon: expandRowIconRenderer,
|
||||||
|
expandedRowKeys,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{(isFetching || isLoading) && <LoadingContainer />}
|
|
||||||
|
|
||||||
{showsNodesTable && (
|
|
||||||
<Table
|
|
||||||
className="k8s-list-table nodes-list-table"
|
|
||||||
dataSource={isFetching || isLoading ? [] : formattedNodesData}
|
|
||||||
columns={columns}
|
|
||||||
pagination={{
|
|
||||||
current: currentPage,
|
|
||||||
pageSize,
|
|
||||||
total: totalCount,
|
|
||||||
showSizeChanger: false,
|
|
||||||
hideOnSinglePage: true,
|
|
||||||
}}
|
|
||||||
scroll={{ x: true }}
|
|
||||||
loading={{
|
|
||||||
spinning: isFetching || isLoading,
|
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
|
||||||
}}
|
|
||||||
tableLayout="fixed"
|
|
||||||
onChange={handleTableChange}
|
|
||||||
onRow={(record): { onClick: () => void; className: string } => ({
|
|
||||||
onClick: (): void => handleRowClick(record),
|
|
||||||
className: 'clickable-row',
|
|
||||||
})}
|
|
||||||
expandable={{
|
|
||||||
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
|
||||||
expandIcon: expandRowIconRenderer,
|
|
||||||
expandedRowKeys,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<NodeDetails
|
<NodeDetails
|
||||||
node={selectedNodeData}
|
node={selectedNodeData}
|
||||||
isModalTimeSelection
|
isModalTimeSelection
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ export default function Events({
|
|||||||
id: event.data.id,
|
id: event.data.id,
|
||||||
key: event.data.id,
|
key: event.data.id,
|
||||||
resources_string: event.data.resources_string,
|
resources_string: event.data.resources_string,
|
||||||
|
attributes_string: event.data.attributes_string,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -174,7 +175,9 @@ export default function Events({
|
|||||||
}, [eventsData]);
|
}, [eventsData]);
|
||||||
|
|
||||||
const handleExpandRow = (record: EventDataType): JSX.Element => (
|
const handleExpandRow = (record: EventDataType): JSX.Element => (
|
||||||
<EventContents data={record.resources_string} />
|
<EventContents
|
||||||
|
data={{ ...record.attributes_string, ...record.resources_string }}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePrev = (): void => {
|
const handlePrev = (): void => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
initialQueryState,
|
initialQueryState,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
|
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils';
|
||||||
import {
|
import {
|
||||||
CustomTimeType,
|
CustomTimeType,
|
||||||
Time,
|
Time,
|
||||||
@@ -97,22 +98,9 @@ function NodeDetails({
|
|||||||
op: '=',
|
op: '=',
|
||||||
value: node?.meta.k8s_node_name || '',
|
value: node?.meta.k8s_node_name || '',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
key: {
|
|
||||||
key: QUERY_KEYS.K8S_CLUSTER_NAME,
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'resource',
|
|
||||||
isColumn: false,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'k8s_node_name--string--resource--false',
|
|
||||||
},
|
|
||||||
op: '=',
|
|
||||||
value: node?.meta.k8s_cluster_name || '',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
[node?.meta.k8s_node_name, node?.meta.k8s_cluster_name],
|
[node?.meta.k8s_node_name],
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialEventsFilters = useMemo(
|
const initialEventsFilters = useMemo(
|
||||||
@@ -239,11 +227,13 @@ function NodeDetails({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
items: [
|
items: filterDuplicateFilters(
|
||||||
...primaryFilters,
|
[
|
||||||
...newFilters,
|
...primaryFilters,
|
||||||
...(paginationFilter ? [paginationFilter] : []),
|
...newFilters,
|
||||||
].filter((item): item is TagFilterItem => item !== undefined),
|
...(paginationFilter ? [paginationFilter] : []),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -266,12 +256,14 @@ function NodeDetails({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
items: [
|
items: filterDuplicateFilters(
|
||||||
...primaryFilters,
|
[
|
||||||
...value.items.filter(
|
...primaryFilters,
|
||||||
(item) => item.key?.key !== QUERY_KEYS.K8S_NODE_NAME,
|
...value.items.filter(
|
||||||
),
|
(item) => item.key?.key !== QUERY_KEYS.K8S_NODE_NAME,
|
||||||
].filter((item): item is TagFilterItem => item !== undefined),
|
),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export interface K8sNodesRowData {
|
|||||||
|
|
||||||
const nodeGroupColumnConfig = {
|
const nodeGroupColumnConfig = {
|
||||||
title: (
|
title: (
|
||||||
<div className="column-header node-group-header">
|
<div className="column-header entity-group-header">
|
||||||
<Group size={14} /> NODE GROUP
|
<Group size={14} /> NODE GROUP
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -74,6 +74,7 @@ const nodeGroupColumnConfig = {
|
|||||||
width: 150,
|
width: 150,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
sorter: false,
|
sorter: false,
|
||||||
|
className: 'column entity-group-header',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getK8sNodesListQuery = (): K8sNodesListPayload => ({
|
export const getK8sNodesListQuery = (): K8sNodesListPayload => ({
|
||||||
@@ -86,7 +87,7 @@ export const getK8sNodesListQuery = (): K8sNodesListPayload => ({
|
|||||||
|
|
||||||
const columnsConfig = [
|
const columnsConfig = [
|
||||||
{
|
{
|
||||||
title: <div className="column-header-left">Node Name</div>,
|
title: <div className="column-header-left name-header">Node Name</div>,
|
||||||
dataIndex: 'nodeName',
|
dataIndex: 'nodeName',
|
||||||
key: 'nodeName',
|
key: 'nodeName',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
@@ -95,7 +96,7 @@ const columnsConfig = [
|
|||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <div className="column-header-left">Cluster Name</div>,
|
title: <div className="column-header-left name-header">Cluster Name</div>,
|
||||||
dataIndex: 'clusterName',
|
dataIndex: 'clusterName',
|
||||||
key: 'clusterName',
|
key: 'clusterName',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import get from 'api/browser/localstorage/get';
|
|||||||
import set from 'api/browser/localstorage/set';
|
import set from 'api/browser/localstorage/set';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { K8sPodsListPayload } from 'api/infraMonitoring/getK8sPodsList';
|
import { K8sPodsListPayload } from 'api/infraMonitoring/getK8sPodsList';
|
||||||
|
import classNames from 'classnames';
|
||||||
import { useGetK8sPodsList } from 'hooks/infraMonitoring/useGetK8sPodsList';
|
import { useGetK8sPodsList } from 'hooks/infraMonitoring/useGetK8sPodsList';
|
||||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
@@ -38,7 +39,7 @@ import {
|
|||||||
formatDataForTable,
|
formatDataForTable,
|
||||||
getK8sPodsListColumns,
|
getK8sPodsListColumns,
|
||||||
getK8sPodsListQuery,
|
getK8sPodsListQuery,
|
||||||
IPodColumn,
|
IEntityColumn,
|
||||||
K8sPodsRowData,
|
K8sPodsRowData,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import PodDetails from './PodDetails/PodDetails';
|
import PodDetails from './PodDetails/PodDetails';
|
||||||
@@ -47,9 +48,11 @@ import PodDetails from './PodDetails/PodDetails';
|
|||||||
function K8sPodsList({
|
function K8sPodsList({
|
||||||
isFiltersVisible,
|
isFiltersVisible,
|
||||||
handleFilterVisibilityChange,
|
handleFilterVisibilityChange,
|
||||||
|
quickFiltersLastUpdated,
|
||||||
}: {
|
}: {
|
||||||
isFiltersVisible: boolean;
|
isFiltersVisible: boolean;
|
||||||
handleFilterVisibilityChange: () => void;
|
handleFilterVisibilityChange: () => void;
|
||||||
|
quickFiltersLastUpdated: number;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
@@ -57,9 +60,9 @@ function K8sPodsList({
|
|||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
const [addedColumns, setAddedColumns] = useState<IPodColumn[]>([]);
|
const [addedColumns, setAddedColumns] = useState<IEntityColumn[]>([]);
|
||||||
|
|
||||||
const [availableColumns, setAvailableColumns] = useState<IPodColumn[]>(
|
const [availableColumns, setAvailableColumns] = useState<IEntityColumn[]>(
|
||||||
defaultAvailableColumns,
|
defaultAvailableColumns,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -104,6 +107,11 @@ function K8sPodsList({
|
|||||||
K8sCategory.PODS, // infraMonitoringEntity
|
K8sCategory.PODS, // infraMonitoringEntity
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reset pagination every time quick filters are changed
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const addedColumns = JSON.parse(get('k8sPodsAddedColumns') ?? '[]');
|
const addedColumns = JSON.parse(get('k8sPodsAddedColumns') ?? '[]');
|
||||||
|
|
||||||
@@ -124,7 +132,7 @@ function K8sPodsList({
|
|||||||
const [orderBy, setOrderBy] = useState<{
|
const [orderBy, setOrderBy] = useState<{
|
||||||
columnName: string;
|
columnName: string;
|
||||||
order: 'asc' | 'desc';
|
order: 'asc' | 'desc';
|
||||||
} | null>(null);
|
} | null>({ columnName: 'cpu', order: 'desc' });
|
||||||
|
|
||||||
const [selectedPodUID, setSelectedPodUID] = useState<string | null>(null);
|
const [selectedPodUID, setSelectedPodUID] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -162,7 +170,7 @@ function K8sPodsList({
|
|||||||
selectedRowData: K8sPodsRowData,
|
selectedRowData: K8sPodsRowData,
|
||||||
): IBuilderQuery['filters'] => {
|
): IBuilderQuery['filters'] => {
|
||||||
const baseFilters: IBuilderQuery['filters'] = {
|
const baseFilters: IBuilderQuery['filters'] = {
|
||||||
items: [],
|
items: [...query.filters.items],
|
||||||
op: 'and',
|
op: 'and',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -201,6 +209,7 @@ function K8sPodsList({
|
|||||||
end: Math.floor(maxTime / 1000000),
|
end: Math.floor(maxTime / 1000000),
|
||||||
orderBy,
|
orderBy,
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [minTime, maxTime, orderBy, selectedRowData]);
|
}, [minTime, maxTime, orderBy, selectedRowData]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -338,20 +347,8 @@ function K8sPodsList({
|
|||||||
setSelectedPodUID(null);
|
setSelectedPodUID(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showPodsTable =
|
|
||||||
!isError &&
|
|
||||||
!isLoading &&
|
|
||||||
!isFetching &&
|
|
||||||
!(formattedPodsData.length === 0 && queryFilters.items.length > 0);
|
|
||||||
|
|
||||||
const showNoFilteredPodsMessage =
|
|
||||||
!isFetching &&
|
|
||||||
!isLoading &&
|
|
||||||
formattedPodsData.length === 0 &&
|
|
||||||
queryFilters.items.length > 0;
|
|
||||||
|
|
||||||
const handleAddColumn = useCallback(
|
const handleAddColumn = useCallback(
|
||||||
(column: IPodColumn): void => {
|
(column: IEntityColumn): void => {
|
||||||
setAddedColumns((prev) => [...prev, column]);
|
setAddedColumns((prev) => [...prev, column]);
|
||||||
|
|
||||||
setAvailableColumns((prev) => prev.filter((c) => c.value !== column.value));
|
setAvailableColumns((prev) => prev.filter((c) => c.value !== column.value));
|
||||||
@@ -378,7 +375,7 @@ function K8sPodsList({
|
|||||||
}, [groupByFiltersData]);
|
}, [groupByFiltersData]);
|
||||||
|
|
||||||
const handleRemoveColumn = useCallback(
|
const handleRemoveColumn = useCallback(
|
||||||
(column: IPodColumn): void => {
|
(column: IEntityColumn): void => {
|
||||||
setAddedColumns((prev) => prev.filter((c) => c.value !== column.value));
|
setAddedColumns((prev) => prev.filter((c) => c.value !== column.value));
|
||||||
|
|
||||||
setAvailableColumns((prev) => [...prev, column]);
|
setAvailableColumns((prev) => [...prev, column]);
|
||||||
@@ -505,54 +502,54 @@ function K8sPodsList({
|
|||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
{showNoFilteredPodsMessage && (
|
<Table
|
||||||
<div className="no-filtered-hosts-message-container">
|
className={classNames('k8s-list-table', {
|
||||||
<div className="no-filtered-hosts-message-content">
|
'expanded-k8s-list-table': isGroupedByAttribute,
|
||||||
<img
|
})}
|
||||||
src="/Icons/emptyState.svg"
|
dataSource={isFetching || isLoading ? [] : formattedPodsData}
|
||||||
alt="thinking-emoji"
|
columns={columns}
|
||||||
className="empty-state-svg"
|
pagination={{
|
||||||
/>
|
current: currentPage,
|
||||||
|
pageSize,
|
||||||
|
total: totalCount,
|
||||||
|
showSizeChanger: false,
|
||||||
|
hideOnSinglePage: true,
|
||||||
|
}}
|
||||||
|
loading={{
|
||||||
|
spinning: isFetching || isLoading,
|
||||||
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
|
}}
|
||||||
|
locale={{
|
||||||
|
emptyText:
|
||||||
|
isFetching || isLoading ? null : (
|
||||||
|
<div className="no-filtered-hosts-message-container">
|
||||||
|
<div className="no-filtered-hosts-message-content">
|
||||||
|
<img
|
||||||
|
src="/Icons/emptyState.svg"
|
||||||
|
alt="thinking-emoji"
|
||||||
|
className="empty-state-svg"
|
||||||
|
/>
|
||||||
|
|
||||||
<Typography.Text className="no-filtered-hosts-message">
|
<Typography.Text className="no-filtered-hosts-message">
|
||||||
This query had no results. Edit your query and try again!
|
This query had no results. Edit your query and try again!
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
),
|
||||||
|
}}
|
||||||
{(isFetching || isLoading) && <LoadingContainer />}
|
scroll={{ x: true }}
|
||||||
|
tableLayout="fixed"
|
||||||
{showPodsTable && (
|
onChange={handleTableChange}
|
||||||
<Table
|
onRow={(record): { onClick: () => void; className: string } => ({
|
||||||
className="k8s-list-table"
|
onClick: (): void => handleRowClick(record),
|
||||||
dataSource={isFetching || isLoading ? [] : formattedPodsData}
|
className: 'clickable-row',
|
||||||
columns={columns}
|
})}
|
||||||
pagination={{
|
expandable={{
|
||||||
current: currentPage,
|
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||||
pageSize,
|
expandIcon: expandRowIconRenderer,
|
||||||
total: totalCount,
|
expandedRowKeys,
|
||||||
showSizeChanger: false,
|
}}
|
||||||
hideOnSinglePage: true,
|
/>
|
||||||
}}
|
|
||||||
loading={{
|
|
||||||
spinning: isFetching || isLoading,
|
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
|
||||||
}}
|
|
||||||
scroll={{ x: true }}
|
|
||||||
tableLayout="fixed"
|
|
||||||
onChange={handleTableChange}
|
|
||||||
onRow={(record): { onClick: () => void; className: string } => ({
|
|
||||||
onClick: (): void => handleRowClick(record),
|
|
||||||
className: 'clickable-row',
|
|
||||||
})}
|
|
||||||
expandable={{
|
|
||||||
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
|
||||||
expandIcon: expandRowIconRenderer,
|
|
||||||
expandedRowKeys,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedPodData && (
|
{selectedPodData && (
|
||||||
<PodDetails
|
<PodDetails
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ export default function Events({
|
|||||||
id: event.data.id,
|
id: event.data.id,
|
||||||
key: event.data.id,
|
key: event.data.id,
|
||||||
resources_string: event.data.resources_string,
|
resources_string: event.data.resources_string,
|
||||||
|
attributes_string: event.data.attributes_string,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -174,7 +175,9 @@ export default function Events({
|
|||||||
}, [eventsData]);
|
}, [eventsData]);
|
||||||
|
|
||||||
const handleExpandRow = (record: EventDataType): JSX.Element => (
|
const handleExpandRow = (record: EventDataType): JSX.Element => (
|
||||||
<EventContents data={record.resources_string} />
|
<EventContents
|
||||||
|
data={{ ...record.attributes_string, ...record.resources_string }}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePrev = (): void => {
|
const handlePrev = (): void => {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
initialQueryState,
|
initialQueryState,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
|
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils';
|
||||||
import {
|
import {
|
||||||
CustomTimeType,
|
CustomTimeType,
|
||||||
Time,
|
Time,
|
||||||
@@ -50,7 +51,7 @@ import { PodDetailProps } from './PodDetail.interfaces';
|
|||||||
import PodLogsDetailedView from './PodLogs/PodLogsDetailedView';
|
import PodLogsDetailedView from './PodLogs/PodLogsDetailedView';
|
||||||
import PodTraces from './PodTraces/PodTraces';
|
import PodTraces from './PodTraces/PodTraces';
|
||||||
|
|
||||||
const TimeRangeOffset = 1000000;
|
const TimeRangeOffset = 1000000000;
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
function PodDetails({
|
function PodDetails({
|
||||||
@@ -101,19 +102,6 @@ function PodDetails({
|
|||||||
op: '=',
|
op: '=',
|
||||||
value: pod?.meta.k8s_pod_name || '',
|
value: pod?.meta.k8s_pod_name || '',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
key: {
|
|
||||||
key: QUERY_KEYS.K8S_CLUSTER_NAME,
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'resource',
|
|
||||||
isColumn: false,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'k8s_pod_name--string--resource--false',
|
|
||||||
},
|
|
||||||
op: '=',
|
|
||||||
value: pod?.meta.k8s_cluster_name || '',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
key: {
|
key: {
|
||||||
@@ -129,11 +117,7 @@ function PodDetails({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
[
|
[pod?.meta.k8s_namespace_name, pod?.meta.k8s_pod_name],
|
||||||
pod?.meta.k8s_cluster_name,
|
|
||||||
pod?.meta.k8s_namespace_name,
|
|
||||||
pod?.meta.k8s_pod_name,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialEventsFilters = useMemo(
|
const initialEventsFilters = useMemo(
|
||||||
@@ -262,11 +246,13 @@ function PodDetails({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
items: [
|
items: filterDuplicateFilters(
|
||||||
...primaryFilters,
|
[
|
||||||
...newFilters,
|
...primaryFilters,
|
||||||
...(paginationFilter ? [paginationFilter] : []),
|
...newFilters,
|
||||||
].filter((item): item is TagFilterItem => item !== undefined),
|
...(paginationFilter ? [paginationFilter] : []),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -291,12 +277,14 @@ function PodDetails({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
op: 'AND',
|
op: 'AND',
|
||||||
items: [
|
items: filterDuplicateFilters(
|
||||||
...primaryFilters,
|
[
|
||||||
...value.items.filter(
|
...primaryFilters,
|
||||||
(item) => item.key?.key !== QUERY_KEYS.K8S_POD_NAME,
|
...value.items.filter(
|
||||||
),
|
(item) => item.key?.key !== QUERY_KEYS.K8S_POD_NAME,
|
||||||
].filter((item): item is TagFilterItem => item !== undefined),
|
),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -78,8 +78,6 @@ function PodTraces({
|
|||||||
[currentQuery],
|
[currentQuery],
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log({ updatedCurrentQuery });
|
|
||||||
|
|
||||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||||
|
|
||||||
const { queryData: paginationQueryData } = useUrlQueryData<Pagination>(
|
const { queryData: paginationQueryData } = useUrlQueryData<Pagination>(
|
||||||
|
|||||||
@@ -100,7 +100,13 @@ export function getStrokeColorForLimitUtilization(value: number): string {
|
|||||||
export const getProgressBarText = (percent: number): React.ReactNode =>
|
export const getProgressBarText = (percent: number): React.ReactNode =>
|
||||||
`${percent}%`;
|
`${percent}%`;
|
||||||
|
|
||||||
export function EntityProgressBar({ value }: { value: number }): JSX.Element {
|
export function EntityProgressBar({
|
||||||
|
value,
|
||||||
|
type,
|
||||||
|
}: {
|
||||||
|
value: number;
|
||||||
|
type: 'request' | 'limit';
|
||||||
|
}): JSX.Element {
|
||||||
const percentage = Number((value * 100).toFixed(1));
|
const percentage = Number((value * 100).toFixed(1));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -110,7 +116,11 @@ export function EntityProgressBar({ value }: { value: number }): JSX.Element {
|
|||||||
strokeLinecap="butt"
|
strokeLinecap="butt"
|
||||||
size="small"
|
size="small"
|
||||||
status="normal"
|
status="normal"
|
||||||
strokeColor={getStrokeColorForLimitUtilization(value)}
|
strokeColor={
|
||||||
|
type === 'limit'
|
||||||
|
? getStrokeColorForLimitUtilization(value)
|
||||||
|
: getStrokeColorForRequestUtilization(value)
|
||||||
|
}
|
||||||
className="progress-bar"
|
className="progress-bar"
|
||||||
showInfo={false}
|
showInfo={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ export const PodsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
|||||||
isColumn: false,
|
isColumn: false,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
},
|
},
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
aggregateAttribute: 'k8s_pod_cpu_utilization',
|
||||||
dataSource: DataSource.METRICS,
|
dataSource: DataSource.METRICS,
|
||||||
defaultOpen: false,
|
defaultOpen: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
export const filterDuplicateFilters = (
|
||||||
|
filters: TagFilterItem[],
|
||||||
|
): TagFilterItem[] => {
|
||||||
|
const uniqueFilters = [];
|
||||||
|
const seenIds = new Set();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const filter of filters) {
|
||||||
|
if (!seenIds.has(filter.id)) {
|
||||||
|
seenIds.add(filter.id);
|
||||||
|
uniqueFilters.push(filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniqueFilters;
|
||||||
|
};
|
||||||
@@ -26,16 +26,9 @@ export interface IEntityColumn {
|
|||||||
canRemove: boolean;
|
canRemove: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPodColumn {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
id: string;
|
|
||||||
canRemove: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnProgressBarClassName = 'column-progress-bar';
|
const columnProgressBarClassName = 'column-progress-bar';
|
||||||
|
|
||||||
export const defaultAddedColumns: IPodColumn[] = [
|
export const defaultAddedColumns: IEntityColumn[] = [
|
||||||
{
|
{
|
||||||
label: 'Pod name',
|
label: 'Pod name',
|
||||||
value: 'podName',
|
value: 'podName',
|
||||||
@@ -78,12 +71,13 @@ export const defaultAddedColumns: IPodColumn[] = [
|
|||||||
id: 'memory',
|
id: 'memory',
|
||||||
canRemove: false,
|
canRemove: false,
|
||||||
},
|
},
|
||||||
{
|
// TODO - Re-enable the column once backend issue is fixed
|
||||||
label: 'Restarts',
|
// {
|
||||||
value: 'restarts',
|
// label: 'Restarts',
|
||||||
id: 'restarts',
|
// value: 'restarts',
|
||||||
canRemove: false,
|
// id: 'restarts',
|
||||||
},
|
// canRemove: false,
|
||||||
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const defaultAvailableColumns = [
|
export const defaultAvailableColumns = [
|
||||||
@@ -131,7 +125,7 @@ export const getK8sPodsListQuery = (): K8sPodsListPayload => ({
|
|||||||
|
|
||||||
const podGroupColumnConfig = {
|
const podGroupColumnConfig = {
|
||||||
title: (
|
title: (
|
||||||
<div className="column-header pod-group-header">
|
<div className="column-header entity-group-header">
|
||||||
<Group size={14} /> POD GROUP
|
<Group size={14} /> POD GROUP
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -140,7 +134,7 @@ const podGroupColumnConfig = {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
width: 180,
|
width: 180,
|
||||||
sorter: false,
|
sorter: false,
|
||||||
className: 'column column-pod-group',
|
className: 'column entity-group-header',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dummyColumnConfig = {
|
export const dummyColumnConfig = {
|
||||||
@@ -160,11 +154,11 @@ const columnsConfig = [
|
|||||||
key: 'podName',
|
key: 'podName',
|
||||||
width: 180,
|
width: 180,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: true,
|
sorter: false,
|
||||||
className: 'column column-pod-name',
|
className: 'column column-pod-name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <div className="column-header">CPU Req Usage (%)</div>,
|
title: <div className="column-header med-col">CPU Req Usage (%)</div>,
|
||||||
dataIndex: 'cpu_request',
|
dataIndex: 'cpu_request',
|
||||||
key: 'cpu_request',
|
key: 'cpu_request',
|
||||||
width: 180,
|
width: 180,
|
||||||
@@ -174,7 +168,7 @@ const columnsConfig = [
|
|||||||
className: `column ${columnProgressBarClassName}`,
|
className: `column ${columnProgressBarClassName}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <div className="column-header">CPU Limit Usage (%)</div>,
|
title: <div className="column-header med-col">CPU Limit Usage (%)</div>,
|
||||||
dataIndex: 'cpu_limit',
|
dataIndex: 'cpu_limit',
|
||||||
key: 'cpu_limit',
|
key: 'cpu_limit',
|
||||||
width: 120,
|
width: 120,
|
||||||
@@ -192,7 +186,7 @@ const columnsConfig = [
|
|||||||
className: `column ${columnProgressBarClassName}`,
|
className: `column ${columnProgressBarClassName}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <div className="column-header">Mem Req Usage (%)</div>,
|
title: <div className="column-heade med-col">Mem Req Usage (%)</div>,
|
||||||
dataIndex: 'memory_request',
|
dataIndex: 'memory_request',
|
||||||
key: 'memory_request',
|
key: 'memory_request',
|
||||||
width: 120,
|
width: 120,
|
||||||
@@ -201,7 +195,7 @@ const columnsConfig = [
|
|||||||
className: `column ${columnProgressBarClassName}`,
|
className: `column ${columnProgressBarClassName}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <div className="column-header">Mem Limit Usage (%)</div>,
|
title: <div className="column-header med-col">Mem Limit Usage (%)</div>,
|
||||||
dataIndex: 'memory_limit',
|
dataIndex: 'memory_limit',
|
||||||
key: 'memory_limit',
|
key: 'memory_limit',
|
||||||
width: 120,
|
width: 120,
|
||||||
@@ -219,20 +213,21 @@ const columnsConfig = [
|
|||||||
align: 'left',
|
align: 'left',
|
||||||
className: `column ${columnProgressBarClassName}`,
|
className: `column ${columnProgressBarClassName}`,
|
||||||
},
|
},
|
||||||
{
|
// TODO - Re-enable the column once backend issue is fixed
|
||||||
title: (
|
// {
|
||||||
<div className="column-header">
|
// title: (
|
||||||
<Tooltip title="Container Restarts">Restarts</Tooltip>
|
// <div className="column-header">
|
||||||
</div>
|
// <Tooltip title="Container Restarts">Restarts</Tooltip>
|
||||||
),
|
// </div>
|
||||||
dataIndex: 'restarts',
|
// ),
|
||||||
key: 'restarts',
|
// dataIndex: 'restarts',
|
||||||
width: 40,
|
// key: 'restarts',
|
||||||
ellipsis: true,
|
// width: 40,
|
||||||
sorter: true,
|
// ellipsis: true,
|
||||||
align: 'left',
|
// sorter: true,
|
||||||
className: `column ${columnProgressBarClassName}`,
|
// align: 'left',
|
||||||
},
|
// className: `column ${columnProgressBarClassName}`,
|
||||||
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const namespaceColumnConfig = {
|
export const namespaceColumnConfig = {
|
||||||
@@ -251,7 +246,7 @@ export const nodeColumnConfig = {
|
|||||||
dataIndex: 'node',
|
dataIndex: 'node',
|
||||||
key: 'node',
|
key: 'node',
|
||||||
width: 100,
|
width: 100,
|
||||||
sorter: true,
|
sorter: false,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
className: 'column column-node',
|
className: 'column column-node',
|
||||||
@@ -262,7 +257,7 @@ export const clusterColumnConfig = {
|
|||||||
dataIndex: 'cluster',
|
dataIndex: 'cluster',
|
||||||
key: 'cluster',
|
key: 'cluster',
|
||||||
width: 100,
|
width: 100,
|
||||||
sorter: true,
|
sorter: false,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
className: 'column column-cluster',
|
className: 'column column-cluster',
|
||||||
@@ -275,7 +270,7 @@ export const columnConfigMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getK8sPodsListColumns = (
|
export const getK8sPodsListColumns = (
|
||||||
addedColumns: IPodColumn[],
|
addedColumns: IEntityColumn[],
|
||||||
groupBy: IBuilderQuery['groupBy'],
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
): ColumnType<K8sPodsRowData>[] => {
|
): ColumnType<K8sPodsRowData>[] => {
|
||||||
const updatedColumnsConfig = [...columnsConfig];
|
const updatedColumnsConfig = [...columnsConfig];
|
||||||
@@ -341,7 +336,7 @@ export const formatDataForTable = (
|
|||||||
attribute="CPU Request"
|
attribute="CPU Request"
|
||||||
>
|
>
|
||||||
<div className="progress-container">
|
<div className="progress-container">
|
||||||
<EntityProgressBar value={pod.podCPURequest} />
|
<EntityProgressBar value={pod.podCPURequest} type="request" />
|
||||||
</div>
|
</div>
|
||||||
</ValidateColumnValueWrapper>
|
</ValidateColumnValueWrapper>
|
||||||
),
|
),
|
||||||
@@ -352,7 +347,7 @@ export const formatDataForTable = (
|
|||||||
attribute="CPU Limit"
|
attribute="CPU Limit"
|
||||||
>
|
>
|
||||||
<div className="progress-container">
|
<div className="progress-container">
|
||||||
<EntityProgressBar value={pod.podCPULimit} />
|
<EntityProgressBar value={pod.podCPULimit} type="limit" />
|
||||||
</div>
|
</div>
|
||||||
</ValidateColumnValueWrapper>
|
</ValidateColumnValueWrapper>
|
||||||
),
|
),
|
||||||
@@ -368,7 +363,7 @@ export const formatDataForTable = (
|
|||||||
attribute="Memory Request"
|
attribute="Memory Request"
|
||||||
>
|
>
|
||||||
<div className="progress-container">
|
<div className="progress-container">
|
||||||
<EntityProgressBar value={pod.podMemoryRequest} />
|
<EntityProgressBar value={pod.podMemoryRequest} type="request" />
|
||||||
</div>
|
</div>
|
||||||
</ValidateColumnValueWrapper>
|
</ValidateColumnValueWrapper>
|
||||||
),
|
),
|
||||||
@@ -379,7 +374,7 @@ export const formatDataForTable = (
|
|||||||
attribute="Memory Limit"
|
attribute="Memory Limit"
|
||||||
>
|
>
|
||||||
<div className="progress-container">
|
<div className="progress-container">
|
||||||
<EntityProgressBar value={pod.podMemoryLimit} />
|
<EntityProgressBar value={pod.podMemoryLimit} type="limit" />
|
||||||
</div>
|
</div>
|
||||||
</ValidateColumnValueWrapper>
|
</ValidateColumnValueWrapper>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -58,7 +58,11 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
import { ErrorResponse } from 'types/api';
|
import { ErrorResponse } from 'types/api';
|
||||||
import { LimitProps } from 'types/api/ingestionKeys/limits/types';
|
import {
|
||||||
|
AddLimitProps,
|
||||||
|
LimitProps,
|
||||||
|
UpdateLimitProps,
|
||||||
|
} from 'types/api/ingestionKeys/limits/types';
|
||||||
import {
|
import {
|
||||||
IngestionKeyProps,
|
IngestionKeyProps,
|
||||||
PaginationProps,
|
PaginationProps,
|
||||||
@@ -69,6 +73,18 @@ const { Option } = Select;
|
|||||||
|
|
||||||
const BYTES = 1073741824;
|
const BYTES = 1073741824;
|
||||||
|
|
||||||
|
const COUNT_MULTIPLIER = {
|
||||||
|
thousand: 1000,
|
||||||
|
million: 1000000,
|
||||||
|
billion: 1000000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SIGNALS_CONFIG = [
|
||||||
|
{ name: 'logs', usesSize: true, usesCount: false },
|
||||||
|
{ name: 'traces', usesSize: true, usesCount: false },
|
||||||
|
{ name: 'metrics', usesSize: false, usesCount: true },
|
||||||
|
];
|
||||||
|
|
||||||
// Using any type here because antd's DatePicker expects its own internal Dayjs type
|
// Using any type here because antd's DatePicker expects its own internal Dayjs type
|
||||||
// which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc).
|
// which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc).
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
||||||
@@ -76,8 +92,6 @@ export const disabledDate = (current: any): boolean =>
|
|||||||
// Disable all dates before today
|
// Disable all dates before today
|
||||||
current && current < dayjs().endOf('day');
|
current && current < dayjs().endOf('day');
|
||||||
|
|
||||||
const SIGNALS = ['logs', 'traces', 'metrics'];
|
|
||||||
|
|
||||||
export const showErrorNotification = (
|
export const showErrorNotification = (
|
||||||
notifications: NotificationInstance,
|
notifications: NotificationInstance,
|
||||||
err: Error,
|
err: Error,
|
||||||
@@ -101,6 +115,31 @@ export const API_KEY_EXPIRY_OPTIONS: ExpiryOption[] = [
|
|||||||
{ value: '0', label: 'No Expiry' },
|
{ value: '0', label: 'No Expiry' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const countToUnit = (count: number): { value: number; unit: string } => {
|
||||||
|
if (
|
||||||
|
count >= COUNT_MULTIPLIER.billion ||
|
||||||
|
count / COUNT_MULTIPLIER.million >= 1000
|
||||||
|
) {
|
||||||
|
return { value: count / COUNT_MULTIPLIER.billion, unit: 'billion' };
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
count >= COUNT_MULTIPLIER.million ||
|
||||||
|
count / COUNT_MULTIPLIER.thousand >= 1000
|
||||||
|
) {
|
||||||
|
return { value: count / COUNT_MULTIPLIER.million, unit: 'million' };
|
||||||
|
}
|
||||||
|
if (count >= COUNT_MULTIPLIER.thousand) {
|
||||||
|
return { value: count / COUNT_MULTIPLIER.thousand, unit: 'thousand' };
|
||||||
|
}
|
||||||
|
// Default to million for small numbers
|
||||||
|
return { value: count / COUNT_MULTIPLIER.million, unit: 'million' };
|
||||||
|
};
|
||||||
|
|
||||||
|
const countFromUnit = (value: number, unit: string): number =>
|
||||||
|
value *
|
||||||
|
(COUNT_MULTIPLIER[unit as keyof typeof COUNT_MULTIPLIER] ||
|
||||||
|
COUNT_MULTIPLIER.million);
|
||||||
|
|
||||||
function MultiIngestionSettings(): JSX.Element {
|
function MultiIngestionSettings(): JSX.Element {
|
||||||
const { user } = useAppContext();
|
const { user } = useAppContext();
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
@@ -181,7 +220,6 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
|
|
||||||
const showEditModal = (apiKey: IngestionKeyProps): void => {
|
const showEditModal = (apiKey: IngestionKeyProps): void => {
|
||||||
setActiveAPIKey(apiKey);
|
setActiveAPIKey(apiKey);
|
||||||
|
|
||||||
handleFormReset();
|
handleFormReset();
|
||||||
setUpdatedTags(apiKey.tags || []);
|
setUpdatedTags(apiKey.tags || []);
|
||||||
|
|
||||||
@@ -424,44 +462,90 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
addEditLimitForm.resetFields();
|
addEditLimitForm.resetFields();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
const handleAddLimit = (
|
const handleAddLimit = (
|
||||||
APIKey: IngestionKeyProps,
|
APIKey: IngestionKeyProps,
|
||||||
signalName: string,
|
signalName: string,
|
||||||
): void => {
|
): void => {
|
||||||
const { dailyLimit, secondsLimit } = addEditLimitForm.getFieldsValue();
|
const {
|
||||||
|
dailyLimit,
|
||||||
|
secondsLimit,
|
||||||
|
dailyCount,
|
||||||
|
dailyCountUnit,
|
||||||
|
secondsCount,
|
||||||
|
secondsCountUnit,
|
||||||
|
} = addEditLimitForm.getFieldsValue();
|
||||||
|
|
||||||
const payload = {
|
const payload: AddLimitProps = {
|
||||||
keyID: APIKey.id,
|
keyID: APIKey.id,
|
||||||
signal: signalName,
|
signal: signalName,
|
||||||
config: {},
|
config: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isUndefined(dailyLimit)) {
|
const signalCfg = SIGNALS_CONFIG.find((cfg) => cfg.name === signalName);
|
||||||
payload.config = {
|
if (!signalCfg) return;
|
||||||
day: {
|
|
||||||
|
// Only set size if usesSize is true
|
||||||
|
if (signalCfg.usesSize) {
|
||||||
|
if (!isUndefined(dailyLimit)) {
|
||||||
|
payload.config.day = {
|
||||||
|
...payload.config.day,
|
||||||
size: gbToBytes(dailyLimit),
|
size: gbToBytes(dailyLimit),
|
||||||
},
|
};
|
||||||
};
|
}
|
||||||
}
|
if (!isUndefined(secondsLimit)) {
|
||||||
|
payload.config.second = {
|
||||||
if (!isUndefined(secondsLimit)) {
|
...payload.config.second,
|
||||||
payload.config = {
|
|
||||||
...payload.config,
|
|
||||||
second: {
|
|
||||||
size: gbToBytes(secondsLimit),
|
size: gbToBytes(secondsLimit),
|
||||||
},
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUndefined(dailyLimit) && isUndefined(secondsLimit)) {
|
// Only set count if usesCount is true
|
||||||
// No need to save as no limit is provided, close the edit view and reset active signal and api key
|
if (signalCfg.usesCount) {
|
||||||
|
if (!isUndefined(dailyCount)) {
|
||||||
|
payload.config.day = {
|
||||||
|
...payload.config.day,
|
||||||
|
count: countFromUnit(dailyCount, dailyCountUnit || 'million'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!isUndefined(secondsCount)) {
|
||||||
|
payload.config.second = {
|
||||||
|
...payload.config.second,
|
||||||
|
count: countFromUnit(secondsCount, secondsCountUnit || 'million'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If neither size nor count was given, skip
|
||||||
|
const noSizeProvided =
|
||||||
|
isUndefined(dailyLimit) && isUndefined(secondsLimit) && signalCfg.usesSize;
|
||||||
|
const noCountProvided =
|
||||||
|
isUndefined(dailyCount) && isUndefined(secondsCount) && signalCfg.usesCount;
|
||||||
|
|
||||||
|
if (
|
||||||
|
signalCfg.usesSize &&
|
||||||
|
signalCfg.usesCount &&
|
||||||
|
noSizeProvided &&
|
||||||
|
noCountProvided
|
||||||
|
) {
|
||||||
|
// Both size and count are effectively empty
|
||||||
setActiveSignal(null);
|
setActiveSignal(null);
|
||||||
setActiveAPIKey(null);
|
setActiveAPIKey(null);
|
||||||
setIsEditAddLimitOpen(false);
|
setIsEditAddLimitOpen(false);
|
||||||
setUpdatedTags([]);
|
setUpdatedTags([]);
|
||||||
hideAddViewModal();
|
hideAddViewModal();
|
||||||
setHasCreateLimitForIngestionKeyError(false);
|
setHasCreateLimitForIngestionKeyError(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!signalCfg.usesSize && !signalCfg.usesCount) {
|
||||||
|
// Edge case: If there's no count or size usage at all
|
||||||
|
setActiveSignal(null);
|
||||||
|
setActiveAPIKey(null);
|
||||||
|
setIsEditAddLimitOpen(false);
|
||||||
|
setUpdatedTags([]);
|
||||||
|
hideAddViewModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,44 +556,73 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
APIKey: IngestionKeyProps,
|
APIKey: IngestionKeyProps,
|
||||||
signal: LimitProps,
|
signal: LimitProps,
|
||||||
): void => {
|
): void => {
|
||||||
const { dailyLimit, secondsLimit } = addEditLimitForm.getFieldsValue();
|
const {
|
||||||
const payload = {
|
dailyLimit,
|
||||||
|
secondsLimit,
|
||||||
|
dailyCount,
|
||||||
|
dailyCountUnit,
|
||||||
|
secondsCount,
|
||||||
|
secondsCountUnit,
|
||||||
|
} = addEditLimitForm.getFieldsValue();
|
||||||
|
|
||||||
|
const payload: UpdateLimitProps = {
|
||||||
limitID: signal.id,
|
limitID: signal.id,
|
||||||
signal: signal.signal,
|
signal: signal.signal,
|
||||||
config: {},
|
config: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isUndefined(dailyLimit) && isUndefined(secondsLimit)) {
|
const signalCfg = SIGNALS_CONFIG.find((cfg) => cfg.name === signal.signal);
|
||||||
showDeleteLimitModal(APIKey, signal);
|
if (!signalCfg) return;
|
||||||
|
|
||||||
|
const noSizeProvided =
|
||||||
|
isUndefined(dailyLimit) && isUndefined(secondsLimit) && signalCfg.usesSize;
|
||||||
|
const noCountProvided =
|
||||||
|
isUndefined(dailyCount) && isUndefined(secondsCount) && signalCfg.usesCount;
|
||||||
|
|
||||||
|
// If the user cleared out all fields, remove the limit
|
||||||
|
if (noSizeProvided && noCountProvided) {
|
||||||
|
showDeleteLimitModal(APIKey, signal);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isUndefined(dailyLimit)) {
|
if (signalCfg.usesSize) {
|
||||||
payload.config = {
|
if (!isUndefined(dailyLimit)) {
|
||||||
day: {
|
payload.config.day = {
|
||||||
|
...payload.config.day,
|
||||||
size: gbToBytes(dailyLimit),
|
size: gbToBytes(dailyLimit),
|
||||||
},
|
};
|
||||||
};
|
}
|
||||||
|
if (!isUndefined(secondsLimit)) {
|
||||||
|
payload.config.second = {
|
||||||
|
...payload.config.second,
|
||||||
|
size: gbToBytes(secondsLimit),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isUndefined(secondsLimit)) {
|
if (signalCfg.usesCount) {
|
||||||
payload.config = {
|
if (!isUndefined(dailyCount)) {
|
||||||
...payload.config,
|
payload.config.day = {
|
||||||
second: {
|
...payload.config.day,
|
||||||
size: gbToBytes(secondsLimit),
|
count: countFromUnit(dailyCount, dailyCountUnit || 'million'),
|
||||||
},
|
};
|
||||||
};
|
}
|
||||||
|
if (!isUndefined(secondsCount)) {
|
||||||
|
payload.config.second = {
|
||||||
|
...payload.config.second,
|
||||||
|
count: countFromUnit(secondsCount, secondsCountUnit || 'million'),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLimitForIngestionKey(payload);
|
updateLimitForIngestionKey(payload);
|
||||||
};
|
};
|
||||||
|
/* eslint-enable sonarjs/cognitive-complexity */
|
||||||
|
|
||||||
const bytesToGb = (size: number | undefined): number => {
|
const bytesToGb = (size: number | undefined): number => {
|
||||||
if (!size) {
|
if (!size) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return size / BYTES;
|
return size / BYTES;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -517,6 +630,12 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
APIKey: IngestionKeyProps,
|
APIKey: IngestionKeyProps,
|
||||||
signal: LimitProps,
|
signal: LimitProps,
|
||||||
): void => {
|
): void => {
|
||||||
|
const dayCount = signal?.config?.day?.count;
|
||||||
|
const secondCount = signal?.config?.second?.count;
|
||||||
|
|
||||||
|
const dayCountConverted = countToUnit(dayCount || 0);
|
||||||
|
const secondCountConverted = countToUnit(secondCount || 0);
|
||||||
|
|
||||||
setActiveAPIKey(APIKey);
|
setActiveAPIKey(APIKey);
|
||||||
setActiveSignal({
|
setActiveSignal({
|
||||||
...signal,
|
...signal,
|
||||||
@@ -524,11 +643,14 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
...signal.config,
|
...signal.config,
|
||||||
day: {
|
day: {
|
||||||
...signal.config?.day,
|
...signal.config?.day,
|
||||||
enabled: !isNil(signal?.config?.day?.size),
|
enabled:
|
||||||
|
!isNil(signal?.config?.day?.size) || !isNil(signal?.config?.day?.count),
|
||||||
},
|
},
|
||||||
second: {
|
second: {
|
||||||
...signal.config?.second,
|
...signal.config?.second,
|
||||||
enabled: !isNil(signal?.config?.second?.size),
|
enabled:
|
||||||
|
!isNil(signal?.config?.second?.size) ||
|
||||||
|
!isNil(signal?.config?.second?.count),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -536,15 +658,22 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
addEditLimitForm.setFieldsValue({
|
addEditLimitForm.setFieldsValue({
|
||||||
dailyLimit: bytesToGb(signal?.config?.day?.size || 0),
|
dailyLimit: bytesToGb(signal?.config?.day?.size || 0),
|
||||||
secondsLimit: bytesToGb(signal?.config?.second?.size || 0),
|
secondsLimit: bytesToGb(signal?.config?.second?.size || 0),
|
||||||
enableDailyLimit: !isNil(signal?.config?.day?.size),
|
enableDailyLimit:
|
||||||
enableSecondLimit: !isNil(signal?.config?.second?.size),
|
!isNil(signal?.config?.day?.size) || !isNil(signal?.config?.day?.count),
|
||||||
|
enableSecondLimit:
|
||||||
|
!isNil(signal?.config?.second?.size) ||
|
||||||
|
!isNil(signal?.config?.second?.count),
|
||||||
|
dailyCount: dayCountConverted.value,
|
||||||
|
dailyCountUnit: dayCountConverted.unit,
|
||||||
|
secondsCount: secondCountConverted.value,
|
||||||
|
secondsCountUnit: secondCountConverted.unit,
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsEditAddLimitOpen(true);
|
setIsEditAddLimitOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDeleteLimitHandler = (): void => {
|
const onDeleteLimitHandler = (): void => {
|
||||||
if (activeSignal && activeSignal?.id) {
|
if (activeSignal && activeSignal.id) {
|
||||||
deleteLimitForKey(activeSignal.id);
|
deleteLimitForKey(activeSignal.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -572,13 +701,13 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
formatTimezoneAdjustedTimestamp,
|
formatTimezoneAdjustedTimestamp,
|
||||||
);
|
);
|
||||||
|
|
||||||
const limits: { [key: string]: LimitProps } = {};
|
// Convert array of limits to a dictionary for quick access
|
||||||
|
const limitsDict: Record<string, LimitProps> = {};
|
||||||
APIKey.limits?.forEach((limit: LimitProps) => {
|
APIKey.limits?.forEach((limitItem: LimitProps) => {
|
||||||
limits[limit.signal] = limit;
|
limitsDict[limitItem.signal] = limitItem;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasLimits = (signal: string): boolean => !!limits[signal];
|
const hasLimits = (signalName: string): boolean => !!limitsDict[signalName];
|
||||||
|
|
||||||
const items: CollapseProps['items'] = [
|
const items: CollapseProps['items'] = [
|
||||||
{
|
{
|
||||||
@@ -614,11 +743,9 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
onClick={(e): void => {
|
onClick={(e): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
showEditModal(APIKey);
|
showEditModal(APIKey);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="periscope-btn ghost"
|
className="periscope-btn ghost"
|
||||||
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
|
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
|
||||||
@@ -670,18 +797,23 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
|
|
||||||
<div className="limits-data">
|
<div className="limits-data">
|
||||||
<div className="signals">
|
<div className="signals">
|
||||||
{SIGNALS.map((signal) => {
|
{SIGNALS_CONFIG.map((signalCfg) => {
|
||||||
const hasValidDayLimit = !isNil(limits[signal]?.config?.day?.size);
|
const signalName = signalCfg.name;
|
||||||
const hasValidSecondLimit = !isNil(
|
const limit = limitsDict[signalName];
|
||||||
limits[signal]?.config?.second?.size,
|
|
||||||
);
|
const hasValidDayLimit =
|
||||||
|
limit?.config?.day?.size !== undefined ||
|
||||||
|
limit?.config?.day?.count !== undefined;
|
||||||
|
const hasValidSecondLimit =
|
||||||
|
limit?.config?.second?.size !== undefined ||
|
||||||
|
limit?.config?.second?.count !== undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="signal" key={signal}>
|
<div className="signal" key={signalName}>
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<div className="signal-name">{signal}</div>
|
<div className="signal-name">{signalName}</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
{hasLimits(signal) ? (
|
{hasLimits(signalName) ? (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
className="periscope-btn ghost"
|
className="periscope-btn ghost"
|
||||||
@@ -690,10 +822,9 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
onClick={(e): void => {
|
onClick={(e): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
enableEditLimitMode(APIKey, limits[signal]);
|
enableEditLimitMode(APIKey, limit);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="periscope-btn ghost"
|
className="periscope-btn ghost"
|
||||||
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
|
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
|
||||||
@@ -701,7 +832,7 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
onClick={(e): void => {
|
onClick={(e): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showDeleteLimitModal(APIKey, limits[signal]);
|
showDeleteLimitModal(APIKey, limit);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -712,14 +843,12 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
shape="round"
|
shape="round"
|
||||||
icon={<PlusIcon size={14} />}
|
icon={<PlusIcon size={14} />}
|
||||||
disabled={!!(activeAPIKey?.id === APIKey.id && activeSignal)}
|
disabled={!!(activeAPIKey?.id === APIKey.id && activeSignal)}
|
||||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
|
||||||
onClick={(e): void => {
|
onClick={(e): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
enableEditLimitMode(APIKey, {
|
enableEditLimitMode(APIKey, {
|
||||||
id: signal,
|
id: signalName,
|
||||||
signal,
|
signal: signalName,
|
||||||
config: {},
|
config: {},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -732,7 +861,7 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
|
|
||||||
<div className="signal-limit-values">
|
<div className="signal-limit-values">
|
||||||
{activeAPIKey?.id === APIKey.id &&
|
{activeAPIKey?.id === APIKey.id &&
|
||||||
activeSignal?.signal === signal &&
|
activeSignal?.signal === signalName &&
|
||||||
isEditAddLimitOpen ? (
|
isEditAddLimitOpen ? (
|
||||||
<Form
|
<Form
|
||||||
name="edit-ingestion-key-limit-form"
|
name="edit-ingestion-key-limit-form"
|
||||||
@@ -740,8 +869,8 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
form={addEditLimitForm}
|
form={addEditLimitForm}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
initialValues={{
|
initialValues={{
|
||||||
dailyLimit: bytesToGb(limits[signal]?.config?.day?.size),
|
dailyLimit: bytesToGb(limit?.config?.day?.size || 0),
|
||||||
secondsLimit: bytesToGb(limits[signal]?.config?.second?.size),
|
secondsLimit: bytesToGb(limit?.config?.second?.size || 0),
|
||||||
}}
|
}}
|
||||||
className="edit-ingestion-key-limit-form"
|
className="edit-ingestion-key-limit-form"
|
||||||
>
|
>
|
||||||
@@ -756,16 +885,20 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
size="small"
|
size="small"
|
||||||
checked={activeSignal?.config?.day?.enabled}
|
checked={activeSignal?.config?.day?.enabled}
|
||||||
onChange={(value): void => {
|
onChange={(value): void => {
|
||||||
setActiveSignal({
|
setActiveSignal((prev) =>
|
||||||
...activeSignal,
|
prev
|
||||||
config: {
|
? {
|
||||||
...activeSignal.config,
|
...prev,
|
||||||
day: {
|
config: {
|
||||||
...activeSignal.config?.day,
|
...prev.config,
|
||||||
enabled: value,
|
day: {
|
||||||
},
|
...prev.config?.day,
|
||||||
},
|
enabled: value,
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -775,50 +908,87 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
Add a limit for data ingested daily
|
Add a limit for data ingested daily
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="size">
|
{signalCfg.usesSize && (
|
||||||
{activeSignal?.config?.day?.enabled ? (
|
<div className="size">
|
||||||
<Form.Item name="dailyLimit" key="dailyLimit">
|
{activeSignal?.config?.day?.enabled ? (
|
||||||
<InputNumber
|
<Form.Item name="dailyLimit" key="dailyLimit">
|
||||||
disabled={!activeSignal?.config?.day?.enabled}
|
<InputNumber
|
||||||
key="dailyLimit"
|
disabled={!activeSignal?.config?.day?.enabled}
|
||||||
addonAfter={
|
addonAfter={
|
||||||
<Select defaultValue="GiB" disabled>
|
<Select defaultValue="GiB" disabled>
|
||||||
<Option value="TiB"> TiB</Option>
|
<Option value="TiB">TiB</Option>
|
||||||
<Option value="GiB"> GiB</Option>
|
<Option value="GiB">GiB</Option>
|
||||||
<Option value="MiB"> MiB </Option>
|
<Option value="MiB">MiB</Option>
|
||||||
<Option value="KiB"> KiB </Option>
|
<Option value="KiB">KiB</Option>
|
||||||
</Select>
|
</Select>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
) : (
|
) : (
|
||||||
<div className="no-limit">
|
<div className="no-limit">
|
||||||
<Infinity size={16} /> NO LIMIT
|
<Infinity size={16} /> NO LIMIT
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{signalCfg.usesCount && (
|
||||||
|
<div className="count">
|
||||||
|
{activeSignal?.config?.day?.enabled ? (
|
||||||
|
<Form.Item name="dailyCount" key="dailyCount">
|
||||||
|
<InputNumber
|
||||||
|
placeholder="Enter max # of samples/day"
|
||||||
|
addonAfter={
|
||||||
|
<Form.Item
|
||||||
|
name="dailyCountUnit"
|
||||||
|
noStyle
|
||||||
|
initialValue="million"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
style={{
|
||||||
|
width: 90,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Option value="thousand">Thousand</Option>
|
||||||
|
<Option value="million">Million</Option>
|
||||||
|
<Option value="billion">Billion</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<div className="no-limit">
|
||||||
|
<Infinity size={16} /> NO LIMIT
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="second-limit">
|
<div className="second-limit">
|
||||||
<div className="heading">
|
<div className="heading">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
Per Second limit{' '}
|
Per Second limit
|
||||||
<div className="limit-enable-disable-toggle">
|
<div className="limit-enable-disable-toggle">
|
||||||
<Form.Item name="enableSecondLimit">
|
<Form.Item name="enableSecondLimit">
|
||||||
<Switch
|
<Switch
|
||||||
size="small"
|
size="small"
|
||||||
checked={activeSignal?.config?.second?.enabled}
|
checked={activeSignal?.config?.second?.enabled}
|
||||||
onChange={(value): void => {
|
onChange={(value): void => {
|
||||||
setActiveSignal({
|
setActiveSignal((prev) =>
|
||||||
...activeSignal,
|
prev
|
||||||
config: {
|
? {
|
||||||
...activeSignal.config,
|
...prev,
|
||||||
second: {
|
config: {
|
||||||
...activeSignal.config?.second,
|
...prev.config,
|
||||||
enabled: value,
|
second: {
|
||||||
},
|
...prev.config?.second,
|
||||||
},
|
enabled: value,
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -828,37 +998,68 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
Add a limit for data ingested every second
|
Add a limit for data ingested every second
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{signalCfg.usesSize && (
|
||||||
<div className="size">
|
<div className="size">
|
||||||
{activeSignal?.config?.second?.enabled ? (
|
{activeSignal?.config?.second?.enabled ? (
|
||||||
<Form.Item name="secondsLimit" key="secondsLimit">
|
<Form.Item name="secondsLimit" key="secondsLimit">
|
||||||
<InputNumber
|
<InputNumber
|
||||||
key="secondsLimit"
|
disabled={!activeSignal?.config?.second?.enabled}
|
||||||
disabled={!activeSignal?.config?.second?.enabled}
|
addonAfter={
|
||||||
addonAfter={
|
<Select defaultValue="GiB" disabled>
|
||||||
<Select defaultValue="GiB" disabled>
|
<Option value="TiB">TiB</Option>
|
||||||
<Option value="TiB"> TiB</Option>
|
<Option value="GiB">GiB</Option>
|
||||||
<Option value="GiB"> GiB</Option>
|
<Option value="MiB">MiB</Option>
|
||||||
<Option value="MiB"> MiB </Option>
|
<Option value="KiB">KiB</Option>
|
||||||
<Option value="KiB"> KiB </Option>
|
</Select>
|
||||||
</Select>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
</Form.Item>
|
||||||
</Form.Item>
|
) : (
|
||||||
) : (
|
<div className="no-limit">
|
||||||
<div className="no-limit">
|
<Infinity size={16} /> NO LIMIT
|
||||||
<Infinity size={16} /> NO LIMIT
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
{signalCfg.usesCount && (
|
||||||
|
<div className="count">
|
||||||
|
{activeSignal?.config?.second?.enabled ? (
|
||||||
|
<Form.Item name="secondsCount" key="secondsCount">
|
||||||
|
<InputNumber
|
||||||
|
placeholder="Enter max # of samples/s"
|
||||||
|
addonAfter={
|
||||||
|
<Form.Item
|
||||||
|
name="secondsCountUnit"
|
||||||
|
noStyle
|
||||||
|
initialValue="million"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
style={{
|
||||||
|
width: 90,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Option value="thousand">Thousand</Option>
|
||||||
|
<Option value="million">Million</Option>
|
||||||
|
<Option value="billion">Billion</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<div className="no-limit">
|
||||||
|
<Infinity size={16} /> NO LIMIT
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeAPIKey?.id === APIKey.id &&
|
{activeAPIKey?.id === APIKey.id &&
|
||||||
activeSignal.signal === signal &&
|
activeSignal.signal === signalName &&
|
||||||
!isLoadingLimitForKey &&
|
!isLoadingLimitForKey &&
|
||||||
hasCreateLimitForIngestionKeyError &&
|
hasCreateLimitForIngestionKeyError &&
|
||||||
createLimitForIngestionKeyError &&
|
|
||||||
createLimitForIngestionKeyError?.error && (
|
createLimitForIngestionKeyError?.error && (
|
||||||
<div className="error">
|
<div className="error">
|
||||||
{createLimitForIngestionKeyError?.error}
|
{createLimitForIngestionKeyError?.error}
|
||||||
@@ -866,17 +1067,17 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{activeAPIKey?.id === APIKey.id &&
|
{activeAPIKey?.id === APIKey.id &&
|
||||||
activeSignal.signal === signal &&
|
activeSignal.signal === signalName &&
|
||||||
!isLoadingLimitForKey &&
|
!isLoadingLimitForKey &&
|
||||||
hasUpdateLimitForIngestionKeyError &&
|
hasUpdateLimitForIngestionKeyError &&
|
||||||
updateLimitForIngestionKeyError && (
|
updateLimitForIngestionKeyError?.error && (
|
||||||
<div className="error">
|
<div className="error">
|
||||||
{updateLimitForIngestionKeyError?.error}
|
{updateLimitForIngestionKeyError?.error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeAPIKey?.id === APIKey.id &&
|
{activeAPIKey?.id === APIKey.id &&
|
||||||
activeSignal.signal === signal &&
|
activeSignal.signal === signalName &&
|
||||||
isEditAddLimitOpen && (
|
isEditAddLimitOpen && (
|
||||||
<div className="signal-limit-save-discard">
|
<div className="signal-limit-save-discard">
|
||||||
<Button
|
<Button
|
||||||
@@ -890,10 +1091,10 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
isLoadingLimitForKey || isLoadingUpdatedLimitForKey
|
isLoadingLimitForKey || isLoadingUpdatedLimitForKey
|
||||||
}
|
}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
if (!hasLimits(signal)) {
|
if (!hasLimits(signalName)) {
|
||||||
handleAddLimit(APIKey, signal);
|
handleAddLimit(APIKey, signalName);
|
||||||
} else {
|
} else {
|
||||||
handleUpdateLimit(APIKey, limits[signal]);
|
handleUpdateLimit(APIKey, limitsDict[signalName]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -915,55 +1116,99 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
</Form>
|
</Form>
|
||||||
) : (
|
) : (
|
||||||
<div className="signal-limit-view-mode">
|
<div className="signal-limit-view-mode">
|
||||||
|
{/* DAILY limit usage/limit */}
|
||||||
<div className="signal-limit-value">
|
<div className="signal-limit-value">
|
||||||
<div className="limit-type">
|
<div className="limit-type">
|
||||||
Daily <Minus size={16} />{' '}
|
Daily <Minus size={16} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="limit-value">
|
<div className="limit-value">
|
||||||
{hasValidDayLimit ? (
|
{/* Size (if usesSize) */}
|
||||||
<>
|
{signalCfg.usesSize &&
|
||||||
{getYAxisFormattedValue(
|
(hasValidDayLimit &&
|
||||||
(limits[signal]?.metric?.day?.size || 0).toString(),
|
limit?.config?.day?.size !== undefined ? (
|
||||||
'bytes',
|
<>
|
||||||
)}{' '}
|
{getYAxisFormattedValue(
|
||||||
/{' '}
|
(limit?.metric?.day?.size || 0).toString(),
|
||||||
{getYAxisFormattedValue(
|
'bytes',
|
||||||
(limits[signal]?.config?.day?.size || 0).toString(),
|
)}{' '}
|
||||||
'bytes',
|
/{' '}
|
||||||
)}
|
{getYAxisFormattedValue(
|
||||||
</>
|
(limit?.config?.day?.size || 0).toString(),
|
||||||
) : (
|
'bytes',
|
||||||
<>
|
)}
|
||||||
<Infinity size={16} /> NO LIMIT
|
</>
|
||||||
</>
|
) : (
|
||||||
)}
|
<>
|
||||||
|
<Infinity size={16} /> NO LIMIT
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Count (if usesCount) */}
|
||||||
|
{signalCfg.usesCount &&
|
||||||
|
(limit?.config?.day?.count !== undefined ? (
|
||||||
|
<div style={{ marginTop: 4 }}>
|
||||||
|
{countToUnit(
|
||||||
|
limit?.metric?.day?.count || 0,
|
||||||
|
).value.toFixed(2)}{' '}
|
||||||
|
{countToUnit(limit?.metric?.day?.count || 0).unit} /{' '}
|
||||||
|
{countToUnit(
|
||||||
|
limit?.config?.day?.count || 0,
|
||||||
|
).value.toFixed(2)}{' '}
|
||||||
|
{countToUnit(limit?.config?.day?.count || 0).unit}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Infinity size={16} /> NO LIMIT
|
||||||
|
</>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* SECOND limit usage/limit */}
|
||||||
<div className="signal-limit-value">
|
<div className="signal-limit-value">
|
||||||
<div className="limit-type">
|
<div className="limit-type">
|
||||||
Seconds <Minus size={16} />
|
Seconds <Minus size={16} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="limit-value">
|
<div className="limit-value">
|
||||||
{hasValidSecondLimit ? (
|
{/* Size (if usesSize) */}
|
||||||
<>
|
{signalCfg.usesSize &&
|
||||||
{getYAxisFormattedValue(
|
(hasValidSecondLimit &&
|
||||||
(limits[signal]?.metric?.second?.size || 0).toString(),
|
limit?.config?.second?.size !== undefined ? (
|
||||||
'bytes',
|
<>
|
||||||
)}{' '}
|
{getYAxisFormattedValue(
|
||||||
/{' '}
|
(limit?.metric?.second?.size || 0).toString(),
|
||||||
{getYAxisFormattedValue(
|
'bytes',
|
||||||
(limits[signal]?.config?.second?.size || 0).toString(),
|
)}{' '}
|
||||||
'bytes',
|
/{' '}
|
||||||
)}
|
{getYAxisFormattedValue(
|
||||||
</>
|
(limit?.config?.second?.size || 0).toString(),
|
||||||
) : (
|
'bytes',
|
||||||
<>
|
)}
|
||||||
<Infinity size={16} /> NO LIMIT
|
</>
|
||||||
</>
|
) : (
|
||||||
)}
|
<>
|
||||||
|
<Infinity size={16} /> NO LIMIT
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Count (if usesCount) */}
|
||||||
|
{signalCfg.usesCount &&
|
||||||
|
(limit?.config?.second?.count !== undefined ? (
|
||||||
|
<div style={{ marginTop: 4 }}>
|
||||||
|
{countToUnit(
|
||||||
|
limit?.metric?.second?.count || 0,
|
||||||
|
).value.toFixed(2)}{' '}
|
||||||
|
{countToUnit(limit?.metric?.second?.count || 0).unit} /{' '}
|
||||||
|
{countToUnit(
|
||||||
|
limit?.config?.second?.count || 0,
|
||||||
|
).value.toFixed(2)}{' '}
|
||||||
|
{countToUnit(limit?.config?.second?.count || 0).unit}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Infinity size={16} /> NO LIMIT
|
||||||
|
</>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1033,7 +1278,6 @@ function MultiIngestionSettings(): JSX.Element {
|
|||||||
className="learn-more"
|
className="learn-more"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
{' '}
|
|
||||||
Learn more <ArrowUpRight size={14} />
|
Learn more <ArrowUpRight size={14} />
|
||||||
</a>
|
</a>
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
|
|||||||
@@ -121,23 +121,25 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
|||||||
const tableHeader = useCallback(
|
const tableHeader = useCallback(
|
||||||
() => (
|
() => (
|
||||||
<tr>
|
<tr>
|
||||||
{tableColumns.map((column) => {
|
{tableColumns
|
||||||
const isDragColumn = column.key !== 'expand';
|
.filter((column) => column.key)
|
||||||
|
.map((column) => {
|
||||||
|
const isDragColumn = column.key !== 'expand';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableHeaderCellStyled
|
<TableHeaderCellStyled
|
||||||
$isLogIndicator={column.key === 'state-indicator'}
|
$isLogIndicator={column.key === 'state-indicator'}
|
||||||
$isDarkMode={isDarkMode}
|
$isDarkMode={isDarkMode}
|
||||||
$isDragColumn={isDragColumn}
|
$isDragColumn={isDragColumn}
|
||||||
key={column.key}
|
key={column.key}
|
||||||
fontSize={tableViewProps?.fontSize}
|
fontSize={tableViewProps?.fontSize}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...(isDragColumn && { className: 'dragHandler' })}
|
{...(isDragColumn && { className: 'dragHandler' })}
|
||||||
>
|
>
|
||||||
{(column.title as string).replace(/^\w/, (c) => c.toUpperCase())}
|
{(column.title as string).replace(/^\w/, (c) => c.toUpperCase())}
|
||||||
</TableHeaderCellStyled>
|
</TableHeaderCellStyled>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
),
|
),
|
||||||
[tableColumns, isDarkMode, tableViewProps?.fontSize],
|
[tableColumns, isDarkMode, tableViewProps?.fontSize],
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const TableCellStyled = styled.td<TableHeaderCellStyledProps>`
|
|||||||
props.$isDarkMode ? 'inherit' : themeColors.whiteCream};
|
props.$isDarkMode ? 'inherit' : themeColors.whiteCream};
|
||||||
|
|
||||||
${({ $isLogIndicator }): string =>
|
${({ $isLogIndicator }): string =>
|
||||||
$isLogIndicator ? 'padding: 0 0 0 8px;' : ''}
|
$isLogIndicator ? 'padding: 0 0 0 8px;width: 15px;' : ''}
|
||||||
color: ${(props): string =>
|
color: ${(props): string =>
|
||||||
props.$isDarkMode ? themeColors.white : themeColors.bckgGrey};
|
props.$isDarkMode ? themeColors.white : themeColors.bckgGrey};
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -5,7 +5,26 @@ import { FontSize, OptionsQuery } from './types';
|
|||||||
export const URL_OPTIONS = 'options';
|
export const URL_OPTIONS = 'options';
|
||||||
|
|
||||||
export const defaultOptionsQuery: OptionsQuery = {
|
export const defaultOptionsQuery: OptionsQuery = {
|
||||||
selectColumns: [],
|
selectColumns: [
|
||||||
|
{
|
||||||
|
key: 'timestamp',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'tag',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'timestamp--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'body',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'tag',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'body--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
format: 'raw',
|
format: 'raw',
|
||||||
fontSize: FontSize.SMALL,
|
fontSize: FontSize.SMALL,
|
||||||
|
|||||||
@@ -169,6 +169,15 @@ const useOptionsMenu = ({
|
|||||||
|
|
||||||
const searchedAttributeKeys = useMemo(() => {
|
const searchedAttributeKeys = useMemo(() => {
|
||||||
if (searchedAttributesData?.payload?.attributeKeys?.length) {
|
if (searchedAttributesData?.payload?.attributeKeys?.length) {
|
||||||
|
if (dataSource === DataSource.LOGS) {
|
||||||
|
// add timestamp and body to the list of attributes
|
||||||
|
return [
|
||||||
|
...defaultOptionsQuery.selectColumns,
|
||||||
|
...searchedAttributesData.payload.attributeKeys.filter(
|
||||||
|
(attribute) => attribute.key !== 'body',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
return searchedAttributesData.payload.attributeKeys;
|
return searchedAttributesData.payload.attributeKeys;
|
||||||
}
|
}
|
||||||
if (dataSource === DataSource.TRACES) {
|
if (dataSource === DataSource.TRACES) {
|
||||||
@@ -198,12 +207,17 @@ const useOptionsMenu = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const optionsFromAttributeKeys = useMemo(() => {
|
const optionsFromAttributeKeys = useMemo(() => {
|
||||||
const filteredAttributeKeys = searchedAttributeKeys.filter(
|
const filteredAttributeKeys = searchedAttributeKeys.filter((item) => {
|
||||||
(item) => item.key !== 'body',
|
// For other data sources, only filter out 'body' if it exists
|
||||||
);
|
if (dataSource !== DataSource.LOGS) {
|
||||||
|
return item.key !== 'body';
|
||||||
|
}
|
||||||
|
// For LOGS, keep all keys
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
return getOptionsFromKeys(filteredAttributeKeys, selectedColumnKeys);
|
return getOptionsFromKeys(filteredAttributeKeys, selectedColumnKeys);
|
||||||
}, [searchedAttributeKeys, selectedColumnKeys]);
|
}, [dataSource, searchedAttributeKeys, selectedColumnKeys]);
|
||||||
|
|
||||||
const handleRedirectWithOptionsData = useCallback(
|
const handleRedirectWithOptionsData = useCallback(
|
||||||
(newQueryData: OptionsQuery) => {
|
(newQueryData: OptionsQuery) => {
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ function QueryBuilderSearch({
|
|||||||
isMulti,
|
isMulti,
|
||||||
isFetching,
|
isFetching,
|
||||||
setSearchKey,
|
setSearchKey,
|
||||||
|
setSearchValue,
|
||||||
searchKey,
|
searchKey,
|
||||||
key,
|
key,
|
||||||
exampleQueries,
|
exampleQueries,
|
||||||
@@ -145,7 +146,11 @@ function QueryBuilderSearch({
|
|||||||
|
|
||||||
const tagEditHandler = (value: string): void => {
|
const tagEditHandler = (value: string): void => {
|
||||||
updateTag(value);
|
updateTag(value);
|
||||||
handleSearch(value);
|
if (isInfraMonitoring) {
|
||||||
|
setSearchValue(value);
|
||||||
|
} else {
|
||||||
|
handleSearch(value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDisabled = !!searchValue;
|
const isDisabled = !!searchValue;
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ export const useAutoComplete = (
|
|||||||
isMulti,
|
isMulti,
|
||||||
isFetching,
|
isFetching,
|
||||||
setSearchKey,
|
setSearchKey,
|
||||||
|
setSearchValue,
|
||||||
searchKey,
|
searchKey,
|
||||||
key,
|
key,
|
||||||
exampleQueries,
|
exampleQueries,
|
||||||
@@ -172,6 +173,7 @@ interface IAutoComplete {
|
|||||||
isMulti: boolean;
|
isMulti: boolean;
|
||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
setSearchKey: (value: string) => void;
|
setSearchKey: (value: string) => void;
|
||||||
|
setSearchValue: (value: string) => void;
|
||||||
searchKey: string;
|
searchKey: string;
|
||||||
key: string;
|
key: string;
|
||||||
exampleQueries: TagFilter[];
|
exampleQueries: TagFilter[];
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import RouteTab from 'components/RouteTab';
|
|||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -80,6 +80,11 @@ function AlertDetails(): JSX.Element {
|
|||||||
alertDetailsResponse,
|
alertDetailsResponse,
|
||||||
} = useGetAlertRuleDetails();
|
} = useGetAlertRuleDetails();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const alertTitle = alertDetailsResponse?.payload?.data.alert;
|
||||||
|
document.title = alertTitle || document.title;
|
||||||
|
}, [alertDetailsResponse?.payload?.data.alert, isRefetching]);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isError ||
|
isError ||
|
||||||
!isValidRuleId ||
|
!isValidRuleId ||
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import { TabRoutes } from 'components/RouteTab/types';
|
|||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useLocation } from 'react-use';
|
import { useLocation } from 'react-use';
|
||||||
|
|
||||||
import { Hosts } from './constants';
|
import { Hosts, Kubernetes } from './constants';
|
||||||
|
|
||||||
export default function InfrastructureMonitoringPage(): JSX.Element {
|
export default function InfrastructureMonitoringPage(): JSX.Element {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const routes: TabRoutes[] = [Hosts];
|
const routes: TabRoutes[] = [Hosts, Kubernetes];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="infra-monitoring-module-container">
|
<div className="infra-monitoring-module-container">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import NotFound from 'components/NotFound';
|
|||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import NewDashboard from 'container/NewDashboard';
|
import NewDashboard from 'container/NewDashboard';
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { ErrorType } from 'types/common';
|
import { ErrorType } from 'types/common';
|
||||||
|
|
||||||
function DashboardPage(): JSX.Element {
|
function DashboardPage(): JSX.Element {
|
||||||
@@ -17,6 +18,11 @@ function DashboardPage(): JSX.Element {
|
|||||||
(dashboardResponse?.error as AxiosError)?.response?.data?.errorType
|
(dashboardResponse?.error as AxiosError)?.response?.data?.errorType
|
||||||
: 'Something went wrong';
|
: 'Something went wrong';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const dashboardTitle = dashboardResponse.data?.data.title;
|
||||||
|
document.title = dashboardTitle || document.title;
|
||||||
|
}, [dashboardResponse.data?.data.title, isFetching]);
|
||||||
|
|
||||||
if (isError && !isFetching && errorMessage === ErrorType.NotFound) {
|
if (isError && !isFetching && errorMessage === ErrorType.NotFound) {
|
||||||
return <NotFound />;
|
return <NotFound />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
export interface LimitConfig {
|
||||||
|
size?: number;
|
||||||
|
count?: number; // mainly used for metrics
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LimitSettings {
|
||||||
|
day?: LimitConfig;
|
||||||
|
second?: LimitConfig;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LimitProps {
|
export interface LimitProps {
|
||||||
id: string;
|
id: string;
|
||||||
signal: string;
|
signal: string;
|
||||||
@@ -5,56 +16,20 @@ export interface LimitProps {
|
|||||||
key_id?: string;
|
key_id?: string;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
config?: {
|
config?: LimitSettings;
|
||||||
day?: {
|
metric?: LimitSettings;
|
||||||
size?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
second?: {
|
|
||||||
size?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
metric?: {
|
|
||||||
day?: {
|
|
||||||
size?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
second?: {
|
|
||||||
size?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddLimitProps {
|
export interface AddLimitProps {
|
||||||
keyID: string;
|
keyID: string;
|
||||||
signal: string;
|
signal: string;
|
||||||
config: {
|
config: LimitSettings;
|
||||||
day?: {
|
|
||||||
size?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
second?: {
|
|
||||||
size?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateLimitProps {
|
export interface UpdateLimitProps {
|
||||||
limitID: string;
|
limitID: string;
|
||||||
signal: string;
|
signal: string;
|
||||||
config: {
|
config: LimitSettings;
|
||||||
day?: {
|
|
||||||
size?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
second?: {
|
|
||||||
size?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LimitSuccessProps {
|
export interface LimitSuccessProps {
|
||||||
|
|||||||
14
go.mod
14
go.mod
@@ -20,6 +20,7 @@ require (
|
|||||||
github.com/go-kit/log v0.2.1
|
github.com/go-kit/log v0.2.1
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
github.com/go-redis/redismock/v8 v8.11.5
|
github.com/go-redis/redismock/v8 v8.11.5
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.1.0
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
@@ -29,6 +30,7 @@ require (
|
|||||||
github.com/jmoiron/sqlx v1.3.4
|
github.com/jmoiron/sqlx v1.3.4
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/knadh/koanf v1.5.0
|
github.com/knadh/koanf v1.5.0
|
||||||
|
github.com/knadh/koanf/v2 v2.1.1
|
||||||
github.com/mailru/easyjson v0.7.7
|
github.com/mailru/easyjson v0.7.7
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||||
github.com/oklog/oklog v0.3.2
|
github.com/oklog/oklog v0.3.2
|
||||||
@@ -48,6 +50,8 @@ require (
|
|||||||
github.com/soheilhy/cmux v0.1.5
|
github.com/soheilhy/cmux v0.1.5
|
||||||
github.com/srikanthccv/ClickHouse-go-mock v0.9.0
|
github.com/srikanthccv/ClickHouse-go-mock v0.9.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
|
github.com/uptrace/bun v1.2.8
|
||||||
|
github.com/uptrace/bun/dialect/sqlitedialect v1.2.8
|
||||||
go.opentelemetry.io/collector/confmap v1.17.0
|
go.opentelemetry.io/collector/confmap v1.17.0
|
||||||
go.opentelemetry.io/collector/pdata v1.17.0
|
go.opentelemetry.io/collector/pdata v1.17.0
|
||||||
go.opentelemetry.io/collector/processor v0.111.0
|
go.opentelemetry.io/collector/processor v0.111.0
|
||||||
@@ -99,6 +103,7 @@ require (
|
|||||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
|
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/go-faster/city v1.0.1 // indirect
|
github.com/go-faster/city v1.0.1 // indirect
|
||||||
github.com/go-faster/errors v0.7.1 // indirect
|
github.com/go-faster/errors v0.7.1 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||||
@@ -106,7 +111,6 @@ require (
|
|||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
@@ -120,13 +124,13 @@ require (
|
|||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
||||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/jpillora/backoff v1.0.0 // indirect
|
github.com/jpillora/backoff v1.0.0 // indirect
|
||||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||||
github.com/klauspost/compress v1.17.10 // indirect
|
github.com/klauspost/compress v1.17.10 // indirect
|
||||||
github.com/knadh/koanf/v2 v2.1.1 // indirect
|
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/leodido/go-syslog/v4 v4.2.0 // indirect
|
github.com/leodido/go-syslog/v4 v4.2.0 // indirect
|
||||||
github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b // indirect
|
github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b // indirect
|
||||||
@@ -151,6 +155,7 @@ require (
|
|||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common/sigv4 v0.1.0 // indirect
|
github.com/prometheus/common/sigv4 v0.1.0 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
|
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/segmentio/asm v1.2.0 // indirect
|
github.com/segmentio/asm v1.2.0 // indirect
|
||||||
github.com/segmentio/backo-go v1.0.1 // indirect
|
github.com/segmentio/backo-go v1.0.1 // indirect
|
||||||
@@ -162,8 +167,11 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
||||||
github.com/tklauser/numcpus v0.7.0 // indirect
|
github.com/tklauser/numcpus v0.7.0 // indirect
|
||||||
|
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||||
github.com/valyala/fastjson v1.6.4 // indirect
|
github.com/valyala/fastjson v1.6.4 // indirect
|
||||||
github.com/vjeantet/grok v1.0.1 // indirect
|
github.com/vjeantet/grok v1.0.1 // indirect
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
|
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
@@ -212,7 +220,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/time v0.6.0 // indirect
|
golang.org/x/time v0.6.0 // indirect
|
||||||
gonum.org/v1/gonum v0.15.1 // indirect
|
gonum.org/v1/gonum v0.15.1 // indirect
|
||||||
google.golang.org/api v0.199.0 // indirect
|
google.golang.org/api v0.199.0 // indirect
|
||||||
|
|||||||
18
go.sum
18
go.sum
@@ -436,6 +436,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
|||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/ionos-cloud/sdk-go/v6 v6.2.1 h1:mxxN+frNVmbFrmmFfXnBC3g2USYJrl6mc1LW2iNYbFY=
|
github.com/ionos-cloud/sdk-go/v6 v6.2.1 h1:mxxN+frNVmbFrmmFfXnBC3g2USYJrl6mc1LW2iNYbFY=
|
||||||
github.com/ionos-cloud/sdk-go/v6 v6.2.1/go.mod h1:SXrO9OGyWjd2rZhAhEpdYN6VUAODzzqRdqA9BCviQtI=
|
github.com/ionos-cloud/sdk-go/v6 v6.2.1/go.mod h1:SXrO9OGyWjd2rZhAhEpdYN6VUAODzzqRdqA9BCviQtI=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
@@ -661,6 +663,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
|||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
|
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||||
|
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
@@ -740,12 +744,22 @@ github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08
|
|||||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
||||||
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
||||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
||||||
|
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||||
|
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||||
|
github.com/uptrace/bun v1.2.8 h1:HEiLvy9wc7ehU5S02+O6NdV5BLz48lL4REPhTkMX3Dg=
|
||||||
|
github.com/uptrace/bun v1.2.8/go.mod h1:JBq0uBKsKqNT0Ccce1IAFZY337Wkf08c6F6qlmfOHE8=
|
||||||
|
github.com/uptrace/bun/dialect/sqlitedialect v1.2.8 h1:Huqw7YhLFTbocbSv8NETYYXqKtwLa6XsciCWtjzWSWU=
|
||||||
|
github.com/uptrace/bun/dialect/sqlitedialect v1.2.8/go.mod h1:ni7h2uwIc5zPhxgmCMTEbefONc4XsVr/ATfz1Q7d3CE=
|
||||||
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
|
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
|
||||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/vjeantet/grok v1.0.1 h1:2rhIR7J4gThTgcZ1m2JY4TrJZNgjn985U28kT2wQrJ4=
|
github.com/vjeantet/grok v1.0.1 h1:2rhIR7J4gThTgcZ1m2JY4TrJZNgjn985U28kT2wQrJ4=
|
||||||
github.com/vjeantet/grok v1.0.1/go.mod h1:ax1aAchzC6/QMXMcyzHQGZWaW1l195+uMYIkCWPCNIo=
|
github.com/vjeantet/grok v1.0.1/go.mod h1:ax1aAchzC6/QMXMcyzHQGZWaW1l195+uMYIkCWPCNIo=
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||||
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
|
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
|
||||||
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
|
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
@@ -1086,8 +1100,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
|
|||||||
14
pkg/cache/config.go
vendored
14
pkg/cache/config.go
vendored
@@ -4,12 +4,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
go_cache "github.com/patrickmn/go-cache"
|
go_cache "github.com/patrickmn/go-cache"
|
||||||
"go.signoz.io/signoz/pkg/confmap"
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config satisfies the confmap.Config interface
|
|
||||||
var _ confmap.Config = (*Config)(nil)
|
|
||||||
|
|
||||||
type Memory struct {
|
type Memory struct {
|
||||||
TTL time.Duration `mapstructure:"ttl"`
|
TTL time.Duration `mapstructure:"ttl"`
|
||||||
CleanupInterval time.Duration `mapstructure:"cleanupInterval"`
|
CleanupInterval time.Duration `mapstructure:"cleanupInterval"`
|
||||||
@@ -28,7 +25,11 @@ type Config struct {
|
|||||||
Redis Redis `mapstructure:"redis"`
|
Redis Redis `mapstructure:"redis"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) NewWithDefaults() confmap.Config {
|
func NewConfigFactory() factory.ConfigFactory {
|
||||||
|
return factory.NewConfigFactory(factory.MustNewName("cache"), newConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfig() factory.Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Provider: "memory",
|
Provider: "memory",
|
||||||
Memory: Memory{
|
Memory: Memory{
|
||||||
@@ -42,8 +43,9 @@ func (c *Config) NewWithDefaults() confmap.Config {
|
|||||||
DB: 0,
|
DB: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Validate() error {
|
func (c Config) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
101
pkg/cache/memorycache/provider.go
vendored
Normal file
101
pkg/cache/memorycache/provider.go
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package memorycache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
go_cache "github.com/patrickmn/go-cache"
|
||||||
|
"go.signoz.io/signoz/pkg/cache"
|
||||||
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
|
)
|
||||||
|
|
||||||
|
type provider struct {
|
||||||
|
cc *go_cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFactory() factory.ProviderFactory[cache.Cache, cache.Config] {
|
||||||
|
return factory.NewProviderFactory(factory.MustNewName("memory"), New)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, settings factory.ProviderSettings, config cache.Config) (cache.Cache, error) {
|
||||||
|
return &provider{cc: go_cache.New(config.Memory.TTL, config.Memory.CleanupInterval)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect does nothing
|
||||||
|
func (c *provider) Connect(_ context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store stores the data in the cache
|
||||||
|
func (c *provider) Store(_ context.Context, cacheKey string, data cache.CacheableEntity, ttl time.Duration) error {
|
||||||
|
// check if the data being passed is a pointer and is not nil
|
||||||
|
rv := reflect.ValueOf(data)
|
||||||
|
if rv.Kind() != reflect.Pointer || rv.IsNil() {
|
||||||
|
return cache.WrapCacheableEntityErrors(reflect.TypeOf(data), "inmemory")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cc.Set(cacheKey, data, ttl)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve retrieves the data from the cache
|
||||||
|
func (c *provider) Retrieve(_ context.Context, cacheKey string, dest cache.CacheableEntity, allowExpired bool) (cache.RetrieveStatus, error) {
|
||||||
|
// check if the destination being passed is a pointer and is not nil
|
||||||
|
dstv := reflect.ValueOf(dest)
|
||||||
|
if dstv.Kind() != reflect.Pointer || dstv.IsNil() {
|
||||||
|
return cache.RetrieveStatusError, cache.WrapCacheableEntityErrors(reflect.TypeOf(dest), "inmemory")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the destination value is settable
|
||||||
|
if !dstv.Elem().CanSet() {
|
||||||
|
return cache.RetrieveStatusError, fmt.Errorf("destination value is not settable, %s", dstv.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
data, found := c.cc.Get(cacheKey)
|
||||||
|
if !found {
|
||||||
|
return cache.RetrieveStatusKeyMiss, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the type compatbility between the src and dest
|
||||||
|
srcv := reflect.ValueOf(data)
|
||||||
|
if !srcv.Type().AssignableTo(dstv.Type()) {
|
||||||
|
return cache.RetrieveStatusError, fmt.Errorf("src type is not assignable to dst type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the value to from src to dest
|
||||||
|
dstv.Elem().Set(srcv.Elem())
|
||||||
|
return cache.RetrieveStatusHit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTTL sets the TTL for the cache entry
|
||||||
|
func (c *provider) SetTTL(_ context.Context, cacheKey string, ttl time.Duration) {
|
||||||
|
item, found := c.cc.Get(cacheKey)
|
||||||
|
if !found {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.cc.Replace(cacheKey, item, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes the cache entry
|
||||||
|
func (c *provider) Remove(_ context.Context, cacheKey string) {
|
||||||
|
c.cc.Delete(cacheKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BulkRemove removes the cache entries
|
||||||
|
func (c *provider) BulkRemove(_ context.Context, cacheKeys []string) {
|
||||||
|
for _, cacheKey := range cacheKeys {
|
||||||
|
c.cc.Delete(cacheKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close does nothing
|
||||||
|
func (c *provider) Close(_ context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration returns the cache configuration
|
||||||
|
func (c *provider) Configuration() *cache.Memory {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package memory
|
package memorycache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -7,18 +7,21 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
_cache "go.signoz.io/signoz/pkg/cache"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.signoz.io/signoz/pkg/cache"
|
||||||
|
"go.signoz.io/signoz/pkg/factory/providertest"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestNew tests the New function
|
// TestNew tests the New function
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
assert.NotNil(t, c.cc)
|
assert.NotNil(t, c.(*provider).cc)
|
||||||
assert.NoError(t, c.Connect(context.Background()))
|
assert.NoError(t, c.Connect(context.Background()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,32 +56,35 @@ func (dce DCacheableEntity) UnmarshalBinary(data []byte) error {
|
|||||||
// TestStore tests the Store function
|
// TestStore tests the Store function
|
||||||
// this should fail because of nil pointer error
|
// this should fail because of nil pointer error
|
||||||
func TestStoreWithNilPointer(t *testing.T) {
|
func TestStoreWithNilPointer(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
var storeCacheableEntity *CacheableEntity
|
var storeCacheableEntity *CacheableEntity
|
||||||
assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
|
assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
|
||||||
}
|
}
|
||||||
|
|
||||||
// this should fail because of no pointer error
|
// this should fail because of no pointer error
|
||||||
func TestStoreWithStruct(t *testing.T) {
|
func TestStoreWithStruct(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
var storeCacheableEntity CacheableEntity
|
var storeCacheableEntity CacheableEntity
|
||||||
assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
|
assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStoreWithNonNilPointer(t *testing.T) {
|
func TestStoreWithNonNilPointer(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
storeCacheableEntity := &CacheableEntity{
|
storeCacheableEntity := &CacheableEntity{
|
||||||
Key: "some-random-key",
|
Key: "some-random-key",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@@ -89,11 +95,12 @@ func TestStoreWithNonNilPointer(t *testing.T) {
|
|||||||
|
|
||||||
// TestRetrieve tests the Retrieve function
|
// TestRetrieve tests the Retrieve function
|
||||||
func TestRetrieveWithNilPointer(t *testing.T) {
|
func TestRetrieveWithNilPointer(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
storeCacheableEntity := &CacheableEntity{
|
storeCacheableEntity := &CacheableEntity{
|
||||||
Key: "some-random-key",
|
Key: "some-random-key",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@@ -105,15 +112,16 @@ func TestRetrieveWithNilPointer(t *testing.T) {
|
|||||||
|
|
||||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError)
|
assert.Equal(t, retrieveStatus, cache.RetrieveStatusError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRetrieveWitNonPointer(t *testing.T) {
|
func TestRetrieveWitNonPointer(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
storeCacheableEntity := &CacheableEntity{
|
storeCacheableEntity := &CacheableEntity{
|
||||||
Key: "some-random-key",
|
Key: "some-random-key",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@@ -125,15 +133,16 @@ func TestRetrieveWitNonPointer(t *testing.T) {
|
|||||||
|
|
||||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError)
|
assert.Equal(t, retrieveStatus, cache.RetrieveStatusError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRetrieveWithDifferentTypes(t *testing.T) {
|
func TestRetrieveWithDifferentTypes(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
storeCacheableEntity := &CacheableEntity{
|
storeCacheableEntity := &CacheableEntity{
|
||||||
Key: "some-random-key",
|
Key: "some-random-key",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@@ -144,15 +153,16 @@ func TestRetrieveWithDifferentTypes(t *testing.T) {
|
|||||||
retrieveCacheableEntity := new(DCacheableEntity)
|
retrieveCacheableEntity := new(DCacheableEntity)
|
||||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError)
|
assert.Equal(t, retrieveStatus, cache.RetrieveStatusError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRetrieveWithSameTypes(t *testing.T) {
|
func TestRetrieveWithSameTypes(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
storeCacheableEntity := &CacheableEntity{
|
storeCacheableEntity := &CacheableEntity{
|
||||||
Key: "some-random-key",
|
Key: "some-random-key",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@@ -163,13 +173,14 @@ func TestRetrieveWithSameTypes(t *testing.T) {
|
|||||||
retrieveCacheableEntity := new(CacheableEntity)
|
retrieveCacheableEntity := new(CacheableEntity)
|
||||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit)
|
assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit)
|
||||||
assert.Equal(t, storeCacheableEntity, retrieveCacheableEntity)
|
assert.Equal(t, storeCacheableEntity, retrieveCacheableEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSetTTL tests the SetTTL function
|
// TestSetTTL tests the SetTTL function
|
||||||
func TestSetTTL(t *testing.T) {
|
func TestSetTTL(t *testing.T) {
|
||||||
c := New(&_cache.Memory{TTL: 10 * time.Second, CleanupInterval: 1 * time.Second})
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: cache.Memory{TTL: 10 * time.Second, CleanupInterval: 1 * time.Second}})
|
||||||
|
require.NoError(t, err)
|
||||||
storeCacheableEntity := &CacheableEntity{
|
storeCacheableEntity := &CacheableEntity{
|
||||||
Key: "some-random-key",
|
Key: "some-random-key",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@@ -180,7 +191,7 @@ func TestSetTTL(t *testing.T) {
|
|||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
|
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
|
||||||
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
||||||
|
|
||||||
assert.NoError(t, c.Store(context.Background(), "key", storeCacheableEntity, 2*time.Second))
|
assert.NoError(t, c.Store(context.Background(), "key", storeCacheableEntity, 2*time.Second))
|
||||||
@@ -188,17 +199,18 @@ func TestSetTTL(t *testing.T) {
|
|||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
retrieveStatus, err = c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
retrieveStatus, err = c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit)
|
assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit)
|
||||||
assert.Equal(t, retrieveCacheableEntity, storeCacheableEntity)
|
assert.Equal(t, retrieveCacheableEntity, storeCacheableEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRemove tests the Remove function
|
// TestRemove tests the Remove function
|
||||||
func TestRemove(t *testing.T) {
|
func TestRemove(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
storeCacheableEntity := &CacheableEntity{
|
storeCacheableEntity := &CacheableEntity{
|
||||||
Key: "some-random-key",
|
Key: "some-random-key",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@@ -210,17 +222,18 @@ func TestRemove(t *testing.T) {
|
|||||||
|
|
||||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
|
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
|
||||||
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestBulkRemove tests the BulkRemove function
|
// TestBulkRemove tests the BulkRemove function
|
||||||
func TestBulkRemove(t *testing.T) {
|
func TestBulkRemove(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
storeCacheableEntity := &CacheableEntity{
|
storeCacheableEntity := &CacheableEntity{
|
||||||
Key: "some-random-key",
|
Key: "some-random-key",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@@ -233,22 +246,23 @@ func TestBulkRemove(t *testing.T) {
|
|||||||
|
|
||||||
retrieveStatus, err := c.Retrieve(context.Background(), "key1", retrieveCacheableEntity, false)
|
retrieveStatus, err := c.Retrieve(context.Background(), "key1", retrieveCacheableEntity, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
|
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
|
||||||
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
||||||
|
|
||||||
retrieveStatus, err = c.Retrieve(context.Background(), "key2", retrieveCacheableEntity, false)
|
retrieveStatus, err = c.Retrieve(context.Background(), "key2", retrieveCacheableEntity, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
|
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
|
||||||
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCache tests the cache
|
// TestCache tests the cache
|
||||||
func TestCache(t *testing.T) {
|
func TestCache(t *testing.T) {
|
||||||
opts := &_cache.Memory{
|
opts := cache.Memory{
|
||||||
TTL: 10 * time.Second,
|
TTL: 10 * time.Second,
|
||||||
CleanupInterval: 10 * time.Second,
|
CleanupInterval: 10 * time.Second,
|
||||||
}
|
}
|
||||||
c := New(opts)
|
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
|
||||||
|
require.NoError(t, err)
|
||||||
storeCacheableEntity := &CacheableEntity{
|
storeCacheableEntity := &CacheableEntity{
|
||||||
Key: "some-random-key",
|
Key: "some-random-key",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@@ -258,7 +272,7 @@ func TestCache(t *testing.T) {
|
|||||||
assert.NoError(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
|
assert.NoError(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
|
||||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit)
|
assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit)
|
||||||
assert.Equal(t, storeCacheableEntity, retrieveCacheableEntity)
|
assert.Equal(t, storeCacheableEntity, retrieveCacheableEntity)
|
||||||
c.Remove(context.Background(), "key")
|
c.Remove(context.Background(), "key")
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package redis
|
package rediscache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -7,26 +7,31 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
_cache "go.signoz.io/signoz/pkg/cache"
|
"go.signoz.io/signoz/pkg/cache"
|
||||||
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cache struct {
|
type provider struct {
|
||||||
client *redis.Client
|
client *redis.Client
|
||||||
opts *_cache.Redis
|
opts cache.Redis
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(opts *_cache.Redis) *cache {
|
func NewFactory() factory.ProviderFactory[cache.Cache, cache.Config] {
|
||||||
return &cache{opts: opts}
|
return factory.NewProviderFactory(factory.MustNewName("redis"), New)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, settings factory.ProviderSettings, config cache.Config) (cache.Cache, error) {
|
||||||
|
return &provider{opts: config.Redis}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithClient creates a new cache with the given client
|
// WithClient creates a new cache with the given client
|
||||||
func WithClient(client *redis.Client) *cache {
|
func WithClient(client *redis.Client) *provider {
|
||||||
return &cache{client: client}
|
return &provider{client: client}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect connects to the redis server
|
// Connect connects to the redis server
|
||||||
func (c *cache) Connect(_ context.Context) error {
|
func (c *provider) Connect(_ context.Context) error {
|
||||||
c.client = redis.NewClient(&redis.Options{
|
c.client = redis.NewClient(&redis.Options{
|
||||||
Addr: fmt.Sprintf("%s:%d", c.opts.Host, c.opts.Port),
|
Addr: fmt.Sprintf("%s:%d", c.opts.Host, c.opts.Port),
|
||||||
Password: c.opts.Password,
|
Password: c.opts.Password,
|
||||||
@@ -36,24 +41,24 @@ func (c *cache) Connect(_ context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store stores the data in the cache
|
// Store stores the data in the cache
|
||||||
func (c *cache) Store(ctx context.Context, cacheKey string, data _cache.CacheableEntity, ttl time.Duration) error {
|
func (c *provider) Store(ctx context.Context, cacheKey string, data cache.CacheableEntity, ttl time.Duration) error {
|
||||||
return c.client.Set(ctx, cacheKey, data, ttl).Err()
|
return c.client.Set(ctx, cacheKey, data, ttl).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve retrieves the data from the cache
|
// Retrieve retrieves the data from the cache
|
||||||
func (c *cache) Retrieve(ctx context.Context, cacheKey string, dest _cache.CacheableEntity, allowExpired bool) (_cache.RetrieveStatus, error) {
|
func (c *provider) Retrieve(ctx context.Context, cacheKey string, dest cache.CacheableEntity, allowExpired bool) (cache.RetrieveStatus, error) {
|
||||||
err := c.client.Get(ctx, cacheKey).Scan(dest)
|
err := c.client.Get(ctx, cacheKey).Scan(dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, redis.Nil) {
|
if errors.Is(err, redis.Nil) {
|
||||||
return _cache.RetrieveStatusKeyMiss, nil
|
return cache.RetrieveStatusKeyMiss, nil
|
||||||
}
|
}
|
||||||
return _cache.RetrieveStatusError, err
|
return cache.RetrieveStatusError, err
|
||||||
}
|
}
|
||||||
return _cache.RetrieveStatusHit, nil
|
return cache.RetrieveStatusHit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTTL sets the TTL for the cache entry
|
// SetTTL sets the TTL for the cache entry
|
||||||
func (c *cache) SetTTL(ctx context.Context, cacheKey string, ttl time.Duration) {
|
func (c *provider) SetTTL(ctx context.Context, cacheKey string, ttl time.Duration) {
|
||||||
err := c.client.Expire(ctx, cacheKey, ttl).Err()
|
err := c.client.Expire(ctx, cacheKey, ttl).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("error setting TTL for cache key", zap.String("cacheKey", cacheKey), zap.Duration("ttl", ttl), zap.Error(err))
|
zap.L().Error("error setting TTL for cache key", zap.String("cacheKey", cacheKey), zap.Duration("ttl", ttl), zap.Error(err))
|
||||||
@@ -61,39 +66,34 @@ func (c *cache) SetTTL(ctx context.Context, cacheKey string, ttl time.Duration)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the cache entry
|
// Remove removes the cache entry
|
||||||
func (c *cache) Remove(ctx context.Context, cacheKey string) {
|
func (c *provider) Remove(ctx context.Context, cacheKey string) {
|
||||||
c.BulkRemove(ctx, []string{cacheKey})
|
c.BulkRemove(ctx, []string{cacheKey})
|
||||||
}
|
}
|
||||||
|
|
||||||
// BulkRemove removes the cache entries
|
// BulkRemove removes the cache entries
|
||||||
func (c *cache) BulkRemove(ctx context.Context, cacheKeys []string) {
|
func (c *provider) BulkRemove(ctx context.Context, cacheKeys []string) {
|
||||||
if err := c.client.Del(ctx, cacheKeys...).Err(); err != nil {
|
if err := c.client.Del(ctx, cacheKeys...).Err(); err != nil {
|
||||||
zap.L().Error("error deleting cache keys", zap.Strings("cacheKeys", cacheKeys), zap.Error(err))
|
zap.L().Error("error deleting cache keys", zap.Strings("cacheKeys", cacheKeys), zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the connection to the redis server
|
// Close closes the connection to the redis server
|
||||||
func (c *cache) Close(_ context.Context) error {
|
func (c *provider) Close(_ context.Context) error {
|
||||||
return c.client.Close()
|
return c.client.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping pings the redis server
|
// Ping pings the redis server
|
||||||
func (c *cache) Ping(ctx context.Context) error {
|
func (c *provider) Ping(ctx context.Context) error {
|
||||||
return c.client.Ping(ctx).Err()
|
return c.client.Ping(ctx).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClient returns the redis client
|
// GetClient returns the redis client
|
||||||
func (c *cache) GetClient() *redis.Client {
|
func (c *provider) GetClient() *redis.Client {
|
||||||
return c.client
|
return c.client
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOptions returns the options
|
|
||||||
func (c *cache) GetOptions() *_cache.Redis {
|
|
||||||
return c.opts
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTTL returns the TTL for the cache entry
|
// GetTTL returns the TTL for the cache entry
|
||||||
func (c *cache) GetTTL(ctx context.Context, cacheKey string) time.Duration {
|
func (c *provider) GetTTL(ctx context.Context, cacheKey string) time.Duration {
|
||||||
ttl, err := c.client.TTL(ctx, cacheKey).Result()
|
ttl, err := c.client.TTL(ctx, cacheKey).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("error getting TTL for cache key", zap.String("cacheKey", cacheKey), zap.Error(err))
|
zap.L().Error("error getting TTL for cache key", zap.String("cacheKey", cacheKey), zap.Error(err))
|
||||||
@@ -102,12 +102,12 @@ func (c *cache) GetTTL(ctx context.Context, cacheKey string) time.Duration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetKeys returns the keys matching the pattern
|
// GetKeys returns the keys matching the pattern
|
||||||
func (c *cache) GetKeys(ctx context.Context, pattern string) ([]string, error) {
|
func (c *provider) GetKeys(ctx context.Context, pattern string) ([]string, error) {
|
||||||
return c.client.Keys(ctx, pattern).Result()
|
return c.client.Keys(ctx, pattern).Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeysWithTTL returns the keys matching the pattern with their TTL
|
// GetKeysWithTTL returns the keys matching the pattern with their TTL
|
||||||
func (c *cache) GetKeysWithTTL(ctx context.Context, pattern string) (map[string]time.Duration, error) {
|
func (c *provider) GetKeysWithTTL(ctx context.Context, pattern string) (map[string]time.Duration, error) {
|
||||||
keys, err := c.GetKeys(ctx, pattern)
|
keys, err := c.GetKeys(ctx, pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package redis
|
package rediscache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
96
pkg/cache/strategy/memory/memory.go
vendored
96
pkg/cache/strategy/memory/memory.go
vendored
@@ -1,96 +0,0 @@
|
|||||||
package memory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
go_cache "github.com/patrickmn/go-cache"
|
|
||||||
_cache "go.signoz.io/signoz/pkg/cache"
|
|
||||||
)
|
|
||||||
|
|
||||||
type cache struct {
|
|
||||||
cc *go_cache.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(opts *_cache.Memory) *cache {
|
|
||||||
return &cache{cc: go_cache.New(opts.TTL, opts.CleanupInterval)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect does nothing
|
|
||||||
func (c *cache) Connect(_ context.Context) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store stores the data in the cache
|
|
||||||
func (c *cache) Store(_ context.Context, cacheKey string, data _cache.CacheableEntity, ttl time.Duration) error {
|
|
||||||
// check if the data being passed is a pointer and is not nil
|
|
||||||
rv := reflect.ValueOf(data)
|
|
||||||
if rv.Kind() != reflect.Pointer || rv.IsNil() {
|
|
||||||
return _cache.WrapCacheableEntityErrors(reflect.TypeOf(data), "inmemory")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cc.Set(cacheKey, data, ttl)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve retrieves the data from the cache
|
|
||||||
func (c *cache) Retrieve(_ context.Context, cacheKey string, dest _cache.CacheableEntity, allowExpired bool) (_cache.RetrieveStatus, error) {
|
|
||||||
// check if the destination being passed is a pointer and is not nil
|
|
||||||
dstv := reflect.ValueOf(dest)
|
|
||||||
if dstv.Kind() != reflect.Pointer || dstv.IsNil() {
|
|
||||||
return _cache.RetrieveStatusError, _cache.WrapCacheableEntityErrors(reflect.TypeOf(dest), "inmemory")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the destination value is settable
|
|
||||||
if !dstv.Elem().CanSet() {
|
|
||||||
return _cache.RetrieveStatusError, fmt.Errorf("destination value is not settable, %s", dstv.Elem())
|
|
||||||
}
|
|
||||||
|
|
||||||
data, found := c.cc.Get(cacheKey)
|
|
||||||
if !found {
|
|
||||||
return _cache.RetrieveStatusKeyMiss, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the type compatbility between the src and dest
|
|
||||||
srcv := reflect.ValueOf(data)
|
|
||||||
if !srcv.Type().AssignableTo(dstv.Type()) {
|
|
||||||
return _cache.RetrieveStatusError, fmt.Errorf("src type is not assignable to dst type")
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the value to from src to dest
|
|
||||||
dstv.Elem().Set(srcv.Elem())
|
|
||||||
return _cache.RetrieveStatusHit, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTTL sets the TTL for the cache entry
|
|
||||||
func (c *cache) SetTTL(_ context.Context, cacheKey string, ttl time.Duration) {
|
|
||||||
item, found := c.cc.Get(cacheKey)
|
|
||||||
if !found {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.cc.Replace(cacheKey, item, ttl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes the cache entry
|
|
||||||
func (c *cache) Remove(_ context.Context, cacheKey string) {
|
|
||||||
c.cc.Delete(cacheKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BulkRemove removes the cache entries
|
|
||||||
func (c *cache) BulkRemove(_ context.Context, cacheKeys []string) {
|
|
||||||
for _, cacheKey := range cacheKeys {
|
|
||||||
c.cc.Delete(cacheKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close does nothing
|
|
||||||
func (c *cache) Close(_ context.Context) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuration returns the cache configuration
|
|
||||||
func (c *cache) Configuration() *_cache.Memory {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
90
pkg/config/conf.go
Normal file
90
pkg/config/conf.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-viper/mapstructure/v2"
|
||||||
|
"github.com/knadh/koanf/providers/confmap"
|
||||||
|
"github.com/knadh/koanf/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KoanfDelimiter string = "::"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Conf is a wrapper around the koanf library.
|
||||||
|
type Conf struct {
|
||||||
|
*koanf.Koanf
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConf creates a new Conf instance.
|
||||||
|
func NewConf() *Conf {
|
||||||
|
return &Conf{koanf.New(KoanfDelimiter)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfFromMap creates a new Conf instance from a map.
|
||||||
|
func NewConfFromMap(m map[string]any) (*Conf, error) {
|
||||||
|
conf := NewConf()
|
||||||
|
if err := conf.Koanf.Load(confmap.Provider(m, KoanfDelimiter), nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustNewConfFromMap creates a new Conf instance from a map.
|
||||||
|
// It panics if the conf cannot be created.
|
||||||
|
func MustNewConfFromMap(m map[string]any) *Conf {
|
||||||
|
conf, err := NewConfFromMap(m)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges the current configuration with the input configuration.
|
||||||
|
func (conf *Conf) Merge(input *Conf) error {
|
||||||
|
return conf.Koanf.Merge(input.Koanf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges the current configuration with the input configuration.
|
||||||
|
func (conf *Conf) MergeAt(input *Conf, path string) error {
|
||||||
|
return conf.Koanf.MergeAt(input.Koanf, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal unmarshals the configuration at the given path into the input.
|
||||||
|
// It uses a WeaklyTypedInput to allow for more flexible unmarshalling.
|
||||||
|
func (conf *Conf) Unmarshal(path string, input any) error {
|
||||||
|
dc := &mapstructure.DecoderConfig{
|
||||||
|
TagName: "mapstructure",
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||||
|
mapstructure.StringToSliceHookFunc(","),
|
||||||
|
mapstructure.StringToTimeDurationHookFunc(),
|
||||||
|
mapstructure.TextUnmarshallerHookFunc(),
|
||||||
|
),
|
||||||
|
Result: input,
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf.Koanf.UnmarshalWithConf(path, input, koanf.UnmarshalConf{Tag: "mapstructure", DecoderConfig: dc})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the configuration at the given key.
|
||||||
|
// It decodes the input into a map as per mapstructure.Decode and then merges it into the configuration.
|
||||||
|
func (conf *Conf) Set(key string, input any) error {
|
||||||
|
m := map[string]any{}
|
||||||
|
err := mapstructure.Decode(input, &m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newConf := NewConf()
|
||||||
|
if err := newConf.Koanf.Load(confmap.Provider(m, KoanfDelimiter), nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conf.Koanf.MergeAt(newConf.Koanf, key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
38
pkg/config/conf_test.go
Normal file
38
pkg/config/conf_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfMerge(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
conf *Conf
|
||||||
|
input *Conf
|
||||||
|
expected *Conf
|
||||||
|
pass bool
|
||||||
|
}{
|
||||||
|
{name: "Empty", conf: NewConf(), input: NewConf(), expected: NewConf(), pass: true},
|
||||||
|
{name: "Merge", conf: MustNewConfFromMap(map[string]any{"a": "b"}), input: MustNewConfFromMap(map[string]any{"c": "d"}), expected: MustNewConfFromMap(map[string]any{"a": "b", "c": "d"}), pass: true},
|
||||||
|
{name: "NestedMerge", conf: MustNewConfFromMap(map[string]any{"a": map[string]any{"b": "v1", "c": "v2"}}), input: MustNewConfFromMap(map[string]any{"a": map[string]any{"d": "v1", "e": "v2"}}), expected: MustNewConfFromMap(map[string]any{"a": map[string]any{"b": "v1", "c": "v2", "d": "v1", "e": "v2"}}), pass: true},
|
||||||
|
{name: "Override", conf: MustNewConfFromMap(map[string]any{"a": "b"}), input: MustNewConfFromMap(map[string]any{"a": "c"}), expected: MustNewConfFromMap(map[string]any{"a": "c"}), pass: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := tc.conf.Merge(tc.input)
|
||||||
|
if !tc.pass {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.expected.Raw(), tc.conf.Raw())
|
||||||
|
assert.Equal(t, tc.expected.Raw(), tc.conf.Raw())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,33 +3,34 @@ package config
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/cache"
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
signozconfmap "go.signoz.io/signoz/pkg/confmap"
|
|
||||||
"go.signoz.io/signoz/pkg/instrumentation"
|
|
||||||
"go.signoz.io/signoz/pkg/web"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// This map contains the default values of all config structs
|
func New(ctx context.Context, resolverConfig ResolverConfig, configFactories []factory.ConfigFactory) (*Conf, error) {
|
||||||
var (
|
// Get the config from the resolver
|
||||||
defaults = map[string]signozconfmap.Config{
|
resolver, err := NewResolver(resolverConfig)
|
||||||
"instrumentation": &instrumentation.Config{},
|
|
||||||
"web": &web.Config{},
|
|
||||||
"cache": &cache.Config{},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config defines the entire configuration of signoz.
|
|
||||||
type Config struct {
|
|
||||||
Instrumentation instrumentation.Config `mapstructure:"instrumentation"`
|
|
||||||
Web web.Config `mapstructure:"web"`
|
|
||||||
Cache cache.Config `mapstructure:"cache"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(ctx context.Context, settings ProviderSettings) (*Config, error) {
|
|
||||||
provider, err := NewProvider(settings)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider.Get(ctx)
|
resolvedConf, err := resolver.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := NewConf()
|
||||||
|
// Set the default configs
|
||||||
|
for _, factory := range configFactories {
|
||||||
|
c := factory.New()
|
||||||
|
if err := conf.Set(factory.Name().String(), c); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conf.Merge(resolvedConf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.opentelemetry.io/collector/confmap"
|
|
||||||
"go.signoz.io/signoz/pkg/cache"
|
|
||||||
"go.signoz.io/signoz/pkg/confmap/provider/signozenvprovider"
|
|
||||||
"go.signoz.io/signoz/pkg/web"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewWithSignozEnvProvider(t *testing.T) {
|
|
||||||
|
|
||||||
t.Setenv("SIGNOZ__WEB__PREFIX", "/web")
|
|
||||||
t.Setenv("SIGNOZ__WEB__DIRECTORY", "/build")
|
|
||||||
t.Setenv("SIGNOZ__CACHE__PROVIDER", "redis")
|
|
||||||
t.Setenv("SIGNOZ__CACHE__REDIS__HOST", "127.0.0.1")
|
|
||||||
|
|
||||||
config, err := New(context.Background(), ProviderSettings{
|
|
||||||
ResolverSettings: confmap.ResolverSettings{
|
|
||||||
URIs: []string{"signozenv:"},
|
|
||||||
ProviderFactories: []confmap.ProviderFactory{
|
|
||||||
signozenvprovider.NewFactory(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected := &Config{
|
|
||||||
Web: web.Config{
|
|
||||||
Prefix: "/web",
|
|
||||||
Directory: "/build",
|
|
||||||
},
|
|
||||||
Cache: cache.Config{
|
|
||||||
Provider: "redis",
|
|
||||||
Memory: cache.Memory{
|
|
||||||
TTL: time.Duration(-1),
|
|
||||||
CleanupInterval: 1 * time.Minute,
|
|
||||||
},
|
|
||||||
Redis: cache.Redis{
|
|
||||||
Host: "127.0.0.1",
|
|
||||||
Port: 6379,
|
|
||||||
Password: "",
|
|
||||||
DB: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, expected, config)
|
|
||||||
}
|
|
||||||
71
pkg/config/envprovider/provider.go
Normal file
71
pkg/config/envprovider/provider.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package envprovider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
koanfenv "github.com/knadh/koanf/providers/env"
|
||||||
|
"go.signoz.io/signoz/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
prefix string = "SIGNOZ_"
|
||||||
|
scheme string = "env"
|
||||||
|
)
|
||||||
|
|
||||||
|
type provider struct{}
|
||||||
|
|
||||||
|
func NewFactory() config.ProviderFactory {
|
||||||
|
return config.NewProviderFactory(New)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config config.ProviderConfig) config.Provider {
|
||||||
|
return &provider{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Scheme() string {
|
||||||
|
return scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Get(ctx context.Context, uri config.Uri) (*config.Conf, error) {
|
||||||
|
conf := config.NewConf()
|
||||||
|
err := conf.Load(
|
||||||
|
koanfenv.Provider(
|
||||||
|
prefix,
|
||||||
|
// Do not set this to `_`. The correct delimiter is being set by the custom callback provided below.
|
||||||
|
// Since this had to be passed, using `config.KoanfDelimiter` eliminates any possible side effect.
|
||||||
|
config.KoanfDelimiter,
|
||||||
|
func(s string) string {
|
||||||
|
s = strings.ToLower(strings.TrimPrefix(s, prefix))
|
||||||
|
return provider.cb(s, config.KoanfDelimiter)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
return conf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) cb(s string, delim string) string {
|
||||||
|
delims := []rune(delim)
|
||||||
|
runes := []rune(s)
|
||||||
|
result := make([]rune, 0, len(runes))
|
||||||
|
|
||||||
|
for i := 0; i < len(runes); i++ {
|
||||||
|
// Check for double underscore pattern
|
||||||
|
if i < len(runes)-1 && runes[i] == '_' && runes[i+1] == '_' {
|
||||||
|
result = append(result, '_')
|
||||||
|
i++ // Skip next underscore
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if runes[i] == '_' {
|
||||||
|
result = append(result, delims...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, runes[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
90
pkg/config/envprovider/provider_test.go
Normal file
90
pkg/config/envprovider/provider_test.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package envprovider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.signoz.io/signoz/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetWithStrings(t *testing.T) {
|
||||||
|
t.Setenv("SIGNOZ_K1_K2", "string")
|
||||||
|
t.Setenv("SIGNOZ_K3__K4", "string")
|
||||||
|
t.Setenv("SIGNOZ_K5__K6_K7__K8", "string")
|
||||||
|
t.Setenv("SIGNOZ_K9___K10", "string")
|
||||||
|
t.Setenv("SIGNOZ_K11____K12", "string")
|
||||||
|
expected := map[string]any{
|
||||||
|
"k1::k2": "string",
|
||||||
|
"k3_k4": "string",
|
||||||
|
"k5_k6::k7_k8": "string",
|
||||||
|
"k9_::k10": "string",
|
||||||
|
"k11__k12": "string",
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := New(config.ProviderConfig{})
|
||||||
|
actual, err := provider.Get(context.Background(), config.MustNewUri("env:"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual.All())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetWithNoPrefix(t *testing.T) {
|
||||||
|
t.Setenv("K1_K2", "string")
|
||||||
|
t.Setenv("K3_K4", "string")
|
||||||
|
expected := map[string]any{}
|
||||||
|
|
||||||
|
provider := New(config.ProviderConfig{})
|
||||||
|
actual, err := provider.Get(context.Background(), config.MustNewUri("env:"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual.All())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetWithGoTypes(t *testing.T) {
|
||||||
|
t.Setenv("SIGNOZ_BOOL", "true")
|
||||||
|
t.Setenv("SIGNOZ_STRING", "string")
|
||||||
|
t.Setenv("SIGNOZ_INT", "1")
|
||||||
|
t.Setenv("SIGNOZ_SLICE", "[1,2]")
|
||||||
|
expected := map[string]any{
|
||||||
|
"bool": "true",
|
||||||
|
"int": "1",
|
||||||
|
"slice": "[1,2]",
|
||||||
|
"string": "string",
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := New(config.ProviderConfig{})
|
||||||
|
actual, err := provider.Get(context.Background(), config.MustNewUri("env:"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual.All())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetWithGoTypesWithUnmarshal(t *testing.T) {
|
||||||
|
t.Setenv("SIGNOZ_BOOL", "true")
|
||||||
|
t.Setenv("SIGNOZ_STRING", "string")
|
||||||
|
t.Setenv("SIGNOZ_INT", "1")
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
Bool bool `mapstructure:"bool"`
|
||||||
|
String string `mapstructure:"string"`
|
||||||
|
Int int `mapstructure:"int"`
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := test{
|
||||||
|
Bool: true,
|
||||||
|
String: "string",
|
||||||
|
Int: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := New(config.ProviderConfig{})
|
||||||
|
conf, err := provider.Get(context.Background(), config.MustNewUri("env:"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
actual := test{}
|
||||||
|
err = conf.Unmarshal("", &actual)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
34
pkg/config/fileprovider/provider.go
Normal file
34
pkg/config/fileprovider/provider.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package fileprovider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
koanfyaml "github.com/knadh/koanf/parsers/yaml"
|
||||||
|
koanffile "github.com/knadh/koanf/providers/file"
|
||||||
|
"go.signoz.io/signoz/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
scheme string = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
type provider struct{}
|
||||||
|
|
||||||
|
func NewFactory() config.ProviderFactory {
|
||||||
|
return config.NewProviderFactory(New)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config config.ProviderConfig) config.Provider {
|
||||||
|
return &provider{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Scheme() string {
|
||||||
|
return scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Get(ctx context.Context, uri config.Uri) (*config.Conf, error) {
|
||||||
|
conf := config.NewConf()
|
||||||
|
err := conf.Load(koanffile.Provider(uri.Value()), koanfyaml.Parser())
|
||||||
|
|
||||||
|
return conf, err
|
||||||
|
}
|
||||||
68
pkg/config/fileprovider/provider_test.go
Normal file
68
pkg/config/fileprovider/provider_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package fileprovider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.signoz.io/signoz/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetWithStrings(t *testing.T) {
|
||||||
|
expected := map[string]any{
|
||||||
|
"k1::k2": "string",
|
||||||
|
"k3_k4": "string",
|
||||||
|
"k5_k6::k7_k8": "string",
|
||||||
|
"k9_::k10": "string",
|
||||||
|
"k11__k12": "string",
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := New(config.ProviderConfig{})
|
||||||
|
actual, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "strings.yaml")))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual.All())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetWithGoTypes(t *testing.T) {
|
||||||
|
expected := map[string]any{
|
||||||
|
"bool": true,
|
||||||
|
"int": 1,
|
||||||
|
"slice": []any{1, 2},
|
||||||
|
"string": "string",
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := New(config.ProviderConfig{})
|
||||||
|
actual, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "gotypes.yaml")))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual.All())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetWithGoTypesWithUnmarshal(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
Bool bool `mapstructure:"bool"`
|
||||||
|
String string `mapstructure:"string"`
|
||||||
|
Int int `mapstructure:"int"`
|
||||||
|
Slice []any `mapstructure:"slice"`
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := test{
|
||||||
|
Bool: true,
|
||||||
|
String: "string",
|
||||||
|
Int: 1,
|
||||||
|
Slice: []any{1, 2},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := New(config.ProviderConfig{})
|
||||||
|
conf, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "gotypes.yaml")))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
actual := test{}
|
||||||
|
err = conf.Unmarshal("", &actual)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
6
pkg/config/fileprovider/testdata/gotypes.yaml
vendored
Normal file
6
pkg/config/fileprovider/testdata/gotypes.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
bool: true
|
||||||
|
string: string
|
||||||
|
int: 1
|
||||||
|
slice:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
8
pkg/config/fileprovider/testdata/strings.yaml
vendored
Normal file
8
pkg/config/fileprovider/testdata/strings.yaml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
k1:
|
||||||
|
k2: string
|
||||||
|
k3_k4: string
|
||||||
|
k5_k6:
|
||||||
|
k7_k8: string
|
||||||
|
k9_:
|
||||||
|
k10: string
|
||||||
|
k11__k12: string
|
||||||
@@ -2,51 +2,38 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/collector/confmap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provides the configuration for signoz.
|
// NewProviderFunc is a function that creates a new provider.
|
||||||
|
type NewProviderFunc = func(ProviderConfig) Provider
|
||||||
|
|
||||||
|
// ProviderFactory is a factory that creates a new provider.
|
||||||
|
type ProviderFactory interface {
|
||||||
|
New(ProviderConfig) Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProviderFactory creates a new provider factory.
|
||||||
|
func NewProviderFactory(f NewProviderFunc) ProviderFactory {
|
||||||
|
return &providerFactory{f: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
// providerFactory is a factory that implements the ProviderFactory interface.
|
||||||
|
type providerFactory struct {
|
||||||
|
f NewProviderFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new provider.
|
||||||
|
func (factory *providerFactory) New(config ProviderConfig) Provider {
|
||||||
|
return factory.f(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProviderConfig is the configuration for a provider.
|
||||||
|
type ProviderConfig struct{}
|
||||||
|
|
||||||
|
// Provider is an interface that represents a configuration provider.
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
// Get returns the configuration, or error otherwise.
|
// Get returns the configuration for the given URI.
|
||||||
Get(ctx context.Context) (*Config, error)
|
Get(context.Context, Uri) (*Conf, error)
|
||||||
}
|
// Scheme returns the scheme of the provider.
|
||||||
|
Scheme() string
|
||||||
type provider struct {
|
|
||||||
resolver *confmap.Resolver
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProviderSettings are the settings to configure the behavior of the Provider.
|
|
||||||
type ProviderSettings struct {
|
|
||||||
// ResolverSettings are the settings to configure the behavior of the confmap.Resolver.
|
|
||||||
ResolverSettings confmap.ResolverSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewProvider returns a new Provider that provides the entire configuration.
|
|
||||||
// See https://github.com/open-telemetry/opentelemetry-collector/blob/main/otelcol/configprovider.go for
|
|
||||||
// more details
|
|
||||||
func NewProvider(settings ProviderSettings) (Provider, error) {
|
|
||||||
resolver, err := confmap.NewResolver(settings.ResolverSettings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &provider{
|
|
||||||
resolver: resolver,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *provider) Get(ctx context.Context) (*Config, error) {
|
|
||||||
conf, err := provider.resolver.Resolve(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot resolve configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := unmarshal(conf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot unmarshal configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
87
pkg/config/resolver.go
Normal file
87
pkg/config/resolver.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResolverConfig struct {
|
||||||
|
// Each string or `uri` must follow "<scheme>:<value>" format. This format is compatible with the URI definition
|
||||||
|
// defined at https://datatracker.ietf.org/doc/html/rfc3986".
|
||||||
|
// It is required to have at least one uri.
|
||||||
|
Uris []string
|
||||||
|
|
||||||
|
// ProviderFactories is a slice of Provider factories.
|
||||||
|
// It is required to have at least one factory.
|
||||||
|
ProviderFactories []ProviderFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resolver struct {
|
||||||
|
uris []Uri
|
||||||
|
providers map[string]Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResolver(config ResolverConfig) (*Resolver, error) {
|
||||||
|
if len(config.Uris) == 0 {
|
||||||
|
return nil, errors.New("cannot build resolver, no uris have been provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.ProviderFactories) == 0 {
|
||||||
|
return nil, errors.New("cannot build resolver, no providers have been provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
uris := make([]Uri, len(config.Uris))
|
||||||
|
for i, inputUri := range config.Uris {
|
||||||
|
uri, err := NewUri(inputUri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uris[i] = uri
|
||||||
|
}
|
||||||
|
|
||||||
|
providers := make(map[string]Provider, len(config.ProviderFactories))
|
||||||
|
for _, factory := range config.ProviderFactories {
|
||||||
|
provider := factory.New(ProviderConfig{})
|
||||||
|
|
||||||
|
scheme := provider.Scheme()
|
||||||
|
// Check that the scheme is unique.
|
||||||
|
if _, ok := providers[scheme]; ok {
|
||||||
|
return nil, fmt.Errorf("cannot build resolver, duplicate scheme %q found", scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
providers[provider.Scheme()] = provider
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Resolver{
|
||||||
|
uris: uris,
|
||||||
|
providers: providers,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) Do(ctx context.Context) (*Conf, error) {
|
||||||
|
conf := NewConf()
|
||||||
|
|
||||||
|
for _, uri := range resolver.uris {
|
||||||
|
currentConf, err := resolver.get(ctx, uri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = conf.Merge(currentConf); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot merge config: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resolver *Resolver) get(ctx context.Context, uri Uri) (*Conf, error) {
|
||||||
|
provider, ok := resolver.providers[uri.scheme]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("cannot find provider with schema %q", uri.scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.Get(ctx, uri)
|
||||||
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/collector/confmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// unmarshal converts a confmap.Conf into a Config struct.
|
|
||||||
// It splits the input confmap into a map of key-value pairs, fetches the corresponding
|
|
||||||
// signozconfmap.Config interface by name, merges it with the default config, validates it,
|
|
||||||
// and then creates a new confmap from the parsed map to unmarshal into the Config struct.
|
|
||||||
func unmarshal(conf *confmap.Conf) (*Config, error) {
|
|
||||||
raw := make(map[string]any)
|
|
||||||
if err := conf.Unmarshal(&raw); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed := make(map[string]any)
|
|
||||||
|
|
||||||
// To help the defaults kick in, we need iterate over the default map instead of the raw values
|
|
||||||
for k, v := range defaults {
|
|
||||||
sub, err := conf.Sub(k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot read config for %q: %w", k, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d := v.NewWithDefaults()
|
|
||||||
if err := sub.Unmarshal(&d); err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot merge config for %q: %w", k, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.Validate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to validate config for for %q: %w", k, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed[k] = d
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedConf := confmap.NewFromStringMap(parsed)
|
|
||||||
config := new(Config)
|
|
||||||
err := parsedConf.Unmarshal(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot unmarshal config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.opentelemetry.io/collector/confmap"
|
|
||||||
"go.signoz.io/signoz/pkg/instrumentation"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUnmarshalForInstrumentation(t *testing.T) {
|
|
||||||
input := confmap.NewFromStringMap(
|
|
||||||
map[string]any{
|
|
||||||
"instrumentation": map[string]any{
|
|
||||||
"logs": map[string]bool{
|
|
||||||
"enabled": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
expected := &Config{
|
|
||||||
Instrumentation: instrumentation.Config{
|
|
||||||
Logs: instrumentation.LogsConfig{
|
|
||||||
Enabled: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cfg, err := unmarshal(input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, expected.Instrumentation, cfg.Instrumentation)
|
|
||||||
}
|
|
||||||
46
pkg/config/uri.go
Normal file
46
pkg/config/uri.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// uriRegex is a regex that matches the URI format. It complies with the URI definition defined at https://datatracker.ietf.org/doc/html/rfc3986.
|
||||||
|
// The format is "<scheme>:<value>".
|
||||||
|
uriRegex = regexp.MustCompile(`(?s:^(?P<Scheme>[A-Za-z][A-Za-z0-9+.-]+):(?P<Value>.*)$)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Uri struct {
|
||||||
|
scheme string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUri(input string) (Uri, error) {
|
||||||
|
submatches := uriRegex.FindStringSubmatch(input)
|
||||||
|
|
||||||
|
if len(submatches) != 3 {
|
||||||
|
return Uri{}, fmt.Errorf("invalid uri: %q", input)
|
||||||
|
}
|
||||||
|
return Uri{
|
||||||
|
scheme: submatches[1],
|
||||||
|
value: submatches[2],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustNewUri(input string) Uri {
|
||||||
|
uri, err := NewUri(input)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri Uri) Scheme() string {
|
||||||
|
return uri.scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uri Uri) Value() string {
|
||||||
|
return uri.value
|
||||||
|
}
|
||||||
35
pkg/config/uri_test.go
Normal file
35
pkg/config/uri_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewUri(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
input string
|
||||||
|
expected Uri
|
||||||
|
pass bool
|
||||||
|
}{
|
||||||
|
{input: "file:/path/1", expected: Uri{scheme: "file", value: "/path/1"}, pass: true},
|
||||||
|
{input: "file:", expected: Uri{scheme: "file", value: ""}, pass: true},
|
||||||
|
{input: "env:", expected: Uri{scheme: "env", value: ""}, pass: true},
|
||||||
|
{input: "scheme", expected: Uri{}, pass: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
uri, err := NewUri(tc.input)
|
||||||
|
if !tc.pass {
|
||||||
|
assert.Error(t, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotPanics(t, func() { MustNewUri(tc.input) })
|
||||||
|
assert.Equal(t, tc.expected, uri)
|
||||||
|
assert.Equal(t, tc.expected.Scheme(), uri.scheme)
|
||||||
|
assert.Equal(t, tc.expected.Value(), uri.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package confmap
|
|
||||||
|
|
||||||
// Config is an interface that defines methods for creating and validating configurations.
|
|
||||||
type Config interface {
|
|
||||||
// New creates a new instance of the configuration with default values.
|
|
||||||
NewWithDefaults() Config
|
|
||||||
// Validate the configuration and returns an error if invalid.
|
|
||||||
Validate() error
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// Package confmap is a wrapper on top of the confmap defined here:
|
|
||||||
// https://github.com/open-telemetry/opentelemetry-collector/blob/main/otelcol/configprovider.go/
|
|
||||||
package confmap
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package signozenvprovider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/collector/confmap"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
schemeName string = "signozenv"
|
|
||||||
envPrefix string = "signoz"
|
|
||||||
separator string = "__"
|
|
||||||
envPrefixWithOneSeparator string = "signoz_"
|
|
||||||
envRegexString string = `^[a-zA-Z][a-zA-Z0-9_]*$`
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
envRegex = regexp.MustCompile(envRegexString)
|
|
||||||
)
|
|
||||||
|
|
||||||
type provider struct {
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFactory returns a factory for a confmap.Provider that reads the configuration from the environment.
|
|
||||||
// All variables starting with `SIGNOZ__` are read from the environment.
|
|
||||||
// The separator is `__` (2 underscores) in order to incorporate env variables having keys with a single `_`
|
|
||||||
func NewFactory() confmap.ProviderFactory {
|
|
||||||
return confmap.NewProviderFactory(newProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newProvider(settings confmap.ProviderSettings) confmap.Provider {
|
|
||||||
return &provider{
|
|
||||||
logger: settings.Logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
|
|
||||||
if !strings.HasPrefix(uri, schemeName+":") {
|
|
||||||
return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, schemeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read and Sort environment variables for consistent output
|
|
||||||
envvars := os.Environ()
|
|
||||||
sort.Strings(envvars)
|
|
||||||
|
|
||||||
// Create a map m containing key value pairs
|
|
||||||
m := make(map[string]any)
|
|
||||||
for _, envvar := range envvars {
|
|
||||||
parts := strings.SplitN(envvar, "=", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key := strings.ToLower(parts[0])
|
|
||||||
val := parts[1]
|
|
||||||
|
|
||||||
if strings.HasPrefix(key, envPrefixWithOneSeparator) {
|
|
||||||
// Remove the envPrefix from the key
|
|
||||||
key = strings.Replace(key, envPrefix+separator, "", 1)
|
|
||||||
|
|
||||||
// Check whether the resulting key matches with the regex
|
|
||||||
if !envRegex.MatchString(key) {
|
|
||||||
provider.logger.Warn("Configuration references invalid environment variable key", zap.String("key", key))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert key into yaml format
|
|
||||||
key = strings.ToLower(strings.ReplaceAll(key, separator, confmap.KeyDelimiter))
|
|
||||||
m[key] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := yaml.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return confmap.NewRetrievedFromYAML(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*provider) Scheme() string {
|
|
||||||
return schemeName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*provider) Shutdown(context.Context) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package signozenvprovider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.opentelemetry.io/collector/confmap"
|
|
||||||
"go.opentelemetry.io/collector/confmap/confmaptest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createProvider() confmap.Provider {
|
|
||||||
return NewFactory().Create(confmaptest.NewNopProviderSettings())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateProviderScheme(t *testing.T) {
|
|
||||||
assert.NoError(t, confmaptest.ValidateProviderScheme(createProvider()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRetrieve(t *testing.T) {
|
|
||||||
t.Setenv("SIGNOZ__STORAGE__DSN", "localhost:9000")
|
|
||||||
t.Setenv("SIGNOZ__SIGNOZ_ENABLED", "true")
|
|
||||||
t.Setenv("SIGNOZ__INSTRUMENTATION__LOGS__ENABLED", "true")
|
|
||||||
expected := confmap.NewFromStringMap(map[string]any{
|
|
||||||
"storage::dsn": "localhost:9000",
|
|
||||||
"signoz_enabled": "true",
|
|
||||||
"instrumentation::logs::enabled": "true",
|
|
||||||
})
|
|
||||||
|
|
||||||
signoz := createProvider()
|
|
||||||
retrieved, err := signoz.Retrieve(context.Background(), schemeName+":", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
actual, err := retrieved.AsConf()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, expected.ToStringMap(), actual.ToStringMap())
|
|
||||||
assert.NoError(t, signoz.Shutdown(context.Background()))
|
|
||||||
}
|
|
||||||
37
pkg/factory/config.go
Normal file
37
pkg/factory/config.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package factory
|
||||||
|
|
||||||
|
// Config is an interface that defines methods for creating and validating configurations.
|
||||||
|
type Config interface {
|
||||||
|
// Validate the configuration and returns an error if invalid.
|
||||||
|
Validate() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigFunc is a function that creates a new config.
|
||||||
|
type NewConfigFunc func() Config
|
||||||
|
|
||||||
|
// ConfigFactory is a factory that creates a new config.
|
||||||
|
type ConfigFactory interface {
|
||||||
|
Named
|
||||||
|
New() Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// configFactory is a factory that implements the ConfigFactory interface.
|
||||||
|
type configFactory struct {
|
||||||
|
name Name
|
||||||
|
newConfigFunc NewConfigFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the factory.
|
||||||
|
func (factory *configFactory) Name() Name {
|
||||||
|
return factory.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new config.
|
||||||
|
func (factory *configFactory) New() Config {
|
||||||
|
return factory.newConfigFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new config factory.
|
||||||
|
func NewConfigFactory(name Name, f NewConfigFunc) ConfigFactory {
|
||||||
|
return &configFactory{name: name, newConfigFunc: f}
|
||||||
|
}
|
||||||
29
pkg/factory/config_test.go
Normal file
29
pkg/factory/config_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package factory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type c1 struct{}
|
||||||
|
|
||||||
|
func (c1) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewConfigFactory(t *testing.T) {
|
||||||
|
cf := NewConfigFactory(MustNewName("c1"), func() Config {
|
||||||
|
return c1{}
|
||||||
|
})
|
||||||
|
assert.Equal(t, MustNewName("c1"), cf.Name())
|
||||||
|
assert.IsType(t, c1{}, cf.New())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewConfigFactoryWithPointer(t *testing.T) {
|
||||||
|
cfp := NewConfigFactory(MustNewName("c1"), func() Config {
|
||||||
|
return &c1{}
|
||||||
|
})
|
||||||
|
assert.Equal(t, MustNewName("c1"), cfp.Name())
|
||||||
|
assert.IsType(t, &c1{}, cfp.New())
|
||||||
|
}
|
||||||
38
pkg/factory/name.go
Normal file
38
pkg/factory/name.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package factory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// nameRegex is a regex that matches a valid name.
|
||||||
|
// It must start with a alphabet, and can only contain alphabets, numbers, underscores or hyphens.
|
||||||
|
nameRegex = regexp.MustCompile(`^[a-z][a-z0-9_-]{0,30}$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Name struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Name) String() string {
|
||||||
|
return n.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewName creates a new name.
|
||||||
|
func NewName(name string) (Name, error) {
|
||||||
|
if !nameRegex.MatchString(name) {
|
||||||
|
return Name{}, fmt.Errorf("invalid factory name %q", name)
|
||||||
|
}
|
||||||
|
return Name{name: name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustNewName creates a new name.
|
||||||
|
// It panics if the name is invalid.
|
||||||
|
func MustNewName(name string) Name {
|
||||||
|
n, err := NewName(name)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
20
pkg/factory/name_test.go
Normal file
20
pkg/factory/name_test.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package factory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestName(t *testing.T) {
|
||||||
|
assert.Equal(t, Name{name: "c1"}, MustNewName("c1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNameWithInvalidCharacters(t *testing.T) {
|
||||||
|
_, err := NewName("c1%")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
MustNewName("c1%")
|
||||||
|
})
|
||||||
|
}
|
||||||
74
pkg/factory/named.go
Normal file
74
pkg/factory/named.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package factory
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Named is implemented by all types of factories.
|
||||||
|
type Named interface {
|
||||||
|
Name() Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type NamedMap[T Named] struct {
|
||||||
|
factories map[Name]T
|
||||||
|
factoriesInOrder []T
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNamedMap creates a new NamedMap from a list of factories.
|
||||||
|
// It returns an error if the factories have duplicate names.
|
||||||
|
func NewNamedMap[T Named](factories ...T) (NamedMap[T], error) {
|
||||||
|
fmap := make(map[Name]T)
|
||||||
|
for _, factory := range factories {
|
||||||
|
if _, ok := fmap[factory.Name()]; ok {
|
||||||
|
return NamedMap[T]{}, fmt.Errorf("cannot build factory map, duplicate name %q found", factory.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmap[factory.Name()] = factory
|
||||||
|
}
|
||||||
|
|
||||||
|
return NamedMap[T]{factories: fmap, factoriesInOrder: factories}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustNewNamedMap creates a new NamedMap from a list of factories.
|
||||||
|
// It panics if the factories have duplicate names.
|
||||||
|
func MustNewNamedMap[T Named](factories ...T) NamedMap[T] {
|
||||||
|
nm, err := NewNamedMap(factories...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return nm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the factory for the given name by string.
|
||||||
|
// It returns an error if the factory is not found or the name is invalid.
|
||||||
|
func (n *NamedMap[T]) Get(namestr string) (t T, err error) {
|
||||||
|
name, err := NewName(namestr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
factory, ok := n.factories[name]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("factory %q not found or not registered", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t = factory
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a factory to the NamedMap.
|
||||||
|
// It returns an error if the factory already exists.
|
||||||
|
func (n *NamedMap[T]) Add(factory T) (err error) {
|
||||||
|
name := factory.Name()
|
||||||
|
if _, ok := n.factories[name]; ok {
|
||||||
|
return fmt.Errorf("factory %q already exists", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
n.factories[name] = factory
|
||||||
|
n.factoriesInOrder = append(n.factoriesInOrder, factory)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInOrder returns the factories in the order they were added.
|
||||||
|
func (n *NamedMap[T]) GetInOrder() []T {
|
||||||
|
return n.factoriesInOrder
|
||||||
|
}
|
||||||
72
pkg/factory/named_test.go
Normal file
72
pkg/factory/named_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package factory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type f1 struct{}
|
||||||
|
|
||||||
|
func (*f1) Name() Name {
|
||||||
|
return MustNewName("f1")
|
||||||
|
}
|
||||||
|
|
||||||
|
type f2 struct{}
|
||||||
|
|
||||||
|
func (*f2) Name() Name {
|
||||||
|
return MustNewName("f2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewNamedMap(t *testing.T) {
|
||||||
|
nm, err := NewNamedMap[Named](&f1{}, &f2{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, map[Name]Named{
|
||||||
|
MustNewName("f1"): &f1{},
|
||||||
|
MustNewName("f2"): &f2{},
|
||||||
|
}, nm.factories)
|
||||||
|
assert.Equal(t, []Named{&f1{}, &f2{}}, nm.GetInOrder())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewNamedMapWithDuplicateNames(t *testing.T) {
|
||||||
|
_, err := NewNamedMap[Named](&f1{}, &f1{})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMustNewNamedMap(t *testing.T) {
|
||||||
|
nm := MustNewNamedMap[Named](&f1{}, &f2{})
|
||||||
|
assert.Equal(t, map[Name]Named{
|
||||||
|
MustNewName("f1"): &f1{},
|
||||||
|
MustNewName("f2"): &f2{},
|
||||||
|
}, nm.factories)
|
||||||
|
assert.Equal(t, []Named{&f1{}, &f2{}}, nm.GetInOrder())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMustNewNamedMapDuplicateNames(t *testing.T) {
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
MustNewNamedMap[Named](&f1{}, &f1{})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamedMapGet(t *testing.T) {
|
||||||
|
nm := MustNewNamedMap[Named](&f1{}, &f2{})
|
||||||
|
|
||||||
|
nf1, err := nm.Get("f1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, &f1{}, nf1)
|
||||||
|
|
||||||
|
_, err = nm.Get("f3")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamedMapAdd(t *testing.T) {
|
||||||
|
nm := MustNewNamedMap[Named](&f1{})
|
||||||
|
|
||||||
|
err := nm.Add(&f2{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, map[Name]Named{
|
||||||
|
MustNewName("f1"): &f1{},
|
||||||
|
MustNewName("f2"): &f2{},
|
||||||
|
}, nm.factories)
|
||||||
|
assert.Equal(t, []Named{&f1{}, &f2{}}, nm.GetInOrder())
|
||||||
|
}
|
||||||
58
pkg/factory/provider.go
Normal file
58
pkg/factory/provider.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package factory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Provider = any
|
||||||
|
|
||||||
|
// NewProviderFunc is a function that creates a new Provider.
|
||||||
|
type NewProviderFunc[P Provider, C Config] func(context.Context, ProviderSettings, C) (P, error)
|
||||||
|
|
||||||
|
type ProviderFactory[P Provider, C Config] interface {
|
||||||
|
Named
|
||||||
|
New(context.Context, ProviderSettings, C) (P, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type providerFactory[P Provider, C Config] struct {
|
||||||
|
name Name
|
||||||
|
newProviderFunc NewProviderFunc[P, C]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (factory *providerFactory[P, C]) Name() Name {
|
||||||
|
return factory.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (factory *providerFactory[P, C]) New(ctx context.Context, settings ProviderSettings, config C) (p P, err error) {
|
||||||
|
provider, err := factory.newProviderFunc(ctx, settings, config)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p = provider
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProviderFactory creates a new provider factory.
|
||||||
|
func NewProviderFactory[P Provider, C Config](name Name, newProviderFunc NewProviderFunc[P, C]) ProviderFactory[P, C] {
|
||||||
|
return &providerFactory[P, C]{
|
||||||
|
name: name,
|
||||||
|
newProviderFunc: newProviderFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProviderFromNamedMap creates a new provider from a factory based on the input key.
|
||||||
|
func NewProviderFromNamedMap[P Provider, C Config](ctx context.Context, settings ProviderSettings, config C, factories NamedMap[ProviderFactory[P, C]], key string) (p P, err error) {
|
||||||
|
providerFactory, err := factories.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := providerFactory.New(ctx, settings, config)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p = provider
|
||||||
|
return
|
||||||
|
}
|
||||||
41
pkg/factory/provider_test.go
Normal file
41
pkg/factory/provider_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package factory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type p1 struct{}
|
||||||
|
|
||||||
|
type pc1 struct{}
|
||||||
|
|
||||||
|
func (pc1) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewProviderFactory(t *testing.T) {
|
||||||
|
pf := NewProviderFactory(MustNewName("p1"), func(ctx context.Context, settings ProviderSettings, config pc1) (p1, error) {
|
||||||
|
return p1{}, nil
|
||||||
|
})
|
||||||
|
assert.Equal(t, MustNewName("p1"), pf.Name())
|
||||||
|
p, err := pf.New(context.Background(), ProviderSettings{}, pc1{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, p1{}, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewProviderFactoryFromFactory(t *testing.T) {
|
||||||
|
pf := NewProviderFactory(MustNewName("p1"), func(ctx context.Context, settings ProviderSettings, config pc1) (p1, error) {
|
||||||
|
return p1{}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
m := MustNewNamedMap(pf)
|
||||||
|
assert.Equal(t, MustNewName("p1"), pf.Name())
|
||||||
|
p, err := NewProviderFromNamedMap(context.Background(), ProviderSettings{}, pc1{}, m, "p1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, p1{}, p)
|
||||||
|
|
||||||
|
_, err = NewProviderFromNamedMap(context.Background(), ProviderSettings{}, pc1{}, m, "p2")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
10
pkg/factory/providertest/setting.go
Normal file
10
pkg/factory/providertest/setting.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package providertest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
|
"go.signoz.io/signoz/pkg/instrumentation/instrumentationtest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSettings() factory.ProviderSettings {
|
||||||
|
return instrumentationtest.New().ToProviderSettings()
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package registry
|
package factory
|
||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
@@ -8,9 +8,3 @@ type Service interface {
|
|||||||
// Stops a service.
|
// Stops a service.
|
||||||
Stop(context.Context) error
|
Stop(context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type NamedService interface {
|
|
||||||
// Identifier of a service. It should be unique across all services.
|
|
||||||
Name() string
|
|
||||||
Service
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
package registry
|
package servicetest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ NamedService = (*httpService)(nil)
|
var _ factory.Service = (*httpService)(nil)
|
||||||
|
|
||||||
type httpService struct {
|
type httpService struct {
|
||||||
Listener net.Listener
|
Listener net.Listener
|
||||||
@@ -14,15 +16,15 @@ type httpService struct {
|
|||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHttpService(name string) (*httpService, error) {
|
func NewHttpService(name string) (*httpService, error) {
|
||||||
return &httpService{
|
return &httpService{
|
||||||
name: name,
|
name: name,
|
||||||
Server: &http.Server{},
|
Server: &http.Server{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *httpService) Name() string {
|
func (service *httpService) Name() factory.Name {
|
||||||
return service.name
|
return factory.MustNewName(service.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *httpService) Start(ctx context.Context) error {
|
func (service *httpService) Start(ctx context.Context) error {
|
||||||
58
pkg/factory/setting.go
Normal file
58
pkg/factory/setting.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package factory
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdklog "go.opentelemetry.io/otel/log"
|
||||||
|
sdkmetric "go.opentelemetry.io/otel/metric"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/trace"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProviderSettings struct {
|
||||||
|
// LoggerProvider is the otel logger.
|
||||||
|
LoggerProvider sdklog.LoggerProvider
|
||||||
|
// ZapLogger is the zap logger.
|
||||||
|
ZapLogger *zap.Logger
|
||||||
|
// MeterProvider is the meter provider.
|
||||||
|
MeterProvider sdkmetric.MeterProvider
|
||||||
|
// TracerProvider is the tracer provider.
|
||||||
|
TracerProvider sdktrace.TracerProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScopedProviderSettings interface {
|
||||||
|
Logger() sdklog.Logger
|
||||||
|
ZapLogger() *zap.Logger
|
||||||
|
Meter() sdkmetric.Meter
|
||||||
|
Tracer() sdktrace.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
type scoped struct {
|
||||||
|
logger sdklog.Logger
|
||||||
|
zapLogger *zap.Logger
|
||||||
|
meter sdkmetric.Meter
|
||||||
|
tracer sdktrace.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScopedProviderSettings(settings ProviderSettings, pkgName string) *scoped {
|
||||||
|
return &scoped{
|
||||||
|
logger: settings.LoggerProvider.Logger(pkgName),
|
||||||
|
zapLogger: settings.ZapLogger.Named(pkgName),
|
||||||
|
meter: settings.MeterProvider.Meter(pkgName),
|
||||||
|
tracer: settings.TracerProvider.Tracer(pkgName),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scoped) Logger() sdklog.Logger {
|
||||||
|
return s.logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scoped) ZapLogger() *zap.Logger {
|
||||||
|
return s.zapLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scoped) Meter() sdkmetric.Meter {
|
||||||
|
return s.meter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scoped) Tracer() sdktrace.Tracer {
|
||||||
|
return s.tracer
|
||||||
|
}
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
|
||||||
"go.signoz.io/signoz/pkg/confmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config satisfies the confmap.Config interface
|
|
||||||
var _ confmap.Config = (*Config)(nil)
|
|
||||||
|
|
||||||
// Config holds the configuration for http.
|
// Config holds the configuration for http.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
//Address specifies the TCP address for the server to listen on, in the form "host:port".
|
//Address specifies the TCP address for the server to listen on, in the form "host:port".
|
||||||
@@ -14,14 +7,3 @@ type Config struct {
|
|||||||
// See net.Dial for details of the address format.
|
// See net.Dial for details of the address format.
|
||||||
Address string `mapstructure:"address"`
|
Address string `mapstructure:"address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) NewWithDefaults() confmap.Config {
|
|
||||||
return &Config{
|
|
||||||
Address: "0.0.0.0:8080",
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Validate() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,21 +6,20 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/registry"
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ registry.NamedService = (*Server)(nil)
|
var _ factory.Service = (*Server)(nil)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
srv *http.Server
|
srv *http.Server
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
handler http.Handler
|
handler http.Handler
|
||||||
cfg Config
|
cfg Config
|
||||||
name string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Server, error) {
|
func New(logger *zap.Logger, cfg Config, handler http.Handler) (*Server, error) {
|
||||||
if handler == nil {
|
if handler == nil {
|
||||||
return nil, fmt.Errorf("cannot build http server, handler is required")
|
return nil, fmt.Errorf("cannot build http server, handler is required")
|
||||||
}
|
}
|
||||||
@@ -29,10 +28,6 @@ func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Se
|
|||||||
return nil, fmt.Errorf("cannot build http server, logger is required")
|
return nil, fmt.Errorf("cannot build http server, logger is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if name == "" {
|
|
||||||
return nil, fmt.Errorf("cannot build http server, name is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: cfg.Address,
|
Addr: cfg.Address,
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
@@ -46,14 +41,9 @@ func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Se
|
|||||||
logger: logger.Named("go.signoz.io/pkg/http/server"),
|
logger: logger.Named("go.signoz.io/pkg/http/server"),
|
||||||
handler: handler,
|
handler: handler,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
name: name,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Name() string {
|
|
||||||
return server.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (server *Server) Start(ctx context.Context) error {
|
func (server *Server) Start(ctx context.Context) error {
|
||||||
server.logger.Info("starting http server", zap.String("address", server.srv.Addr))
|
server.logger.Info("starting http server", zap.String("address", server.srv.Addr))
|
||||||
if err := server.srv.ListenAndServe(); err != nil {
|
if err := server.srv.ListenAndServe(); err != nil {
|
||||||
|
|||||||
@@ -2,13 +2,10 @@ package instrumentation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
contribsdkconfig "go.opentelemetry.io/contrib/config"
|
contribsdkconfig "go.opentelemetry.io/contrib/config"
|
||||||
"go.signoz.io/signoz/pkg/confmap"
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config satisfies the confmap.Config interface
|
|
||||||
var _ confmap.Config = (*Config)(nil)
|
|
||||||
|
|
||||||
// Config holds the configuration for all instrumentation components.
|
// Config holds the configuration for all instrumentation components.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Logs LogsConfig `mapstructure:"logs"`
|
Logs LogsConfig `mapstructure:"logs"`
|
||||||
@@ -24,39 +21,69 @@ type Resource struct {
|
|||||||
|
|
||||||
// LogsConfig holds the configuration for the logging component.
|
// LogsConfig holds the configuration for the logging component.
|
||||||
type LogsConfig struct {
|
type LogsConfig struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
Level zapcore.Level `mapstructure:"level"`
|
Level zapcore.Level `mapstructure:"level"`
|
||||||
contribsdkconfig.LoggerProvider `mapstructure:",squash"`
|
Processors LogsProcessors `mapstructure:"processors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogsProcessors struct {
|
||||||
|
Batch contribsdkconfig.BatchLogRecordProcessor `mapstructure:"batch"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TracesConfig holds the configuration for the tracing component.
|
// TracesConfig holds the configuration for the tracing component.
|
||||||
type TracesConfig struct {
|
type TracesConfig struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
contribsdkconfig.TracerProvider `mapstructure:",squash"`
|
Processors TracesProcessors `mapstructure:"processors"`
|
||||||
|
Sampler contribsdkconfig.Sampler `mapstructure:"sampler"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TracesProcessors struct {
|
||||||
|
Batch contribsdkconfig.BatchSpanProcessor `mapstructure:"batch"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MetricsConfig holds the configuration for the metrics component.
|
// MetricsConfig holds the configuration for the metrics component.
|
||||||
type MetricsConfig struct {
|
type MetricsConfig struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
contribsdkconfig.MeterProvider `mapstructure:",squash"`
|
Readers MetricsReaders `mapstructure:"readers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) NewWithDefaults() confmap.Config {
|
type MetricsReaders struct {
|
||||||
return &Config{
|
Pull contribsdkconfig.PullMetricReader `mapstructure:"pull"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfigFactory() factory.ConfigFactory {
|
||||||
|
return factory.NewConfigFactory(factory.MustNewName("instrumentation"), newConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfig() factory.Config {
|
||||||
|
host := "0.0.0.0"
|
||||||
|
port := 9090
|
||||||
|
|
||||||
|
return Config{
|
||||||
Logs: LogsConfig{
|
Logs: LogsConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
Level: zapcore.InfoLevel,
|
Level: zapcore.DebugLevel,
|
||||||
},
|
},
|
||||||
Traces: TracesConfig{
|
Traces: TracesConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
},
|
},
|
||||||
Metrics: MetricsConfig{
|
Metrics: MetricsConfig{
|
||||||
Enabled: false,
|
Enabled: true,
|
||||||
|
Readers: MetricsReaders{
|
||||||
|
Pull: contribsdkconfig.PullMetricReader{
|
||||||
|
Exporter: contribsdkconfig.MetricExporter{
|
||||||
|
Prometheus: &contribsdkconfig.Prometheus{
|
||||||
|
Host: &host,
|
||||||
|
Port: &port,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Validate() error {
|
func (c Config) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +1,34 @@
|
|||||||
package instrumentation
|
package instrumentation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"os"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
contribsdkconfig "go.opentelemetry.io/contrib/config"
|
"go.opentelemetry.io/contrib/bridges/otelzap"
|
||||||
sdklog "go.opentelemetry.io/otel/log"
|
sdklog "go.opentelemetry.io/otel/log"
|
||||||
sdkmetric "go.opentelemetry.io/otel/metric"
|
sdkmetric "go.opentelemetry.io/otel/metric"
|
||||||
sdkresource "go.opentelemetry.io/otel/sdk/resource"
|
sdkresource "go.opentelemetry.io/otel/sdk/resource"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
|
||||||
sdktrace "go.opentelemetry.io/otel/trace"
|
sdktrace "go.opentelemetry.io/otel/trace"
|
||||||
"go.signoz.io/signoz/pkg/version"
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Instrumentation holds the core components for application instrumentation.
|
// Instrumentation provides the core components for application instrumentation.
|
||||||
type Instrumentation struct {
|
type Instrumentation interface {
|
||||||
LoggerProvider sdklog.LoggerProvider
|
// LoggerProvider returns the OpenTelemetry logger provider.
|
||||||
Logger *zap.Logger
|
LoggerProvider() sdklog.LoggerProvider
|
||||||
MeterProvider sdkmetric.MeterProvider
|
// Logger returns the Zap logger.
|
||||||
TracerProvider sdktrace.TracerProvider
|
Logger() *zap.Logger
|
||||||
|
// MeterProvider returns the OpenTelemetry meter provider.
|
||||||
|
MeterProvider() sdkmetric.MeterProvider
|
||||||
|
// TracerProvider returns the OpenTelemetry tracer provider.
|
||||||
|
TracerProvider() sdktrace.TracerProvider
|
||||||
|
// ToProviderSettings converts instrumentation to provider settings.
|
||||||
|
ToProviderSettings() factory.ProviderSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Instrumentation instance with configured providers.
|
// Merges the input attributes with the resource attributes.
|
||||||
// It sets up logging, tracing, and metrics based on the provided configuration.
|
func mergeAttributes(input map[string]any, resource *sdkresource.Resource) map[string]any {
|
||||||
func New(ctx context.Context, build version.Build, cfg Config) (*Instrumentation, error) {
|
|
||||||
// Set default resource attributes if not provided
|
|
||||||
if cfg.Resource.Attributes == nil {
|
|
||||||
cfg.Resource.Attributes = map[string]any{
|
|
||||||
string(semconv.ServiceNameKey): build.Name,
|
|
||||||
string(semconv.ServiceVersionKey): build.Version,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new resource with default detectors.
|
|
||||||
// The upstream contrib repository is not taking detectors into account.
|
|
||||||
// We are, therefore, using some sensible defaults here.
|
|
||||||
resource, err := sdkresource.New(
|
|
||||||
ctx,
|
|
||||||
sdkresource.WithContainer(),
|
|
||||||
sdkresource.WithFromEnv(),
|
|
||||||
sdkresource.WithHost(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the resource configuration by merging
|
|
||||||
// resource and attributes.
|
|
||||||
sch := semconv.SchemaURL
|
|
||||||
configResource := contribsdkconfig.Resource{
|
|
||||||
Attributes: attributes(cfg.Resource.Attributes, resource),
|
|
||||||
Detectors: nil,
|
|
||||||
SchemaUrl: &sch,
|
|
||||||
}
|
|
||||||
|
|
||||||
loggerProvider, err := newLoggerProvider(ctx, cfg, configResource)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot create logger provider: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tracerProvider, err := newTracerProvider(ctx, cfg, configResource)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot create tracer provider: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
meterProvider, err := newMeterProvider(ctx, cfg, configResource)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot create meter provider: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Instrumentation{
|
|
||||||
LoggerProvider: loggerProvider,
|
|
||||||
TracerProvider: tracerProvider,
|
|
||||||
MeterProvider: meterProvider,
|
|
||||||
Logger: newLogger(cfg, loggerProvider),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// attributes merges the input attributes with the resource attributes.
|
|
||||||
func attributes(input map[string]any, resource *sdkresource.Resource) map[string]any {
|
|
||||||
output := make(map[string]any)
|
output := make(map[string]any)
|
||||||
|
|
||||||
for k, v := range input {
|
for k, v := range input {
|
||||||
@@ -93,3 +42,14 @@ func attributes(input map[string]any, resource *sdkresource.Resource) map[string
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newLogger creates a new Zap logger with the configured level and output.
|
||||||
|
// It combines a JSON encoder for stdout and an OpenTelemetry bridge.
|
||||||
|
func newLogger(cfg Config, provider sdklog.LoggerProvider) *zap.Logger {
|
||||||
|
core := zapcore.NewTee(
|
||||||
|
zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(os.Stdout), cfg.Logs.Level),
|
||||||
|
otelzap.NewCore("go.signoz.io/pkg/instrumentation", otelzap.WithLoggerProvider(provider)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))
|
||||||
|
}
|
||||||
|
|||||||
54
pkg/instrumentation/instrumentationtest/instrumentation.go
Normal file
54
pkg/instrumentation/instrumentationtest/instrumentation.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package instrumentationtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdklog "go.opentelemetry.io/otel/log"
|
||||||
|
nooplog "go.opentelemetry.io/otel/log/noop"
|
||||||
|
sdkmetric "go.opentelemetry.io/otel/metric"
|
||||||
|
noopmetric "go.opentelemetry.io/otel/metric/noop"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/trace"
|
||||||
|
nooptrace "go.opentelemetry.io/otel/trace/noop"
|
||||||
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
|
"go.signoz.io/signoz/pkg/instrumentation"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type noopInstrumentation struct {
|
||||||
|
logger *zap.Logger
|
||||||
|
loggerProvider sdklog.LoggerProvider
|
||||||
|
meterProvider sdkmetric.MeterProvider
|
||||||
|
tracerProvider sdktrace.TracerProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() instrumentation.Instrumentation {
|
||||||
|
return &noopInstrumentation{
|
||||||
|
logger: zap.NewNop(),
|
||||||
|
loggerProvider: nooplog.NewLoggerProvider(),
|
||||||
|
meterProvider: noopmetric.NewMeterProvider(),
|
||||||
|
tracerProvider: nooptrace.NewTracerProvider(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *noopInstrumentation) LoggerProvider() sdklog.LoggerProvider {
|
||||||
|
return i.loggerProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *noopInstrumentation) Logger() *zap.Logger {
|
||||||
|
return i.logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *noopInstrumentation) MeterProvider() sdkmetric.MeterProvider {
|
||||||
|
return i.meterProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *noopInstrumentation) TracerProvider() sdktrace.TracerProvider {
|
||||||
|
return i.tracerProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *noopInstrumentation) ToProviderSettings() factory.ProviderSettings {
|
||||||
|
return factory.ProviderSettings{
|
||||||
|
LoggerProvider: i.LoggerProvider(),
|
||||||
|
ZapLogger: i.Logger(),
|
||||||
|
MeterProvider: i.MeterProvider(),
|
||||||
|
TracerProvider: i.TracerProvider(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package instrumentation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/contrib/bridges/otelzap"
|
|
||||||
contribsdkconfig "go.opentelemetry.io/contrib/config"
|
|
||||||
sdklog "go.opentelemetry.io/otel/log"
|
|
||||||
nooplog "go.opentelemetry.io/otel/log/noop"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newLoggerProvider creates a new logger provider based on the configuration.
|
|
||||||
// If logging is disabled, it returns a no-op logger provider.
|
|
||||||
func newLoggerProvider(ctx context.Context, cfg Config, cfgResource contribsdkconfig.Resource) (sdklog.LoggerProvider, error) {
|
|
||||||
if !cfg.Logs.Enabled {
|
|
||||||
return nooplog.NewLoggerProvider(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sdk, err := contribsdkconfig.NewSDK(
|
|
||||||
contribsdkconfig.WithContext(ctx),
|
|
||||||
contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{
|
|
||||||
LoggerProvider: &cfg.Logs.LoggerProvider,
|
|
||||||
Resource: &cfgResource,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return sdk.LoggerProvider(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newLogger creates a new Zap logger with the configured level and output.
|
|
||||||
// It combines a JSON encoder for stdout and an OpenTelemetry bridge.
|
|
||||||
func newLogger(cfg Config, provider sdklog.LoggerProvider) *zap.Logger {
|
|
||||||
core := zapcore.NewTee(
|
|
||||||
zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(os.Stdout), cfg.Logs.Level),
|
|
||||||
otelzap.NewCore("go.signoz.io/pkg/instrumentation", otelzap.WithLoggerProvider(provider)),
|
|
||||||
)
|
|
||||||
|
|
||||||
return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package instrumentation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
contribsdkconfig "go.opentelemetry.io/contrib/config"
|
|
||||||
sdkmetric "go.opentelemetry.io/otel/metric"
|
|
||||||
noopmetric "go.opentelemetry.io/otel/metric/noop"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newMeterProvider creates a new meter provider based on the configuration.
|
|
||||||
// If metrics are disabled, it returns a no-op meter provider.
|
|
||||||
func newMeterProvider(ctx context.Context, cfg Config, cfgResource contribsdkconfig.Resource) (sdkmetric.MeterProvider, error) {
|
|
||||||
if !cfg.Metrics.Enabled {
|
|
||||||
return noopmetric.NewMeterProvider(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sdk, err := contribsdkconfig.NewSDK(
|
|
||||||
contribsdkconfig.WithContext(ctx),
|
|
||||||
contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{
|
|
||||||
MeterProvider: &cfg.Metrics.MeterProvider,
|
|
||||||
Resource: &cfgResource,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return sdk.MeterProvider(), nil
|
|
||||||
}
|
|
||||||
137
pkg/instrumentation/sdk.go
Normal file
137
pkg/instrumentation/sdk.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package instrumentation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
contribsdkconfig "go.opentelemetry.io/contrib/config"
|
||||||
|
sdklog "go.opentelemetry.io/otel/log"
|
||||||
|
sdkmetric "go.opentelemetry.io/otel/metric"
|
||||||
|
sdkresource "go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/trace"
|
||||||
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
|
"go.signoz.io/signoz/pkg/version"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ factory.Service = (*SDK)(nil)
|
||||||
|
var _ Instrumentation = (*SDK)(nil)
|
||||||
|
|
||||||
|
// SDK holds the core components for application instrumentation.
|
||||||
|
type SDK struct {
|
||||||
|
sdk contribsdkconfig.SDK
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Instrumentation instance with configured providers.
|
||||||
|
// It sets up logging, tracing, and metrics based on the provided configuration.
|
||||||
|
func New(ctx context.Context, build version.Build, cfg Config) (*SDK, error) {
|
||||||
|
// Set default resource attributes if not provided
|
||||||
|
if cfg.Resource.Attributes == nil {
|
||||||
|
cfg.Resource.Attributes = map[string]any{
|
||||||
|
string(semconv.ServiceNameKey): build.Name,
|
||||||
|
string(semconv.ServiceVersionKey): build.Version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new resource with default detectors.
|
||||||
|
// The upstream contrib repository is not taking detectors into account.
|
||||||
|
// We are, therefore, using some sensible defaults here.
|
||||||
|
resource, err := sdkresource.New(
|
||||||
|
ctx,
|
||||||
|
sdkresource.WithContainer(),
|
||||||
|
sdkresource.WithFromEnv(),
|
||||||
|
sdkresource.WithHost(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the resource configuration by merging
|
||||||
|
// resource and attributes.
|
||||||
|
sch := semconv.SchemaURL
|
||||||
|
configResource := contribsdkconfig.Resource{
|
||||||
|
Attributes: mergeAttributes(cfg.Resource.Attributes, resource),
|
||||||
|
Detectors: nil,
|
||||||
|
SchemaUrl: &sch,
|
||||||
|
}
|
||||||
|
|
||||||
|
var loggerProvider *contribsdkconfig.LoggerProvider
|
||||||
|
if cfg.Logs.Enabled {
|
||||||
|
loggerProvider = &contribsdkconfig.LoggerProvider{
|
||||||
|
Processors: []contribsdkconfig.LogRecordProcessor{
|
||||||
|
{Batch: &cfg.Logs.Processors.Batch},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tracerProvider *contribsdkconfig.TracerProvider
|
||||||
|
if cfg.Traces.Enabled {
|
||||||
|
tracerProvider = &contribsdkconfig.TracerProvider{
|
||||||
|
Processors: []contribsdkconfig.SpanProcessor{
|
||||||
|
{Batch: &cfg.Traces.Processors.Batch},
|
||||||
|
},
|
||||||
|
Sampler: &cfg.Traces.Sampler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var meterProvider *contribsdkconfig.MeterProvider
|
||||||
|
if cfg.Metrics.Enabled {
|
||||||
|
meterProvider = &contribsdkconfig.MeterProvider{
|
||||||
|
Readers: []contribsdkconfig.MetricReader{
|
||||||
|
{Pull: &cfg.Metrics.Readers.Pull},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sdk, err := contribsdkconfig.NewSDK(
|
||||||
|
contribsdkconfig.WithContext(ctx),
|
||||||
|
contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{
|
||||||
|
LoggerProvider: loggerProvider,
|
||||||
|
TracerProvider: tracerProvider,
|
||||||
|
MeterProvider: meterProvider,
|
||||||
|
Resource: &configResource,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SDK{
|
||||||
|
sdk: sdk,
|
||||||
|
logger: newLogger(cfg, sdk.LoggerProvider()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *SDK) Start(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *SDK) Stop(ctx context.Context) error {
|
||||||
|
return i.sdk.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *SDK) LoggerProvider() sdklog.LoggerProvider {
|
||||||
|
return i.sdk.LoggerProvider()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *SDK) Logger() *zap.Logger {
|
||||||
|
return i.logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *SDK) MeterProvider() sdkmetric.MeterProvider {
|
||||||
|
return i.sdk.MeterProvider()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *SDK) TracerProvider() sdktrace.TracerProvider {
|
||||||
|
return i.sdk.TracerProvider()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *SDK) ToProviderSettings() factory.ProviderSettings {
|
||||||
|
return factory.ProviderSettings{
|
||||||
|
LoggerProvider: i.LoggerProvider(),
|
||||||
|
ZapLogger: i.Logger(),
|
||||||
|
MeterProvider: i.MeterProvider(),
|
||||||
|
TracerProvider: i.TracerProvider(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package instrumentation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
contribsdkconfig "go.opentelemetry.io/contrib/config"
|
|
||||||
sdktrace "go.opentelemetry.io/otel/trace"
|
|
||||||
nooptrace "go.opentelemetry.io/otel/trace/noop"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newTracerProvider creates a new tracer provider based on the configuration.
|
|
||||||
// If tracing is disabled, it returns a no-op tracer provider.
|
|
||||||
func newTracerProvider(ctx context.Context, cfg Config, cfgResource contribsdkconfig.Resource) (sdktrace.TracerProvider, error) {
|
|
||||||
if !cfg.Traces.Enabled {
|
|
||||||
return nooptrace.NewTracerProvider(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sdk, err := contribsdkconfig.NewSDK(
|
|
||||||
contribsdkconfig.WithContext(ctx),
|
|
||||||
contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{
|
|
||||||
TracerProvider: &cfg.Traces.TracerProvider,
|
|
||||||
Resource: &cfgResource,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return sdk.TracerProvider(), nil
|
|
||||||
}
|
|
||||||
@@ -218,6 +218,7 @@ func NewReaderFromClickhouseConnection(
|
|||||||
MaxBytesToRead: os.Getenv("ClickHouseMaxBytesToRead"),
|
MaxBytesToRead: os.Getenv("ClickHouseMaxBytesToRead"),
|
||||||
OptimizeReadInOrderRegex: os.Getenv("ClickHouseOptimizeReadInOrderRegex"),
|
OptimizeReadInOrderRegex: os.Getenv("ClickHouseOptimizeReadInOrderRegex"),
|
||||||
OptimizeReadInOrderRegexCompiled: regexCompiled,
|
OptimizeReadInOrderRegexCompiled: regexCompiled,
|
||||||
|
MaxResultRowsForCHQuery: constants.MaxResultRowsForCHQuery,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4198,9 +4199,26 @@ func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([
|
|||||||
var t time.Time
|
var t time.Time
|
||||||
for idx, v := range vars {
|
for idx, v := range vars {
|
||||||
if columnNames[idx] == "timestamp" {
|
if columnNames[idx] == "timestamp" {
|
||||||
t = time.Unix(0, int64(*v.(*uint64)))
|
switch v := v.(type) {
|
||||||
|
case *uint64:
|
||||||
|
t = time.Unix(0, int64(*v))
|
||||||
|
case *time.Time:
|
||||||
|
t = *v
|
||||||
|
}
|
||||||
} else if columnNames[idx] == "timestamp_datetime" {
|
} else if columnNames[idx] == "timestamp_datetime" {
|
||||||
t = *v.(*time.Time)
|
t = *v.(*time.Time)
|
||||||
|
} else if columnNames[idx] == "events" {
|
||||||
|
var events []map[string]interface{}
|
||||||
|
eventsFromDB, ok := v.(*[]string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, event := range *eventsFromDB {
|
||||||
|
var eventMap map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(event), &eventMap)
|
||||||
|
events = append(events, eventMap)
|
||||||
|
}
|
||||||
|
row[columnNames[idx]] = events
|
||||||
} else {
|
} else {
|
||||||
row[columnNames[idx]] = v
|
row[columnNames[idx]] = v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type ClickhouseQuerySettings struct {
|
|||||||
MaxBytesToRead string
|
MaxBytesToRead string
|
||||||
OptimizeReadInOrderRegex string
|
OptimizeReadInOrderRegex string
|
||||||
OptimizeReadInOrderRegexCompiled *regexp.Regexp
|
OptimizeReadInOrderRegexCompiled *regexp.Regexp
|
||||||
|
MaxResultRowsForCHQuery int
|
||||||
}
|
}
|
||||||
|
|
||||||
type clickhouseConnWrapper struct {
|
type clickhouseConnWrapper struct {
|
||||||
@@ -44,6 +45,10 @@ func (c clickhouseConnWrapper) addClickHouseSettings(ctx context.Context, query
|
|||||||
settings["log_comment"] = logComment
|
settings["log_comment"] = logComment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.Value("enforce_max_result_rows") != nil {
|
||||||
|
settings["max_result_rows"] = c.settings.MaxResultRowsForCHQuery
|
||||||
|
}
|
||||||
|
|
||||||
if c.settings.MaxBytesToRead != "" {
|
if c.settings.MaxBytesToRead != "" {
|
||||||
settings["max_bytes_to_read"] = c.settings.MaxBytesToRead
|
settings["max_bytes_to_read"] = c.settings.MaxBytesToRead
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ type cloudProviderAccountsRepository interface {
|
|||||||
func newCloudProviderAccountsRepository(db *sqlx.DB) (
|
func newCloudProviderAccountsRepository(db *sqlx.DB) (
|
||||||
*cloudProviderAccountsSQLRepository, error,
|
*cloudProviderAccountsSQLRepository, error,
|
||||||
) {
|
) {
|
||||||
if err := InitSqliteDBIfNeeded(db); err != nil {
|
if err := initAccountsSqliteDBIfNeeded(db); err != nil {
|
||||||
return nil, fmt.Errorf("could not init sqlite DB for cloudintegrations: %w", err)
|
return nil, fmt.Errorf("could not init sqlite DB for cloudintegrations accounts: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cloudProviderAccountsSQLRepository{
|
return &cloudProviderAccountsSQLRepository{
|
||||||
@@ -46,7 +46,7 @@ func newCloudProviderAccountsRepository(db *sqlx.DB) (
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitSqliteDBIfNeeded(db *sqlx.DB) error {
|
func initAccountsSqliteDBIfNeeded(db *sqlx.DB) error {
|
||||||
if db == nil {
|
if db == nil {
|
||||||
return fmt.Errorf("db is required")
|
return fmt.Errorf("db is required")
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func InitSqliteDBIfNeeded(db *sqlx.DB) error {
|
|||||||
_, err := db.Exec(createTablesStatements)
|
_, err := db.Exec(createTablesStatements)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"could not ensure cloud provider integrations schema in sqlite DB: %w", err,
|
"could not ensure cloud provider accounts schema in sqlite DB: %w", err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
217
pkg/query-service/app/cloudintegrations/availableServices.go
Normal file
217
pkg/query-service/app/cloudintegrations/availableServices.go
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
package cloudintegrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
koanfJson "github.com/knadh/koanf/parsers/json"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/integrations"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func listCloudProviderServices(
|
||||||
|
cloudProvider string,
|
||||||
|
) ([]CloudServiceDetails, *model.ApiError) {
|
||||||
|
cloudServices := availableServices[cloudProvider]
|
||||||
|
if cloudServices == nil {
|
||||||
|
return nil, model.NotFoundError(fmt.Errorf(
|
||||||
|
"unsupported cloud provider: %s", cloudProvider,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
services := maps.Values(cloudServices)
|
||||||
|
sort.Slice(services, func(i, j int) bool {
|
||||||
|
return services[i].Id < services[j].Id
|
||||||
|
})
|
||||||
|
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCloudProviderService(
|
||||||
|
cloudProvider string, serviceId string,
|
||||||
|
) (*CloudServiceDetails, *model.ApiError) {
|
||||||
|
cloudServices := availableServices[cloudProvider]
|
||||||
|
if cloudServices == nil {
|
||||||
|
return nil, model.NotFoundError(fmt.Errorf(
|
||||||
|
"unsupported cloud provider: %s", cloudProvider,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
svc, exists := cloudServices[serviceId]
|
||||||
|
if !exists {
|
||||||
|
return nil, model.NotFoundError(fmt.Errorf(
|
||||||
|
"%s service not found: %s", cloudProvider, serviceId,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &svc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of API. Logic for reading service definition files follows
|
||||||
|
|
||||||
|
// Service details read from ./serviceDefinitions
|
||||||
|
// { "providerName": { "service_id": {...}} }
|
||||||
|
var availableServices map[string]map[string]CloudServiceDetails
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
err := readAllServiceDefinitions()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf(
|
||||||
|
"couldn't read cloud service definitions: %w", err,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed serviceDefinitions/*
|
||||||
|
var serviceDefinitionFiles embed.FS
|
||||||
|
|
||||||
|
func readAllServiceDefinitions() error {
|
||||||
|
availableServices = map[string]map[string]CloudServiceDetails{}
|
||||||
|
|
||||||
|
rootDirName := "serviceDefinitions"
|
||||||
|
|
||||||
|
cloudProviderDirs, err := fs.ReadDir(serviceDefinitionFiles, rootDirName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't read dirs in %s: %w", rootDirName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range cloudProviderDirs {
|
||||||
|
if !d.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudProviderDirPath := path.Join(rootDirName, d.Name())
|
||||||
|
cloudServices, err := readServiceDefinitionsFromDir(cloudProviderDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't read %s service definitions", d.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cloudServices) < 1 {
|
||||||
|
return fmt.Errorf("no %s services could be read", d.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
availableServices[d.Name()] = cloudServices
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readServiceDefinitionsFromDir(cloudProviderDirPath string) (
|
||||||
|
map[string]CloudServiceDetails, error,
|
||||||
|
) {
|
||||||
|
svcDefDirs, err := fs.ReadDir(serviceDefinitionFiles, cloudProviderDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("couldn't list integrations dirs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svcDefs := map[string]CloudServiceDetails{}
|
||||||
|
|
||||||
|
for _, d := range svcDefDirs {
|
||||||
|
if !d.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
svcDirPath := path.Join(cloudProviderDirPath, d.Name())
|
||||||
|
s, err := readServiceDefinition(svcDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("couldn't read svc definition for %s: %w", d.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := svcDefs[s.Id]
|
||||||
|
if exists {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"duplicate service definition for id %s at %s", s.Id, d.Name(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
svcDefs[s.Id] = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
return svcDefs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readServiceDefinition(dirpath string) (*CloudServiceDetails, error) {
|
||||||
|
integrationJsonPath := path.Join(dirpath, "integration.json")
|
||||||
|
|
||||||
|
serializedSpec, err := serviceDefinitionFiles.ReadFile(integrationJsonPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"couldn't find integration.json in %s: %w",
|
||||||
|
dirpath, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
integrationSpec, err := koanfJson.Parser().Unmarshal(serializedSpec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"couldn't parse integration.json from %s: %w",
|
||||||
|
integrationJsonPath, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
hydrated, err := integrations.HydrateFileUris(
|
||||||
|
integrationSpec, serviceDefinitionFiles, dirpath,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"couldn't hydrate files referenced in service definition %s: %w",
|
||||||
|
integrationJsonPath, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
hydratedSpec := hydrated.(map[string]interface{})
|
||||||
|
hydratedSpecJson, err := koanfJson.Parser().Marshal(hydratedSpec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"couldn't serialize hydrated integration spec back to JSON %s: %w",
|
||||||
|
integrationJsonPath, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceDef CloudServiceDetails
|
||||||
|
decoder := json.NewDecoder(bytes.NewReader(hydratedSpecJson))
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
err = decoder.Decode(&serviceDef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"couldn't parse hydrated JSON spec read from %s: %w",
|
||||||
|
integrationJsonPath, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateServiceDefinition(serviceDef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid service definition %s: %w", serviceDef.Id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &serviceDef, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateServiceDefinition(s CloudServiceDetails) error {
|
||||||
|
// Validate dashboard data
|
||||||
|
seenDashboardIds := map[string]interface{}{}
|
||||||
|
for _, dd := range s.Assets.Dashboards {
|
||||||
|
did, exists := dd["id"]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("id is required. not specified in dashboard titled %v", dd["title"])
|
||||||
|
}
|
||||||
|
dashboardId, ok := did.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("id must be string in dashboard titled %v", dd["title"])
|
||||||
|
}
|
||||||
|
if _, seen := seenDashboardIds[dashboardId]; seen {
|
||||||
|
return fmt.Errorf("multiple dashboards found with id %s", dashboardId)
|
||||||
|
}
|
||||||
|
seenDashboardIds[dashboardId] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// potentially more to follow
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package cloudintegrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAvailableServices(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
// should be able to list available services.
|
||||||
|
_, apiErr := listCloudProviderServices("bad-cloud-provider")
|
||||||
|
require.NotNil(apiErr)
|
||||||
|
require.Equal(model.ErrorNotFound, apiErr.Type())
|
||||||
|
|
||||||
|
awsSvcs, apiErr := listCloudProviderServices("aws")
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Greater(len(awsSvcs), 0)
|
||||||
|
|
||||||
|
// should be able to get details of a service
|
||||||
|
_, apiErr = getCloudProviderService(
|
||||||
|
"aws", "bad-service-id",
|
||||||
|
)
|
||||||
|
require.NotNil(apiErr)
|
||||||
|
require.Equal(model.ErrorNotFound, apiErr.Type())
|
||||||
|
|
||||||
|
svc, apiErr := getCloudProviderService(
|
||||||
|
"aws", awsSvcs[0].Id,
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(*svc, awsSvcs[0])
|
||||||
|
}
|
||||||
@@ -22,19 +22,26 @@ func validateCloudProviderName(name string) *model.ApiError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
repo cloudProviderAccountsRepository
|
accountsRepo cloudProviderAccountsRepository
|
||||||
|
serviceConfigRepo serviceConfigRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewController(db *sqlx.DB) (
|
func NewController(db *sqlx.DB) (
|
||||||
*Controller, error,
|
*Controller, error,
|
||||||
) {
|
) {
|
||||||
repo, err := newCloudProviderAccountsRepository(db)
|
accountsRepo, err := newCloudProviderAccountsRepository(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't create cloud provider accounts repo: %w", err)
|
return nil, fmt.Errorf("couldn't create cloud provider accounts repo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serviceConfigRepo, err := newServiceConfigRepository(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("couldn't create cloud provider service config repo: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &Controller{
|
return &Controller{
|
||||||
repo: repo,
|
accountsRepo: accountsRepo,
|
||||||
|
serviceConfigRepo: serviceConfigRepo,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +65,7 @@ func (c *Controller) ListConnectedAccounts(
|
|||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
accountRecords, apiErr := c.repo.listConnected(ctx, cloudProvider)
|
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, cloudProvider)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't list cloud accounts")
|
return nil, model.WrapApiError(apiErr, "couldn't list cloud accounts")
|
||||||
}
|
}
|
||||||
@@ -100,7 +107,7 @@ func (c *Controller) GenerateConnectionUrl(
|
|||||||
return nil, model.BadRequest(fmt.Errorf("unsupported cloud provider: %s", cloudProvider))
|
return nil, model.BadRequest(fmt.Errorf("unsupported cloud provider: %s", cloudProvider))
|
||||||
}
|
}
|
||||||
|
|
||||||
account, apiErr := c.repo.upsert(
|
account, apiErr := c.accountsRepo.upsert(
|
||||||
ctx, cloudProvider, req.AccountId, &req.AccountConfig, nil, nil, nil,
|
ctx, cloudProvider, req.AccountId, &req.AccountConfig, nil, nil, nil,
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
@@ -120,8 +127,9 @@ func (c *Controller) GenerateConnectionUrl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AccountStatusResponse struct {
|
type AccountStatusResponse struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Status AccountStatus `json:"status"`
|
CloudAccountId *string `json:"cloud_account_id,omitempty"`
|
||||||
|
Status AccountStatus `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) GetAccountStatus(
|
func (c *Controller) GetAccountStatus(
|
||||||
@@ -133,14 +141,15 @@ func (c *Controller) GetAccountStatus(
|
|||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
account, apiErr := c.repo.get(ctx, cloudProvider, accountId)
|
account, apiErr := c.accountsRepo.get(ctx, cloudProvider, accountId)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := AccountStatusResponse{
|
resp := AccountStatusResponse{
|
||||||
Id: account.Id,
|
Id: account.Id,
|
||||||
Status: account.status(),
|
CloudAccountId: account.CloudAccountId,
|
||||||
|
Status: account.status(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return &resp, nil
|
return &resp, nil
|
||||||
@@ -164,7 +173,7 @@ func (c *Controller) CheckInAsAgent(
|
|||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
existingAccount, apiErr := c.repo.get(ctx, cloudProvider, req.AccountId)
|
existingAccount, apiErr := c.accountsRepo.get(ctx, cloudProvider, req.AccountId)
|
||||||
if existingAccount != nil && existingAccount.CloudAccountId != nil && *existingAccount.CloudAccountId != req.CloudAccountId {
|
if existingAccount != nil && existingAccount.CloudAccountId != nil && *existingAccount.CloudAccountId != req.CloudAccountId {
|
||||||
return nil, model.BadRequest(fmt.Errorf(
|
return nil, model.BadRequest(fmt.Errorf(
|
||||||
"can't check in with new %s account id %s for account %s with existing %s id %s",
|
"can't check in with new %s account id %s for account %s with existing %s id %s",
|
||||||
@@ -172,7 +181,7 @@ func (c *Controller) CheckInAsAgent(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
existingAccount, apiErr = c.repo.getConnectedCloudAccount(ctx, cloudProvider, req.CloudAccountId)
|
existingAccount, apiErr = c.accountsRepo.getConnectedCloudAccount(ctx, cloudProvider, req.CloudAccountId)
|
||||||
if existingAccount != nil && existingAccount.Id != req.AccountId {
|
if existingAccount != nil && existingAccount.Id != req.AccountId {
|
||||||
return nil, model.BadRequest(fmt.Errorf(
|
return nil, model.BadRequest(fmt.Errorf(
|
||||||
"can't check in to %s account %s with id %s. already connected with id %s",
|
"can't check in to %s account %s with id %s. already connected with id %s",
|
||||||
@@ -185,7 +194,7 @@ func (c *Controller) CheckInAsAgent(
|
|||||||
Data: req.Data,
|
Data: req.Data,
|
||||||
}
|
}
|
||||||
|
|
||||||
account, apiErr := c.repo.upsert(
|
account, apiErr := c.accountsRepo.upsert(
|
||||||
ctx, cloudProvider, &req.AccountId, nil, &req.CloudAccountId, &agentReport, nil,
|
ctx, cloudProvider, &req.AccountId, nil, &req.CloudAccountId, &agentReport, nil,
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
@@ -211,7 +220,7 @@ func (c *Controller) UpdateAccountConfig(
|
|||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
accountRecord, apiErr := c.repo.upsert(
|
accountRecord, apiErr := c.accountsRepo.upsert(
|
||||||
ctx, cloudProvider, &accountId, &req.Config, nil, nil, nil,
|
ctx, cloudProvider, &accountId, &req.Config, nil, nil, nil,
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
@@ -230,13 +239,13 @@ func (c *Controller) DisconnectAccount(
|
|||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
account, apiErr := c.repo.get(ctx, cloudProvider, accountId)
|
account, apiErr := c.accountsRepo.get(ctx, cloudProvider, accountId)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't disconnect account")
|
return nil, model.WrapApiError(apiErr, "couldn't disconnect account")
|
||||||
}
|
}
|
||||||
|
|
||||||
tsNow := time.Now()
|
tsNow := time.Now()
|
||||||
account, apiErr = c.repo.upsert(
|
account, apiErr = c.accountsRepo.upsert(
|
||||||
ctx, cloudProvider, &accountId, nil, nil, nil, &tsNow,
|
ctx, cloudProvider, &accountId, nil, nil, nil, &tsNow,
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
@@ -245,3 +254,127 @@ func (c *Controller) DisconnectAccount(
|
|||||||
|
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListServicesResponse struct {
|
||||||
|
Services []CloudServiceSummary `json:"services"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ListServices(
|
||||||
|
ctx context.Context,
|
||||||
|
cloudProvider string,
|
||||||
|
cloudAccountId *string,
|
||||||
|
) (*ListServicesResponse, *model.ApiError) {
|
||||||
|
|
||||||
|
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
services, apiErr := listCloudProviderServices(cloudProvider)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, model.WrapApiError(apiErr, "couldn't list cloud services")
|
||||||
|
}
|
||||||
|
|
||||||
|
svcConfigs := map[string]*CloudServiceConfig{}
|
||||||
|
if cloudAccountId != nil {
|
||||||
|
svcConfigs, apiErr = c.serviceConfigRepo.getAllForAccount(
|
||||||
|
ctx, cloudProvider, *cloudAccountId,
|
||||||
|
)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, model.WrapApiError(
|
||||||
|
apiErr, "couldn't get service configs for cloud account",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summaries := []CloudServiceSummary{}
|
||||||
|
for _, s := range services {
|
||||||
|
summary := s.CloudServiceSummary
|
||||||
|
summary.Config = svcConfigs[summary.Id]
|
||||||
|
|
||||||
|
summaries = append(summaries, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ListServicesResponse{
|
||||||
|
Services: summaries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) GetServiceDetails(
|
||||||
|
ctx context.Context,
|
||||||
|
cloudProvider string,
|
||||||
|
serviceId string,
|
||||||
|
cloudAccountId *string,
|
||||||
|
) (*CloudServiceDetails, *model.ApiError) {
|
||||||
|
|
||||||
|
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
service, apiErr := getCloudProviderService(cloudProvider, serviceId)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if cloudAccountId != nil {
|
||||||
|
config, apiErr := c.serviceConfigRepo.get(
|
||||||
|
ctx, cloudProvider, *cloudAccountId, serviceId,
|
||||||
|
)
|
||||||
|
if apiErr != nil && apiErr.Type() != model.ErrorNotFound {
|
||||||
|
return nil, model.WrapApiError(apiErr, "couldn't fetch service config")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config != nil {
|
||||||
|
service.Config = config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateServiceConfigRequest struct {
|
||||||
|
CloudAccountId string `json:"cloud_account_id"`
|
||||||
|
Config CloudServiceConfig `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateServiceConfigResponse struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Config CloudServiceConfig `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) UpdateServiceConfig(
|
||||||
|
ctx context.Context,
|
||||||
|
cloudProvider string,
|
||||||
|
serviceId string,
|
||||||
|
req UpdateServiceConfigRequest,
|
||||||
|
) (*UpdateServiceConfigResponse, *model.ApiError) {
|
||||||
|
|
||||||
|
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// can only update config for a connected cloud account id
|
||||||
|
_, apiErr := c.accountsRepo.getConnectedCloudAccount(
|
||||||
|
ctx, cloudProvider, req.CloudAccountId,
|
||||||
|
)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, model.WrapApiError(apiErr, "couldn't find connected cloud account")
|
||||||
|
}
|
||||||
|
|
||||||
|
// can only update config for a valid service.
|
||||||
|
_, apiErr = getCloudProviderService(cloudProvider, serviceId)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, model.WrapApiError(apiErr, "unsupported service")
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedConfig, apiErr := c.serviceConfigRepo.upsert(
|
||||||
|
ctx, cloudProvider, req.CloudAccountId, serviceId, req.Config,
|
||||||
|
)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, model.WrapApiError(apiErr, "couldn't update service config")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &UpdateServiceConfigResponse{
|
||||||
|
Id: serviceId,
|
||||||
|
Config: *updatedConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) {
|
|||||||
require.NotEmpty(resp1.AccountId)
|
require.NotEmpty(resp1.AccountId)
|
||||||
|
|
||||||
testAccountId := resp1.AccountId
|
testAccountId := resp1.AccountId
|
||||||
account, apiErr := controller.repo.get(
|
account, apiErr := controller.accountsRepo.get(
|
||||||
context.TODO(), "aws", testAccountId,
|
context.TODO(), "aws", testAccountId,
|
||||||
)
|
)
|
||||||
require.Nil(apiErr)
|
require.Nil(apiErr)
|
||||||
@@ -47,7 +47,7 @@ func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) {
|
|||||||
require.Nil(apiErr)
|
require.Nil(apiErr)
|
||||||
require.Equal(testAccountId, resp2.AccountId)
|
require.Equal(testAccountId, resp2.AccountId)
|
||||||
|
|
||||||
account, apiErr = controller.repo.get(
|
account, apiErr = controller.accountsRepo.get(
|
||||||
context.TODO(), "aws", testAccountId,
|
context.TODO(), "aws", testAccountId,
|
||||||
)
|
)
|
||||||
require.Nil(apiErr)
|
require.Nil(apiErr)
|
||||||
@@ -89,7 +89,7 @@ func TestAgentCheckIns(t *testing.T) {
|
|||||||
// if another connected AccountRecord exists for same cloud account
|
// if another connected AccountRecord exists for same cloud account
|
||||||
// i.e. there can't be 2 connected account records for the same cloud account id
|
// i.e. there can't be 2 connected account records for the same cloud account id
|
||||||
// at any point in time.
|
// at any point in time.
|
||||||
existingConnected, apiErr := controller.repo.getConnectedCloudAccount(
|
existingConnected, apiErr := controller.accountsRepo.getConnectedCloudAccount(
|
||||||
context.TODO(), "aws", testCloudAccountId1,
|
context.TODO(), "aws", testCloudAccountId1,
|
||||||
)
|
)
|
||||||
require.Nil(apiErr)
|
require.Nil(apiErr)
|
||||||
@@ -112,7 +112,7 @@ func TestAgentCheckIns(t *testing.T) {
|
|||||||
context.TODO(), "aws", testAccountId1,
|
context.TODO(), "aws", testAccountId1,
|
||||||
)
|
)
|
||||||
|
|
||||||
existingConnected, apiErr = controller.repo.getConnectedCloudAccount(
|
existingConnected, apiErr = controller.accountsRepo.getConnectedCloudAccount(
|
||||||
context.TODO(), "aws", testCloudAccountId1,
|
context.TODO(), "aws", testCloudAccountId1,
|
||||||
)
|
)
|
||||||
require.Nil(existingConnected)
|
require.Nil(existingConnected)
|
||||||
@@ -151,3 +151,120 @@ func TestCantDisconnectNonExistentAccount(t *testing.T) {
|
|||||||
require.Equal(model.ErrorNotFound, apiErr.Type())
|
require.Equal(model.ErrorNotFound, apiErr.Type())
|
||||||
require.Nil(account)
|
require.Nil(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigureService(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
testDB, _ := utils.NewTestSqliteDB(t)
|
||||||
|
controller, err := NewController(testDB)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
testCloudAccountId := "546311234"
|
||||||
|
|
||||||
|
// should start out without any service config
|
||||||
|
svcListResp, apiErr := controller.ListServices(
|
||||||
|
context.TODO(), "aws", &testCloudAccountId,
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
|
||||||
|
testSvcId := svcListResp.Services[0].Id
|
||||||
|
require.Nil(svcListResp.Services[0].Config)
|
||||||
|
|
||||||
|
svcDetails, apiErr := controller.GetServiceDetails(
|
||||||
|
context.TODO(), "aws", testSvcId, &testCloudAccountId,
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(testSvcId, svcDetails.Id)
|
||||||
|
require.Nil(svcDetails.Config)
|
||||||
|
|
||||||
|
// should be able to configure a service for a connected account
|
||||||
|
testConnectedAccount := makeTestConnectedAccount(t, controller, testCloudAccountId)
|
||||||
|
require.Nil(testConnectedAccount.RemovedAt)
|
||||||
|
require.NotNil(testConnectedAccount.CloudAccountId)
|
||||||
|
require.Equal(testCloudAccountId, *testConnectedAccount.CloudAccountId)
|
||||||
|
|
||||||
|
testSvcConfig := CloudServiceConfig{
|
||||||
|
Metrics: &CloudServiceMetricsConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
updateSvcConfigResp, apiErr := controller.UpdateServiceConfig(
|
||||||
|
context.TODO(), "aws", testSvcId, UpdateServiceConfigRequest{
|
||||||
|
CloudAccountId: testCloudAccountId,
|
||||||
|
Config: testSvcConfig,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(testSvcId, updateSvcConfigResp.Id)
|
||||||
|
require.Equal(testSvcConfig, updateSvcConfigResp.Config)
|
||||||
|
|
||||||
|
svcDetails, apiErr = controller.GetServiceDetails(
|
||||||
|
context.TODO(), "aws", testSvcId, &testCloudAccountId,
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(testSvcId, svcDetails.Id)
|
||||||
|
require.Equal(testSvcConfig, *svcDetails.Config)
|
||||||
|
|
||||||
|
svcListResp, apiErr = controller.ListServices(
|
||||||
|
context.TODO(), "aws", &testCloudAccountId,
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
for _, svc := range svcListResp.Services {
|
||||||
|
if svc.Id == testSvcId {
|
||||||
|
require.Equal(testSvcConfig, *svc.Config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// should not be able to configure service after cloud account has been disconnected
|
||||||
|
_, apiErr = controller.DisconnectAccount(
|
||||||
|
context.TODO(), "aws", testConnectedAccount.Id,
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
|
||||||
|
_, apiErr = controller.UpdateServiceConfig(
|
||||||
|
context.TODO(), "aws", testSvcId,
|
||||||
|
UpdateServiceConfigRequest{
|
||||||
|
CloudAccountId: testCloudAccountId,
|
||||||
|
Config: testSvcConfig,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NotNil(apiErr)
|
||||||
|
|
||||||
|
// should not be able to configure a service for a cloud account id that is not connected yet
|
||||||
|
_, apiErr = controller.UpdateServiceConfig(
|
||||||
|
context.TODO(), "aws", testSvcId,
|
||||||
|
UpdateServiceConfigRequest{
|
||||||
|
CloudAccountId: "9999999999",
|
||||||
|
Config: testSvcConfig,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NotNil(apiErr)
|
||||||
|
|
||||||
|
// should not be able to set config for an unsupported service
|
||||||
|
_, apiErr = controller.UpdateServiceConfig(
|
||||||
|
context.TODO(), "aws", "bad-service", UpdateServiceConfigRequest{
|
||||||
|
CloudAccountId: testCloudAccountId,
|
||||||
|
Config: testSvcConfig,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NotNil(apiErr)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestConnectedAccount(t *testing.T, controller *Controller, cloudAccountId string) *AccountRecord {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
// a check in from SigNoz agent creates or updates a connected account.
|
||||||
|
testAccountId := uuid.NewString()
|
||||||
|
resp, apiErr := controller.CheckInAsAgent(
|
||||||
|
context.TODO(), "aws", AgentCheckInRequest{
|
||||||
|
AccountId: testAccountId,
|
||||||
|
CloudAccountId: cloudAccountId,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(testAccountId, resp.Account.Id)
|
||||||
|
require.Equal(cloudAccountId, *resp.Account.CloudAccountId)
|
||||||
|
|
||||||
|
return &resp.Account
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Represents a cloud provider account for cloud integrations
|
// Represents a cloud provider account for cloud integrations
|
||||||
@@ -115,3 +117,102 @@ func (a *AccountRecord) account() Account {
|
|||||||
|
|
||||||
return ca
|
return ca
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CloudServiceSummary struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
|
||||||
|
// Present only if the service has been configured in the
|
||||||
|
// context of a cloud provider account.
|
||||||
|
Config *CloudServiceConfig `json:"config,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudServiceDetails struct {
|
||||||
|
CloudServiceSummary
|
||||||
|
|
||||||
|
Overview string `json:"overview"` // markdown
|
||||||
|
|
||||||
|
Assets CloudServiceAssets `json:"assets"`
|
||||||
|
|
||||||
|
SupportedSignals SupportedSignals `json:"supported_signals"`
|
||||||
|
|
||||||
|
DataCollected DataCollectedForService `json:"data_collected"`
|
||||||
|
|
||||||
|
ConnectionStatus *CloudServiceConnectionStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudServiceConfig struct {
|
||||||
|
Logs *CloudServiceLogsConfig `json:"logs,omitempty"`
|
||||||
|
Metrics *CloudServiceMetricsConfig `json:"metrics,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// For serializing from db
|
||||||
|
func (c *CloudServiceConfig) Scan(src any) error {
|
||||||
|
data, ok := src.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("tried to scan from %T instead of bytes", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(data, &c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For serializing to db
|
||||||
|
func (c *CloudServiceConfig) Value() (driver.Value, error) {
|
||||||
|
if c == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"couldn't serialize cloud service config to JSON: %w", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return serialized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudServiceLogsConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudServiceMetricsConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudServiceAssets struct {
|
||||||
|
Dashboards []dashboards.Data `json:"dashboards"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupportedSignals struct {
|
||||||
|
Logs bool `json:"logs"`
|
||||||
|
Metrics bool `json:"metrics"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataCollectedForService struct {
|
||||||
|
Logs []CollectedLogAttribute `json:"logs"`
|
||||||
|
Metrics []CollectedMetric `json:"metrics"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectedLogAttribute struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectedMetric struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Unit string `json:"unit"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudServiceConnectionStatus struct {
|
||||||
|
Logs *SignalConnectionStatus `json:"logs"`
|
||||||
|
Metrics *SignalConnectionStatus `json:"metrics"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignalConnectionStatus struct {
|
||||||
|
LastReceivedTsMillis int64 `json:"last_received_ts_ms"` // epoch milliseconds
|
||||||
|
LastReceivedFrom string `json:"last_received_from"` // resource identifier
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user