Compare commits
12 Commits
config
...
v0.69.0-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e3ffd555d | ||
|
|
c565c2b865 | ||
|
|
4ec1e66c7e | ||
|
|
89541862cc | ||
|
|
610f4d43e7 | ||
|
|
044a124cc1 | ||
|
|
0cf9003e3a | ||
|
|
644135a933 | ||
|
|
b465f74e4a | ||
|
|
e00e365964 | ||
|
|
5c45e1f7b3 | ||
|
|
16e61b45ac |
@@ -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
|
||||
}
|
||||
|
||||
# Check if docker compose plugin is present
|
||||
has_docker_compose_plugin() {
|
||||
docker compose version > /dev/null 2>&1
|
||||
}
|
||||
|
||||
is_mac() {
|
||||
[[ $OSTYPE == darwin* ]]
|
||||
}
|
||||
@@ -183,9 +188,7 @@ install_docker() {
|
||||
$sudo_cmd yum-config-manager --add-repo https://download.docker.com/linux/$os/docker-ce.repo
|
||||
echo "Installing docker"
|
||||
$yum_cmd install docker-ce docker-ce-cli containerd.io
|
||||
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
compose_version () {
|
||||
@@ -227,12 +230,6 @@ start_docker() {
|
||||
echo "Starting docker service"
|
||||
$sudo_cmd systemctl start docker.service
|
||||
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 ! docker ps > /dev/null && true; then
|
||||
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 ""
|
||||
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 "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
|
||||
sudo_cmd="sudo"
|
||||
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
|
||||
echo "Need sudo privileges to proceed with the installation."
|
||||
exit 1;
|
||||
@@ -317,6 +309,7 @@ echo -e "👋 Thank you for trying out SigNoz! "
|
||||
echo ""
|
||||
|
||||
sudo_cmd=""
|
||||
docker_compose_cmd=""
|
||||
|
||||
# Check sudo permissions
|
||||
if (( $EUID != 0 )); then
|
||||
@@ -362,28 +355,8 @@ else
|
||||
SIGNOZ_INSTALLATION_ID=$(echo "$sysinfo" | $digest_cmd | grep -E -o '[a-zA-Z0-9]{64}')
|
||||
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'
|
||||
|
||||
# echo -e "\n✅ ${CYAN}You have chosen: ${setup_type} setup\n"
|
||||
|
||||
# Run bye if failure happens
|
||||
trap bye EXIT
|
||||
|
||||
@@ -455,8 +428,6 @@ if [[ $desired_os -eq 0 ]]; then
|
||||
send_event "os_not_supported"
|
||||
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
|
||||
if ! is_command_present docker; then
|
||||
|
||||
@@ -486,27 +457,39 @@ if ! is_command_present docker; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if has_docker_compose_plugin; then
|
||||
echo "docker compose plugin is present, using it"
|
||||
docker_compose_cmd="docker compose"
|
||||
# Install docker-compose
|
||||
if ! is_command_present docker-compose; then
|
||||
request_sudo
|
||||
install_docker_compose
|
||||
else
|
||||
docker_compose_cmd="docker-compose"
|
||||
if ! is_command_present docker-compose; then
|
||||
request_sudo
|
||||
install_docker_compose
|
||||
fi
|
||||
fi
|
||||
|
||||
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 -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 "🟡 Starting the SigNoz containers. It may take a few minutes ..."
|
||||
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.
|
||||
$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
|
||||
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 ""
|
||||
|
||||
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 "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 -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 "+++++++++++++++++++++++++++++++++++++++++++++++++"
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"go.signoz.io/signoz/ee/query-service/interfaces"
|
||||
"go.signoz.io/signoz/ee/query-service/rules"
|
||||
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"
|
||||
"go.signoz.io/signoz/pkg/signoz"
|
||||
"go.signoz.io/signoz/pkg/web"
|
||||
@@ -82,7 +81,6 @@ type ServerOptions struct {
|
||||
GatewayUrl string
|
||||
UseLogsNewSchema bool
|
||||
UseTraceNewSchema bool
|
||||
SkipWebFrontend bool
|
||||
}
|
||||
|
||||
// Server runs HTTP api service
|
||||
@@ -202,13 +200,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
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
|
||||
_, err = opAmpModel.InitDB(localDB)
|
||||
if err != nil {
|
||||
@@ -396,11 +387,9 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
|
||||
handler = handlers.CompressHandler(handler)
|
||||
|
||||
if !s.serverOptions.SkipWebFrontend {
|
||||
err := web.AddToRouter(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err := web.AddToRouter(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &http.Server{
|
||||
|
||||
@@ -10,12 +10,12 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/collector/confmap"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||
"go.signoz.io/signoz/ee/query-service/app"
|
||||
signozconfig "go.signoz.io/signoz/pkg/config"
|
||||
"go.signoz.io/signoz/pkg/confmap/provider/signozenvprovider"
|
||||
"go.signoz.io/signoz/pkg/config"
|
||||
"go.signoz.io/signoz/pkg/config/envprovider"
|
||||
"go.signoz.io/signoz/pkg/config/fileprovider"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/migrate"
|
||||
@@ -108,7 +108,6 @@ func main() {
|
||||
var dialTimeout time.Duration
|
||||
var gatewayUrl string
|
||||
var useLicensesV3 bool
|
||||
var skipWebFrontend bool
|
||||
|
||||
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")
|
||||
@@ -126,7 +125,6 @@ func main() {
|
||||
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
|
||||
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
|
||||
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()
|
||||
|
||||
loggerMgr := initZapLog(enableQueryServiceLogOTLPExport)
|
||||
@@ -136,19 +134,18 @@ func main() {
|
||||
|
||||
version.PrintVersion()
|
||||
|
||||
config, err := signozconfig.New(context.Background(), signozconfig.ProviderSettings{
|
||||
ResolverSettings: confmap.ResolverSettings{
|
||||
URIs: []string{"signozenv:"},
|
||||
ProviderFactories: []confmap.ProviderFactory{
|
||||
signozenvprovider.NewFactory(),
|
||||
},
|
||||
config, err := signoz.NewConfig(context.Background(), config.ResolverConfig{
|
||||
Uris: []string{"env:"},
|
||||
ProviderFactories: []config.ProviderFactory{
|
||||
envprovider.NewFactory(),
|
||||
fileprovider.NewFactory(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
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 {
|
||||
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
|
||||
}
|
||||
@@ -171,7 +168,6 @@ func main() {
|
||||
GatewayUrl: gatewayUrl,
|
||||
UseLogsNewSchema: useLogsNewSchema,
|
||||
UseTraceNewSchema: useTraceNewSchema,
|
||||
SkipWebFrontend: skipWebFrontend,
|
||||
}
|
||||
|
||||
// Read the jwt secret key
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiBaseInstance } from 'api';
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
@@ -47,7 +47,7 @@ export const getK8sNodesList = async (
|
||||
headers?: Record<string, string>,
|
||||
): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await ApiBaseInstance.post('/nodes/list', props, {
|
||||
const response = await axios.post('/nodes/list', props, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiBaseInstance } from 'api';
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
@@ -75,7 +75,7 @@ export const getK8sPodsList = async (
|
||||
headers?: Record<string, string>,
|
||||
): Promise<SuccessResponse<K8sPodsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await ApiBaseInstance.post('/pods/list', props, {
|
||||
const response = await axios.post('/pods/list', props, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
|
||||
@@ -219,12 +219,14 @@ function ListLogView({
|
||||
<LogStateIndicator type={logType} fontSize={fontSize} />
|
||||
<div>
|
||||
<LogContainer fontSize={fontSize}>
|
||||
<LogGeneralField
|
||||
fieldKey="Log"
|
||||
fieldValue={flattenLogData.body}
|
||||
linesPerRow={linesPerRow}
|
||||
fontSize={fontSize}
|
||||
/>
|
||||
{updatedSelecedFields.some((field) => field.name === 'body') && (
|
||||
<LogGeneralField
|
||||
fieldKey="Log"
|
||||
fieldValue={flattenLogData.body}
|
||||
linesPerRow={linesPerRow}
|
||||
fontSize={fontSize}
|
||||
/>
|
||||
)}
|
||||
{flattenLogData.stream && (
|
||||
<LogGeneralField
|
||||
fieldKey="Stream"
|
||||
@@ -232,23 +234,27 @@ function ListLogView({
|
||||
fontSize={fontSize}
|
||||
/>
|
||||
)}
|
||||
<LogGeneralField
|
||||
fieldKey="Timestamp"
|
||||
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.some((field) => field.name === 'timestamp') && (
|
||||
<LogGeneralField
|
||||
fieldKey="Timestamp"
|
||||
fieldValue={timestampValue}
|
||||
fontSize={fontSize}
|
||||
/>
|
||||
)}
|
||||
|
||||
{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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -73,6 +73,7 @@ function RawLogView({
|
||||
);
|
||||
|
||||
const attributesValues = updatedSelecedFields
|
||||
.filter((field) => !['timestamp', 'body'].includes(field.name))
|
||||
.map((field) => flattenLogData[field.name])
|
||||
.filter((attribute) => {
|
||||
// loadash isEmpty doesnot work with numbers
|
||||
@@ -92,19 +93,40 @@ function RawLogView({
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const text = useMemo(() => {
|
||||
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',
|
||||
);
|
||||
const parts = [];
|
||||
|
||||
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.body,
|
||||
attributesText,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
]);
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
|
||||
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
||||
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
|
||||
.filter((e) => e.name !== 'id')
|
||||
.filter((e) => !['id', 'body', 'timestamp'].includes(e.name))
|
||||
.map(({ name }) => ({
|
||||
title: name,
|
||||
dataIndex: name,
|
||||
@@ -91,55 +91,67 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
),
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'timestamp',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
// https://github.com/ant-design/ant-design/discussions/36886
|
||||
render: (field): ColumnTypeRender<Record<string, unknown>> => {
|
||||
const date =
|
||||
typeof field === 'string'
|
||||
? formatTimezoneAdjustedTimestamp(field, 'YYYY-MM-DD HH:mm:ss.SSS')
|
||||
: formatTimezoneAdjustedTimestamp(
|
||||
field / 1e6,
|
||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
||||
);
|
||||
return {
|
||||
children: (
|
||||
<div className="table-timestamp">
|
||||
<Typography.Paragraph ellipsis className={cx('text', fontSize)}>
|
||||
{date}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
...(fields.some((field) => field.name === 'timestamp')
|
||||
? [
|
||||
{
|
||||
title: 'timestamp',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
// https://github.com/ant-design/ant-design/discussions/36886
|
||||
render: (
|
||||
field: string | number,
|
||||
): ColumnTypeRender<Record<string, unknown>> => {
|
||||
const date =
|
||||
typeof field === 'string'
|
||||
? formatTimezoneAdjustedTimestamp(field, 'YYYY-MM-DD HH:mm:ss.SSS')
|
||||
: formatTimezoneAdjustedTimestamp(
|
||||
field / 1e6,
|
||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
||||
);
|
||||
return {
|
||||
children: (
|
||||
<div className="table-timestamp">
|
||||
<Typography.Paragraph ellipsis className={cx('text', fontSize)}>
|
||||
{date}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(appendTo === 'center' ? fieldColumns : []),
|
||||
{
|
||||
title: 'body',
|
||||
dataIndex: 'body',
|
||||
key: 'body',
|
||||
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
|
||||
props: {
|
||||
style: defaultTableStyle,
|
||||
},
|
||||
children: (
|
||||
<TableBodyContent
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: convert.toHtml(
|
||||
dompurify.sanitize(unescapeString(field), {
|
||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||
}),
|
||||
...(fields.some((field) => field.name === 'body')
|
||||
? [
|
||||
{
|
||||
title: 'body',
|
||||
dataIndex: 'body',
|
||||
key: 'body',
|
||||
render: (
|
||||
field: string | number,
|
||||
): ColumnTypeRender<Record<string, unknown>> => ({
|
||||
props: {
|
||||
style: defaultTableStyle,
|
||||
},
|
||||
children: (
|
||||
<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 : []),
|
||||
];
|
||||
}, [
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'uplot/dist/uPlot.min.css';
|
||||
import './AnomalyAlertEvaluationView.styles.scss';
|
||||
|
||||
import { Checkbox, Typography } from 'antd';
|
||||
import Search from 'antd/es/input/Search';
|
||||
import { Checkbox, Input, Typography } from 'antd';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
@@ -16,6 +15,8 @@ import uPlot from 'uplot';
|
||||
|
||||
import tooltipPlugin from './tooltipPlugin';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
function UplotChart({
|
||||
data,
|
||||
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 {
|
||||
border-bottom: 1px solid var(--bg-slate-400);
|
||||
padding: 12px 8px;
|
||||
|
||||
&[aria-expanded='true'] {
|
||||
background: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-collapse-content-box {
|
||||
@@ -271,8 +267,6 @@
|
||||
|
||||
.group-by-label {
|
||||
min-width: max-content;
|
||||
|
||||
color: var(--bg-vanilla-100, #c0c1c3);
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
@@ -282,7 +276,6 @@
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--bg-slate-400, #1d212d);
|
||||
border-right: none;
|
||||
background: var(--bg-ink-100, #16181d);
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
|
||||
@@ -488,7 +481,7 @@
|
||||
.expanded-table-container {
|
||||
border: 1px solid var(--bg-ink-400);
|
||||
overflow-x: auto;
|
||||
padding-left: 16px;
|
||||
padding-left: 48px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.1rem;
|
||||
@@ -710,8 +703,34 @@
|
||||
}
|
||||
|
||||
.ant-table-cell {
|
||||
min-width: 170px !important;
|
||||
max-width: 170px !important;
|
||||
min-width: 140px !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 {
|
||||
@@ -808,6 +827,24 @@
|
||||
}
|
||||
|
||||
.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 {
|
||||
.ant-table {
|
||||
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 { Container, Workflow } from 'lucide-react';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import {
|
||||
@@ -24,6 +24,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
const [showFilters, setShowFilters] = useState(true);
|
||||
|
||||
const [selectedCategory, setSelectedCategory] = useState(K8sCategories.PODS);
|
||||
const [quickFiltersLastUpdated, setQuickFiltersLastUpdated] = useState(-1);
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
@@ -37,14 +38,12 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
entityVersion: '',
|
||||
});
|
||||
|
||||
const handleFilterChange = useCallback(
|
||||
(query: Query): void => {
|
||||
// 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
|
||||
handleChangeQueryData('filters', query.builder.queryData[0].filters);
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
const handleFilterChange = (query: Query): void => {
|
||||
// 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
|
||||
handleChangeQueryData('filters', query.builder.queryData[0].filters);
|
||||
setQuickFiltersLastUpdated(Date.now());
|
||||
};
|
||||
|
||||
const items: CollapseProps['items'] = [
|
||||
{
|
||||
@@ -262,6 +261,8 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
const handleCategoryChange = (key: string | string[]): void => {
|
||||
if (Array.isArray(key) && key.length > 0) {
|
||||
setSelectedCategory(key[0] as string);
|
||||
// Reset filters
|
||||
handleChangeQueryData('filters', { items: [], op: 'and' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -302,6 +303,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
<K8sPodLists
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -309,6 +311,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
<K8sNodesList
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Button, Input } from 'antd';
|
||||
import { GripVertical, TableColumnsSplit, X } from 'lucide-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { IPodColumn } from '../utils';
|
||||
import { IEntityColumn } from '../utils';
|
||||
|
||||
function K8sFiltersSidePanel({
|
||||
defaultAddedColumns,
|
||||
@@ -17,12 +17,12 @@ function K8sFiltersSidePanel({
|
||||
onAddColumn = () => {},
|
||||
onRemoveColumn = () => {},
|
||||
}: {
|
||||
defaultAddedColumns: IPodColumn[];
|
||||
defaultAddedColumns: IEntityColumn[];
|
||||
onClose: () => void;
|
||||
addedColumns?: IPodColumn[];
|
||||
availableColumns?: IPodColumn[];
|
||||
onAddColumn?: (column: IPodColumn) => void;
|
||||
onRemoveColumn?: (column: IPodColumn) => void;
|
||||
addedColumns?: IEntityColumn[];
|
||||
availableColumns?: IEntityColumn[];
|
||||
onAddColumn?: (column: IEntityColumn) => void;
|
||||
onRemoveColumn?: (column: IEntityColumn) => void;
|
||||
}): JSX.Element {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const sidePanelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -12,7 +12,7 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { K8sCategory } from './constants';
|
||||
import K8sFiltersSidePanel from './K8sFiltersSidePanel/K8sFiltersSidePanel';
|
||||
import { IPodColumn } from './utils';
|
||||
import { IEntityColumn } from './utils';
|
||||
|
||||
interface K8sHeaderProps {
|
||||
selectedGroupBy: BaseAutocompleteData[];
|
||||
@@ -20,11 +20,11 @@ interface K8sHeaderProps {
|
||||
isLoadingGroupByFilters: boolean;
|
||||
handleFiltersChange: (value: IBuilderQuery['filters']) => void;
|
||||
handleGroupByChange: (value: IBuilderQuery['groupBy']) => void;
|
||||
defaultAddedColumns: IPodColumn[];
|
||||
addedColumns?: IPodColumn[];
|
||||
availableColumns?: IPodColumn[];
|
||||
onAddColumn?: (column: IPodColumn) => void;
|
||||
onRemoveColumn?: (column: IPodColumn) => void;
|
||||
defaultAddedColumns: IEntityColumn[];
|
||||
addedColumns?: IEntityColumn[];
|
||||
availableColumns?: IEntityColumn[];
|
||||
onAddColumn?: (column: IEntityColumn) => void;
|
||||
onRemoveColumn?: (column: IEntityColumn) => void;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
isFiltersVisible: boolean;
|
||||
entity: K8sCategory;
|
||||
|
||||
@@ -45,9 +45,11 @@ import {
|
||||
function K8sNodesList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -60,7 +62,7 @@ function K8sNodesList({
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(null);
|
||||
} | null>({ columnName: 'cpu', order: 'desc' });
|
||||
|
||||
const [selectedNodeUID, setselectedNodeUID] = useState<string | null>(null);
|
||||
|
||||
@@ -76,12 +78,28 @@ function K8sNodesList({
|
||||
{ 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 = (
|
||||
selectedRowData: K8sNodesRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
): IBuilderQuery['filters'] => {
|
||||
const baseFilters: IBuilderQuery['filters'] = {
|
||||
items: [],
|
||||
items: [...queryFilters.items],
|
||||
op: 'and',
|
||||
};
|
||||
|
||||
@@ -120,6 +138,7 @@ function K8sNodesList({
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||
|
||||
const {
|
||||
@@ -133,8 +152,6 @@ function K8sNodesList({
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
isLoading: isLoadingGroupByFilters,
|
||||
@@ -153,15 +170,6 @@ function K8sNodesList({
|
||||
K8sCategory.NODES,
|
||||
);
|
||||
|
||||
const queryFilters = useMemo(
|
||||
() =>
|
||||
currentQuery?.builder?.queryData[0]?.filters || {
|
||||
items: [],
|
||||
op: 'and',
|
||||
},
|
||||
[currentQuery?.builder?.queryData],
|
||||
);
|
||||
|
||||
const query = useMemo(() => {
|
||||
const baseQuery = getK8sNodesListQuery();
|
||||
const queryPayload = {
|
||||
@@ -308,6 +316,7 @@ function K8sNodesList({
|
||||
) : (
|
||||
<div className="expanded-table">
|
||||
<Table
|
||||
className="expanded-table-view"
|
||||
columns={nestedColumns as ColumnType<K8sNodesRowData>[]}
|
||||
dataSource={formattedGroupedByNodesData}
|
||||
pagination={false}
|
||||
@@ -382,18 +391,6 @@ function K8sNodesList({
|
||||
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(
|
||||
(value: IBuilderQuery['groupBy']) => {
|
||||
const groupBy = [];
|
||||
@@ -442,54 +439,53 @@ function K8sNodesList({
|
||||
/>
|
||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||
|
||||
{showNoFilteredNodesMessage && (
|
||||
<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"
|
||||
/>
|
||||
<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 />} />,
|
||||
}}
|
||||
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">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
</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
|
||||
node={selectedNodeData}
|
||||
isModalTimeSelection
|
||||
|
||||
@@ -155,6 +155,7 @@ export default function Events({
|
||||
id: event.data.id,
|
||||
key: event.data.id,
|
||||
resources_string: event.data.resources_string,
|
||||
attributes_string: event.data.attributes_string,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -174,7 +175,9 @@ export default function Events({
|
||||
}, [eventsData]);
|
||||
|
||||
const handleExpandRow = (record: EventDataType): JSX.Element => (
|
||||
<EventContents data={record.resources_string} />
|
||||
<EventContents
|
||||
data={{ ...record.attributes_string, ...record.resources_string }}
|
||||
/>
|
||||
);
|
||||
|
||||
const handlePrev = (): void => {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -97,22 +98,9 @@ function NodeDetails({
|
||||
op: '=',
|
||||
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(
|
||||
@@ -239,11 +227,13 @@ function NodeDetails({
|
||||
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
...newFilters,
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...primaryFilters,
|
||||
...newFilters,
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
@@ -266,12 +256,14 @@ function NodeDetails({
|
||||
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
...value.items.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_NODE_NAME,
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...primaryFilters,
|
||||
...value.items.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_NODE_NAME,
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@@ -64,7 +64,7 @@ export interface K8sNodesRowData {
|
||||
|
||||
const nodeGroupColumnConfig = {
|
||||
title: (
|
||||
<div className="column-header node-group-header">
|
||||
<div className="column-header entity-group-header">
|
||||
<Group size={14} /> NODE GROUP
|
||||
</div>
|
||||
),
|
||||
@@ -74,6 +74,7 @@ const nodeGroupColumnConfig = {
|
||||
width: 150,
|
||||
align: 'left',
|
||||
sorter: false,
|
||||
className: 'column entity-group-header',
|
||||
};
|
||||
|
||||
export const getK8sNodesListQuery = (): K8sNodesListPayload => ({
|
||||
@@ -86,7 +87,7 @@ export const getK8sNodesListQuery = (): K8sNodesListPayload => ({
|
||||
|
||||
const columnsConfig = [
|
||||
{
|
||||
title: <div className="column-header-left">Node Name</div>,
|
||||
title: <div className="column-header-left name-header">Node Name</div>,
|
||||
dataIndex: 'nodeName',
|
||||
key: 'nodeName',
|
||||
ellipsis: true,
|
||||
@@ -95,7 +96,7 @@ const columnsConfig = [
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: <div className="column-header-left">Cluster Name</div>,
|
||||
title: <div className="column-header-left name-header">Cluster Name</div>,
|
||||
dataIndex: 'clusterName',
|
||||
key: 'clusterName',
|
||||
ellipsis: true,
|
||||
|
||||
@@ -15,6 +15,7 @@ import get from 'api/browser/localstorage/get';
|
||||
import set from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { K8sPodsListPayload } from 'api/infraMonitoring/getK8sPodsList';
|
||||
import classNames from 'classnames';
|
||||
import { useGetK8sPodsList } from 'hooks/infraMonitoring/useGetK8sPodsList';
|
||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
@@ -38,7 +39,7 @@ import {
|
||||
formatDataForTable,
|
||||
getK8sPodsListColumns,
|
||||
getK8sPodsListQuery,
|
||||
IPodColumn,
|
||||
IEntityColumn,
|
||||
K8sPodsRowData,
|
||||
} from '../utils';
|
||||
import PodDetails from './PodDetails/PodDetails';
|
||||
@@ -47,9 +48,11 @@ import PodDetails from './PodDetails/PodDetails';
|
||||
function K8sPodsList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -57,9 +60,9 @@ function K8sPodsList({
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
@@ -104,6 +107,11 @@ function K8sPodsList({
|
||||
K8sCategory.PODS, // infraMonitoringEntity
|
||||
);
|
||||
|
||||
// Reset pagination every time quick filters are changed
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
useEffect(() => {
|
||||
const addedColumns = JSON.parse(get('k8sPodsAddedColumns') ?? '[]');
|
||||
|
||||
@@ -124,7 +132,7 @@ function K8sPodsList({
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(null);
|
||||
} | null>({ columnName: 'cpu', order: 'desc' });
|
||||
|
||||
const [selectedPodUID, setSelectedPodUID] = useState<string | null>(null);
|
||||
|
||||
@@ -162,7 +170,7 @@ function K8sPodsList({
|
||||
selectedRowData: K8sPodsRowData,
|
||||
): IBuilderQuery['filters'] => {
|
||||
const baseFilters: IBuilderQuery['filters'] = {
|
||||
items: [],
|
||||
items: [...query.filters.items],
|
||||
op: 'and',
|
||||
};
|
||||
|
||||
@@ -201,6 +209,7 @@ function K8sPodsList({
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minTime, maxTime, orderBy, selectedRowData]);
|
||||
|
||||
const {
|
||||
@@ -338,20 +347,8 @@ function K8sPodsList({
|
||||
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(
|
||||
(column: IPodColumn): void => {
|
||||
(column: IEntityColumn): void => {
|
||||
setAddedColumns((prev) => [...prev, column]);
|
||||
|
||||
setAvailableColumns((prev) => prev.filter((c) => c.value !== column.value));
|
||||
@@ -378,7 +375,7 @@ function K8sPodsList({
|
||||
}, [groupByFiltersData]);
|
||||
|
||||
const handleRemoveColumn = useCallback(
|
||||
(column: IPodColumn): void => {
|
||||
(column: IEntityColumn): void => {
|
||||
setAddedColumns((prev) => prev.filter((c) => c.value !== column.value));
|
||||
|
||||
setAvailableColumns((prev) => [...prev, column]);
|
||||
@@ -505,54 +502,54 @@ function K8sPodsList({
|
||||
/>
|
||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||
|
||||
{showNoFilteredPodsMessage && (
|
||||
<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"
|
||||
/>
|
||||
<Table
|
||||
className={classNames('k8s-list-table', {
|
||||
'expanded-k8s-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedPodsData}
|
||||
columns={columns}
|
||||
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">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(isFetching || isLoading) && <LoadingContainer />}
|
||||
|
||||
{showPodsTable && (
|
||||
<Table
|
||||
className="k8s-list-table"
|
||||
dataSource={isFetching || isLoading ? [] : formattedPodsData}
|
||||
columns={columns}
|
||||
pagination={{
|
||||
current: currentPage,
|
||||
pageSize,
|
||||
total: totalCount,
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
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 && (
|
||||
<PodDetails
|
||||
|
||||
@@ -155,6 +155,7 @@ export default function Events({
|
||||
id: event.data.id,
|
||||
key: event.data.id,
|
||||
resources_string: event.data.resources_string,
|
||||
attributes_string: event.data.attributes_string,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -174,7 +175,9 @@ export default function Events({
|
||||
}, [eventsData]);
|
||||
|
||||
const handleExpandRow = (record: EventDataType): JSX.Element => (
|
||||
<EventContents data={record.resources_string} />
|
||||
<EventContents
|
||||
data={{ ...record.attributes_string, ...record.resources_string }}
|
||||
/>
|
||||
);
|
||||
|
||||
const handlePrev = (): void => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -50,7 +51,7 @@ import { PodDetailProps } from './PodDetail.interfaces';
|
||||
import PodLogsDetailedView from './PodLogs/PodLogsDetailedView';
|
||||
import PodTraces from './PodTraces/PodTraces';
|
||||
|
||||
const TimeRangeOffset = 1000000;
|
||||
const TimeRangeOffset = 1000000000;
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function PodDetails({
|
||||
@@ -101,19 +102,6 @@ function PodDetails({
|
||||
op: '=',
|
||||
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(),
|
||||
key: {
|
||||
@@ -129,11 +117,7 @@ function PodDetails({
|
||||
},
|
||||
],
|
||||
}),
|
||||
[
|
||||
pod?.meta.k8s_cluster_name,
|
||||
pod?.meta.k8s_namespace_name,
|
||||
pod?.meta.k8s_pod_name,
|
||||
],
|
||||
[pod?.meta.k8s_namespace_name, pod?.meta.k8s_pod_name],
|
||||
);
|
||||
|
||||
const initialEventsFilters = useMemo(
|
||||
@@ -262,11 +246,13 @@ function PodDetails({
|
||||
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
...newFilters,
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...primaryFilters,
|
||||
...newFilters,
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
@@ -291,12 +277,14 @@ function PodDetails({
|
||||
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
...value.items.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_POD_NAME,
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...primaryFilters,
|
||||
...value.items.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_POD_NAME,
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@@ -78,8 +78,6 @@ function PodTraces({
|
||||
[currentQuery],
|
||||
);
|
||||
|
||||
console.log({ updatedCurrentQuery });
|
||||
|
||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||
|
||||
const { queryData: paginationQueryData } = useUrlQueryData<Pagination>(
|
||||
|
||||
@@ -100,7 +100,13 @@ export function getStrokeColorForLimitUtilization(value: number): string {
|
||||
export const getProgressBarText = (percent: number): React.ReactNode =>
|
||||
`${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));
|
||||
|
||||
return (
|
||||
@@ -110,7 +116,11 @@ export function EntityProgressBar({ value }: { value: number }): JSX.Element {
|
||||
strokeLinecap="butt"
|
||||
size="small"
|
||||
status="normal"
|
||||
strokeColor={getStrokeColorForLimitUtilization(value)}
|
||||
strokeColor={
|
||||
type === 'limit'
|
||||
? getStrokeColorForLimitUtilization(value)
|
||||
: getStrokeColorForRequestUtilization(value)
|
||||
}
|
||||
className="progress-bar"
|
||||
showInfo={false}
|
||||
/>
|
||||
|
||||
@@ -150,6 +150,8 @@ export const PodsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s_pod_cpu_utilization',
|
||||
dataSource: DataSource.METRICS,
|
||||
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;
|
||||
}
|
||||
|
||||
export interface IPodColumn {
|
||||
label: string;
|
||||
value: string;
|
||||
id: string;
|
||||
canRemove: boolean;
|
||||
}
|
||||
|
||||
const columnProgressBarClassName = 'column-progress-bar';
|
||||
|
||||
export const defaultAddedColumns: IPodColumn[] = [
|
||||
export const defaultAddedColumns: IEntityColumn[] = [
|
||||
{
|
||||
label: 'Pod name',
|
||||
value: 'podName',
|
||||
@@ -78,12 +71,13 @@ export const defaultAddedColumns: IPodColumn[] = [
|
||||
id: 'memory',
|
||||
canRemove: false,
|
||||
},
|
||||
{
|
||||
label: 'Restarts',
|
||||
value: 'restarts',
|
||||
id: 'restarts',
|
||||
canRemove: false,
|
||||
},
|
||||
// TODO - Re-enable the column once backend issue is fixed
|
||||
// {
|
||||
// label: 'Restarts',
|
||||
// value: 'restarts',
|
||||
// id: 'restarts',
|
||||
// canRemove: false,
|
||||
// },
|
||||
];
|
||||
|
||||
export const defaultAvailableColumns = [
|
||||
@@ -131,7 +125,7 @@ export const getK8sPodsListQuery = (): K8sPodsListPayload => ({
|
||||
|
||||
const podGroupColumnConfig = {
|
||||
title: (
|
||||
<div className="column-header pod-group-header">
|
||||
<div className="column-header entity-group-header">
|
||||
<Group size={14} /> POD GROUP
|
||||
</div>
|
||||
),
|
||||
@@ -140,7 +134,7 @@ const podGroupColumnConfig = {
|
||||
ellipsis: true,
|
||||
width: 180,
|
||||
sorter: false,
|
||||
className: 'column column-pod-group',
|
||||
className: 'column entity-group-header',
|
||||
};
|
||||
|
||||
export const dummyColumnConfig = {
|
||||
@@ -160,11 +154,11 @@ const columnsConfig = [
|
||||
key: 'podName',
|
||||
width: 180,
|
||||
ellipsis: true,
|
||||
sorter: true,
|
||||
sorter: false,
|
||||
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',
|
||||
key: 'cpu_request',
|
||||
width: 180,
|
||||
@@ -174,7 +168,7 @@ const columnsConfig = [
|
||||
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',
|
||||
key: 'cpu_limit',
|
||||
width: 120,
|
||||
@@ -192,7 +186,7 @@ const columnsConfig = [
|
||||
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',
|
||||
key: 'memory_request',
|
||||
width: 120,
|
||||
@@ -201,7 +195,7 @@ const columnsConfig = [
|
||||
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',
|
||||
key: 'memory_limit',
|
||||
width: 120,
|
||||
@@ -219,20 +213,21 @@ const columnsConfig = [
|
||||
align: 'left',
|
||||
className: `column ${columnProgressBarClassName}`,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<div className="column-header">
|
||||
<Tooltip title="Container Restarts">Restarts</Tooltip>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'restarts',
|
||||
key: 'restarts',
|
||||
width: 40,
|
||||
ellipsis: true,
|
||||
sorter: true,
|
||||
align: 'left',
|
||||
className: `column ${columnProgressBarClassName}`,
|
||||
},
|
||||
// TODO - Re-enable the column once backend issue is fixed
|
||||
// {
|
||||
// title: (
|
||||
// <div className="column-header">
|
||||
// <Tooltip title="Container Restarts">Restarts</Tooltip>
|
||||
// </div>
|
||||
// ),
|
||||
// dataIndex: 'restarts',
|
||||
// key: 'restarts',
|
||||
// width: 40,
|
||||
// ellipsis: true,
|
||||
// sorter: true,
|
||||
// align: 'left',
|
||||
// className: `column ${columnProgressBarClassName}`,
|
||||
// },
|
||||
];
|
||||
|
||||
export const namespaceColumnConfig = {
|
||||
@@ -251,7 +246,7 @@ export const nodeColumnConfig = {
|
||||
dataIndex: 'node',
|
||||
key: 'node',
|
||||
width: 100,
|
||||
sorter: true,
|
||||
sorter: false,
|
||||
ellipsis: true,
|
||||
align: 'left',
|
||||
className: 'column column-node',
|
||||
@@ -262,7 +257,7 @@ export const clusterColumnConfig = {
|
||||
dataIndex: 'cluster',
|
||||
key: 'cluster',
|
||||
width: 100,
|
||||
sorter: true,
|
||||
sorter: false,
|
||||
ellipsis: true,
|
||||
align: 'left',
|
||||
className: 'column column-cluster',
|
||||
@@ -275,7 +270,7 @@ export const columnConfigMap = {
|
||||
};
|
||||
|
||||
export const getK8sPodsListColumns = (
|
||||
addedColumns: IPodColumn[],
|
||||
addedColumns: IEntityColumn[],
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
): ColumnType<K8sPodsRowData>[] => {
|
||||
const updatedColumnsConfig = [...columnsConfig];
|
||||
@@ -341,7 +336,7 @@ export const formatDataForTable = (
|
||||
attribute="CPU Request"
|
||||
>
|
||||
<div className="progress-container">
|
||||
<EntityProgressBar value={pod.podCPURequest} />
|
||||
<EntityProgressBar value={pod.podCPURequest} type="request" />
|
||||
</div>
|
||||
</ValidateColumnValueWrapper>
|
||||
),
|
||||
@@ -352,7 +347,7 @@ export const formatDataForTable = (
|
||||
attribute="CPU Limit"
|
||||
>
|
||||
<div className="progress-container">
|
||||
<EntityProgressBar value={pod.podCPULimit} />
|
||||
<EntityProgressBar value={pod.podCPULimit} type="limit" />
|
||||
</div>
|
||||
</ValidateColumnValueWrapper>
|
||||
),
|
||||
@@ -368,7 +363,7 @@ export const formatDataForTable = (
|
||||
attribute="Memory Request"
|
||||
>
|
||||
<div className="progress-container">
|
||||
<EntityProgressBar value={pod.podMemoryRequest} />
|
||||
<EntityProgressBar value={pod.podMemoryRequest} type="request" />
|
||||
</div>
|
||||
</ValidateColumnValueWrapper>
|
||||
),
|
||||
@@ -379,7 +374,7 @@ export const formatDataForTable = (
|
||||
attribute="Memory Limit"
|
||||
>
|
||||
<div className="progress-container">
|
||||
<EntityProgressBar value={pod.podMemoryLimit} />
|
||||
<EntityProgressBar value={pod.podMemoryLimit} type="limit" />
|
||||
</div>
|
||||
</ValidateColumnValueWrapper>
|
||||
),
|
||||
|
||||
@@ -121,23 +121,25 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
||||
const tableHeader = useCallback(
|
||||
() => (
|
||||
<tr>
|
||||
{tableColumns.map((column) => {
|
||||
const isDragColumn = column.key !== 'expand';
|
||||
{tableColumns
|
||||
.filter((column) => column.key)
|
||||
.map((column) => {
|
||||
const isDragColumn = column.key !== 'expand';
|
||||
|
||||
return (
|
||||
<TableHeaderCellStyled
|
||||
$isLogIndicator={column.key === 'state-indicator'}
|
||||
$isDarkMode={isDarkMode}
|
||||
$isDragColumn={isDragColumn}
|
||||
key={column.key}
|
||||
fontSize={tableViewProps?.fontSize}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...(isDragColumn && { className: 'dragHandler' })}
|
||||
>
|
||||
{(column.title as string).replace(/^\w/, (c) => c.toUpperCase())}
|
||||
</TableHeaderCellStyled>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<TableHeaderCellStyled
|
||||
$isLogIndicator={column.key === 'state-indicator'}
|
||||
$isDarkMode={isDarkMode}
|
||||
$isDragColumn={isDragColumn}
|
||||
key={column.key}
|
||||
fontSize={tableViewProps?.fontSize}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...(isDragColumn && { className: 'dragHandler' })}
|
||||
>
|
||||
{(column.title as string).replace(/^\w/, (c) => c.toUpperCase())}
|
||||
</TableHeaderCellStyled>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
),
|
||||
[tableColumns, isDarkMode, tableViewProps?.fontSize],
|
||||
|
||||
@@ -29,7 +29,7 @@ export const TableCellStyled = styled.td<TableHeaderCellStyledProps>`
|
||||
props.$isDarkMode ? 'inherit' : themeColors.whiteCream};
|
||||
|
||||
${({ $isLogIndicator }): string =>
|
||||
$isLogIndicator ? 'padding: 0 0 0 8px;' : ''}
|
||||
$isLogIndicator ? 'padding: 0 0 0 8px;width: 15px;' : ''}
|
||||
color: ${(props): string =>
|
||||
props.$isDarkMode ? themeColors.white : themeColors.bckgGrey};
|
||||
`;
|
||||
|
||||
@@ -76,6 +76,7 @@ receivers:
|
||||
azureeventhub:
|
||||
connection: Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234=;EntityPath=hubName
|
||||
format: "azure"
|
||||
apply_semantic_conventions: true
|
||||
azuremonitor:
|
||||
subscription_id: "<Subscription ID>"
|
||||
tenant_id: "<AD Tenant ID>"
|
||||
|
||||
@@ -22,6 +22,7 @@ receivers:
|
||||
azureeventhub:
|
||||
connection: <Primary Connection String>
|
||||
format: "azure"
|
||||
apply_semantic_conventions: true
|
||||
azuremonitor:
|
||||
subscription_id: "<Subscription ID>"
|
||||
tenant_id: "<AD Tenant ID>"
|
||||
|
||||
@@ -22,6 +22,7 @@ receivers:
|
||||
azureeventhub:
|
||||
connection: <Primary Connection String>
|
||||
format: "azure"
|
||||
apply_semantic_conventions: true
|
||||
azuremonitor:
|
||||
subscription_id: "<Subscription ID>"
|
||||
tenant_id: "<AD Tenant ID>"
|
||||
|
||||
@@ -22,6 +22,7 @@ receivers:
|
||||
azureeventhub:
|
||||
connection: <Primary Connection String>
|
||||
format: "azure"
|
||||
apply_semantic_conventions: true
|
||||
azuremonitor:
|
||||
subscription_id: "<Subscription ID>"
|
||||
tenant_id: "<AD Tenant ID>"
|
||||
|
||||
@@ -22,6 +22,7 @@ receivers:
|
||||
azureeventhub:
|
||||
connection: <Primary Connection String>
|
||||
format: "azure"
|
||||
apply_semantic_conventions: true
|
||||
azuremonitor:
|
||||
subscription_id: "<Subscription ID>"
|
||||
tenant_id: "<AD Tenant ID>"
|
||||
|
||||
@@ -22,6 +22,7 @@ receivers:
|
||||
azureeventhub:
|
||||
connection: <Primary Connection String>
|
||||
format: "azure"
|
||||
apply_semantic_conventions: true
|
||||
azuremonitor:
|
||||
subscription_id: "<Subscription ID>"
|
||||
tenant_id: "<AD Tenant ID>"
|
||||
|
||||
@@ -22,6 +22,7 @@ receivers:
|
||||
azureeventhub:
|
||||
connection: <Primary Connection String>
|
||||
format: "azure"
|
||||
apply_semantic_conventions: true
|
||||
azuremonitor:
|
||||
subscription_id: "<Subscription ID>"
|
||||
tenant_id: "<AD Tenant ID>"
|
||||
|
||||
@@ -5,7 +5,26 @@ import { FontSize, OptionsQuery } from './types';
|
||||
export const URL_OPTIONS = 'options';
|
||||
|
||||
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,
|
||||
format: 'raw',
|
||||
fontSize: FontSize.SMALL,
|
||||
|
||||
@@ -169,6 +169,15 @@ const useOptionsMenu = ({
|
||||
|
||||
const searchedAttributeKeys = useMemo(() => {
|
||||
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;
|
||||
}
|
||||
if (dataSource === DataSource.TRACES) {
|
||||
@@ -198,12 +207,17 @@ const useOptionsMenu = ({
|
||||
);
|
||||
|
||||
const optionsFromAttributeKeys = useMemo(() => {
|
||||
const filteredAttributeKeys = searchedAttributeKeys.filter(
|
||||
(item) => item.key !== 'body',
|
||||
);
|
||||
const filteredAttributeKeys = searchedAttributeKeys.filter((item) => {
|
||||
// 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);
|
||||
}, [searchedAttributeKeys, selectedColumnKeys]);
|
||||
}, [dataSource, searchedAttributeKeys, selectedColumnKeys]);
|
||||
|
||||
const handleRedirectWithOptionsData = useCallback(
|
||||
(newQueryData: OptionsQuery) => {
|
||||
|
||||
@@ -95,6 +95,7 @@ function QueryBuilderSearch({
|
||||
isMulti,
|
||||
isFetching,
|
||||
setSearchKey,
|
||||
setSearchValue,
|
||||
searchKey,
|
||||
key,
|
||||
exampleQueries,
|
||||
@@ -145,7 +146,11 @@ function QueryBuilderSearch({
|
||||
|
||||
const tagEditHandler = (value: string): void => {
|
||||
updateTag(value);
|
||||
handleSearch(value);
|
||||
if (isInfraMonitoring) {
|
||||
setSearchValue(value);
|
||||
} else {
|
||||
handleSearch(value);
|
||||
}
|
||||
};
|
||||
|
||||
const isDisabled = !!searchValue;
|
||||
|
||||
@@ -153,6 +153,7 @@ export const useAutoComplete = (
|
||||
isMulti,
|
||||
isFetching,
|
||||
setSearchKey,
|
||||
setSearchValue,
|
||||
searchKey,
|
||||
key,
|
||||
exampleQueries,
|
||||
@@ -172,6 +173,7 @@ interface IAutoComplete {
|
||||
isMulti: boolean;
|
||||
isFetching: boolean;
|
||||
setSearchKey: (value: string) => void;
|
||||
setSearchValue: (value: string) => void;
|
||||
searchKey: string;
|
||||
key: string;
|
||||
exampleQueries: TagFilter[];
|
||||
|
||||
@@ -5,12 +5,12 @@ import { TabRoutes } from 'components/RouteTab/types';
|
||||
import history from 'lib/history';
|
||||
import { useLocation } from 'react-use';
|
||||
|
||||
import { Hosts } from './constants';
|
||||
import { Hosts, Kubernetes } from './constants';
|
||||
|
||||
export default function InfrastructureMonitoringPage(): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const routes: TabRoutes[] = [Hosts];
|
||||
const routes: TabRoutes[] = [Hosts, Kubernetes];
|
||||
|
||||
return (
|
||||
<div className="infra-monitoring-module-container">
|
||||
|
||||
5
go.mod
5
go.mod
@@ -20,6 +20,7 @@ require (
|
||||
github.com/go-kit/log v0.2.1
|
||||
github.com/go-redis/redis/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/google/uuid v1.6.0
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
@@ -29,6 +30,7 @@ require (
|
||||
github.com/jmoiron/sqlx v1.3.4
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/knadh/koanf v1.5.0
|
||||
github.com/knadh/koanf/v2 v2.1.1
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/oklog/oklog v0.3.2
|
||||
@@ -101,6 +103,7 @@ require (
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // 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/errors v0.7.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||
@@ -108,7 +111,6 @@ require (
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // 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/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
@@ -129,7 +131,6 @@ require (
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/jtolds/gls v4.20.0+incompatible // 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/leodido/go-syslog/v4 v4.2.0 // indirect
|
||||
github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b // indirect
|
||||
|
||||
14
pkg/cache/config.go
vendored
14
pkg/cache/config.go
vendored
@@ -4,12 +4,9 @@ import (
|
||||
"time"
|
||||
|
||||
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 {
|
||||
TTL time.Duration `mapstructure:"ttl"`
|
||||
CleanupInterval time.Duration `mapstructure:"cleanupInterval"`
|
||||
@@ -28,7 +25,11 @@ type Config struct {
|
||||
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{
|
||||
Provider: "memory",
|
||||
Memory: Memory{
|
||||
@@ -42,8 +43,9 @@ func (c *Config) NewWithDefaults() confmap.Config {
|
||||
DB: 0,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *Config) Validate() error {
|
||||
func (c Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
29
pkg/cache/memorycache/provider.go
vendored
29
pkg/cache/memorycache/provider.go
vendored
@@ -7,15 +7,20 @@ import (
|
||||
"time"
|
||||
|
||||
go_cache "github.com/patrickmn/go-cache"
|
||||
_cache "go.signoz.io/signoz/pkg/cache"
|
||||
"go.signoz.io/signoz/pkg/cache"
|
||||
"go.signoz.io/signoz/pkg/factory"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
cc *go_cache.Cache
|
||||
}
|
||||
|
||||
func New(opts *_cache.Memory) *provider {
|
||||
return &provider{cc: go_cache.New(opts.TTL, opts.CleanupInterval)}
|
||||
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
|
||||
@@ -24,11 +29,11 @@ func (c *provider) Connect(_ context.Context) error {
|
||||
}
|
||||
|
||||
// Store stores the data in the cache
|
||||
func (c *provider) Store(_ context.Context, cacheKey string, data _cache.CacheableEntity, ttl time.Duration) error {
|
||||
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")
|
||||
return cache.WrapCacheableEntityErrors(reflect.TypeOf(data), "inmemory")
|
||||
}
|
||||
|
||||
c.cc.Set(cacheKey, data, ttl)
|
||||
@@ -36,32 +41,32 @@ func (c *provider) Store(_ context.Context, cacheKey string, data _cache.Cacheab
|
||||
}
|
||||
|
||||
// Retrieve retrieves the data from the cache
|
||||
func (c *provider) Retrieve(_ context.Context, cacheKey string, dest _cache.CacheableEntity, allowExpired bool) (_cache.RetrieveStatus, error) {
|
||||
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")
|
||||
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())
|
||||
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
|
||||
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")
|
||||
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
|
||||
return cache.RetrieveStatusHit, nil
|
||||
}
|
||||
|
||||
// SetTTL sets the TTL for the cache entry
|
||||
@@ -91,6 +96,6 @@ func (c *provider) Close(_ context.Context) error {
|
||||
}
|
||||
|
||||
// Configuration returns the cache configuration
|
||||
func (c *provider) Configuration() *_cache.Memory {
|
||||
func (c *provider) Configuration() *cache.Memory {
|
||||
return nil
|
||||
}
|
||||
|
||||
84
pkg/cache/memorycache/provider_test.go
vendored
84
pkg/cache/memorycache/provider_test.go
vendored
@@ -7,18 +7,21 @@ import (
|
||||
"time"
|
||||
|
||||
"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
|
||||
func TestNew(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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.cc)
|
||||
assert.NotNil(t, c.(*provider).cc)
|
||||
assert.NoError(t, c.Connect(context.Background()))
|
||||
}
|
||||
|
||||
@@ -53,32 +56,35 @@ func (dce DCacheableEntity) UnmarshalBinary(data []byte) error {
|
||||
// TestStore tests the Store function
|
||||
// this should fail because of nil pointer error
|
||||
func TestStoreWithNilPointer(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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
|
||||
assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
|
||||
}
|
||||
|
||||
// this should fail because of no pointer error
|
||||
func TestStoreWithStruct(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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
|
||||
assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
|
||||
}
|
||||
|
||||
func TestStoreWithNonNilPointer(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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{
|
||||
Key: "some-random-key",
|
||||
Value: 1,
|
||||
@@ -89,11 +95,12 @@ func TestStoreWithNonNilPointer(t *testing.T) {
|
||||
|
||||
// TestRetrieve tests the Retrieve function
|
||||
func TestRetrieveWithNilPointer(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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{
|
||||
Key: "some-random-key",
|
||||
Value: 1,
|
||||
@@ -105,15 +112,16 @@ func TestRetrieveWithNilPointer(t *testing.T) {
|
||||
|
||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError)
|
||||
assert.Equal(t, retrieveStatus, cache.RetrieveStatusError)
|
||||
}
|
||||
|
||||
func TestRetrieveWitNonPointer(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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{
|
||||
Key: "some-random-key",
|
||||
Value: 1,
|
||||
@@ -125,15 +133,16 @@ func TestRetrieveWitNonPointer(t *testing.T) {
|
||||
|
||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError)
|
||||
assert.Equal(t, retrieveStatus, cache.RetrieveStatusError)
|
||||
}
|
||||
|
||||
func TestRetrieveWithDifferentTypes(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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{
|
||||
Key: "some-random-key",
|
||||
Value: 1,
|
||||
@@ -144,15 +153,16 @@ func TestRetrieveWithDifferentTypes(t *testing.T) {
|
||||
retrieveCacheableEntity := new(DCacheableEntity)
|
||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError)
|
||||
assert.Equal(t, retrieveStatus, cache.RetrieveStatusError)
|
||||
}
|
||||
|
||||
func TestRetrieveWithSameTypes(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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{
|
||||
Key: "some-random-key",
|
||||
Value: 1,
|
||||
@@ -163,13 +173,14 @@ func TestRetrieveWithSameTypes(t *testing.T) {
|
||||
retrieveCacheableEntity := new(CacheableEntity)
|
||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit)
|
||||
assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit)
|
||||
assert.Equal(t, storeCacheableEntity, retrieveCacheableEntity)
|
||||
}
|
||||
|
||||
// TestSetTTL tests the SetTTL function
|
||||
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{
|
||||
Key: "some-random-key",
|
||||
Value: 1,
|
||||
@@ -180,7 +191,7 @@ func TestSetTTL(t *testing.T) {
|
||||
time.Sleep(3 * time.Second)
|
||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
|
||||
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
|
||||
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
||||
|
||||
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)
|
||||
retrieveStatus, err = c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit)
|
||||
assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit)
|
||||
assert.Equal(t, retrieveCacheableEntity, storeCacheableEntity)
|
||||
}
|
||||
|
||||
// TestRemove tests the Remove function
|
||||
func TestRemove(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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{
|
||||
Key: "some-random-key",
|
||||
Value: 1,
|
||||
@@ -210,17 +222,18 @@ func TestRemove(t *testing.T) {
|
||||
|
||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
|
||||
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
|
||||
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
||||
}
|
||||
|
||||
// TestBulkRemove tests the BulkRemove function
|
||||
func TestBulkRemove(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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{
|
||||
Key: "some-random-key",
|
||||
Value: 1,
|
||||
@@ -233,22 +246,23 @@ func TestBulkRemove(t *testing.T) {
|
||||
|
||||
retrieveStatus, err := c.Retrieve(context.Background(), "key1", retrieveCacheableEntity, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
|
||||
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
|
||||
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
||||
|
||||
retrieveStatus, err = c.Retrieve(context.Background(), "key2", retrieveCacheableEntity, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
|
||||
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
|
||||
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
|
||||
}
|
||||
|
||||
// TestCache tests the cache
|
||||
func TestCache(t *testing.T) {
|
||||
opts := &_cache.Memory{
|
||||
opts := cache.Memory{
|
||||
TTL: 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{
|
||||
Key: "some-random-key",
|
||||
Value: 1,
|
||||
@@ -258,7 +272,7 @@ func TestCache(t *testing.T) {
|
||||
assert.NoError(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
|
||||
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit)
|
||||
assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit)
|
||||
assert.Equal(t, storeCacheableEntity, retrieveCacheableEntity)
|
||||
c.Remove(context.Background(), "key")
|
||||
}
|
||||
|
||||
28
pkg/cache/rediscache/provider.go
vendored
28
pkg/cache/rediscache/provider.go
vendored
@@ -7,17 +7,22 @@ import (
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
client *redis.Client
|
||||
opts *_cache.Redis
|
||||
opts cache.Redis
|
||||
}
|
||||
|
||||
func New(opts *_cache.Redis) *provider {
|
||||
return &provider{opts: opts}
|
||||
func NewFactory() factory.ProviderFactory[cache.Cache, cache.Config] {
|
||||
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
|
||||
@@ -36,20 +41,20 @@ func (c *provider) Connect(_ context.Context) error {
|
||||
}
|
||||
|
||||
// Store stores the data in the cache
|
||||
func (c *provider) 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()
|
||||
}
|
||||
|
||||
// Retrieve retrieves the data from the cache
|
||||
func (c *provider) 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)
|
||||
if err != 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
|
||||
@@ -87,11 +92,6 @@ func (c *provider) GetClient() *redis.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
// GetOptions returns the options
|
||||
func (c *provider) GetOptions() *_cache.Redis {
|
||||
return c.opts
|
||||
}
|
||||
|
||||
// GetTTL returns the TTL for the cache entry
|
||||
func (c *provider) GetTTL(ctx context.Context, cacheKey string) time.Duration {
|
||||
ttl, err := c.client.TTL(ctx, cacheKey).Result()
|
||||
|
||||
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,32 +3,34 @@ package config
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.signoz.io/signoz/pkg/cache"
|
||||
signozconfmap "go.signoz.io/signoz/pkg/confmap"
|
||||
"go.signoz.io/signoz/pkg/instrumentation"
|
||||
"go.signoz.io/signoz/pkg/web"
|
||||
"go.signoz.io/signoz/pkg/factory"
|
||||
)
|
||||
|
||||
// This map contains the default values of all config structs
|
||||
var (
|
||||
defaults = map[string]signozconfmap.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)
|
||||
func New(ctx context.Context, resolverConfig ResolverConfig, configFactories []factory.ConfigFactory) (*Conf, error) {
|
||||
// Get the config from the resolver
|
||||
resolver, err := NewResolver(resolverConfig)
|
||||
if err != nil {
|
||||
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 (
|
||||
"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 {
|
||||
// Get returns the configuration, or error otherwise.
|
||||
Get(ctx context.Context) (*Config, error)
|
||||
}
|
||||
|
||||
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
|
||||
// Get returns the configuration for the given URI.
|
||||
Get(context.Context, Uri) (*Conf, error)
|
||||
// Scheme returns the scheme of the provider.
|
||||
Scheme() string
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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()))
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package factory
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Provider = any
|
||||
|
||||
@@ -21,10 +23,17 @@ func (factory *providerFactory[P, C]) Name() Name {
|
||||
return factory.name
|
||||
}
|
||||
|
||||
func (factory *providerFactory[P, C]) New(ctx context.Context, settings ProviderSettings, config C) (P, error) {
|
||||
return factory.newProviderFunc(ctx, settings, config)
|
||||
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,
|
||||
@@ -32,7 +41,8 @@ func NewProviderFactory[P Provider, C Config](name Name, newProviderFunc NewProv
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromFactory[P Provider, C Config](ctx context.Context, settings ProviderSettings, config C, factories NamedMap[ProviderFactory[P, C]], key string) (p P, err error) {
|
||||
// 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
|
||||
|
||||
@@ -32,10 +32,10 @@ func TestNewProviderFactoryFromFactory(t *testing.T) {
|
||||
|
||||
m := MustNewNamedMap(pf)
|
||||
assert.Equal(t, MustNewName("p1"), pf.Name())
|
||||
p, err := NewFromFactory(context.Background(), ProviderSettings{}, pc1{}, m, "p1")
|
||||
p, err := NewProviderFromNamedMap(context.Background(), ProviderSettings{}, pc1{}, m, "p1")
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, p1{}, p)
|
||||
|
||||
_, err = NewFromFactory(context.Background(), ProviderSettings{}, pc1{}, m, "p2")
|
||||
_, 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,12 +1,5 @@
|
||||
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.
|
||||
type Config struct {
|
||||
//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.
|
||||
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"
|
||||
"time"
|
||||
|
||||
"go.signoz.io/signoz/pkg/registry"
|
||||
"go.signoz.io/signoz/pkg/factory"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ registry.NamedService = (*Server)(nil)
|
||||
var _ factory.Service = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
srv *http.Server
|
||||
logger *zap.Logger
|
||||
handler http.Handler
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("cannot build http server, name is required")
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: cfg.Address,
|
||||
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"),
|
||||
handler: handler,
|
||||
cfg: cfg,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (server *Server) Name() string {
|
||||
return server.name
|
||||
}
|
||||
|
||||
func (server *Server) Start(ctx context.Context) error {
|
||||
server.logger.Info("starting http server", zap.String("address", server.srv.Addr))
|
||||
if err := server.srv.ListenAndServe(); err != nil {
|
||||
|
||||
@@ -218,6 +218,7 @@ func NewReaderFromClickhouseConnection(
|
||||
MaxBytesToRead: os.Getenv("ClickHouseMaxBytesToRead"),
|
||||
OptimizeReadInOrderRegex: os.Getenv("ClickHouseOptimizeReadInOrderRegex"),
|
||||
OptimizeReadInOrderRegexCompiled: regexCompiled,
|
||||
MaxResultRowsForCHQuery: constants.MaxResultRowsForCHQuery,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4198,9 +4199,26 @@ func (r *ClickHouseReader) GetListResultV3(ctx context.Context, query string) ([
|
||||
var t time.Time
|
||||
for idx, v := range vars {
|
||||
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" {
|
||||
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 {
|
||||
row[columnNames[idx]] = v
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type ClickhouseQuerySettings struct {
|
||||
MaxBytesToRead string
|
||||
OptimizeReadInOrderRegex string
|
||||
OptimizeReadInOrderRegexCompiled *regexp.Regexp
|
||||
MaxResultRowsForCHQuery int
|
||||
}
|
||||
|
||||
type clickhouseConnWrapper struct {
|
||||
@@ -44,6 +45,10 @@ func (c clickhouseConnWrapper) addClickHouseSettings(ctx context.Context, query
|
||||
settings["log_comment"] = logComment
|
||||
}
|
||||
|
||||
if ctx.Value("enforce_max_result_rows") != nil {
|
||||
settings["max_result_rows"] = c.settings.MaxResultRowsForCHQuery
|
||||
}
|
||||
|
||||
if c.settings.MaxBytesToRead != "" {
|
||||
settings["max_bytes_to_read"] = c.settings.MaxBytesToRead
|
||||
}
|
||||
|
||||
@@ -288,11 +288,7 @@ func GetDashboard(ctx context.Context, uuid string) (*Dashboard, *model.ApiError
|
||||
if err != nil {
|
||||
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no dashboard found with uuid: %s", uuid)}
|
||||
}
|
||||
|
||||
if dashboard.Data["title"] == "Ingestion" && dashboard.Data["description"] != nil {
|
||||
dashboard.Data["description"] = "This dashboard is deprecated. Please use the new Ingestion V2 dashboard. " + dashboard.Data["description"].(string)
|
||||
}
|
||||
|
||||
|
||||
return &dashboard, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -338,5 +338,7 @@ func (p *ClustersRepo) GetClusterList(ctx context.Context, req model.ClusterList
|
||||
resp.Total = len(allClusterGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -440,5 +440,7 @@ func (d *DaemonSetsRepo) GetDaemonSetList(ctx context.Context, req model.DaemonS
|
||||
resp.Total = len(allDaemonSetGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -440,5 +440,7 @@ func (d *DeploymentsRepo) GetDeploymentList(ctx context.Context, req model.Deplo
|
||||
resp.Total = len(allDeploymentGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -494,5 +494,7 @@ func (d *JobsRepo) GetJobList(ctx context.Context, req model.JobListRequest) (mo
|
||||
resp.Total = len(allJobGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -341,5 +341,7 @@ func (p *NamespacesRepo) GetNamespaceList(ctx context.Context, req model.Namespa
|
||||
resp.Total = len(allNamespaceGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ var (
|
||||
|
||||
nodeAttrsToEnrich = []string{"k8s_node_name", "k8s_node_uid", "k8s_cluster_name"}
|
||||
|
||||
k8sNodeUIDAttrKey = "k8s_node_uid"
|
||||
k8sNodeGroupAttrKey = "k8s_node_name"
|
||||
|
||||
queryNamesForNodes = map[string][]string{
|
||||
"cpu": {"A"},
|
||||
@@ -125,7 +125,7 @@ func (p *NodesRepo) getMetadataAttributes(ctx context.Context, req model.NodeLis
|
||||
}
|
||||
}
|
||||
|
||||
nodeUID := stringData[k8sNodeUIDAttrKey]
|
||||
nodeUID := stringData[k8sNodeGroupAttrKey]
|
||||
if _, ok := nodeAttrs[nodeUID]; !ok {
|
||||
nodeAttrs[nodeUID] = map[string]string{}
|
||||
}
|
||||
@@ -220,7 +220,7 @@ func (p *NodesRepo) GetNodeList(ctx context.Context, req model.NodeListRequest)
|
||||
}
|
||||
|
||||
if req.GroupBy == nil {
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sNodeUIDAttrKey}}
|
||||
req.GroupBy = []v3.AttributeKey{{Key: k8sNodeGroupAttrKey}}
|
||||
resp.Type = model.ResponseTypeList
|
||||
} else {
|
||||
resp.Type = model.ResponseTypeGroupedList
|
||||
@@ -306,7 +306,7 @@ func (p *NodesRepo) GetNodeList(ctx context.Context, req model.NodeListRequest)
|
||||
NodeMemoryAllocatable: -1,
|
||||
}
|
||||
|
||||
if nodeUID, ok := row.Data[k8sNodeUIDAttrKey].(string); ok {
|
||||
if nodeUID, ok := row.Data[k8sNodeGroupAttrKey].(string); ok {
|
||||
record.NodeUID = nodeUID
|
||||
}
|
||||
|
||||
@@ -354,5 +354,6 @@ func (p *NodesRepo) GetNodeList(ctx context.Context, req model.NodeListRequest)
|
||||
resp.Total = len(allNodeGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ var NodesTableListQuery = v3.QueryRangeParamsV3{
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeUIDAttrKey,
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
@@ -46,7 +46,7 @@ var NodesTableListQuery = v3.QueryRangeParamsV3{
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeUIDAttrKey,
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
@@ -72,7 +72,7 @@ var NodesTableListQuery = v3.QueryRangeParamsV3{
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeUIDAttrKey,
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
@@ -98,7 +98,7 @@ var NodesTableListQuery = v3.QueryRangeParamsV3{
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeUIDAttrKey,
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
@@ -132,7 +132,7 @@ var NodesTableListQuery = v3.QueryRangeParamsV3{
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeUIDAttrKey,
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
@@ -166,7 +166,7 @@ var NodesTableListQuery = v3.QueryRangeParamsV3{
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{
|
||||
Key: k8sNodeUIDAttrKey,
|
||||
Key: k8sNodeGroupAttrKey,
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
|
||||
@@ -404,5 +404,7 @@ func (p *PodsRepo) GetPodList(ctx context.Context, req model.PodListRequest) (mo
|
||||
resp.Total = len(allPodGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -374,5 +374,7 @@ func (p *PvcsRepo) GetPvcList(ctx context.Context, req model.VolumeListRequest)
|
||||
resp.Total = len(allVolumeGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -440,5 +440,7 @@ func (d *StatefulSetsRepo) GetStatefulSetList(ctx context.Context, req model.Sta
|
||||
resp.Total = len(allStatefulSetGroups)
|
||||
resp.Records = records
|
||||
|
||||
resp.SortBy(req.OrderBy)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -167,6 +167,22 @@ func jsonFilterEnrich(filter v3.FilterItem) v3.FilterItem {
|
||||
// check if the value is a int, float, string, bool
|
||||
valueType := ""
|
||||
switch filter.Value.(type) {
|
||||
// even the filter value is an array the actual type of the value is string.
|
||||
case []interface{}:
|
||||
// check first value type in array and use that
|
||||
if len(filter.Value.([]interface{})) > 0 {
|
||||
firstVal := filter.Value.([]interface{})[0]
|
||||
switch firstVal.(type) {
|
||||
case uint8, uint16, uint32, uint64, int, int8, int16, int32, int64:
|
||||
valueType = "int64"
|
||||
case float32, float64:
|
||||
valueType = "float64"
|
||||
case bool:
|
||||
valueType = "bool"
|
||||
default:
|
||||
valueType = "string"
|
||||
}
|
||||
}
|
||||
case uint8, uint16, uint32, uint64, int, int8, int16, int32, int64:
|
||||
valueType = "int64"
|
||||
case float32, float64:
|
||||
|
||||
@@ -563,6 +563,50 @@ var testJSONFilterEnrichData = []struct {
|
||||
Value: 10.0,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "check IN",
|
||||
Filter: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body.attr",
|
||||
DataType: v3.AttributeKeyDataTypeUnspecified,
|
||||
Type: v3.AttributeKeyTypeUnspecified,
|
||||
},
|
||||
Operator: "IN",
|
||||
Value: []interface{}{"hello", "world"},
|
||||
},
|
||||
Result: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body.attr",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeUnspecified,
|
||||
IsJSON: true,
|
||||
},
|
||||
Operator: "IN",
|
||||
Value: []interface{}{"hello", "world"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "check NOT_IN",
|
||||
Filter: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body.attr",
|
||||
DataType: v3.AttributeKeyDataTypeUnspecified,
|
||||
Type: v3.AttributeKeyTypeUnspecified,
|
||||
},
|
||||
Operator: "NOT_IN",
|
||||
Value: []interface{}{10, 20},
|
||||
},
|
||||
Result: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body.attr",
|
||||
DataType: v3.AttributeKeyDataTypeInt64,
|
||||
Type: v3.AttributeKeyTypeUnspecified,
|
||||
IsJSON: true,
|
||||
},
|
||||
Operator: "NOT_IN",
|
||||
Value: []interface{}{10, 20},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestJsonEnrich(t *testing.T) {
|
||||
|
||||
@@ -183,6 +183,71 @@ var testGetJSONFilterData = []struct {
|
||||
},
|
||||
Filter: "lower(body) like lower('%message%') AND JSON_EXISTS(body, '$.\"message\"')",
|
||||
},
|
||||
{
|
||||
Name: "test json in array string",
|
||||
FilterItem: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body.name",
|
||||
DataType: "string",
|
||||
IsJSON: true,
|
||||
},
|
||||
Operator: "in",
|
||||
Value: []interface{}{"hello", "world"},
|
||||
},
|
||||
Filter: "lower(body) like lower('%name%') AND JSON_EXISTS(body, '$.\"name\"') AND JSON_VALUE(body, '$.\"name\"') IN ['hello','world']",
|
||||
},
|
||||
{
|
||||
Name: "test json in array number",
|
||||
FilterItem: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body.value",
|
||||
DataType: "int64",
|
||||
IsJSON: true,
|
||||
},
|
||||
Operator: "in",
|
||||
Value: []interface{}{10, 11},
|
||||
},
|
||||
Filter: "lower(body) like lower('%value%') AND JSON_EXISTS(body, '$.\"value\"') AND JSONExtract(JSON_VALUE(body, '$.\"value\"'), 'Int64') IN [10,11]",
|
||||
},
|
||||
{
|
||||
Name: "test json in array mixed data- allow",
|
||||
FilterItem: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body.value",
|
||||
DataType: "int64",
|
||||
IsJSON: true,
|
||||
},
|
||||
Operator: "in",
|
||||
Value: []interface{}{11, "11"},
|
||||
},
|
||||
Filter: "lower(body) like lower('%value%') AND JSON_EXISTS(body, '$.\"value\"') AND JSONExtract(JSON_VALUE(body, '$.\"value\"'), 'Int64') IN [11,11]",
|
||||
},
|
||||
{
|
||||
Name: "test json in array mixed data- fail",
|
||||
FilterItem: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body.value",
|
||||
DataType: "int64",
|
||||
IsJSON: true,
|
||||
},
|
||||
Operator: "in",
|
||||
Value: []interface{}{11, "11", "hello"},
|
||||
},
|
||||
Error: true,
|
||||
},
|
||||
{
|
||||
Name: "test json in array mixed data- allow",
|
||||
FilterItem: v3.FilterItem{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "body.value",
|
||||
DataType: "string",
|
||||
IsJSON: true,
|
||||
},
|
||||
Operator: "in",
|
||||
Value: []interface{}{"hello", 11},
|
||||
},
|
||||
Filter: "lower(body) like lower('%value%') AND JSON_EXISTS(body, '$.\"value\"') AND JSON_VALUE(body, '$.\"value\"') IN ['hello','11']",
|
||||
},
|
||||
}
|
||||
|
||||
func TestGetJSONFilter(t *testing.T) {
|
||||
|
||||
@@ -465,7 +465,15 @@ func (q *querier) runBuilderListQueries(ctx context.Context, params *v3.QueryRan
|
||||
}
|
||||
}
|
||||
|
||||
queries, err := q.builder.PrepareQueries(params)
|
||||
queries := make(map[string]string)
|
||||
var err error
|
||||
if params.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
||||
queries, err = q.builder.PrepareQueries(params)
|
||||
} else if params.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
|
||||
for name, chQuery := range params.CompositeQuery.ClickHouseQueries {
|
||||
queries[name] = chQuery.Query
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -534,7 +542,12 @@ func (q *querier) QueryRange(ctx context.Context, params *v3.QueryRangeParamsV3)
|
||||
case v3.QueryTypePromQL:
|
||||
results, errQueriesByName, err = q.runPromQueries(ctx, params)
|
||||
case v3.QueryTypeClickHouseSQL:
|
||||
results, errQueriesByName, err = q.runClickHouseQueries(ctx, params)
|
||||
ctx = context.WithValue(ctx, "enforce_max_result_rows", true)
|
||||
if params.CompositeQuery.PanelType == v3.PanelTypeList || params.CompositeQuery.PanelType == v3.PanelTypeTrace {
|
||||
results, errQueriesByName, err = q.runBuilderListQueries(ctx, params)
|
||||
} else {
|
||||
results, errQueriesByName, err = q.runClickHouseQueries(ctx, params)
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("invalid query type")
|
||||
}
|
||||
|
||||
@@ -548,6 +548,7 @@ func (q *querier) QueryRange(ctx context.Context, params *v3.QueryRangeParamsV3)
|
||||
if params.CompositeQuery.PanelType == v3.PanelTypeList || params.CompositeQuery.PanelType == v3.PanelTypeTrace {
|
||||
results, errQueriesByName, err = q.runBuilderListQueries(ctx, params)
|
||||
} else {
|
||||
ctx = context.WithValue(ctx, "enforce_max_result_rows", true)
|
||||
results, errQueriesByName, err = q.runClickHouseQueries(ctx, params)
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -33,8 +33,9 @@ import (
|
||||
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/preferences"
|
||||
"go.signoz.io/signoz/pkg/query-service/common"
|
||||
"go.signoz.io/signoz/pkg/query-service/migrate"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/signoz"
|
||||
"go.signoz.io/signoz/pkg/web"
|
||||
|
||||
"go.signoz.io/signoz/pkg/query-service/app/explorer"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
@@ -70,6 +71,7 @@ type ServerOptions struct {
|
||||
Cluster string
|
||||
UseLogsNewSchema bool
|
||||
UseTraceNewSchema bool
|
||||
SigNoz *signoz.SigNoz
|
||||
}
|
||||
|
||||
// Server runs HTTP, Mux and a grpc server
|
||||
@@ -166,13 +168,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
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))
|
||||
}
|
||||
}()
|
||||
|
||||
fluxInterval, err := time.ParseDuration(serverOptions.FluxInterval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -226,7 +221,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
unavailableChannel: make(chan healthcheck.Status),
|
||||
}
|
||||
|
||||
httpServer, err := s.createPublicServer(apiHandler)
|
||||
httpServer, err := s.createPublicServer(apiHandler, serverOptions.SigNoz.Web)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -290,7 +285,7 @@ func (s *Server) createPrivateServer(api *APIHandler) (*http.Server, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) createPublicServer(api *APIHandler) (*http.Server, error) {
|
||||
func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server, error) {
|
||||
|
||||
r := NewRouter()
|
||||
|
||||
@@ -335,6 +330,11 @@ func (s *Server) createPublicServer(api *APIHandler) (*http.Server, error) {
|
||||
|
||||
handler = handlers.CompressHandler(handler)
|
||||
|
||||
err := web.AddToRouter(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &http.Server{
|
||||
Handler: handler,
|
||||
}, nil
|
||||
|
||||
@@ -93,8 +93,8 @@ func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) {
|
||||
if fs != nil && len(fs.Items) != 0 {
|
||||
for _, item := range fs.Items {
|
||||
|
||||
// skip if it's a resource attribute
|
||||
if item.Key.Type == v3.AttributeKeyTypeResource {
|
||||
// skip if it's a resource attribute or Span search scope attribute
|
||||
if item.Key.Type == v3.AttributeKeyTypeResource || item.Key.Type == v3.AttributeKeyTypeSpanSearchScope {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -213,6 +213,31 @@ func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags []
|
||||
return str
|
||||
}
|
||||
|
||||
func buildSpanScopeQuery(fs *v3.FilterSet) (string, error) {
|
||||
var query string
|
||||
if fs == nil || len(fs.Items) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
for _, item := range fs.Items {
|
||||
// skip anything other than Span Search scope attribute
|
||||
if item.Key.Type != v3.AttributeKeyTypeSpanSearchScope {
|
||||
continue
|
||||
}
|
||||
keyName := strings.ToLower(item.Key.Key)
|
||||
|
||||
if keyName == constants.SpanSearchScopeRoot {
|
||||
query = "parent_span_id = '' "
|
||||
return query, nil
|
||||
} else if keyName == constants.SpanSearchScopeEntryPoint {
|
||||
query = "((name, `resource_string_service$$name`) IN ( SELECT DISTINCT name, serviceName from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_TOP_LEVEL_OPERATIONS_TABLENAME + " )) "
|
||||
return query, nil
|
||||
} else {
|
||||
return "", fmt.Errorf("invalid scope item type: %s", item.Key.Type)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3.PanelType, options v3.QBOptions) (string, error) {
|
||||
tracesStart := utils.GetEpochNanoSecs(start)
|
||||
tracesEnd := utils.GetEpochNanoSecs(end)
|
||||
@@ -248,6 +273,11 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3.
|
||||
filterSubQuery = filterSubQuery + " AND (resource_fingerprint GLOBAL IN " + resourceSubQuery + ")"
|
||||
}
|
||||
|
||||
spanScopeSubQuery, err := buildSpanScopeQuery(mq.Filters)
|
||||
if spanScopeSubQuery != "" {
|
||||
filterSubQuery = filterSubQuery + " AND " + spanScopeSubQuery
|
||||
}
|
||||
|
||||
// timerange will be sent in epoch millisecond
|
||||
selectLabels := getSelectLabels(mq.GroupBy)
|
||||
if selectLabels != "" {
|
||||
@@ -274,8 +304,8 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3.
|
||||
if len(mq.SelectColumns) == 0 {
|
||||
return "", fmt.Errorf("select columns cannot be empty for panelType %s", panelType)
|
||||
}
|
||||
// add it to the select labels
|
||||
selectLabels = getSelectLabels(mq.SelectColumns)
|
||||
// add it to the select labels
|
||||
queryNoOpTmpl := fmt.Sprintf("SELECT timestamp as timestamp_datetime, spanID, traceID,%s ", selectLabels) + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_V3 + " where %s %s" + "%s"
|
||||
query = fmt.Sprintf(queryNoOpTmpl, timeFilter, filterSubQuery, orderBy)
|
||||
} else {
|
||||
|
||||
@@ -552,6 +552,70 @@ func Test_buildTracesQuery(t *testing.T) {
|
||||
want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
|
||||
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) order by timestamp ASC",
|
||||
},
|
||||
{
|
||||
name: "test noop list view with entry_point_spans",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeList,
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
mq: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
||||
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{{Key: v3.AttributeKey{Key: "isEntryPoint", Type: v3.AttributeKeyTypeSpanSearchScope, IsColumn: false}, Value: true, Operator: v3.FilterOperatorEqual}}},
|
||||
SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}},
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: "ASC"}},
|
||||
},
|
||||
},
|
||||
want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
|
||||
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND ((name, `resource_string_service$$name`) IN ( SELECT DISTINCT name, serviceName from signoz_traces.distributed_top_level_operations )) order by timestamp ASC",
|
||||
},
|
||||
{
|
||||
name: "test noop list view with root_spans",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeList,
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
mq: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
||||
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{{Key: v3.AttributeKey{Key: "isRoot", Type: v3.AttributeKeyTypeSpanSearchScope, IsColumn: false}, Value: true, Operator: v3.FilterOperatorEqual}}},
|
||||
SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}},
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: "ASC"}},
|
||||
},
|
||||
},
|
||||
want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
|
||||
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND parent_span_id = '' order by timestamp ASC",
|
||||
},
|
||||
{
|
||||
name: "test noop list view with root_spans and entry_point_spans both existing",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeList,
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
mq: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
||||
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{{Key: v3.AttributeKey{Key: "isRoot", Type: v3.AttributeKeyTypeSpanSearchScope, IsColumn: false}, Value: true, Operator: v3.FilterOperatorEqual}, {Key: v3.AttributeKey{Key: "isEntryPoint", Type: v3.AttributeKeyTypeSpanSearchScope, IsColumn: false}, Value: true, Operator: v3.FilterOperatorEqual}}},
|
||||
SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}},
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: "ASC"}},
|
||||
},
|
||||
},
|
||||
want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
|
||||
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND parent_span_id = '' order by timestamp ASC",
|
||||
},
|
||||
{
|
||||
name: "test noop list view with root_spans with other attributes",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeList,
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
mq: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
||||
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{{Key: v3.AttributeKey{Key: "isRoot", Type: v3.AttributeKeyTypeSpanSearchScope, IsColumn: false}, Value: true, Operator: v3.FilterOperatorEqual}, {Key: v3.AttributeKey{Key: "service.name", Type: v3.AttributeKeyTypeResource, IsColumn: true, DataType: v3.AttributeKeyDataTypeString}, Value: "cartservice", Operator: v3.FilterOperatorEqual}}},
|
||||
SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}},
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: "ASC"}},
|
||||
},
|
||||
},
|
||||
want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
|
||||
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'cartservice' AND labels like '%service.name%cartservice%')) AND parent_span_id = '' order by timestamp ASC",
|
||||
},
|
||||
{
|
||||
name: "test noop list view-without ts",
|
||||
args: args{
|
||||
|
||||
@@ -54,6 +54,9 @@ const DurationSort = "DurationSort"
|
||||
const TimestampSort = "TimestampSort"
|
||||
const PreferRPM = "PreferRPM"
|
||||
|
||||
const SpanSearchScopeRoot = "isroot"
|
||||
const SpanSearchScopeEntryPoint = "isentrypoint"
|
||||
|
||||
func GetAlertManagerApiPrefix() string {
|
||||
if os.Getenv("ALERTMANAGER_API_PREFIX") != "" {
|
||||
return os.Getenv("ALERTMANAGER_API_PREFIX")
|
||||
@@ -248,6 +251,7 @@ const (
|
||||
SIGNOZ_TIMESERIES_v4_1DAY_LOCAL_TABLENAME = "time_series_v4_1day"
|
||||
SIGNOZ_TIMESERIES_v4_1WEEK_LOCAL_TABLENAME = "time_series_v4_1week"
|
||||
SIGNOZ_TIMESERIES_v4_1DAY_TABLENAME = "distributed_time_series_v4_1day"
|
||||
SIGNOZ_TOP_LEVEL_OPERATIONS_TABLENAME = "distributed_top_level_operations"
|
||||
)
|
||||
|
||||
var TimeoutExcludedRoutes = map[string]bool{
|
||||
@@ -734,3 +738,5 @@ func init() {
|
||||
}
|
||||
|
||||
const TRACE_V4_MAX_PAGINATION_LIMIT = 10000
|
||||
|
||||
const MaxResultRowsForCHQuery = 1_000_000
|
||||
|
||||
@@ -9,11 +9,15 @@ import (
|
||||
"time"
|
||||
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
"go.signoz.io/signoz/pkg/config"
|
||||
"go.signoz.io/signoz/pkg/config/envprovider"
|
||||
"go.signoz.io/signoz/pkg/config/fileprovider"
|
||||
"go.signoz.io/signoz/pkg/query-service/app"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/migrate"
|
||||
"go.signoz.io/signoz/pkg/query-service/version"
|
||||
"go.signoz.io/signoz/pkg/signoz"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
@@ -74,6 +78,22 @@ func main() {
|
||||
logger := loggerMgr.Sugar()
|
||||
version.PrintVersion()
|
||||
|
||||
config, err := signoz.NewConfig(context.Background(), config.ResolverConfig{
|
||||
Uris: []string{"env:"},
|
||||
ProviderFactories: []config.ProviderFactory{
|
||||
envprovider.NewFactory(),
|
||||
fileprovider.NewFactory(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
zap.L().Fatal("Failed to create config", zap.Error(err))
|
||||
}
|
||||
|
||||
signoz, err := signoz.New(context.Background(), config, signoz.NewProviderConfig())
|
||||
if err != nil {
|
||||
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
|
||||
}
|
||||
|
||||
serverOptions := &app.ServerOptions{
|
||||
HTTPHostPort: constants.HTTPHostPort,
|
||||
PromConfigPath: promConfigPath,
|
||||
@@ -90,6 +110,7 @@ func main() {
|
||||
Cluster: cluster,
|
||||
UseLogsNewSchema: useLogsNewSchema,
|
||||
UseTraceNewSchema: useTraceNewSchema,
|
||||
SigNoz: signoz,
|
||||
}
|
||||
|
||||
// Read the jwt secret key
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
@@ -55,90 +52,3 @@ func Migrate(dsn string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ClickHouseMigrate(conn driver.Conn, cluster string) error {
|
||||
|
||||
database := "CREATE DATABASE IF NOT EXISTS signoz_analytics ON CLUSTER %s"
|
||||
|
||||
localTable := `CREATE TABLE IF NOT EXISTS signoz_analytics.rule_state_history_v0 ON CLUSTER %s
|
||||
(
|
||||
_retention_days UInt32 DEFAULT 180,
|
||||
rule_id LowCardinality(String),
|
||||
rule_name LowCardinality(String),
|
||||
overall_state LowCardinality(String),
|
||||
overall_state_changed Bool,
|
||||
state LowCardinality(String),
|
||||
state_changed Bool,
|
||||
unix_milli Int64 CODEC(Delta(8), ZSTD(1)),
|
||||
fingerprint UInt64 CODEC(ZSTD(1)),
|
||||
value Float64 CODEC(Gorilla, ZSTD(1)),
|
||||
labels String CODEC(ZSTD(5)),
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
PARTITION BY toDate(unix_milli / 1000)
|
||||
ORDER BY (rule_id, unix_milli)
|
||||
TTL toDateTime(unix_milli / 1000) + toIntervalDay(_retention_days)
|
||||
SETTINGS ttl_only_drop_parts = 1, index_granularity = 8192`
|
||||
|
||||
distributedTable := `CREATE TABLE IF NOT EXISTS signoz_analytics.distributed_rule_state_history_v0 ON CLUSTER %s
|
||||
(
|
||||
rule_id LowCardinality(String),
|
||||
rule_name LowCardinality(String),
|
||||
overall_state LowCardinality(String),
|
||||
overall_state_changed Bool,
|
||||
state LowCardinality(String),
|
||||
state_changed Bool,
|
||||
unix_milli Int64 CODEC(Delta(8), ZSTD(1)),
|
||||
fingerprint UInt64 CODEC(ZSTD(1)),
|
||||
value Float64 CODEC(Gorilla, ZSTD(1)),
|
||||
labels String CODEC(ZSTD(5)),
|
||||
)
|
||||
ENGINE = Distributed(%s, signoz_analytics, rule_state_history_v0, cityHash64(rule_id, rule_name, fingerprint))`
|
||||
|
||||
// check if db exists
|
||||
dbExists := `SELECT count(*) FROM system.databases WHERE name = 'signoz_analytics'`
|
||||
var count uint64
|
||||
err := conn.QueryRow(context.Background(), dbExists).Scan(&count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
err = conn.Exec(context.Background(), fmt.Sprintf(database, cluster))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// check if table exists
|
||||
tableExists := `SELECT count(*) FROM system.tables WHERE name = 'rule_state_history_v0' AND database = 'signoz_analytics'`
|
||||
var tableCount uint64
|
||||
err = conn.QueryRow(context.Background(), tableExists).Scan(&tableCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tableCount == 0 {
|
||||
err = conn.Exec(context.Background(), fmt.Sprintf(localTable, cluster))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// check if distributed table exists
|
||||
distributedTableExists := `SELECT count(*) FROM system.tables WHERE name = 'distributed_rule_state_history_v0' AND database = 'signoz_analytics'`
|
||||
var distributedTableCount uint64
|
||||
err = conn.QueryRow(context.Background(), distributedTableExists).Scan(&distributedTableCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if distributedTableCount == 0 {
|
||||
err = conn.Exec(context.Background(), fmt.Sprintf(distributedTable, cluster, cluster))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -114,6 +114,47 @@ type PodListResponse struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *PodListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodCPU > r.Records[j].PodCPU
|
||||
})
|
||||
case "cpu_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodCPURequest > r.Records[j].PodCPURequest
|
||||
})
|
||||
case "cpu_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodCPULimit > r.Records[j].PodCPULimit
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodMemory > r.Records[j].PodMemory
|
||||
})
|
||||
case "memory_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodMemoryRequest > r.Records[j].PodMemoryRequest
|
||||
})
|
||||
case "memory_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].PodMemoryLimit > r.Records[j].PodMemoryLimit
|
||||
})
|
||||
case "restarts":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].RestartCount > r.Records[j].RestartCount
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type PodListRecord struct {
|
||||
PodUID string `json:"podUID,omitempty"`
|
||||
PodCPU float64 `json:"podCPU"`
|
||||
@@ -151,6 +192,35 @@ type NodeListResponse struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *NodeListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].NodeCPUUsage > r.Records[j].NodeCPUUsage
|
||||
})
|
||||
case "cpu_allocatable":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].NodeCPUAllocatable > r.Records[j].NodeCPUAllocatable
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].NodeMemoryUsage > r.Records[j].NodeMemoryUsage
|
||||
})
|
||||
case "memory_allocatable":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].NodeMemoryAllocatable > r.Records[j].NodeMemoryAllocatable
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type NodeCountByCondition struct {
|
||||
Ready int `json:"ready"`
|
||||
NotReady int `json:"notReady"`
|
||||
@@ -183,6 +253,31 @@ type NamespaceListResponse struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *NamespaceListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "pod_phase":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CountByPhase.Pending > r.Records[j].CountByPhase.Pending
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type NamespaceListRecord struct {
|
||||
NamespaceName string `json:"namespaceName"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
@@ -207,6 +302,35 @@ type ClusterListResponse struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *ClusterListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "cpu_allocatable":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUAllocatable > r.Records[j].CPUAllocatable
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "memory_allocatable":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryAllocatable > r.Records[j].MemoryAllocatable
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ClusterListRecord struct {
|
||||
ClusterUID string `json:"clusterUID"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
@@ -232,6 +356,55 @@ type DeploymentListResponse struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *DeploymentListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "cpu_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPURequest > r.Records[j].CPURequest
|
||||
})
|
||||
case "cpu_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPULimit > r.Records[j].CPULimit
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "memory_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryRequest > r.Records[j].MemoryRequest
|
||||
})
|
||||
case "memory_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryLimit > r.Records[j].MemoryLimit
|
||||
})
|
||||
case "desired_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].DesiredPods > r.Records[j].DesiredPods
|
||||
})
|
||||
case "available_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].AvailablePods > r.Records[j].AvailablePods
|
||||
})
|
||||
case "restarts":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Restarts > r.Records[j].Restarts
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DeploymentListRecord struct {
|
||||
DeploymentName string `json:"deploymentName"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
@@ -262,6 +435,55 @@ type DaemonSetListResponse struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *DaemonSetListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "cpu_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPURequest > r.Records[j].CPURequest
|
||||
})
|
||||
case "cpu_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPULimit > r.Records[j].CPULimit
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "memory_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryRequest > r.Records[j].MemoryRequest
|
||||
})
|
||||
case "memory_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryLimit > r.Records[j].MemoryLimit
|
||||
})
|
||||
case "restarts":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Restarts > r.Records[j].Restarts
|
||||
})
|
||||
case "desired_nodes":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].DesiredNodes > r.Records[j].DesiredNodes
|
||||
})
|
||||
case "available_nodes":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].AvailableNodes > r.Records[j].AvailableNodes
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DaemonSetListRecord struct {
|
||||
DaemonSetName string `json:"daemonSetName"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
@@ -292,6 +514,55 @@ type StatefulSetListResponse struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *StatefulSetListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "cpu_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPURequest > r.Records[j].CPURequest
|
||||
})
|
||||
case "cpu_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPULimit > r.Records[j].CPULimit
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "memory_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryRequest > r.Records[j].MemoryRequest
|
||||
})
|
||||
case "memory_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryLimit > r.Records[j].MemoryLimit
|
||||
})
|
||||
case "restarts":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Restarts > r.Records[j].Restarts
|
||||
})
|
||||
case "desired_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].DesiredPods > r.Records[j].DesiredPods
|
||||
})
|
||||
case "available_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].AvailablePods > r.Records[j].AvailablePods
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type StatefulSetListRecord struct {
|
||||
StatefulSetName string `json:"statefulSetName"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
@@ -322,6 +593,63 @@ type JobListResponse struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *JobListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "cpu":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPUUsage > r.Records[j].CPUUsage
|
||||
})
|
||||
case "cpu_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPURequest > r.Records[j].CPURequest
|
||||
})
|
||||
case "cpu_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].CPULimit > r.Records[j].CPULimit
|
||||
})
|
||||
case "memory":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryUsage > r.Records[j].MemoryUsage
|
||||
})
|
||||
case "memory_request":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryRequest > r.Records[j].MemoryRequest
|
||||
})
|
||||
case "memory_limit":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].MemoryLimit > r.Records[j].MemoryLimit
|
||||
})
|
||||
case "restarts":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].Restarts > r.Records[j].Restarts
|
||||
})
|
||||
case "desired_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].DesiredSuccessfulPods > r.Records[j].DesiredSuccessfulPods
|
||||
})
|
||||
case "active_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].ActivePods > r.Records[j].ActivePods
|
||||
})
|
||||
case "failed_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].FailedPods > r.Records[j].FailedPods
|
||||
})
|
||||
case "successful_pods":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].SuccessfulPods > r.Records[j].SuccessfulPods
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type JobListRecord struct {
|
||||
JobName string `json:"jobName"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
@@ -354,6 +682,43 @@ type VolumeListResponse struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func (r *VolumeListResponse) SortBy(orderBy *v3.OrderBy) {
|
||||
switch orderBy.ColumnName {
|
||||
case "available":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeAvailable > r.Records[j].VolumeAvailable
|
||||
})
|
||||
case "capacity":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeCapacity > r.Records[j].VolumeCapacity
|
||||
})
|
||||
case "usage":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeUsage > r.Records[j].VolumeUsage
|
||||
})
|
||||
case "inodes":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeInodes > r.Records[j].VolumeInodes
|
||||
})
|
||||
case "inodes_free":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeInodesFree > r.Records[j].VolumeInodesFree
|
||||
})
|
||||
case "inodes_used":
|
||||
sort.Slice(r.Records, func(i, j int) bool {
|
||||
return r.Records[i].VolumeInodesUsed > r.Records[j].VolumeInodesUsed
|
||||
})
|
||||
}
|
||||
|
||||
// the default is descending
|
||||
if orderBy.Order == v3.DirectionAsc {
|
||||
// reverse the list
|
||||
for i, j := 0, len(r.Records)-1; i < j; i, j = i+1, j-1 {
|
||||
r.Records[i], r.Records[j] = r.Records[j], r.Records[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type VolumeListRecord struct {
|
||||
PersistentVolumeClaimName string `json:"persistentVolumeClaimName"`
|
||||
VolumeAvailable float64 `json:"volumeAvailable"`
|
||||
|
||||
@@ -322,6 +322,7 @@ const (
|
||||
AttributeKeyTypeTag AttributeKeyType = "tag"
|
||||
AttributeKeyTypeResource AttributeKeyType = "resource"
|
||||
AttributeKeyTypeInstrumentationScope AttributeKeyType = "scope"
|
||||
AttributeKeyTypeSpanSearchScope AttributeKeyType = "spanSearchScope"
|
||||
)
|
||||
|
||||
func (t AttributeKeyType) String() string {
|
||||
|
||||
@@ -8,18 +8,19 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"go.signoz.io/signoz/pkg/factory"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Registry struct {
|
||||
services []NamedService
|
||||
services []factory.Service
|
||||
logger *zap.Logger
|
||||
startCh chan error
|
||||
stopCh chan error
|
||||
}
|
||||
|
||||
// New creates a new registry of services. It needs at least one service in the input.
|
||||
func New(logger *zap.Logger, services ...NamedService) (*Registry, error) {
|
||||
func New(logger *zap.Logger, services ...factory.Service) (*Registry, error) {
|
||||
if logger == nil {
|
||||
return nil, fmt.Errorf("cannot build registry, logger is required")
|
||||
}
|
||||
@@ -38,7 +39,7 @@ func New(logger *zap.Logger, services ...NamedService) (*Registry, error) {
|
||||
|
||||
func (r *Registry) Start(ctx context.Context) error {
|
||||
for _, s := range r.services {
|
||||
go func(s Service) {
|
||||
go func(s factory.Service) {
|
||||
err := s.Start(ctx)
|
||||
r.startCh <- err
|
||||
}(s)
|
||||
@@ -66,7 +67,7 @@ func (r *Registry) Wait(ctx context.Context) error {
|
||||
|
||||
func (r *Registry) Stop(ctx context.Context) error {
|
||||
for _, s := range r.services {
|
||||
go func(s Service) {
|
||||
go func(s factory.Service) {
|
||||
err := s.Stop(ctx)
|
||||
r.stopCh <- err
|
||||
}(s)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user