Compare commits

..

3 Commits

Author SHA1 Message Date
Prashant Shahi
abea45f255 Merge branch 'main' into fix/install-no-sudo 2025-01-30 18:47:43 +05:45
Prashant Shahi
c0d8f8de3a Merge branch 'main' into fix/install-no-sudo 2025-01-27 11:35:23 +05:45
Prashant Shahi
860fa4a995 fix(install-script): handle no sudo command scenerio
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2025-01-27 01:59:35 +05:30
256 changed files with 1948 additions and 24024 deletions

View File

@@ -181,7 +181,7 @@ services:
- query-service
query-service:
!!merge <<: *db-depend
image: signoz/query-service:0.71.0
image: signoz/query-service:0.70.0
command:
- --config=/root/config/prometheus.yml
- --use-logs-new-schema=true
@@ -214,7 +214,7 @@ services:
retries: 3
frontend:
!!merge <<: *common
image: signoz/frontend:0.71.0
image: signoz/frontend:0.70.0
depends_on:
- alertmanager
- query-service
@@ -224,7 +224,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:0.111.26
image: signoz/signoz-otel-collector:0.111.24
command:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
@@ -262,6 +262,7 @@ services:
networks:
signoz-net:
name: signoz-net
volumes:
alertmanager:
name: signoz-alertmanager

View File

@@ -117,7 +117,7 @@ services:
- query-service
query-service:
!!merge <<: *db-depend
image: signoz/query-service:0.71.0
image: signoz/query-service:0.70.0
command:
- --config=/root/config/prometheus.yml
- --use-logs-new-schema=true
@@ -150,7 +150,7 @@ services:
retries: 3
frontend:
!!merge <<: *common
image: signoz/frontend:0.71.0
image: signoz/frontend:0.70.0
depends_on:
- alertmanager
- query-service
@@ -160,7 +160,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:0.111.26
image: signoz/signoz-otel-collector:0.111.24
command:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
@@ -198,6 +198,7 @@ services:
networks:
signoz-net:
name: signoz-net
volumes:
alertmanager:
name: signoz-alertmanager

View File

@@ -188,7 +188,7 @@ services:
condition: service_healthy
query-service:
!!merge <<: *db-depend
image: signoz/query-service:${DOCKER_TAG:-0.71.0}
image: signoz/query-service:${DOCKER_TAG:-0.70.0}
container_name: signoz-query-service
command:
- --config=/root/config/prometheus.yml
@@ -222,7 +222,7 @@ services:
retries: 3
frontend:
!!merge <<: *common
image: signoz/frontend:${DOCKER_TAG:-0.71.0}
image: signoz/frontend:${DOCKER_TAG:-0.70.0}
container_name: signoz-frontend
depends_on:
- alertmanager
@@ -234,7 +234,7 @@ services:
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.26}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.24}
container_name: signoz-otel-collector
command:
- --config=/etc/otel-collector-config.yaml
@@ -280,6 +280,7 @@ services:
networks:
signoz-net:
name: signoz-net
volumes:
alertmanager:
name: signoz-alertmanager

View File

@@ -121,7 +121,7 @@ services:
condition: service_healthy
query-service:
!!merge <<: *db-depend
image: signoz/query-service:${DOCKER_TAG:-0.71.0}
image: signoz/query-service:${DOCKER_TAG:-0.70.0}
container_name: signoz-query-service
command:
- --config=/root/config/prometheus.yml
@@ -157,7 +157,7 @@ services:
retries: 3
frontend:
!!merge <<: *common
image: signoz/frontend:${DOCKER_TAG:-0.71.0}
image: signoz/frontend:${DOCKER_TAG:-0.70.0}
container_name: signoz-frontend
depends_on:
- alertmanager
@@ -168,7 +168,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.26}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.24}
container_name: signoz-otel-collector
command:
- --config=/etc/otel-collector-config.yaml
@@ -210,6 +210,7 @@ services:
networks:
signoz-net:
name: signoz-net
volumes:
alertmanager:
name: signoz-alertmanager

View File

@@ -121,7 +121,7 @@ services:
condition: service_healthy
query-service:
!!merge <<: *db-depend
image: signoz/query-service:${DOCKER_TAG:-0.71.0}
image: signoz/query-service:${DOCKER_TAG:-0.70.0}
container_name: signoz-query-service
command:
- --config=/root/config/prometheus.yml
@@ -155,7 +155,7 @@ services:
retries: 3
frontend:
!!merge <<: *common
image: signoz/frontend:${DOCKER_TAG:-0.71.0}
image: signoz/frontend:${DOCKER_TAG:-0.70.0}
container_name: signoz-frontend
depends_on:
- alertmanager
@@ -166,7 +166,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.26}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.24}
container_name: signoz-otel-collector
command:
- --config=/etc/otel-collector-config.yaml
@@ -208,6 +208,7 @@ services:
networks:
signoz-net:
name: signoz-net
volumes:
alertmanager:
name: signoz-alertmanager

View File

@@ -309,7 +309,7 @@ request_sudo() {
echo -e "Got it! Thanks!! 🙏\n"
echo -e "Okay! We will bring up the SigNoz cluster from here 🚀\n"
fi
fi
fi
}
echo ""
@@ -317,7 +317,7 @@ echo -e "👋 Thank you for trying out SigNoz! "
echo ""
sudo_cmd=""
docker_compose_cmd=""
docker_compose_cmd="docker compose"
# Check sudo permissions
if (( $EUID != 0 )); then
@@ -325,7 +325,13 @@ if (( $EUID != 0 )); then
echo " In case of any failure or prompt, please consider running the script with sudo privileges."
echo ""
else
sudo_cmd="sudo"
if hash sudo 2>/dev/null; then
sudo_cmd="sudo"
else
echo "🟡 Running installer with root permissions but sudo command not found, running without sudo"
echo " In case of any failure or prompt, please consider installing sudo and running the script again."
echo ""
fi
fi
# Checking OS and assigning package manager
@@ -466,11 +472,10 @@ if ! is_command_present docker; then
fi
if has_docker_compose_plugin; then
echo "docker compose plugin is present, using it"
docker_compose_cmd="docker compose"
# Install docker-compose
echo "docker compose plugin found, using it"
else
docker_compose_cmd="docker-compose"
echo "docker compose plugin not found, using docker-compose instead"
if ! is_command_present docker-compose; then
request_sudo
install_docker_compose

View File

@@ -36,7 +36,6 @@ type APIHandlerOptions struct {
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
Cache cache.Cache
Gateway *httputil.ReverseProxy
GatewayUrl string
// Querier Influx Interval
FluxInterval time.Duration
UseLogsNewSchema bool
@@ -182,17 +181,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
}
func (ah *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *baseapp.AuthMiddleware) {
ah.APIHandler.RegisterCloudIntegrationsRoutes(router, am)
router.HandleFunc(
"/api/v1/cloud-integrations/{cloudProvider}/accounts/generate-connection-params",
am.EditAccess(ah.CloudIntegrationsGenerateConnectionParams),
).Methods(http.MethodGet)
}
func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
version := version.GetVersion()
versionResponse := basemodel.GetVersionResponse{

View File

@@ -1,425 +0,0 @@
package api
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/google/uuid"
"github.com/gorilla/mux"
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model"
"go.signoz.io/signoz/pkg/query-service/auth"
baseconstants "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/dao"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
type CloudIntegrationConnectionParamsResponse struct {
IngestionUrl string `json:"ingestion_url,omitempty"`
IngestionKey string `json:"ingestion_key,omitempty"`
SigNozAPIUrl string `json:"signoz_api_url,omitempty"`
SigNozAPIKey string `json:"signoz_api_key,omitempty"`
}
func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseWriter, r *http.Request) {
cloudProvider := mux.Vars(r)["cloudProvider"]
if cloudProvider != "aws" {
RespondError(w, basemodel.BadRequest(fmt.Errorf(
"cloud provider not supported: %s", cloudProvider,
)), nil)
return
}
currentUser, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, basemodel.UnauthorizedError(fmt.Errorf(
"couldn't deduce current user: %w", err,
)), nil)
return
}
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), currentUser.OrgId, cloudProvider)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't provision PAT for cloud integration:",
), nil)
return
}
result := CloudIntegrationConnectionParamsResponse{
SigNozAPIKey: apiKey,
}
license, apiErr := ah.LM().GetRepo().GetActiveLicense(r.Context())
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't look for active license",
), nil)
return
}
if license == nil {
// Return the API Key (PAT) even if the rest of the params can not be deduced.
// Params not returned from here will be requested from the user via form inputs.
// This enables gracefully degraded but working experience even for non-cloud deployments.
zap.L().Info("ingestion params and signoz api url can not be deduced since no license was found")
ah.Respond(w, result)
return
}
ingestionUrl, signozApiUrl, apiErr := getIngestionUrlAndSigNozAPIUrl(r.Context(), license.Key)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't deduce ingestion url and signoz api url",
), nil)
return
}
result.IngestionUrl = ingestionUrl
result.SigNozAPIUrl = signozApiUrl
gatewayUrl := ah.opts.GatewayUrl
if len(gatewayUrl) > 0 {
ingestionKey, apiErr := getOrCreateCloudProviderIngestionKey(
r.Context(), gatewayUrl, license.Key, cloudProvider,
)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't get or create ingestion key",
), nil)
return
}
result.IngestionKey = ingestionKey
} else {
zap.L().Info("ingestion key can't be deduced since no gateway url has been configured")
}
ah.Respond(w, result)
}
func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId string, cloudProvider string) (
string, *basemodel.ApiError,
) {
integrationPATName := fmt.Sprintf("%s integration", cloudProvider)
integrationUser, apiErr := ah.getOrCreateCloudIntegrationUser(ctx, orgId, cloudProvider)
if apiErr != nil {
return "", apiErr
}
allPats, err := ah.AppDao().ListPATs(ctx)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't list PATs: %w", err.Error(),
))
}
for _, p := range allPats {
if p.UserID == integrationUser.Id && p.Name == integrationPATName {
return p.Token, nil
}
}
zap.L().Info(
"no PAT found for cloud integration, creating a new one",
zap.String("cloudProvider", cloudProvider),
)
newPAT := model.PAT{
Token: generatePATToken(),
UserID: integrationUser.Id,
Name: integrationPATName,
Role: baseconstants.ViewerGroup,
ExpiresAt: 0,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
integrationPAT, err := ah.AppDao().CreatePAT(ctx, newPAT)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't create cloud integration PAT: %w", err.Error(),
))
}
return integrationPAT.Token, nil
}
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
ctx context.Context, orgId string, cloudProvider string,
) (*basemodel.User, *basemodel.ApiError) {
cloudIntegrationUserId := fmt.Sprintf("%s-integration", cloudProvider)
integrationUserResult, apiErr := ah.AppDao().GetUser(ctx, cloudIntegrationUserId)
if apiErr != nil {
return nil, basemodel.WrapApiError(apiErr, "couldn't look for integration user")
}
if integrationUserResult != nil {
return &integrationUserResult.User, nil
}
zap.L().Info(
"cloud integration user not found. Attempting to create the user",
zap.String("cloudProvider", cloudProvider),
)
newUser := &basemodel.User{
Id: cloudIntegrationUserId,
Name: fmt.Sprintf("%s integration", cloudProvider),
Email: fmt.Sprintf("%s@signoz.io", cloudIntegrationUserId),
CreatedAt: time.Now().Unix(),
OrgId: orgId,
}
viewerGroup, apiErr := dao.DB().GetGroupByName(ctx, baseconstants.ViewerGroup)
if apiErr != nil {
return nil, basemodel.WrapApiError(apiErr, "couldn't get viewer group for creating integration user")
}
newUser.GroupId = viewerGroup.Id
passwordHash, err := auth.PasswordHash(uuid.NewString())
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf(
"couldn't hash random password for cloud integration user: %w", err,
))
}
newUser.Password = passwordHash
integrationUser, apiErr := ah.AppDao().CreateUser(ctx, newUser, false)
if apiErr != nil {
return nil, basemodel.WrapApiError(apiErr, "couldn't create cloud integration user")
}
return integrationUser, nil
}
func getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (
string, string, *basemodel.ApiError,
) {
url := fmt.Sprintf(
"%s%s",
strings.TrimSuffix(constants.ZeusURL, "/"),
"/v2/deployments/me",
)
type deploymentResponse struct {
Status string `json:"status"`
Error string `json:"error"`
Data struct {
Name string `json:"name"`
ClusterInfo struct {
Region struct {
DNS string `json:"dns"`
} `json:"region"`
} `json:"cluster"`
} `json:"data"`
}
resp, apiErr := requestAndParseResponse[deploymentResponse](
ctx, url, map[string]string{"X-Signoz-Cloud-Api-Key": licenseKey}, nil,
)
if apiErr != nil {
return "", "", basemodel.WrapApiError(
apiErr, "couldn't query for deployment info",
)
}
if resp.Status != "success" {
return "", "", basemodel.InternalError(fmt.Errorf(
"couldn't query for deployment info: status: %s, error: %s",
resp.Status, resp.Error,
))
}
regionDns := resp.Data.ClusterInfo.Region.DNS
deploymentName := resp.Data.Name
if len(regionDns) < 1 || len(deploymentName) < 1 {
// Fail early if actual response structure and expectation here ever diverge
return "", "", basemodel.InternalError(fmt.Errorf(
"deployment info response not in expected shape. couldn't determine region dns and deployment name",
))
}
ingestionUrl := fmt.Sprintf("https://ingest.%s", regionDns)
signozApiUrl := fmt.Sprintf("https://%s.%s", deploymentName, regionDns)
return ingestionUrl, signozApiUrl, nil
}
type ingestionKey struct {
Name string `json:"name"`
Value string `json:"value"`
// other attributes from gateway response not included here since they are not being used.
}
type ingestionKeysSearchResponse struct {
Status string `json:"status"`
Data []ingestionKey `json:"data"`
Error string `json:"error"`
}
type createIngestionKeyResponse struct {
Status string `json:"status"`
Data ingestionKey `json:"data"`
Error string `json:"error"`
}
func getOrCreateCloudProviderIngestionKey(
ctx context.Context, gatewayUrl string, licenseKey string, cloudProvider string,
) (string, *basemodel.ApiError) {
cloudProviderKeyName := fmt.Sprintf("%s-integration", cloudProvider)
// see if the key already exists
searchResult, apiErr := requestGateway[ingestionKeysSearchResponse](
ctx,
gatewayUrl,
licenseKey,
fmt.Sprintf("/v1/workspaces/me/keys/search?name=%s", cloudProviderKeyName),
nil,
)
if apiErr != nil {
return "", basemodel.WrapApiError(
apiErr, "couldn't search for cloudprovider ingestion key",
)
}
if searchResult.Status != "success" {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't search for cloudprovider ingestion key: status: %s, error: %s",
searchResult.Status, searchResult.Error,
))
}
for _, k := range searchResult.Data {
if k.Name == cloudProviderKeyName {
if len(k.Value) < 1 {
// Fail early if actual response structure and expectation here ever diverge
return "", basemodel.InternalError(fmt.Errorf(
"ingestion keys search response not as expected",
))
}
return k.Value, nil
}
}
zap.L().Info(
"no existing ingestion key found for cloud integration, creating a new one",
zap.String("cloudProvider", cloudProvider),
)
createKeyResult, apiErr := requestGateway[createIngestionKeyResponse](
ctx, gatewayUrl, licenseKey, "/v1/workspaces/me/keys",
map[string]any{
"name": cloudProviderKeyName,
"tags": []string{"integration", cloudProvider},
},
)
if apiErr != nil {
return "", basemodel.WrapApiError(
apiErr, "couldn't create cloudprovider ingestion key",
)
}
if createKeyResult.Status != "success" {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't create cloudprovider ingestion key: status: %s, error: %s",
createKeyResult.Status, createKeyResult.Error,
))
}
ingestionKey := createKeyResult.Data.Value
if len(ingestionKey) < 1 {
// Fail early if actual response structure and expectation here ever diverge
return "", basemodel.InternalError(fmt.Errorf(
"ingestion key creation response not as expected",
))
}
return ingestionKey, nil
}
func requestGateway[ResponseType any](
ctx context.Context, gatewayUrl string, licenseKey string, path string, payload any,
) (*ResponseType, *basemodel.ApiError) {
baseUrl := strings.TrimSuffix(gatewayUrl, "/")
reqUrl := fmt.Sprintf("%s%s", baseUrl, path)
headers := map[string]string{
"X-Signoz-Cloud-Api-Key": licenseKey,
"X-Consumer-Username": "lid:00000000-0000-0000-0000-000000000000",
"X-Consumer-Groups": "ns:default",
}
return requestAndParseResponse[ResponseType](ctx, reqUrl, headers, payload)
}
func requestAndParseResponse[ResponseType any](
ctx context.Context, url string, headers map[string]string, payload any,
) (*ResponseType, *basemodel.ApiError) {
reqMethod := http.MethodGet
var reqBody io.Reader
if payload != nil {
reqMethod = http.MethodPost
bodyJson, err := json.Marshal(payload)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf(
"couldn't serialize request payload to JSON: %w", err,
))
}
reqBody = bytes.NewBuffer([]byte(bodyJson))
}
req, err := http.NewRequestWithContext(ctx, reqMethod, url, reqBody)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf(
"couldn't prepare request: %w", err,
))
}
for k, v := range headers {
req.Header.Set(k, v)
}
client := &http.Client{
Timeout: 10 * time.Second,
}
response, err := client.Do(req)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't make request: %w", err))
}
defer response.Body.Close()
respBody, err := io.ReadAll(response.Body)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't read response: %w", err))
}
var resp ResponseType
err = json.Unmarshal(respBody, &resp)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf(
"couldn't unmarshal gateway response into %T", resp,
))
}
return &resp, nil
}

View File

@@ -11,6 +11,7 @@ import (
"net"
"net/http"
_ "net/http/pprof" // http profiler
"os"
"regexp"
"time"
@@ -148,20 +149,25 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
var reader interfaces.DataConnector
qb := db.NewDataConnector(
serverOptions.SigNoz.SQLStore.SQLxDB(),
serverOptions.SigNoz.TelemetryStore.ClickHouseDB(),
serverOptions.PromConfigPath,
lm,
serverOptions.Cluster,
serverOptions.UseLogsNewSchema,
serverOptions.UseTraceNewSchema,
fluxIntervalForTraceDetail,
serverOptions.SigNoz.Cache,
)
go qb.Start(readerReady)
reader = qb
storage := os.Getenv("STORAGE")
if storage == "clickhouse" {
zap.L().Info("Using ClickHouse as datastore ...")
qb := db.NewDataConnector(
serverOptions.SigNoz.SQLStore.SQLxDB(),
serverOptions.SigNoz.TelemetryStore.ClickHouseDB(),
serverOptions.PromConfigPath,
lm,
serverOptions.Cluster,
serverOptions.UseLogsNewSchema,
serverOptions.UseTraceNewSchema,
fluxIntervalForTraceDetail,
serverOptions.SigNoz.Cache,
)
go qb.Start(readerReady)
reader = qb
} else {
return nil, fmt.Errorf("storage type: %s is not supported in query service", storage)
}
skipConfig := &basemodel.SkipConfig{}
if serverOptions.SkipTopLvlOpsPath != "" {
// read skip config
@@ -266,7 +272,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
Cache: c,
FluxInterval: fluxInterval,
Gateway: gatewayProxy,
GatewayUrl: serverOptions.GatewayUrl,
UseLogsNewSchema: serverOptions.UseLogsNewSchema,
UseTraceNewSchema: serverOptions.UseTraceNewSchema,
}

View File

@@ -7,6 +7,7 @@ import (
basedao "go.signoz.io/signoz/pkg/query-service/dao"
basedsql "go.signoz.io/signoz/pkg/query-service/dao/sqlite"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
"go.uber.org/zap"
)
type modelDao struct {
@@ -28,6 +29,41 @@ func (m *modelDao) checkFeature(key string) error {
return m.flags.CheckFeature(key)
}
func columnExists(db *sqlx.DB, tableName, columnName string) bool {
query := fmt.Sprintf("PRAGMA table_info(%s);", tableName)
rows, err := db.Query(query)
if err != nil {
zap.L().Error("Failed to query table info", zap.Error(err))
return false
}
defer rows.Close()
var (
cid int
name string
ctype string
notnull int
dflt_value *string
pk int
)
for rows.Next() {
err := rows.Scan(&cid, &name, &ctype, &notnull, &dflt_value, &pk)
if err != nil {
zap.L().Error("Failed to scan table info", zap.Error(err))
return false
}
if name == columnName {
return true
}
}
err = rows.Err()
if err != nil {
zap.L().Error("Failed to scan table info", zap.Error(err))
return false
}
return false
}
// InitDB creates and extends base model DB repository
func InitDB(inputDB *sqlx.DB) (*modelDao, error) {
dao, err := basedsql.InitDB(inputDB)
@@ -37,6 +73,69 @@ func InitDB(inputDB *sqlx.DB) (*modelDao, error) {
// set package variable so dependent base methods (e.g. AuthCache) will work
basedao.SetDB(dao)
m := &modelDao{ModelDaoSqlite: dao}
table_schema := `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS org_domains(
id TEXT PRIMARY KEY,
org_id TEXT NOT NULL,
name VARCHAR(50) NOT NULL UNIQUE,
created_at INTEGER NOT NULL,
updated_at INTEGER,
data TEXT NOT NULL,
FOREIGN KEY(org_id) REFERENCES organizations(id)
);
CREATE TABLE IF NOT EXISTS personal_access_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
role TEXT NOT NULL,
user_id TEXT NOT NULL,
token TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
created_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
last_used INTEGER NOT NULL,
revoked BOOLEAN NOT NULL,
updated_by_user_id TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id)
);
`
_, err = m.DB().Exec(table_schema)
if err != nil {
return nil, fmt.Errorf("error in creating tables: %v", err.Error())
}
if !columnExists(m.DB(), "personal_access_tokens", "role") {
_, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN role TEXT NOT NULL DEFAULT 'ADMIN';")
if err != nil {
return nil, fmt.Errorf("error in adding column: %v", err.Error())
}
}
if !columnExists(m.DB(), "personal_access_tokens", "updated_at") {
_, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN updated_at INTEGER NOT NULL DEFAULT 0;")
if err != nil {
return nil, fmt.Errorf("error in adding column: %v", err.Error())
}
}
if !columnExists(m.DB(), "personal_access_tokens", "last_used") {
_, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN last_used INTEGER NOT NULL DEFAULT 0;")
if err != nil {
return nil, fmt.Errorf("error in adding column: %v", err.Error())
}
}
if !columnExists(m.DB(), "personal_access_tokens", "revoked") {
_, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN revoked BOOLEAN NOT NULL DEFAULT FALSE;")
if err != nil {
return nil, fmt.Errorf("error in adding column: %v", err.Error())
}
}
if !columnExists(m.DB(), "personal_access_tokens", "updated_by_user_id") {
_, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN updated_by_user_id TEXT NOT NULL DEFAULT '';")
if err != nil {
return nil, fmt.Errorf("error in adding column: %v", err.Error())
}
}
return m, nil
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/jmoiron/sqlx"
"github.com/mattn/go-sqlite3"
"go.signoz.io/signoz/ee/query-service/license/sqlite"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
@@ -27,6 +28,10 @@ func NewLicenseRepo(db *sqlx.DB) Repo {
}
}
func (r *Repo) InitDB(inputDB *sqlx.DB) error {
return sqlite.InitDB(inputDB)
}
func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
licensesData := []model.LicenseDB{}
licenseV3Data := []*model.LicenseV3{}

View File

@@ -2,6 +2,7 @@ package license
import (
"context"
"fmt"
"sync/atomic"
"time"
@@ -49,6 +50,11 @@ func StartManager(db *sqlx.DB, features ...basemodel.Feature) (*Manager, error)
}
repo := NewLicenseRepo(db)
err := repo.InitDB(db)
if err != nil {
return nil, fmt.Errorf("failed to initiate license repo: %v", err)
}
m := &Manager{
repo: &repo,
}

View File

@@ -0,0 +1,63 @@
package sqlite
import (
"fmt"
"github.com/jmoiron/sqlx"
)
func InitDB(db *sqlx.DB) error {
var err error
if db == nil {
return fmt.Errorf("invalid db connection")
}
table_schema := `CREATE TABLE IF NOT EXISTS licenses(
key TEXT PRIMARY KEY,
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
planDetails TEXT,
activationId TEXT,
validationMessage TEXT,
lastValidated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS sites(
uuid TEXT PRIMARY KEY,
alias VARCHAR(180) DEFAULT 'PROD',
url VARCHAR(300),
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`
_, err = db.Exec(table_schema)
if err != nil {
return fmt.Errorf("error in creating licenses table: %s", err.Error())
}
table_schema = `CREATE TABLE IF NOT EXISTS feature_status (
name TEXT PRIMARY KEY,
active bool,
usage INTEGER DEFAULT 0,
usage_limit INTEGER DEFAULT 0,
route TEXT
);`
_, err = db.Exec(table_schema)
if err != nil {
return fmt.Errorf("error in creating feature_status table: %s", err.Error())
}
table_schema = `CREATE TABLE IF NOT EXISTS licenses_v3 (
id TEXT PRIMARY KEY,
key TEXT NOT NULL UNIQUE,
data TEXT
);`
_, err = db.Exec(table_schema)
if err != nil {
return fmt.Errorf("error in creating licenses_v3 table: %s", err.Error())
}
return nil
}

View File

@@ -18,6 +18,7 @@ import (
"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"
"go.signoz.io/signoz/pkg/query-service/version"
"go.signoz.io/signoz/pkg/signoz"
"google.golang.org/grpc"
@@ -182,6 +183,12 @@ func main() {
zap.L().Info("JWT secret key set successfully.")
}
if err := migrate.Migrate(signoz.SQLStore.SQLxDB()); err != nil {
zap.L().Error("Failed to migrate", zap.Error(err))
} else {
zap.L().Info("Migration successful")
}
server, err := app.NewServer(serverOptions)
if err != nil {
zap.L().Fatal("Failed to create server", zap.Error(err))

View File

@@ -139,8 +139,8 @@ func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
if err != nil {
return nil, err
}
// if license status is invalid then default it to basic
if status == LicenseStatusInvalid {
// if license status is inactive then default it to basic
if status == LicenseStatusInactive {
planName = PlanNameBasic
}

View File

@@ -21,7 +21,7 @@ var (
)
var (
LicenseStatusInvalid = "INVALID"
LicenseStatusInactive = "INACTIVE"
)
const DisableUpsell = "DISABLE_UPSELL"
@@ -157,13 +157,6 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AWSIntegration,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var ProPlan = basemodel.FeatureSet{
@@ -286,13 +279,6 @@ var ProPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AWSIntegration,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var EnterprisePlan = basemodel.FeatureSet{
@@ -429,11 +415,4 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AWSIntegration,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}

View File

@@ -107,7 +107,6 @@
"react-grid-layout": "^1.3.4",
"react-helmet-async": "1.3.0",
"react-i18next": "^11.16.1",
"react-lottie": "1.2.10",
"react-markdown": "8.0.7",
"react-query": "3.39.3",
"react-redux": "^7.2.2",
@@ -179,7 +178,6 @@
"@types/react-dom": "18.0.10",
"@types/react-grid-layout": "^1.1.2",
"@types/react-helmet-async": "1.0.3",
"@types/react-lottie": "1.2.10",
"@types/react-redux": "^7.1.11",
"@types/react-resizable": "3.0.3",
"@types/react-router-dom": "^5.1.6",
@@ -220,7 +218,6 @@
"portfinder-sync": "^0.0.2",
"postcss": "8.4.38",
"prettier": "2.2.1",
"prop-types": "15.8.1",
"raw-loader": "4.0.2",
"react-hooks-testing-library": "0.6.0",
"react-hot-loader": "^4.13.0",

View File

@@ -1,6 +0,0 @@
<svg width="57" height="57" viewBox="0 0 57 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="solid-check-circle-2">
<path id="Vector" d="M28.2498 51.9638C41.1368 51.9638 51.5832 41.5175 51.5832 28.6305C51.5832 15.7435 41.1368 5.29712 28.2498 5.29712C15.3628 5.29712 4.9165 15.7435 4.9165 28.6305C4.9165 41.5175 15.3628 51.9638 28.2498 51.9638Z" fill="#4E74F8" stroke="#4E74F8" stroke-width="4.66667" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_2" d="M16.25 27.6304L24.2115 36.6304L39.25 22.6304" stroke="#121317" stroke-width="4.66667" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -1,23 +0,0 @@
<svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="amazon_web_services_logo.svg" clip-path="url(#clip0_2552_26387)">
<g id="Group">
<path id="Vector" d="M6.82058 6.05151C6.82058 6.34672 6.85997 6.58289 6.89935 6.76001C6.95843 6.93714 7.03721 7.11426 7.15537 7.33075C7.19476 7.38979 7.21445 7.44883 7.21445 7.50788C7.21445 7.5866 7.17506 7.66532 7.0569 7.74404L6.56456 8.07861C6.48579 8.11797 6.42671 8.15733 6.36762 8.15733C6.28885 8.15733 6.21008 8.11797 6.1313 8.03925C6.01314 7.92117 5.93437 7.80308 5.85559 7.66532C5.77682 7.52756 5.69804 7.38979 5.61927 7.19299C5.00876 7.92117 4.24071 8.27542 3.29542 8.27542C2.62584 8.27542 2.1138 8.07861 1.71993 7.70468C1.32606 7.33075 1.12912 6.81906 1.12912 6.18928C1.12912 5.52014 1.36544 4.96908 1.83809 4.57547C2.31074 4.18186 2.96063 3.96538 3.76807 3.96538C4.04378 3.96538 4.31949 3.98506 4.5952 4.02442C4.8906 4.06378 5.18601 4.12282 5.50111 4.20154V3.63081C5.50111 3.04039 5.38294 2.60742 5.12693 2.37125C4.87091 2.13508 4.45734 2.017 3.84684 2.017C3.57113 2.017 3.29542 2.05636 3.00001 2.1154C2.70461 2.19413 2.4289 2.27285 2.15319 2.39093C2.03503 2.43029 1.93656 2.46965 1.87748 2.48933C1.8184 2.48933 1.77901 2.50901 1.75932 2.50901C1.64115 2.50901 1.60177 2.43029 1.60177 2.27285V1.87924C1.60177 1.76115 1.62146 1.66275 1.66085 1.60371C1.70024 1.54467 1.77901 1.48563 1.87748 1.44626C2.15319 1.3085 2.48798 1.19042 2.86216 1.09201C3.25603 0.993612 3.6499 0.93457 4.08316 0.93457C5.02846 0.93457 5.69804 1.15106 6.15099 1.56435C6.58425 1.99732 6.80088 2.6271 6.80088 3.51272L6.82058 6.05151ZM3.63021 7.25203C3.88623 7.25203 4.16194 7.21267 4.43765 7.11426C4.71336 7.01586 4.96938 6.83874 5.18601 6.60257C5.30417 6.44513 5.40264 6.28768 5.46172 6.09088C5.50111 5.89407 5.54049 5.67758 5.54049 5.40206V5.06749C5.30417 5.00845 5.06785 4.96908 4.81183 4.92972C4.55581 4.89036 4.31949 4.89036 4.06347 4.89036C3.53174 4.89036 3.15756 4.98876 2.88185 5.20525C2.62584 5.42174 2.48798 5.71695 2.48798 6.11056C2.48798 6.48449 2.58645 6.76001 2.78338 6.93714C2.98032 7.17331 3.25603 7.25203 3.63021 7.25203ZM9.95187 8.11797C9.81401 8.11797 9.71554 8.09829 9.65646 8.03925C9.59738 7.99989 9.5383 7.88181 9.49891 7.72436L7.64771 1.62339C7.60832 1.46595 7.56894 1.36754 7.56894 1.3085C7.56894 1.19042 7.62802 1.1117 7.76587 1.1117H8.53392C8.69147 1.1117 8.78994 1.13138 8.84902 1.19042C8.9081 1.22978 8.96718 1.34786 9.00657 1.50531L10.326 6.72065L11.547 1.50531C11.5864 1.34786 11.6258 1.24946 11.7046 1.19042C11.7637 1.15106 11.8818 1.1117 12.0197 1.1117H12.6499C12.8074 1.1117 12.9059 1.13138 12.965 1.19042C13.0241 1.22978 13.0832 1.34786 13.1225 1.50531L14.3632 6.7797L15.7221 1.50531C15.7615 1.34786 15.8206 1.24946 15.8796 1.19042C15.9387 1.15106 16.0372 1.1117 16.1947 1.1117H16.9234C17.0416 1.1117 17.1203 1.17074 17.1203 1.3085C17.1203 1.34786 17.1203 1.38722 17.1007 1.42658C17.1007 1.46595 17.081 1.54467 17.0416 1.62339L15.1313 7.72436C15.0919 7.88181 15.0328 7.98021 14.9737 8.03925C14.9147 8.07861 14.8162 8.11797 14.6783 8.11797H14.0088C13.8512 8.11797 13.7527 8.09829 13.6937 8.03925C13.6346 7.98021 13.5755 7.88181 13.5361 7.72436L12.3151 2.64678L11.0941 7.72436C11.0547 7.88181 11.0153 7.98021 10.9365 8.03925C10.8775 8.09829 10.7593 8.11797 10.6214 8.11797H9.95187ZM20.0941 8.31478C19.6805 8.31478 19.267 8.27542 18.8731 8.17702C18.4792 8.07861 18.1641 7.98021 17.9672 7.86213C17.849 7.7834 17.7505 7.70468 17.7308 7.64564C17.7112 7.5866 17.6718 7.4882 17.6718 7.42915V7.03554C17.6718 6.8781 17.7308 6.79938 17.849 6.79938C17.8884 6.79938 17.9475 6.79938 17.9869 6.81906C18.0263 6.83874 18.105 6.85842 18.1838 6.89778C18.4595 7.01586 18.7352 7.11426 19.0503 7.17331C19.3654 7.23235 19.6805 7.27171 19.9956 7.27171C20.488 7.27171 20.8818 7.19299 21.1378 7.01586C21.4136 6.83874 21.5514 6.58289 21.5514 6.268C21.5514 6.05151 21.4726 5.87439 21.3348 5.71695C21.1969 5.5595 20.9212 5.44142 20.547 5.30365L19.4048 4.9494C18.8337 4.77228 18.4004 4.49675 18.1444 4.1425C17.8884 3.78825 17.7505 3.41432 17.7505 3.00103C17.7505 2.66646 17.8293 2.37125 17.9672 2.13508C18.105 1.89892 18.302 1.66275 18.5383 1.48563C18.7746 1.3085 19.0503 1.17074 19.3654 1.07233C19.6805 0.973931 20.0153 0.93457 20.3501 0.93457C20.5273 0.93457 20.7046 0.93457 20.8818 0.973931C21.0591 0.993612 21.2363 1.03297 21.3939 1.05265C21.5514 1.09201 21.709 1.13138 21.8468 1.17074C21.9847 1.2101 22.1028 1.26914 22.1816 1.3085C22.2998 1.36754 22.3785 1.42658 22.4179 1.50531C22.4573 1.56435 22.4967 1.66275 22.4967 1.76115V2.13508C22.4967 2.29253 22.4376 2.39093 22.3195 2.39093C22.2604 2.39093 22.1619 2.35157 22.0241 2.29253C21.5711 2.09572 21.0788 1.97764 20.5077 1.97764C20.0547 1.97764 19.7002 2.05636 19.4639 2.19413C19.2276 2.33189 19.0897 2.56806 19.0897 2.90263C19.0897 3.11911 19.1685 3.31592 19.326 3.45368C19.4836 3.61113 19.779 3.74889 20.1926 3.88665L21.3151 4.2409C21.8862 4.41803 22.2998 4.67388 22.5361 4.98876C22.7724 5.30365 22.8906 5.67758 22.8906 6.09088C22.8906 6.42545 22.8118 6.74033 22.6936 6.99618C22.5558 7.27171 22.3589 7.50788 22.1225 7.685C21.8862 7.88181 21.5908 8.01957 21.256 8.11797C20.8621 8.27542 20.488 8.31478 20.0941 8.31478Z" fill="white"/>
<g id="Group_2">
<path id="Vector_2" fill-rule="evenodd" clip-rule="evenodd" d="M21.5908 12.1525C18.9912 14.0615 15.2101 15.0849 11.9803 15.0849C7.43108 15.0849 3.33481 13.4121 0.242906 10.6174C0.00658259 10.4009 0.223213 10.1057 0.518617 10.2632C3.86653 12.2115 7.9825 13.3727 12.256 13.3727C15.1313 13.3727 18.302 12.7823 21.2166 11.5424C21.6302 11.3653 22.0044 11.8376 21.5908 12.1525Z" fill="#FF9900"/>
<path id="Vector_3" fill-rule="evenodd" clip-rule="evenodd" d="M22.6543 10.9324C22.3195 10.4994 20.4683 10.7356 19.6214 10.834C19.3654 10.8734 19.326 10.6372 19.5624 10.4798C21.0394 9.43669 23.4814 9.7319 23.7571 10.0861C24.0328 10.4404 23.6783 12.8808 22.2998 14.0419C22.0831 14.2191 21.8862 14.1207 21.9847 13.8845C22.2998 13.0973 22.989 11.3457 22.6543 10.9324Z" fill="#FF9900"/>
</g>
</g>
<g id="Group_3">
<path id="Vector_4" d="M6.82058 6.05151C6.82058 6.34672 6.85997 6.58289 6.89935 6.76001C6.95843 6.93714 7.03721 7.11426 7.15537 7.33075C7.19476 7.38979 7.21445 7.44883 7.21445 7.50788C7.21445 7.5866 7.17506 7.66532 7.0569 7.74404L6.56456 8.07861C6.48579 8.11797 6.42671 8.15733 6.36762 8.15733C6.28885 8.15733 6.21008 8.11797 6.1313 8.03925C6.01314 7.92117 5.93437 7.80308 5.85559 7.66532C5.77682 7.52756 5.69804 7.38979 5.61927 7.19299C5.00876 7.92117 4.24071 8.27542 3.29542 8.27542C2.62584 8.27542 2.1138 8.07861 1.71993 7.70468C1.32606 7.33075 1.12912 6.81906 1.12912 6.18928C1.12912 5.52014 1.36544 4.96908 1.83809 4.57547C2.31074 4.18186 2.96063 3.96538 3.76807 3.96538C4.04378 3.96538 4.31949 3.98506 4.5952 4.02442C4.8906 4.06378 5.18601 4.12282 5.50111 4.20154V3.63081C5.50111 3.04039 5.38294 2.60742 5.12693 2.37125C4.87091 2.13508 4.45734 2.017 3.84684 2.017C3.57113 2.017 3.29542 2.05636 3.00001 2.1154C2.70461 2.19413 2.4289 2.27285 2.15319 2.39093C2.03503 2.43029 1.93656 2.46965 1.87748 2.48933C1.8184 2.48933 1.77901 2.50901 1.75932 2.50901C1.64115 2.50901 1.60177 2.43029 1.60177 2.27285V1.87924C1.60177 1.76115 1.62146 1.66275 1.66085 1.60371C1.70024 1.54467 1.77901 1.48563 1.87748 1.44626C2.15319 1.3085 2.48798 1.19042 2.86216 1.09201C3.25603 0.993612 3.6499 0.93457 4.08316 0.93457C5.02846 0.93457 5.69804 1.15106 6.15099 1.56435C6.58425 1.99732 6.80088 2.6271 6.80088 3.51272L6.82058 6.05151ZM3.63021 7.25203C3.88623 7.25203 4.16194 7.21267 4.43765 7.11426C4.71336 7.01586 4.96938 6.83874 5.18601 6.60257C5.30417 6.44513 5.40264 6.28768 5.46172 6.09088C5.50111 5.89407 5.54049 5.67758 5.54049 5.40206V5.06749C5.30417 5.00845 5.06785 4.96908 4.81183 4.92972C4.55581 4.89036 4.31949 4.89036 4.06347 4.89036C3.53174 4.89036 3.15756 4.98876 2.88185 5.20525C2.62584 5.42174 2.48798 5.71695 2.48798 6.11056C2.48798 6.48449 2.58645 6.76001 2.78338 6.93714C2.98032 7.17331 3.25603 7.25203 3.63021 7.25203ZM9.95187 8.11797C9.81401 8.11797 9.71554 8.09829 9.65646 8.03925C9.59738 7.99989 9.5383 7.88181 9.49891 7.72436L7.64771 1.62339C7.60832 1.46595 7.56894 1.36754 7.56894 1.3085C7.56894 1.19042 7.62802 1.1117 7.76587 1.1117H8.53392C8.69147 1.1117 8.78994 1.13138 8.84902 1.19042C8.9081 1.22978 8.96718 1.34786 9.00657 1.50531L10.326 6.72065L11.547 1.50531C11.5864 1.34786 11.6258 1.24946 11.7046 1.19042C11.7637 1.15106 11.8818 1.1117 12.0197 1.1117H12.6499C12.8074 1.1117 12.9059 1.13138 12.965 1.19042C13.0241 1.22978 13.0832 1.34786 13.1225 1.50531L14.3632 6.7797L15.7221 1.50531C15.7615 1.34786 15.8206 1.24946 15.8796 1.19042C15.9387 1.15106 16.0372 1.1117 16.1947 1.1117H16.9234C17.0416 1.1117 17.1203 1.17074 17.1203 1.3085C17.1203 1.34786 17.1203 1.38722 17.1007 1.42658C17.1007 1.46595 17.081 1.54467 17.0416 1.62339L15.1313 7.72436C15.0919 7.88181 15.0328 7.98021 14.9737 8.03925C14.9147 8.07861 14.8162 8.11797 14.6783 8.11797H14.0088C13.8512 8.11797 13.7527 8.09829 13.6937 8.03925C13.6346 7.98021 13.5755 7.88181 13.5361 7.72436L12.3151 2.64678L11.0941 7.72436C11.0547 7.88181 11.0153 7.98021 10.9365 8.03925C10.8775 8.09829 10.7593 8.11797 10.6214 8.11797H9.95187ZM20.0941 8.31478C19.6805 8.31478 19.267 8.27542 18.8731 8.17702C18.4792 8.07861 18.1641 7.98021 17.9672 7.86213C17.849 7.7834 17.7505 7.70468 17.7308 7.64564C17.7112 7.5866 17.6718 7.4882 17.6718 7.42915V7.03554C17.6718 6.8781 17.7308 6.79938 17.849 6.79938C17.8884 6.79938 17.9475 6.79938 17.9869 6.81906C18.0263 6.83874 18.105 6.85842 18.1838 6.89778C18.4595 7.01586 18.7352 7.11426 19.0503 7.17331C19.3654 7.23235 19.6805 7.27171 19.9956 7.27171C20.488 7.27171 20.8818 7.19299 21.1378 7.01586C21.4136 6.83874 21.5514 6.58289 21.5514 6.268C21.5514 6.05151 21.4726 5.87439 21.3348 5.71695C21.1969 5.5595 20.9212 5.44142 20.547 5.30365L19.4048 4.9494C18.8337 4.77228 18.4004 4.49675 18.1444 4.1425C17.8884 3.78825 17.7505 3.41432 17.7505 3.00103C17.7505 2.66646 17.8293 2.37125 17.9672 2.13508C18.105 1.89892 18.302 1.66275 18.5383 1.48563C18.7746 1.3085 19.0503 1.17074 19.3654 1.07233C19.6805 0.973931 20.0153 0.93457 20.3501 0.93457C20.5273 0.93457 20.7046 0.93457 20.8818 0.973931C21.0591 0.993612 21.2363 1.03297 21.3939 1.05265C21.5514 1.09201 21.709 1.13138 21.8468 1.17074C21.9847 1.2101 22.1028 1.26914 22.1816 1.3085C22.2998 1.36754 22.3785 1.42658 22.4179 1.50531C22.4573 1.56435 22.4967 1.66275 22.4967 1.76115V2.13508C22.4967 2.29253 22.4376 2.39093 22.3195 2.39093C22.2604 2.39093 22.1619 2.35157 22.0241 2.29253C21.5711 2.09572 21.0788 1.97764 20.5077 1.97764C20.0547 1.97764 19.7002 2.05636 19.4639 2.19413C19.2276 2.33189 19.0897 2.56806 19.0897 2.90263C19.0897 3.11911 19.1685 3.31592 19.326 3.45368C19.4836 3.61113 19.779 3.74889 20.1926 3.88665L21.3151 4.2409C21.8862 4.41803 22.2998 4.67388 22.5361 4.98876C22.7724 5.30365 22.8906 5.67758 22.8906 6.09088C22.8906 6.42545 22.8118 6.74033 22.6936 6.99618C22.5558 7.27171 22.3589 7.50788 22.1225 7.685C21.8862 7.88181 21.5908 8.01957 21.256 8.11797C20.8621 8.27542 20.488 8.31478 20.0941 8.31478Z" fill="white"/>
<g id="Group_4">
<path id="Vector_5" fill-rule="evenodd" clip-rule="evenodd" d="M21.5908 12.1525C18.9912 14.0615 15.2101 15.0849 11.9803 15.0849C7.43108 15.0849 3.33481 13.4121 0.242906 10.6174C0.00658259 10.4009 0.223213 10.1057 0.518617 10.2632C3.86653 12.2115 7.9825 13.3727 12.256 13.3727C15.1313 13.3727 18.302 12.7823 21.2166 11.5424C21.6302 11.3653 22.0044 11.8376 21.5908 12.1525Z" fill="#FF9900"/>
<path id="Vector_6" fill-rule="evenodd" clip-rule="evenodd" d="M22.6543 10.9324C22.3195 10.4994 20.4683 10.7356 19.6214 10.834C19.3654 10.8734 19.326 10.6372 19.5624 10.4798C21.0394 9.43669 23.4814 9.7319 23.7571 10.0861C24.0328 10.4404 23.6783 12.8808 22.2998 14.0419C22.0831 14.2191 21.8862 14.1207 21.9847 13.8845C22.2998 13.0973 22.989 11.3457 22.6543 10.9324Z" fill="#FF9900"/>
</g>
</g>
</g>
<defs>
<clipPath id="clip0_2552_26387">
<rect width="23.7111" height="14.17" fill="white" transform="translate(0.14444 0.915039)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none">
<path fill="#252F3E" d="M4.51 7.687c0 .197.02.357.058.475.042.117.096.245.17.384a.233.233 0 01.037.123c0 .053-.032.107-.1.16l-.336.224a.255.255 0 01-.138.048c-.054 0-.107-.026-.16-.074a1.652 1.652 0 01-.192-.251 4.137 4.137 0 01-.165-.315c-.415.491-.936.737-1.564.737-.447 0-.804-.129-1.064-.385-.261-.256-.394-.598-.394-1.025 0-.454.16-.822.484-1.1.325-.278.756-.416 1.304-.416.18 0 .367.016.564.042.197.027.4.07.612.118v-.39c0-.406-.085-.689-.25-.854-.17-.166-.458-.246-.868-.246-.186 0-.377.022-.574.07a4.23 4.23 0 00-.575.181 1.525 1.525 0 01-.186.07.326.326 0 01-.085.016c-.075 0-.112-.054-.112-.166v-.262c0-.085.01-.15.037-.186a.399.399 0 01.15-.113c.185-.096.409-.176.67-.24.26-.07.537-.101.83-.101.633 0 1.096.144 1.394.432.293.288.442.726.442 1.314v1.73h.01zm-2.161.811c.175 0 .356-.032.548-.096.191-.064.362-.182.505-.342a.848.848 0 00.181-.341c.032-.129.054-.283.054-.465V7.03a4.43 4.43 0 00-.49-.09 3.996 3.996 0 00-.5-.033c-.357 0-.618.07-.793.214-.176.144-.26.347-.26.614 0 .25.063.437.196.566.128.133.314.197.559.197zm4.273.577c-.096 0-.16-.016-.202-.054-.043-.032-.08-.106-.112-.208l-1.25-4.127a.938.938 0 01-.049-.214c0-.085.043-.133.128-.133h.522c.1 0 .17.016.207.053.043.032.075.107.107.208l.894 3.535.83-3.535c.026-.106.058-.176.1-.208a.365.365 0 01.214-.053h.425c.102 0 .17.016.213.053.043.032.08.107.101.208l.841 3.578.92-3.578a.458.458 0 01.107-.208.346.346 0 01.208-.053h.495c.085 0 .133.043.133.133 0 .027-.006.054-.01.086a.76.76 0 01-.038.133l-1.283 4.127c-.032.107-.069.177-.111.209a.34.34 0 01-.203.053h-.457c-.101 0-.17-.016-.213-.053-.043-.038-.08-.107-.101-.214L8.213 5.37l-.82 3.439c-.026.107-.058.176-.1.213-.043.038-.118.054-.213.054h-.458zm6.838.144a3.51 3.51 0 01-.82-.096c-.266-.064-.473-.134-.612-.214-.085-.048-.143-.101-.165-.15a.378.378 0 01-.031-.149v-.272c0-.112.042-.166.122-.166a.3.3 0 01.096.016c.032.011.08.032.133.054.18.08.378.144.585.187.213.042.42.064.633.064.336 0 .596-.059.777-.176a.575.575 0 00.277-.508.52.52 0 00-.144-.373c-.095-.102-.276-.193-.537-.278l-.772-.24c-.388-.123-.676-.305-.851-.545a1.275 1.275 0 01-.266-.774c0-.224.048-.422.143-.593.096-.17.224-.32.384-.438.16-.122.34-.213.553-.277.213-.064.436-.091.67-.091.118 0 .24.005.357.021.122.016.234.038.346.06.106.026.208.052.303.085.096.032.17.064.224.096a.46.46 0 01.16.133.289.289 0 01.047.176v.251c0 .112-.042.171-.122.171a.552.552 0 01-.202-.064 2.427 2.427 0 00-1.022-.208c-.303 0-.543.048-.708.15-.165.1-.25.256-.25.475 0 .149.053.277.16.379.106.101.303.202.585.293l.756.24c.383.123.66.294.825.513.165.219.244.47.244.748 0 .23-.047.437-.138.619a1.436 1.436 0 01-.388.47c-.165.133-.362.23-.591.299-.24.075-.49.112-.761.112z"/>
<g fill="#F90" fill-rule="evenodd" clip-rule="evenodd">

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -57,8 +57,5 @@
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview",
"MESSAGING_QUEUES": "SigNoz | Messaging Queues",
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring",
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring",
"METRICS_EXPLORER": "SigNoz | Metrics Explorer",
"METRICS_EXPLORER_EXPLORER": "SigNoz | Metrics Explorer",
"METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer"
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring"
}

View File

@@ -156,7 +156,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
const currentRoute = mapRoutes.get('current');
const shouldSuspendWorkspace =
activeLicenseV3.status === LicenseStatus.SUSPENDED &&
activeLicenseV3.state === LicenseState.DEFAULTED;
activeLicenseV3.state === LicenseState.PAYMENT_FAILED;
if (shouldSuspendWorkspace && currentRoute) {
navigateToWorkSpaceSuspended(currentRoute);

View File

@@ -264,8 +264,3 @@ export const CeleryOverview = Loadable(
/* webpackChunkName: "CeleryOverview" */ 'pages/Celery/CeleryOverview/CeleryOverview'
),
);
export const MetricsExplorer = Loadable(
() =>
import(/* webpackChunkName: "MetricsExplorer" */ 'pages/MetricsExplorer'),
);

View File

@@ -28,7 +28,6 @@ import {
LogsExplorer,
LogsIndexToFields,
LogsSaveViews,
MetricsExplorer,
MySettings,
NewDashboardPage,
OldLogsExplorer,
@@ -436,27 +435,6 @@ const routes: AppRoutes[] = [
key: 'INFRASTRUCTURE_MONITORING_KUBERNETES',
isPrivate: true,
},
{
path: ROUTES.METRICS_EXPLORER,
exact: true,
component: MetricsExplorer,
key: 'METRICS_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.METRICS_EXPLORER_EXPLORER,
exact: true,
component: MetricsExplorer,
key: 'METRICS_EXPLORER_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.METRICS_EXPLORER_VIEWS,
exact: true,
component: MetricsExplorer,
key: 'METRICS_EXPLORER_VIEWS',
isPrivate: true,
},
];
export const SUPPORT_ROUTE: AppRoutes = {

View File

@@ -1,19 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
const removeAwsIntegrationAccount = async (
accountId: string,
): Promise<SuccessResponse<Record<string, never>> | ErrorResponse> => {
const response = await axios.post(
`/cloud-integrations/aws/accounts/${accountId}/disconnect`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default removeAwsIntegrationAccount;

View File

@@ -1,88 +0,0 @@
import axios from 'api';
import {
CloudAccount,
Service,
ServiceData,
UpdateServiceConfigPayload,
UpdateServiceConfigResponse,
} from 'container/CloudIntegrationPage/ServicesSection/types';
import {
AccountConfigPayload,
AccountConfigResponse,
ConnectionParams,
ConnectionUrlResponse,
} from 'types/api/integrations/aws';
export const getAwsAccounts = async (): Promise<CloudAccount[]> => {
const response = await axios.get('/cloud-integrations/aws/accounts');
return response.data.data.accounts;
};
export const getAwsServices = async (
cloudAccountId?: string,
): Promise<Service[]> => {
const params = cloudAccountId
? { cloud_account_id: cloudAccountId }
: undefined;
const response = await axios.get('/cloud-integrations/aws/services', {
params,
});
return response.data.data.services;
};
export const getServiceDetails = async (
serviceId: string,
cloudAccountId?: string,
): Promise<ServiceData> => {
const params = cloudAccountId
? { cloud_account_id: cloudAccountId }
: undefined;
const response = await axios.get(
`/cloud-integrations/aws/services/${serviceId}`,
{ params },
);
return response.data.data;
};
export const generateConnectionUrl = async (params: {
agent_config: { region: string };
account_config: { regions: string[] };
account_id?: string;
}): Promise<ConnectionUrlResponse> => {
const response = await axios.post(
'/cloud-integrations/aws/accounts/generate-connection-url',
params,
);
return response.data.data;
};
export const updateAccountConfig = async (
accountId: string,
payload: AccountConfigPayload,
): Promise<AccountConfigResponse> => {
const response = await axios.post<AccountConfigResponse>(
`/cloud-integrations/aws/accounts/${accountId}/config`,
payload,
);
return response.data;
};
export const updateServiceConfig = async (
serviceId: string,
payload: UpdateServiceConfigPayload,
): Promise<UpdateServiceConfigResponse> => {
const response = await axios.post<UpdateServiceConfigResponse>(
`/cloud-integrations/aws/services/${serviceId}/config`,
payload,
);
return response.data;
};
export const getConnectionParams = async (): Promise<ConnectionParams> => {
const response = await axios.get(
'/cloud-integrations/aws/accounts/generate-connection-params',
);
return response.data.data;
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
import './CeleryOverviewConfigOptions.styles.scss';
import { Row, Select, Spin } from 'antd';
import { Color } from '@signozhq/design-tokens';
import { Button, Row, Select, Spin, Tooltip } from 'antd';
import {
getValuesFromQueryParams,
setQueryParamsFromOptions,
@@ -9,7 +10,10 @@ import { useCeleryFilterOptions } from 'components/CeleryTask/useCeleryFilterOpt
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';
import { Check, Share2 } from 'lucide-react';
import { useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
interface SelectOptionConfig {
placeholder: string;
@@ -62,6 +66,10 @@ function FilterSelect({
}
function CeleryOverviewConfigOptions(): JSX.Element {
const [isURLCopied, setIsURLCopied] = useState(false);
const [, handleCopyToClipboard] = useCopyToClipboard();
const selectConfigs: SelectOptionConfig[] = [
{
placeholder: 'Service Name',
@@ -90,6 +98,14 @@ function CeleryOverviewConfigOptions(): JSX.Element {
},
];
const handleShareURL = (): void => {
handleCopyToClipboard(window.location.href);
setIsURLCopied(true);
setTimeout(() => {
setIsURLCopied(false);
}, 1000);
};
return (
<div className="celery-overview-filters">
<Row className="celery-filters">
@@ -102,6 +118,19 @@ function CeleryOverviewConfigOptions(): JSX.Element {
/>
))}
</Row>
<Tooltip title="Share this" arrow={false}>
<Button
className="periscope-btn copy-url-btn"
onClick={handleShareURL}
icon={
isURLCopied ? (
<Check size={14} color={Color.BG_FOREST_500} />
) : (
<Share2 size={14} />
)
}
/>
</Tooltip>
</div>
);
}

View File

@@ -37,6 +37,7 @@
font-weight: 600;
line-height: 18px; /* 163.636% */
letter-spacing: 0.44px;
text-transform: uppercase;
&::before {
background-color: transparent;

View File

@@ -218,44 +218,35 @@ function getColumns(data: RowData[]): TableColumnsType<RowData> {
showTitle: false,
},
width: 200,
sorter: (a: RowData, b: RowData): number => {
const aValue = Number(a.error_percentage);
const bValue = Number(b.error_percentage);
return aValue - bValue;
},
sorter: (a: RowData, b: RowData): number =>
String(a.error_percentage).localeCompare(String(b.error_percentage)),
render: ProgressRender,
},
{
title: 'LATENCY (P95) in ms',
title: 'LATENCY (P95)',
dataIndex: 'p95_latency',
key: 'p95_latency',
ellipsis: {
showTitle: false,
},
width: 100,
sorter: (a: RowData, b: RowData): number => {
const aValue = Number(a.p95_latency);
const bValue = Number(b.p95_latency);
return aValue - bValue;
},
sorter: (a: RowData, b: RowData): number =>
String(a.p95_latency).localeCompare(String(b.p95_latency)),
render: (value: number | string): string => {
if (!isNumber(value)) return value.toString();
return (typeof value === 'string' ? parseFloat(value) : value).toFixed(3);
},
},
{
title: 'THROUGHPUT (ops/s)',
title: 'THROUGHPUT',
dataIndex: 'throughput',
key: 'throughput',
ellipsis: {
showTitle: false,
},
width: 100,
sorter: (a: RowData, b: RowData): number => {
const aValue = Number(a.throughput);
const bValue = Number(b.throughput);
return aValue - bValue;
},
sorter: (a: RowData, b: RowData): number =>
String(a.throughput).localeCompare(String(b.throughput)),
render: (value: number | string): string => {
if (!isNumber(value)) return value.toString();
return (typeof value === 'string' ? parseFloat(value) : value).toFixed(3);

View File

@@ -1,10 +1,14 @@
import './CeleryTaskConfigOptions.styles.scss';
import { Select, Spin, Typography } from 'antd';
import { Color } from '@signozhq/design-tokens';
import { Button, Select, Spin, Tooltip, Typography } from 'antd';
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';
import { Check, Share2 } from 'lucide-react';
import { useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import {
getValuesFromQueryParams,
@@ -19,8 +23,11 @@ function CeleryTaskConfigOptions(): JSX.Element {
const history = useHistory();
const location = useLocation();
const [isURLCopied, setIsURLCopied] = useState(false);
const urlQuery = useUrlQuery();
const [, handleCopyToClipboard] = useCopyToClipboard();
return (
<div className="celery-task-filters">
<div className="celery-filters">
@@ -59,6 +66,25 @@ function CeleryTaskConfigOptions(): JSX.Element {
}}
/>
</div>
<Tooltip title="Share this" arrow={false}>
<Button
className="periscope-btn copy-url-btn"
onClick={(): void => {
handleCopyToClipboard(window.location.href);
setIsURLCopied(true);
setTimeout(() => {
setIsURLCopied(false);
}, 1000);
}}
icon={
isURLCopied ? (
<Check size={14} color={Color.BG_FOREST_500} />
) : (
<Share2 size={14} />
)
}
/>
</Tooltip>
</div>
);
}

View File

@@ -1,13 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { DefaultOptionType } from 'antd/es/select';
import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
export interface Filters {
searchText: string;
@@ -35,18 +31,8 @@ export function useGetAllFilters(props: Filters): GetAllFiltersResponse {
tagType,
} = props;
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { data, isLoading } = useQuery(
[
REACT_QUERY_KEY.GET_ATTRIBUTE_VALUES,
attributeKey,
searchText,
minTime,
maxTime,
],
['attributesValues', attributeKey, searchText],
async () => {
const keys = Array.isArray(attributeKey) ? attributeKey : [attributeKey];

View File

@@ -2,16 +2,24 @@ import './CeleryTaskDetail.style.scss';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Divider, Drawer, Typography } from 'antd';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery';
import { X } from 'lucide-react';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 as uuidv4 } from 'uuid';
import CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph';
import { createFiltersFromData } from '../CeleryUtils';
import { useNavigateToTraces } from '../useNavigateToTraces';
export type CeleryTaskData = {
@@ -31,6 +39,40 @@ export type CeleryTaskDetailProps = {
drawerOpen: boolean;
};
const createFiltersFromData = (
data: Record<string, any>,
): Array<{
id: string;
key: {
key: string;
dataType: DataTypes;
type: string;
isColumn: boolean;
isJSON: boolean;
id: string;
};
op: string;
value: string;
}> => {
const excludeKeys = ['A', 'A_without_unit'];
return Object.entries(data)
.filter(([key]) => !excludeKeys.includes(key))
.map(([key, value]) => ({
id: uuidv4(),
key: {
key,
dataType: DataTypes.String,
type: 'tag',
isColumn: false,
isJSON: false,
id: `${key}--string--tag--false`,
},
op: '=',
value: value.toString(),
}));
};
export default function CeleryTaskDetail({
widgetData,
taskData,
@@ -43,7 +85,7 @@ export default function CeleryTaskDetail({
!!taskData.entity && !!taskData.timeRange[0] && drawerOpen;
const formatTimestamp = (timestamp: number): string =>
dayjs(timestamp).format('DD-MM-YYYY hh:mm A');
dayjs(timestamp * 1000).format('MM-DD-YYYY hh:mm A');
const [totalTask, setTotalTask] = useState(0);
@@ -51,9 +93,52 @@ export default function CeleryTaskDetail({
setTotalTask((graphData?.result?.[0] as any)?.table?.rows.length);
};
// set time range
const { minTime, maxTime, selectedTime } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const startTime = taskData.timeRange[0];
const endTime = taskData.timeRange[1];
const urlQuery = useUrlQuery();
const location = useLocation();
const history = useHistory();
const dispatch = useDispatch();
useEffect(() => {
urlQuery.delete(QueryParams.relativeTime);
urlQuery.set(QueryParams.startTime, startTime.toString());
urlQuery.set(QueryParams.endTime, endTime.toString());
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
history.replace(generatedUrl);
if (startTime !== endTime) {
dispatch(UpdateTimeInterval('custom', [startTime, endTime]));
}
return (): void => {
urlQuery.delete(QueryParams.relativeTime);
urlQuery.delete(QueryParams.startTime);
urlQuery.delete(QueryParams.endTime);
if (selectedTime !== 'custom') {
dispatch(UpdateTimeInterval(selectedTime));
urlQuery.set(QueryParams.relativeTime, selectedTime);
} else {
dispatch(UpdateTimeInterval('custom', [minTime / 1e6, maxTime / 1e6]));
urlQuery.set(QueryParams.startTime, Math.floor(minTime / 1e6).toString());
urlQuery.set(QueryParams.endTime, Math.floor(maxTime / 1e6).toString());
}
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
history.replace(generatedUrl);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const navigateToTrace = useNavigateToTraces();
return (
@@ -64,8 +149,10 @@ export default function CeleryTaskDetail({
<Typography.Text className="title">{`Details - ${taskData.entity}`}</Typography.Text>
<div>
<Typography.Text className="subtitle">
{`${formatTimestamp(startTime)} ${
endTime ? `- ${formatTimestamp(endTime)}` : ''
{`${formatTimestamp(taskData.timeRange[0])} ${
taskData.timeRange[1]
? `- ${formatTimestamp(taskData.timeRange[1])}`
: ''
}`}
</Typography.Text>
<Divider type="vertical" />
@@ -98,10 +185,8 @@ export default function CeleryTaskDetail({
...rowData,
[taskData.entity]: taskData.value,
});
navigateToTrace(filters, startTime, endTime);
navigateToTrace(filters);
}}
start={startTime}
end={endTime}
/>
</Drawer>
);

View File

@@ -3,12 +3,14 @@ import './CeleryTaskGraph.style.scss';
import { Color } from '@signozhq/design-tokens';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
import { ViewMenuAction } from 'container/GridCardLayout/config';
import GridCard from 'container/GridCardLayout/GridCard';
import { Card } from 'container/GridCardLayout/styles';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery';
import { isEmpty } from 'lodash-es';
import getLabelName from 'lib/getLabelName';
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
@@ -16,14 +18,11 @@ import { useHistory, useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { QueryData } from 'types/api/widgets/getQuery';
import { GlobalReducer } from 'types/reducer/globalTime';
import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail';
import {
applyCeleryFilterOnWidgetData,
getFiltersFromQueryParams,
} from '../CeleryUtils';
import { useGetGraphCustomSeries } from '../useGetGraphCustomSeries';
import { paths } from '../CeleryUtils';
import {
celeryAllStateWidgetData,
celeryFailedStateWidgetData,
@@ -76,60 +75,26 @@ function CeleryTaskBar({
const [barState, setBarState] = useState<CeleryTaskState>(CeleryTaskState.All);
const selectedFilters = useMemo(
() =>
getFiltersFromQueryParams(
QueryParams.taskName,
urlQuery,
'celery.task_name',
),
[urlQuery],
);
const celeryAllStateData = useMemo(
() => celeryAllStateWidgetData(minTime, maxTime),
[minTime, maxTime],
);
const celeryAllStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryAllStateData),
[selectedFilters, celeryAllStateData],
);
const celeryFailedStateData = useMemo(
() => celeryFailedStateWidgetData(minTime, maxTime),
[minTime, maxTime],
);
const celeryFailedStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryFailedStateData),
[selectedFilters, celeryFailedStateData],
);
const celeryRetryStateData = useMemo(
() => celeryRetryStateWidgetData(minTime, maxTime),
[minTime, maxTime],
);
const celeryRetryStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryRetryStateData),
[selectedFilters, celeryRetryStateData],
);
const celerySuccessStateData = useMemo(
() => celerySuccessStateWidgetData(minTime, maxTime),
[minTime, maxTime],
);
const celerySuccessStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celerySuccessStateData),
[selectedFilters, celerySuccessStateData],
);
const onGraphClick = (
widgetData: Widgets,
xValue: number,
@@ -149,26 +114,58 @@ function CeleryTaskBar({
string,
];
if (!isEmpty(entity) || !isEmpty(value)) {
onClick?.({
entity,
value,
timeRange: [start, end],
widgetData,
});
}
onClick?.({
entity,
value,
timeRange: [start, end],
widgetData,
});
};
const { getCustomSeries } = useGetGraphCustomSeries({
isDarkMode,
const getGraphSeries = (color: string, label: string): any => ({
drawStyle: 'bars',
colorMapping: {
SUCCESS: Color.BG_FOREST_500,
FAILURE: Color.BG_CHERRY_500,
RETRY: Color.BG_AMBER_400,
paths,
lineInterpolation: 'spline',
show: true,
label,
fill: `${color}90`,
stroke: color,
width: 2,
spanGaps: true,
points: {
size: 5,
show: false,
stroke: color,
},
});
const customSeries = (data: QueryData[]): uPlot.Series[] => {
const configurations: uPlot.Series[] = [
{ label: 'Timestamp', stroke: 'purple' },
];
for (let i = 0; i < data.length; i += 1) {
const { metric = {}, queryName = '', legend = '' } = data[i] || {};
const label = getLabelName(metric, queryName || '', legend || '');
let color = generateColor(
label,
isDarkMode ? themeColors.chartcolors : themeColors.lightModeColor,
);
if (label === 'SUCCESS') {
color = Color.BG_FOREST_500;
}
if (label === 'FAILURE') {
color = Color.BG_CHERRY_500;
}
if (label === 'RETRY') {
color = Color.BG_AMBER_400;
}
const series = getGraphSeries(color, label);
configurations.push(series);
}
return configurations;
};
return (
<Card
isDarkMode={isDarkMode}
@@ -179,50 +176,50 @@ function CeleryTaskBar({
<div className="celery-task-graph-grid-content">
{barState === CeleryTaskState.All && (
<GridCard
widget={celeryAllStateFilteredData}
widget={celeryAllStateData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled}
onClickHandler={(...args): void =>
onGraphClick(celerySlowestTasksTableWidgetData, ...args)
}
customSeries={getCustomSeries}
customSeries={customSeries}
/>
)}
{barState === CeleryTaskState.Failed && (
<GridCard
widget={celeryFailedStateFilteredData}
widget={celeryFailedStateData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled}
onClickHandler={(...args): void =>
onGraphClick(celeryFailedTasksTableWidgetData, ...args)
}
customSeries={getCustomSeries}
customSeries={customSeries}
/>
)}
{barState === CeleryTaskState.Retry && (
<GridCard
widget={celeryRetryStateFilteredData}
widget={celeryRetryStateData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled}
onClickHandler={(...args): void =>
onGraphClick(celeryRetryTasksTableWidgetData, ...args)
}
customSeries={getCustomSeries}
customSeries={customSeries}
/>
)}
{barState === CeleryTaskState.Successful && (
<GridCard
widget={celerySuccessStateFilteredData}
widget={celerySuccessStateData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled}
onClickHandler={(...args): void =>
onGraphClick(celerySuccessTasksTableWidgetData, ...args)
}
customSeries={getCustomSeries}
customSeries={customSeries}
/>
)}
</div>

View File

@@ -6,85 +6,93 @@
padding-bottom: 16px;
margin-bottom: 16px;
.celery-task-graph-bar,
.celery-task-graph-task-latency {
height: 380px !important;
.celery-task-graph-grid {
display: grid;
grid-template-columns: 60% 40%;
align-items: flex-start;
gap: 10px;
width: 100%;
box-sizing: border-box;
.celery-task-graph-grid-content {
.celery-task-graph {
height: 380px !important;
padding: 6px;
height: 100%;
}
width: 100%;
box-sizing: border-box;
.ant-card-body {
height: calc(100% - 18px);
.ant-card-body {
height: calc(100% - 18px);
.widget-graph-container {
&.bar {
height: calc(100% - 110px);
}
&.graph {
height: calc(100% - 80px);
.widget-graph-container {
&.bar {
height: calc(100% - 110px);
}
}
}
}
}
.celery-task-graph-worker-count {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--bg-slate-500);
background: linear-gradient(
0deg,
rgba(171, 189, 255, 0) 0%,
rgba(171, 189, 255, 0) 100%
),
#0b0c0e;
.ant-card-body {
.celery-task-graph-worker-count {
height: 100%;
width: 100%;
}
.worker-count-text-container {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--bg-slate-500);
background: linear-gradient(
0deg,
rgba(171, 189, 255, 0) 0%,
rgba(171, 189, 255, 0) 100%
),
#0b0c0e;
.celery-task-graph-worker-count-text {
font-size: 2.5vw;
text-align: center;
.ant-card-body {
height: 100%;
width: 100%;
}
.worker-count-text-container {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.celery-task-graph-worker-count-text {
font-size: 2.5vw;
text-align: center;
}
}
.worker-count-header {
display: flex;
justify-content: start;
align-items: start;
position: absolute;
height: 100%;
width: 100%;
}
}
.worker-count-header {
display: flex;
justify-content: start;
align-items: start;
position: absolute;
height: 100%;
.celery-task-graph-bar,
.celery-task-graph-task-latency {
height: 380px !important;
width: 100%;
}
}
box-sizing: border-box;
.celery-task-graph {
height: 380px !important;
padding: 6px;
width: 100%;
box-sizing: border-box;
.celery-task-graph-grid-content {
padding: 6px;
height: 100%;
}
.ant-card-body {
height: calc(100% - 18px);
.ant-card-body {
height: calc(100% - 18px);
.widget-graph-container {
&.bar {
height: calc(100% - 110px);
.widget-graph-container {
&.bar {
height: calc(100% - 110px);
}
&.graph {
height: calc(100% - 80px);
}
}
}
}
@@ -108,89 +116,6 @@
}
}
}
.metric-based-graphs,
.trace-based-graphs {
display: flex;
flex-direction: column;
gap: 10px;
.metric-page-grid {
display: flex;
flex-direction: row;
gap: 10px;
width: 100%;
.celery-task-graph {
height: 280px !important;
}
}
}
.trace-based-graphs {
.trace-based-graphs-header {
display: grid;
grid-template-columns: 50% 50%;
align-items: center;
gap: 24px;
width: 100%;
}
}
.configure-option-Info {
display: grid;
grid-template-columns: max-content 1fr;
gap: 16px;
align-items: center;
border: 1px dashed var(--bg-slate-50);
border-radius: 4px;
padding: 6px 24px 6px 12px;
width: max-content;
.configure-option-Info-text {
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 400;
}
}
.row-panel {
border-radius: 4px;
background: rgba(18, 19, 23, 0.4);
padding: 8px;
display: flex;
gap: 6px;
align-items: center;
height: 48px !important;
width: 100%;
.ant-typography {
font-size: 14px;
font-weight: 500;
}
.row-panel-section {
display: flex;
gap: 6px;
align-items: center;
.row-icon {
color: var(--bg-vanilla-400);
cursor: pointer;
}
.section-title {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.07px;
}
}
}
}
.celery-task-states {
@@ -245,13 +170,11 @@
.lightMode {
.celery-task-graph-grid-container {
.celery-task-graph-worker-count {
border: 1px solid var(--bg-vanilla-300);
background: unset;
}
.row-panel .row-panel-section .section-title {
color: var(--bg-ink-400);
.celery-task-graph-grid {
.celery-task-graph-worker-count {
border: 1px solid var(--bg-vanilla-300);
background: unset;
}
}
}
@@ -280,8 +203,4 @@
background-color: var(--bg-ink-400);
}
}
.configure-option-Info {
border: 1px dashed var(--bg-robin-400);
}
}

View File

@@ -32,9 +32,6 @@ function CeleryTaskGraph({
openTracesButton,
onOpenTraceBtnClick,
applyCeleryTaskFilter,
customErrorMessage,
start,
end,
}: {
widgetData: Widgets;
onClick?: (task: CaptureDataProps) => void;
@@ -45,9 +42,6 @@ function CeleryTaskGraph({
openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void;
applyCeleryTaskFilter?: boolean;
customErrorMessage?: string;
start?: number;
end?: number;
}): JSX.Element {
const history = useHistory();
const { pathname } = useLocation();
@@ -122,9 +116,6 @@ function CeleryTaskGraph({
openTracesButton={openTracesButton}
onOpenTraceBtnClick={onOpenTraceBtnClick}
version={ENTITY_VERSION_V4}
customErrorMessage={customErrorMessage}
start={start}
end={end}
/>
</Card>
);
@@ -138,9 +129,6 @@ CeleryTaskGraph.defaultProps = {
openTracesButton: false,
onOpenTraceBtnClick: undefined,
applyCeleryTaskFilter: false,
customErrorMessage: undefined,
start: undefined,
end: undefined,
};
export default CeleryTaskGraph;

View File

@@ -1,10 +1,7 @@
import './CeleryTaskGraph.style.scss';
import { Card, Typography } from 'antd';
import { CardContainer } from 'container/GridCardLayout/styles';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { useMemo, useState } from 'react';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
@@ -26,11 +23,9 @@ import CeleryTaskLatencyGraph from './CeleryTaskLatencyGraph';
export default function CeleryTaskGraphGrid({
onClick,
queryEnabled,
configureOptionComponent,
}: {
onClick: (task: CaptureDataProps) => void;
queryEnabled: boolean;
configureOptionComponent?: React.ReactNode;
}): JSX.Element {
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
@@ -76,124 +71,44 @@ export default function CeleryTaskGraphGrid({
DataTypes.String,
'tag',
);
const isDarkMode = useIsDarkMode();
const [collapsedSections, setCollapsedSections] = useState<{
[key: string]: boolean;
}>({
metricBasedGraphs: false,
traceBasedGraphs: false,
});
const toggleCollapse = (key: string): void => {
setCollapsedSections((prev) => ({
...prev,
[key]: !prev[key],
}));
};
return (
<div className="celery-task-graph-grid-container">
<div className="metric-based-graphs">
<CardContainer className="row-card" isDarkMode={isDarkMode}>
<div className="row-panel">
<div className="row-panel-section">
<Typography.Text className="section-title">
Flower Metrics
</Typography.Text>
{collapsedSections.metricBasedGraphs ? (
<ChevronDown
size={14}
onClick={(): void => toggleCollapse('metricBasedGraphs')}
className="row-icon"
/>
) : (
<ChevronUp
size={14}
onClick={(): void => toggleCollapse('metricBasedGraphs')}
className="row-icon"
/>
)}
</div>
</div>
</CardContainer>
{!collapsedSections.metricBasedGraphs && (
<div className="metric-page-grid">
<CeleryTaskGraph
key={celeryActiveTasksData.id}
widgetData={celeryActiveTasksData}
queryEnabled={queryEnabled}
customErrorMessage="Enable Flower metrics to view this graph"
/>
<Card className="celery-task-graph-worker-count">
<div className="worker-count-header">
<Typography.Text className="worker-count-header-text">
Worker Online
</Typography.Text>
</div>
<div className="worker-count-text-container">
<Typography.Text className="celery-task-graph-worker-count-text">
{options.filter((option) => option.value).length}
</Typography.Text>
</div>
</Card>
</div>
)}
</div>
<div className="trace-based-graphs">
<div className="trace-based-graphs-header">
<CardContainer className="row-card" isDarkMode={isDarkMode}>
<div className="row-panel">
<div className="row-panel-section">
<Typography.Text className="section-title">
Span Based Stats
</Typography.Text>
{collapsedSections.traceBasedGraphs ? (
<ChevronDown
size={14}
onClick={(): void => toggleCollapse('traceBasedGraphs')}
className="row-icon"
/>
) : (
<ChevronUp
size={14}
onClick={(): void => toggleCollapse('traceBasedGraphs')}
className="row-icon"
/>
)}
</div>
</div>
</CardContainer>
<div className="configure-option-Info">
{configureOptionComponent}
<Typography.Text className="configure-option-Info-text">
Click on a graph co-ordinate to see more details
<div className="celery-task-graph-grid">
<CeleryTaskBar queryEnabled={queryEnabled} onClick={onClick} />
<Card className="celery-task-graph-worker-count">
<div className="worker-count-header">
<Typography.Text className="worker-count-header-text">
Worker Count
</Typography.Text>
</div>
</div>
{!collapsedSections.traceBasedGraphs && (
<>
<CeleryTaskBar queryEnabled={queryEnabled} onClick={onClick} />
<CeleryTaskLatencyGraph queryEnabled={queryEnabled} />
<div className="celery-task-graph-grid-bottom">
{bottomWidgetData.map((widgetData, index) => (
<CeleryTaskGraph
key={widgetData.id}
widgetData={widgetData}
onClick={onClick}
queryEnabled={queryEnabled}
rightPanelTitle={rightPanelTitle[index]}
applyCeleryTaskFilter
/>
))}
</div>
</>
)}
<div className="worker-count-text-container">
<Typography.Text className="celery-task-graph-worker-count-text">
{options.filter((option) => option.value).length}
</Typography.Text>
</div>
</Card>
</div>
<div className="celery-task-graph-grid">
<CeleryTaskLatencyGraph onClick={onClick} queryEnabled={queryEnabled} />
<CeleryTaskGraph
key={celeryActiveTasksData.id}
widgetData={celeryActiveTasksData}
queryEnabled={queryEnabled}
/>
</div>
<div className="celery-task-graph-grid-bottom">
{bottomWidgetData.map((widgetData, index) => (
<CeleryTaskGraph
key={widgetData.id}
widgetData={widgetData}
onClick={onClick}
queryEnabled={queryEnabled}
rightPanelTitle={rightPanelTitle[index]}
applyCeleryTaskFilter
/>
))}
</div>
</div>
);
}
CeleryTaskGraphGrid.defaultProps = {
configureOptionComponent: null,
};

View File

@@ -42,7 +42,21 @@ export const celeryAllStateWidgetData = (
disabled: false,
expression: 'A',
filters: {
items: [],
items: [
{
id: uuidv4(),
key: {
dataType: DataTypes.String,
id: 'celery.task_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'celery.task_name',
type: 'tag',
},
op: '=',
value: 'tasks.tasks.divide',
},
],
op: 'AND',
},
functions: [],
@@ -99,7 +113,7 @@ export const celeryRetryStateWidgetData = (
filters: {
items: [
{
id: uuidv4(),
id: '6d97eed3',
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@@ -165,7 +179,7 @@ export const celeryFailedStateWidgetData = (
filters: {
items: [
{
id: uuidv4(),
id: '5983eae2',
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@@ -231,7 +245,7 @@ export const celerySuccessStateWidgetData = (
filters: {
items: [
{
id: uuidv4(),
id: '000c5a93',
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@@ -318,7 +332,6 @@ export const celeryTasksByWorkerWidgetData = (
timeAggregation: 'rate',
},
],
yAxisUnit: 'cps',
}),
);
@@ -332,103 +345,44 @@ export const celeryErrorByWorkerWidgetData = (
description: 'Represents the number of errors by each worker.',
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.String,
id: '------false',
isColumn: false,
isJSON: false,
key: '',
type: '',
},
aggregateOperator: 'rate',
dataSource: DataSource.TRACES,
queryName: 'A',
aggregateOperator: 'count_distinct',
aggregateAttribute: {
dataType: 'string',
id: 'span_id--string----true',
isColumn: true,
isJSON: false,
key: 'span_id',
type: '',
},
timeAggregation: 'count_distinct',
spaceAggregation: 'sum',
functions: [],
filters: {
items: [
{
id: uuidv4(),
key: {
dataType: DataTypes.bool,
id: 'has_error--bool----true',
isColumn: true,
isJSON: false,
key: 'has_error',
type: '',
},
op: '=',
value: 'true',
},
],
op: 'AND',
},
disabled: false,
expression: 'A',
disabled: true,
stepInterval: getStepInterval(startTime, endTime),
having: [],
limit: null,
orderBy: [],
groupBy: [
{
dataType: DataTypes.String,
isColumn: false,
isJSON: false,
key: 'celery.hostname',
type: 'tag',
id: 'celery.hostname--string--tag--false',
},
],
legend: '',
reduceTo: 'avg',
},
{
dataSource: 'traces',
queryName: 'B',
aggregateOperator: 'count_distinct',
aggregateAttribute: {
dataType: 'string',
id: 'span_id--string----true',
isColumn: true,
isJSON: false,
key: 'span_id',
type: '',
},
timeAggregation: 'count_distinct',
spaceAggregation: 'sum',
functions: [],
filters: {
items: [],
op: 'AND',
},
expression: 'B',
disabled: true,
stepInterval: getStepInterval(startTime, endTime),
having: [],
limit: null,
orderBy: [],
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'celery.hostname--string--tag--false',
isColumn: false,
isJSON: false,
key: 'celery.hostname',
type: 'tag',
id: 'celery.hostname--string--tag--false',
},
],
legend: '',
reduceTo: 'avg',
},
{
queryName: 'F1',
expression: '(A/B)*100',
disabled: false,
having: [],
legend: '{{celery.hostname}}',
} as any,
limit: 10,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: 'rate',
},
],
yAxisUnit: 'percent',
}),
);
@@ -438,7 +392,7 @@ export const celeryLatencyByWorkerWidgetData = (
): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
title: 'Latency by worker',
title: 'Latency by Worker',
description: 'Represents the latency of tasks by each worker.',
queryData: [
{
@@ -490,7 +444,7 @@ export const celeryActiveTasksWidgetData = (
): Widgets =>
getWidgetQueryBuilder(
getWidgetQuery({
title: 'Active Tasks by worker',
title: 'Tasks/ worker (Active tasks)',
description: 'Represents the number of active tasks.',
queryData: [
{
@@ -533,7 +487,6 @@ export const celeryActiveTasksWidgetData = (
timeAggregation: 'latest',
},
],
yAxisUnit: 'cps',
}),
);
@@ -588,7 +541,7 @@ export const celeryTaskLatencyWidgetData = (
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: type || 'p99',
timeAggregation: 'p99',
},
],
yAxisUnit: 'ns',
@@ -672,7 +625,7 @@ export const celeryRetryTasksTableWidgetData = getWidgetQueryBuilder(
filters: {
items: [
{
id: uuidv4(),
id: '9e09c9ed',
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@@ -741,7 +694,7 @@ export const celeryFailedTasksTableWidgetData = getWidgetQueryBuilder(
filters: {
items: [
{
id: uuidv4(),
id: '2330f906',
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@@ -808,7 +761,7 @@ export const celerySuccessTasksTableWidgetData = getWidgetQueryBuilder(
filters: {
items: [
{
id: uuidv4(),
id: 'ec3df7b7',
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@@ -931,19 +884,33 @@ export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.String,
id: 'span_id--string----true',
isColumn: true,
dataType: DataTypes.EMPTY,
id: '------false',
isColumn: false,
isJSON: false,
key: 'span_id',
key: '',
type: '',
},
aggregateOperator: 'count_distinct',
aggregateOperator: 'count',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
filters: {
items: [],
items: [
{
id: uuidv4(),
key: {
dataType: DataTypes.String,
id: 'celery.task_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'celery.task_name',
type: 'tag',
},
op: '=',
value: 'tasks.tasks.divide',
},
],
op: 'AND',
},
functions: [],
@@ -953,10 +920,10 @@ export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'last',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',
timeAggregation: 'rate',
},
],
}),
@@ -970,14 +937,14 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.String,
id: 'span_id--string----true',
isColumn: true,
dataType: DataTypes.EMPTY,
id: '------false',
isColumn: false,
isJSON: false,
key: 'span_id',
key: '',
type: '',
},
aggregateOperator: 'count_distinct',
aggregateOperator: 'count',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
@@ -1006,10 +973,10 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'last',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',
timeAggregation: 'rate',
},
],
}),
@@ -1023,14 +990,14 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.String,
id: 'span_id--string----true',
isColumn: true,
dataType: DataTypes.EMPTY,
id: '------false',
isColumn: false,
isJSON: false,
key: 'span_id',
key: '',
type: '',
},
aggregateOperator: 'count_distinct',
aggregateOperator: 'count',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
@@ -1059,10 +1026,10 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'last',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',
timeAggregation: 'rate',
},
],
}),
@@ -1076,13 +1043,13 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.String,
id: 'span_id--string----true',
isColumn: true,
key: 'span_id',
dataType: DataTypes.EMPTY,
id: '------false',
isColumn: false,
key: '',
type: '',
},
aggregateOperator: 'count_distinct',
aggregateOperator: 'count',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
@@ -1111,10 +1078,10 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'last',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',
timeAggregation: 'rate',
},
],
}),

View File

@@ -6,11 +6,8 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { ViewMenuAction } from 'container/GridCardLayout/config';
import GridCard from 'container/GridCardLayout/GridCard';
import { Card } from 'container/GridCardLayout/styles';
import { Button } from 'container/MetricsApplication/Tabs/styles';
import { onGraphClickHandler } from 'container/MetricsApplication/Tabs/util';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
@@ -19,13 +16,15 @@ import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail';
import {
applyCeleryFilterOnWidgetData,
createFiltersFromData,
getFiltersFromQueryParams,
} from '../CeleryUtils';
import { useNavigateToTraces } from '../useNavigateToTraces';
import { celeryTaskLatencyWidgetData } from './CeleryTaskGraphUtils';
import {
celeryTaskLatencyWidgetData,
celeryTimeSeriesTablesWidgetData,
} from './CeleryTaskGraphUtils';
interface TabData {
label: string;
@@ -39,8 +38,10 @@ export enum CeleryTaskGraphState {
}
function CeleryTaskLatencyGraph({
onClick,
queryEnabled,
}: {
onClick: (task: CaptureDataProps) => void;
queryEnabled: boolean;
}): JSX.Element {
const history = useHistory();
@@ -105,51 +106,31 @@ function CeleryTaskLatencyGraph({
[celeryTaskLatencyData, selectedFilters],
);
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const [entityData, setEntityData] = useState<{
entity: string;
value: string;
}>();
const handleSetTimeStamp = useCallback((selectTime: number) => {
setSelectedTimeStamp(selectTime);
}, []);
const onGraphClick = useCallback(
(type: string): OnClickPluginOpts['onClick'] => (
xValue,
yValue,
mouseX,
mouseY,
data,
): Promise<void> => {
const [firstDataPoint] = Object.entries(data || {});
const [entity, value] = firstDataPoint;
setEntityData({
entity,
value,
});
return onGraphClickHandler(handleSetTimeStamp)(
xValue,
yValue,
mouseX,
mouseY,
type,
);
const onGraphClick = (
xValue: number,
_yValue: number,
_mouseX: number,
_mouseY: number,
data?: {
[key: string]: string;
},
[handleSetTimeStamp],
);
): void => {
const { start, end } = getStartAndEndTimesInMilliseconds(xValue);
const navigateToTraces = useNavigateToTraces();
// Extract entity and value from data
const [firstDataPoint] = Object.entries(data || {});
const [entity, value] = (firstDataPoint || ([] as unknown)) as [
string,
string,
];
const goToTraces = useCallback(() => {
const { start, end } = getStartAndEndTimesInMilliseconds(selectedTimeStamp);
const filters = createFiltersFromData({
[entityData?.entity as string]: entityData?.value,
onClick?.({
entity,
value,
timeRange: [start, end],
widgetData: celeryTimeSeriesTablesWidgetData(entity, value, 'Task Latency'),
});
navigateToTraces(filters, start, end, true);
}, [entityData, navigateToTraces, selectedTimeStamp]);
};
return (
<Card
@@ -180,62 +161,32 @@ function CeleryTaskLatencyGraph({
</Row>
<div className="celery-task-graph-grid-content">
{graphState === CeleryTaskGraphState.P99 && (
<>
<Button
type="default"
size="small"
id="Celery_p99_latency_button"
onClick={goToTraces}
>
View Traces
</Button>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p99_latency')}
isQueryEnabled={queryEnabled}
/>
</>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick}
isQueryEnabled={queryEnabled}
/>
)}
{graphState === CeleryTaskGraphState.P95 && (
<>
<Button
type="default"
size="small"
id="Celery_p95_latency_button"
onClick={goToTraces}
>
View Traces
</Button>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p95_latency')}
isQueryEnabled={queryEnabled}
/>
</>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick}
isQueryEnabled={queryEnabled}
/>
)}
{graphState === CeleryTaskGraphState.P90 && (
<>
<Button
type="default"
size="small"
id="Celery_p90_latency_button"
onClick={goToTraces}
>
View Traces
</Button>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p90_latency')}
isQueryEnabled={queryEnabled}
/>
</>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick}
isQueryEnabled={queryEnabled}
/>
)}
</div>
</Card>

View File

@@ -2,14 +2,8 @@
import './CeleryTaskGraph.style.scss';
import { Col, Row } from 'antd';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';
import { Dispatch, SetStateAction, useMemo } from 'react';
import { Dispatch, SetStateAction } from 'react';
import {
applyCeleryFilterOnWidgetData,
getFiltersFromQueryParams,
} from '../CeleryUtils';
import {
celeryAllStateCountWidgetData,
celeryFailedStateCountWidgetData,
@@ -48,29 +42,16 @@ function CeleryTaskStateGraphConfig({
setBarState(key as CeleryTaskState);
};
const urlQuery = useUrlQuery();
const selectedFilters = useMemo(
() =>
getFiltersFromQueryParams(
QueryParams.taskName,
urlQuery,
'celery.task_name',
),
[urlQuery],
const { values, isLoading, isError } = useGetValueFromWidget(
[
celeryAllStateCountWidgetData,
celeryFailedStateCountWidgetData,
celeryRetryStateCountWidgetData,
celerySuccessStateCountWidgetData,
],
['celery-task-states'],
);
const widgetData = [
celeryAllStateCountWidgetData,
celeryFailedStateCountWidgetData,
celeryRetryStateCountWidgetData,
celerySuccessStateCountWidgetData,
].map((data) => applyCeleryFilterOnWidgetData(selectedFilters || [], data));
const { values, isLoading, isError } = useGetValueFromWidget(widgetData, [
'celery-task-states',
]);
return (
<Row className="celery-task-states">
{tabs.map((tab, index) => (
@@ -85,13 +66,7 @@ function CeleryTaskStateGraphConfig({
<div className="celery-task-states__label-wrapper">
<div className="celery-task-states__label">{tab.label}</div>
<div className="celery-task-states__value">
{isLoading
? '-'
: isError
? '-'
: Number.isNaN(values[index])
? '-'
: Math.round(Number(values[index]))}
{isLoading ? '-' : isError ? '-' : values[index]}
</div>
</div>
{tab.key === barState && <div className="celery-task-states__indicator" />}

View File

@@ -90,40 +90,3 @@ export const paths = (
return renderer(u, seriesIdx, idx0, idx1, extendGap, buildClip);
};
export const createFiltersFromData = (
data: Record<string, any>,
): Array<{
id: string;
key: {
key: string;
dataType: DataTypes;
type: string;
isColumn: boolean;
isJSON: boolean;
id: string;
};
op: string;
value: string;
}> => {
const excludeKeys = ['A', 'A_without_unit'];
return (
Object.entries(data)
.filter(([key]) => !excludeKeys.includes(key))
// eslint-disable-next-line sonarjs/no-identical-functions
.map(([key, value]) => ({
id: uuidv4(),
key: {
key,
dataType: DataTypes.String,
type: 'tag',
isColumn: false,
isJSON: false,
id: `${key}--string--tag--false`,
},
op: '=',
value: value.toString(),
}))
);
};

View File

@@ -1,70 +0,0 @@
import { Color } from '@signozhq/design-tokens';
import { themeColors } from 'constants/theme';
import getLabelName from 'lib/getLabelName';
import { drawStyles } from 'lib/uPlotLib/utils/constants';
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
import { QueryData } from 'types/api/widgets/getQuery';
import { paths } from './CeleryUtils';
interface UseGetGraphCustomSeriesProps {
isDarkMode: boolean;
drawStyle?: typeof drawStyles[keyof typeof drawStyles];
colorMapping?: Record<string, string>;
}
const defaultColorMapping = {
SUCCESS: Color.BG_FOREST_500,
FAILURE: Color.BG_CHERRY_500,
RETRY: Color.BG_AMBER_400,
};
export const useGetGraphCustomSeries = ({
isDarkMode,
drawStyle = 'bars',
colorMapping = defaultColorMapping,
}: UseGetGraphCustomSeriesProps): {
getCustomSeries: (data: QueryData[]) => uPlot.Series[];
} => {
const getGraphSeries = (color: string, label: string): any => ({
drawStyle,
paths,
lineInterpolation: 'spline',
show: true,
label,
fill: `${color}90`,
stroke: color,
width: 2,
spanGaps: true,
points: {
size: 5,
show: false,
stroke: color,
},
});
const getCustomSeries = (data: QueryData[]): uPlot.Series[] => {
const configurations: uPlot.Series[] = [
{ label: 'Timestamp', stroke: 'purple' },
];
for (let i = 0; i < data.length; i += 1) {
const { metric = {}, queryName = '', legend = '' } = data[i] || {};
const label = getLabelName(metric, queryName || '', legend || '');
// Check if label exists in colorMapping
const color =
colorMapping[label] ||
generateColor(
label,
isDarkMode ? themeColors.chartcolors : themeColors.lightModeColor,
);
const series = getGraphSeries(color, label);
configurations.push(series);
}
return configurations;
};
return { getCustomSeries };
};

View File

@@ -12,7 +12,6 @@ export function useNavigateToTraces(): (
filters: TagFilterItem[],
startTime?: number,
endTime?: number,
sameTab?: boolean,
) => void {
const { currentQuery } = useQueryBuilder();
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
@@ -39,12 +38,7 @@ export function useNavigateToTraces(): (
);
return useCallback(
(
filters: TagFilterItem[],
startTime?: number,
endTime?: number,
sameTab?: boolean,
): void => {
(filters: TagFilterItem[], startTime?: number, endTime?: number): void => {
const urlParams = new URLSearchParams();
if (startTime && endTime) {
urlParams.set(QueryParams.startTime, startTime.toString());
@@ -64,7 +58,7 @@ export function useNavigateToTraces(): (
QueryParams.compositeQuery
}=${JSONCompositeQuery}`;
window.open(newTraceExplorerPath, sameTab ? '_self' : '_blank');
window.open(newTraceExplorerPath, '_blank');
},
[minTime, maxTime, prepareQuery],
);

View File

@@ -133,7 +133,7 @@
.go-to-docs {
display: flex;
flex-direction: column;
gap: 44px;
gap: 64px;
&__container {
display: flex;
flex-direction: column;

View File

@@ -1,117 +0,0 @@
.signoz-modal {
.ant-modal-content {
padding: 0;
background: var(--bg-ink-400);
border: 1px solid var(--bg-slate-500);
}
.ant-modal-header {
background: var(--bg-ink-400);
border-bottom: none;
padding: 12px 24px 8px;
border-bottom: 1px solid var(--bg-slate-500);
margin-bottom: 0;
}
.ant-modal-close {
top: 15px;
height: 14px;
width: 14px;
.ant-modal-close-x {
font-size: 12px;
}
}
.ant-modal-title {
color: var(--bg-vanilla-100);
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
.ant-modal-body {
padding: 16px 24px 24px;
}
.ant-modal-footer {
padding: 24px;
display: flex;
justify-content: end;
gap: 12px;
}
.ant-typography {
color: var(--bg-vanilla-100);
}
.ant-select {
border-radius: 2px !important;
&-selector {
background: var(--bg-ink-300) !important;
border: 1px solid var(--bg-slate-400) !important;
border-radius: 2px !important;
}
}
.ant-select-dropdown {
background: var(--bg-ink-400);
border: 1px solid var(--bg-ink-300);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
}
.ant-select-item {
color: var(--bg-vanilla-100);
&-option-selected {
background: var(--bg-ink-300);
}
}
}
.lightMode {
.signoz-modal {
.ant-modal-content {
background: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-300);
}
.ant-modal-header {
background: var(--bg-vanilla-100);
border-bottom-color: var(--bg-vanilla-300);
}
.ant-modal-title {
color: var(--bg-ink-500);
}
.ant-typography {
color: var(--bg-ink-500);
}
.ant-select {
&-selector {
background: var(--bg-vanilla-100) !important;
border-color: var(--bg-vanilla-300) !important;
}
}
.ant-select-dropdown {
background: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-300);
}
.ant-select-item {
color: var(--bg-ink-400);
&-option-selected {
background: var(--bg-vanilla-300);
color: var(--bg-ink-500);
}
&-option-active {
background: var(--bg-vanilla-200);
}
}
}
}

View File

@@ -1,25 +0,0 @@
import './SignozModal.style.scss';
import { Modal, ModalProps } from 'antd';
function SignozModal({
children,
rootClassName = '',
...rest
}: ModalProps): JSX.Element {
return (
<Modal
centered
width={672}
cancelText="Close"
rootClassName={`signoz-modal ${rootClassName}`}
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
>
{children}
</Modal>
);
}
export default SignozModal;

View File

@@ -23,5 +23,4 @@ export enum FeatureKeys {
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2',
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
AWS_INTEGRATION = 'AWS_INTEGRATION',
}

View File

@@ -32,15 +32,4 @@ export const REACT_QUERY_KEY = {
GET_JOB_LIST: 'GET_JOB_LIST',
GET_DAEMONSET_LIST: 'GET_DAEMONSET_LIST,',
GET_VOLUME_LIST: 'GET_VOLUME_LIST',
// AWS Integration Query Keys
AWS_ACCOUNTS: 'AWS_ACCOUNTS',
AWS_SERVICES: 'AWS_SERVICES',
AWS_SERVICE_DETAILS: 'AWS_SERVICE_DETAILS',
AWS_ACCOUNT_STATUS: 'AWS_ACCOUNT_STATUS',
AWS_UPDATE_ACCOUNT_CONFIG: 'AWS_UPDATE_ACCOUNT_CONFIG',
AWS_UPDATE_SERVICE_CONFIG: 'AWS_UPDATE_SERVICE_CONFIG',
AWS_GENERATE_CONNECTION_URL: 'AWS_GENERATE_CONNECTION_URL',
AWS_GET_CONNECTION_PARAMS: 'AWS_GET_CONNECTION_PARAMS',
GET_ATTRIBUTE_VALUES: 'GET_ATTRIBUTE_VALUES',
};

View File

@@ -65,9 +65,6 @@ const ROUTES = {
INFRASTRUCTURE_MONITORING_KUBERNETES: '/infrastructure-monitoring/kubernetes',
MESSAGING_QUEUES_CELERY_TASK: '/messaging-queues/celery-task',
MESSAGING_QUEUES_OVERVIEW: '/messaging-queues/overview',
METRICS_EXPLORER: '/metrics-explorer/summary',
METRICS_EXPLORER_EXPLORER: '/metrics-explorer/explorer',
METRICS_EXPLORER_VIEWS: '/metrics-explorer/views',
} as const;
export default ROUTES;

View File

@@ -22,7 +22,6 @@ import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { isNull } from 'lodash-es';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { INTEGRATION_TYPES } from 'pages/Integrations/utils';
import { useAppContext } from 'providers/App/App';
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { Helmet } from 'react-helmet-async';
@@ -250,7 +249,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
if (
!isFetchingActiveLicenseV3 &&
!isNull(activeLicenseV3) &&
activeLicenseV3?.event_queue?.event === LicenseEvent.DEFAULT
activeLicenseV3?.event_queue?.event === LicenseEvent.FAILED_PAYMENT
) {
setShowPaymentFailedWarning(true);
}
@@ -293,11 +292,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
routeKey === 'MESSAGING_QUEUES_CELERY_TASK' ||
routeKey === 'MESSAGING_QUEUES_OVERVIEW';
const isCloudIntegrationPage = (): boolean =>
routeKey === 'INTEGRATIONS' &&
new URLSearchParams(window.location.search).get('integration') ===
INTEGRATION_TYPES.AWS_INTEGRATION;
const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD';
const isAlertHistory = (): boolean => routeKey === 'ALERT_HISTORY';
const isAlertOverview = (): boolean => routeKey === 'ALERT_OVERVIEW';
@@ -432,7 +426,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
isAlertHistory() ||
isAlertOverview() ||
isMessagingQueues() ||
isCloudIntegrationPage() ||
isInfraMonitoring()
? 0
: '0 1rem',

View File

@@ -1,15 +0,0 @@
import Header from './Header/Header';
import HeroSection from './HeroSection/HeroSection';
import ServicesTabs from './ServicesSection/ServicesTabs';
function CloudIntegrationPage(): JSX.Element {
return (
<div>
<Header />
<HeroSection />
<ServicesTabs />
</div>
);
}
export default CloudIntegrationPage;

View File

@@ -1,65 +0,0 @@
.cloud-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 18px;
border-bottom: 1px solid var(--bg-slate-400);
&__navigation {
display: flex;
align-items: center;
padding: 6px 0px 6px;
}
&__breadcrumb-link {
display: flex;
align-items: center;
gap: 8px;
}
&__breadcrumb-title {
color: var(--bg-vanilla-400);
font-size: 14px;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
&__help {
display: flex;
align-items: center;
justify-content: center;
padding: 6px;
gap: 6px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
border-radius: 2px;
color: var(--bg-vanilla-400);
font-size: 12px;
line-height: 10px; /* 83.333% */
letter-spacing: 0.12px;
width: 113px;
height: 32px;
cursor: pointer;
}
}
.lightMode {
.cloud-header {
border-bottom: 1px solid var(--bg-slate-300);
&__breadcrumb-title {
color: var(--bg-ink-400);
}
&__help {
border-color: var(--bg-slate-300);
background: var(--bg-vanilla-100);
color: var(--bg-ink-400);
&:hover {
border-color: var(--bg-slate-400);
color: var(--bg-ink-500);
}
}
}
}

View File

@@ -1,45 +0,0 @@
import './Header.styles.scss';
import Breadcrumb from 'antd/es/breadcrumb';
import ROUTES from 'constants/routes';
import { Blocks, LifeBuoy } from 'lucide-react';
import { Link } from 'react-router-dom';
function Header(): JSX.Element {
return (
<div className="cloud-header">
<div className="cloud-header__navigation">
<Breadcrumb
className="cloud-header__breadcrumb"
items={[
{
title: (
<Link to={ROUTES.INTEGRATIONS}>
<span className="cloud-header__breadcrumb-link">
<Blocks size={16} color="var(--bg-vanilla-400)" />
<span className="cloud-header__breadcrumb-title">Integrations</span>
</span>
</Link>
),
},
{
title: (
<div className="cloud-header__breadcrumb-title">
Amazon Web Services
</div>
),
},
]}
/>
</div>
<div className="cloud-header__actions">
<button className="cloud-header__help" type="button">
<LifeBuoy size={12} />
Get Help
</button>
</div>
</div>
);
}
export default Header;

View File

@@ -1,72 +0,0 @@
.hero-section {
height: 308px;
padding: 26px 16px;
display: flex;
gap: 24px;
position: relative;
overflow: hidden;
background-position: right;
background-size: cover;
background-repeat: no-repeat;
border-bottom: 1px solid var(--bg-slate-500);
&__icon {
height: fit-content;
background-color: var(--bg-ink-400);
padding: 12px;
border: 1px solid var(--bg-ink-300);
border-radius: 6px;
width: 60px;
height: 60px;
display: flex;
align-items: center;
img {
width: 100%;
}
}
&__details {
display: flex;
flex-direction: column;
gap: 12px;
.title {
color: var(--bg-vanilla-100);
font-size: 24px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.12px;
}
.description {
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 400;
line-height: 18px;
}
}
}
.lightMode {
.hero-section {
border-bottom: 1px solid var(--bg-vanilla-300);
&__icon {
background-color: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-300);
}
&__details {
.title {
color: var(--bg-ink-500);
}
.description {
color: var(--bg-ink-400);
}
}
}
}
.integrations-select {
height: 44px;
}

View File

@@ -1,34 +0,0 @@
import './HeroSection.style.scss';
import { useIsDarkMode } from 'hooks/useDarkMode';
import AccountActions from './components/AccountActions';
function HeroSection(): JSX.Element {
const isDarkMode = useIsDarkMode();
return (
<div
className="hero-section"
style={
isDarkMode
? {
backgroundImage: `url('/Images/integrations-hero-bg.png')`,
}
: {}
}
>
<div className="hero-section__icon">
<img src="/Logos/aws-dark.svg" alt="aws-logo" />
</div>
<div className="hero-section__details">
<div className="title">Amazon Web Services</div>
<div className="description">
One-click setup for AWS monitoring with SigNoz
</div>
<AccountActions />
</div>
</div>
);
}
export default HeroSection;

View File

@@ -1,142 +0,0 @@
.hero-section {
&__actions {
margin-top: 12px;
&-with-account {
display: flex;
flex-direction: column;
gap: 10px;
}
}
&__input-skeleton {
width: 300px;
margin-bottom: 16px;
}
&__new-account-button-skeleton {
width: 180px;
margin-right: 8px;
}
&__account-settings-button-skeleton {
width: 140px;
}
&__action-buttons {
display: flex;
align-items: center;
gap: 8px;
}
&__action-button {
font-family: 'Inter';
border-radius: 2px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
line-height: 16px;
padding: 8px 17px;
&.primary {
background: var(--bg-robin-500);
border: none;
color: var(--bg-vanilla-100);
}
&.secondary {
display: flex;
align-items: center;
border: 1px solid var(--bg-ink-300);
color: var(--bg-vanilla-100);
border-radius: 2px;
background: var(--bg-slate-400);
box-shadow: none;
}
}
}
.cloud-account-selector {
border-radius: 2px;
border: 1px solid var(--bg-ink-300);
background: linear-gradient(
139deg,
rgba(18, 19, 23, 0.8) 0%,
rgba(18, 19, 23, 0.9) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
.ant-select-selector {
border-color: var(--bg-slate-400) !important;
background: var(--bg-ink-300) !important;
padding: 6px 8px !important;
}
.ant-select-selection-item {
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 400;
line-height: 16px;
}
.account-option-item {
display: flex;
align-items: center;
justify-content: space-between;
&__selected {
display: flex;
align-items: center;
justify-content: center;
height: 14px;
width: 14px;
background-color: rgba(192, 193, 195, 0.2); /* #C0C1C3 with 0.2 opacity */
border-radius: 2px;
}
}
}
.lightMode {
.hero-section__action-button {
&.primary {
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
}
&.secondary {
border-color: var(--bg-vanilla-300);
color: var(--bg-ink-400);
background: var(--bg-vanilla-100);
&:hover {
border-color: var(--bg-vanilla-400);
color: var(--bg-ink-500);
}
}
}
.cloud-account-selector {
background: var(--bg-vanilla-100);
.ant-select-selector {
background: var(--bg-vanilla-100) !important;
border-color: var(--bg-vanilla-400) !important;
}
.ant-select-item-option-active {
background: var(--bg-vanilla-400) !important;
}
.ant-select-selection-item {
color: var(--bg-ink-400);
}
&:hover {
.ant-select-selector {
border-color: var(--bg-vanilla-400) !important;
}
}
}
.account-option-item {
color: var(--bg-ink-400);
&__selected {
background: var(--bg-robin-500);
}
}
}

View File

@@ -1,219 +0,0 @@
import './AccountActions.style.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, Select, Skeleton } from 'antd';
import { SelectProps } from 'antd/lib';
import { useAwsAccounts } from 'hooks/integrations/aws/useAwsAccounts';
import useUrlQuery from 'hooks/useUrlQuery';
import { Check, ChevronDown } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import { CloudAccount } from '../../ServicesSection/types';
import AccountSettingsModal from './AccountSettingsModal';
import CloudAccountSetupModal from './CloudAccountSetupModal';
interface AccountOptionItemProps {
label: React.ReactNode;
isSelected: boolean;
}
function AccountOptionItem({
label,
isSelected,
}: AccountOptionItemProps): JSX.Element {
return (
<div className="account-option-item">
{label}
{isSelected && (
<div className="account-option-item__selected">
<Check size={12} color={Color.BG_VANILLA_100} />
</div>
)}
</div>
);
}
function renderOption(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
option: any,
activeAccountId: string | undefined,
): JSX.Element {
return (
<AccountOptionItem
label={option.label}
isSelected={option.value === activeAccountId}
/>
);
}
const getAccountById = (
accounts: CloudAccount[],
accountId: string,
): CloudAccount | null =>
accounts.find((account) => account.cloud_account_id === accountId) || null;
function AccountActionsRenderer({
accounts,
isLoading,
activeAccount,
selectOptions,
onAccountChange,
onIntegrationModalOpen,
onAccountSettingsModalOpen,
}: {
accounts: CloudAccount[] | undefined;
isLoading: boolean;
activeAccount: CloudAccount | null;
selectOptions: SelectProps['options'];
onAccountChange: (value: string) => void;
onIntegrationModalOpen: () => void;
onAccountSettingsModalOpen: () => void;
}): JSX.Element {
if (isLoading) {
return (
<div className="hero-section__actions-with-account">
<Skeleton.Input
active
size="large"
block
className="hero-section__input-skeleton"
/>
<div className="hero-section__action-buttons">
<Skeleton.Button
active
size="large"
className="hero-section__new-account-button-skeleton"
/>
<Skeleton.Button
active
size="large"
className="hero-section__account-settings-button-skeleton"
/>
</div>
</div>
);
}
if (accounts?.length) {
return (
<div className="hero-section__actions-with-account">
<Select
value={`Account: ${activeAccount?.cloud_account_id}`}
options={selectOptions}
rootClassName="cloud-account-selector"
placeholder="Select AWS Account"
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
optionRender={(option): JSX.Element =>
renderOption(option, activeAccount?.cloud_account_id)
}
onChange={onAccountChange}
/>
<div className="hero-section__action-buttons">
<Button
type="primary"
className="hero-section__action-button primary"
onClick={onIntegrationModalOpen}
>
Add New AWS Account
</Button>
<Button
type="default"
className="hero-section__action-button secondary"
onClick={onAccountSettingsModalOpen}
>
Account Settings
</Button>
</div>
</div>
);
}
return (
<Button
className="hero-section__action-button primary"
onClick={onIntegrationModalOpen}
>
Integrate Now
</Button>
);
}
function AccountActions(): JSX.Element {
const urlQuery = useUrlQuery();
const navigate = useNavigate();
const { data: accounts, isLoading } = useAwsAccounts();
const initialAccount = useMemo(
() =>
accounts?.length
? getAccountById(accounts, urlQuery.get('cloudAccountId') || '') ||
accounts[0]
: null,
[accounts, urlQuery],
);
const [activeAccount, setActiveAccount] = useState<CloudAccount | null>(
initialAccount,
);
// Update state when initial value changes
useEffect(() => {
if (initialAccount !== null) {
setActiveAccount(initialAccount);
urlQuery.set('cloudAccountId', initialAccount.cloud_account_id);
navigate({ search: urlQuery.toString() });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialAccount]);
const [isIntegrationModalOpen, setIsIntegrationModalOpen] = useState(false);
const [isAccountSettingsModalOpen, setIsAccountSettingsModalOpen] = useState(
false,
);
const selectOptions: SelectProps['options'] = useMemo(
() =>
accounts?.length
? accounts.map((account) => ({
value: account.cloud_account_id,
label: account.cloud_account_id,
}))
: [],
[accounts],
);
return (
<div className="hero-section__actions">
<AccountActionsRenderer
accounts={accounts}
isLoading={isLoading}
activeAccount={activeAccount}
selectOptions={selectOptions}
onAccountChange={(value): void => {
if (accounts) {
setActiveAccount(getAccountById(accounts, value));
urlQuery.set('cloudAccountId', value);
navigate({ search: urlQuery.toString() });
}
}}
onIntegrationModalOpen={(): void => setIsIntegrationModalOpen(true)}
onAccountSettingsModalOpen={(): void => setIsAccountSettingsModalOpen(true)}
/>
{isIntegrationModalOpen && (
<CloudAccountSetupModal
onClose={(): void => setIsIntegrationModalOpen(false)}
/>
)}
{isAccountSettingsModalOpen && (
<AccountSettingsModal
onClose={(): void => setIsAccountSettingsModalOpen(false)}
account={activeAccount as CloudAccount}
setActiveAccount={setActiveAccount}
/>
)}
</div>
);
}
export default AccountActions;

View File

@@ -1,189 +0,0 @@
.account-settings-modal {
&__title-account-id {
color: var(--bg-vanilla-100);
font-family: 'Geist Mono';
font-size: 14px;
font-weight: 600;
line-height: 20px;
}
&__body {
display: flex;
flex-direction: column;
gap: 17px;
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
padding: 14px;
&-account-info {
&-connected-account-details {
&-title {
color: var(--bg-vanilla-100);
font-size: 14px;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
&-account-id {
color: var(--bg-vanilla-400);
font-size: 12px;
line-height: 18px;
letter-spacing: -0.06px;
&-account-id {
font-family: 'Geist Mono';
font-size: 12px;
font-weight: 700;
line-height: 18px;
letter-spacing: -0.06px;
}
}
}
}
&-regions-switch {
display: flex;
flex-direction: column;
gap: 10px;
&-title {
color: var(--bg-vanilla-100);
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.07px;
}
&-switch {
display: flex;
align-items: center;
gap: 10px;
&-label {
color: var(--bg-vanilla-400);
background-color: transparent;
border: none;
font-family: Inter;
font-size: 12px;
font-weight: 400;
line-height: 18px;
letter-spacing: -0.005em;
cursor: pointer;
}
}
}
&-regions-select {
margin-top: 8px;
}
}
&__footer {
&-close-button,
&-save-button {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 12px;
font-weight: 500;
padding-left: 16px;
padding-right: 16px;
}
&-close-button {
border-radius: 2px;
background: var(--bg-slate-400);
border: none;
}
&-save-button {
&:disabled {
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
opacity: 0.6;
border: none;
}
border-radius: 2px;
margin: 0 !important;
}
}
.ant-modal-body {
padding-bottom: 0 !important;
}
.ant-modal-footer {
margin: 0;
padding: 24px 24px 12px;
}
.integration-detail-content {
margin: 0;
}
}
.lightMode {
.account-settings-modal {
&__title-account-id {
color: var(--bg-ink-500);
}
&__body {
border-color: var(--bg-vanilla-300);
&-account-info {
&-connected-account-details {
&-title {
color: var(--bg-ink-500);
}
&-account-id {
color: var(--bg-ink-400);
&-account-id {
color: var(--bg-ink-500);
}
}
}
}
&-regions-switch {
&-title {
color: var(--bg-ink-500);
}
&-switch {
&-label {
color: var(--bg-ink-400);
&:hover {
color: var(--bg-ink-500);
}
}
}
}
}
&__footer {
&-close-button,
&-save-button {
color: var(--bg-vanilla-100);
}
&-close-button {
background: var(--bg-vanilla-100);
border: 1px solid var(--bg-vanilla-300);
color: var(--bg-ink-400);
&:hover {
border-color: var(--bg-vanilla-400);
color: var(--bg-ink-500);
}
}
&-save-button {
// Keep primary button same as dark mode
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
&:disabled {
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
opacity: 0.6;
}
&:not(:disabled):hover {
background: var(--bg-robin-400);
}
}
}
}
}

View File

@@ -1,188 +0,0 @@
import './AccountSettingsModal.style.scss';
import { Form, Select, Switch } from 'antd';
import SignozModal from 'components/SignozModal/SignozModal';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import {
getRegionPreviewText,
useAccountSettingsModal,
} from 'hooks/integrations/aws/useAccountSettingsModal';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { Dispatch, SetStateAction, useCallback } from 'react';
import { useQueryClient } from 'react-query';
import { CloudAccount } from '../../ServicesSection/types';
import { RegionSelector } from './RegionSelector';
import RemoveIntegrationAccount from './RemoveIntegrationAccount';
interface AccountSettingsModalProps {
onClose: () => void;
account: CloudAccount;
setActiveAccount: Dispatch<SetStateAction<CloudAccount | null>>;
}
function AccountSettingsModal({
onClose,
account,
setActiveAccount,
}: AccountSettingsModalProps): JSX.Element {
const {
form,
isLoading,
selectedRegions,
includeAllRegions,
isRegionSelectOpen,
isSaveDisabled,
setSelectedRegions,
setIncludeAllRegions,
setIsRegionSelectOpen,
handleIncludeAllRegionsChange,
handleSubmit,
handleClose,
} = useAccountSettingsModal({ onClose, account, setActiveAccount });
const queryClient = useQueryClient();
const urlQuery = useUrlQuery();
const handleRemoveIntegrationAccountSuccess = (): void => {
queryClient.invalidateQueries([REACT_QUERY_KEY.AWS_ACCOUNTS]);
urlQuery.delete('cloudAccountId');
handleClose();
history.replace({ search: urlQuery.toString() });
};
const renderRegionSelector = useCallback(() => {
if (isRegionSelectOpen) {
return (
<RegionSelector
selectedRegions={selectedRegions}
setSelectedRegions={setSelectedRegions}
setIncludeAllRegions={setIncludeAllRegions}
/>
);
}
return (
<>
<div className="account-settings-modal__body-regions-switch-switch ">
<Switch
checked={includeAllRegions}
onChange={handleIncludeAllRegionsChange}
/>
<button
className="account-settings-modal__body-regions-switch-switch-label"
type="button"
onClick={(): void => handleIncludeAllRegionsChange(!includeAllRegions)}
>
Include all regions
</button>
</div>
<Select
suffixIcon={null}
placeholder="Select Region(s)"
className="cloud-account-setup-form__select account-settings-modal__body-regions-select integrations-select"
onClick={(): void => setIsRegionSelectOpen(true)}
mode="multiple"
maxTagCount={3}
value={getRegionPreviewText(selectedRegions)}
open={false}
/>
</>
);
}, [
isRegionSelectOpen,
selectedRegions,
includeAllRegions,
handleIncludeAllRegionsChange,
setIsRegionSelectOpen,
setSelectedRegions,
setIncludeAllRegions,
]);
const renderAccountDetails = useCallback(
() => (
<div className="account-settings-modal__body-account-info">
<div className="account-settings-modal__body-account-info-connected-account-details">
<div className="account-settings-modal__body-account-info-connected-account-details-title">
Connected Account details
</div>
<div className="account-settings-modal__body-account-info-connected-account-details-account-id">
AWS Account:{' '}
<span className="account-settings-modal__body-account-info-connected-account-details-account-id-account-id">
{account?.id}
</span>
</div>
</div>
</div>
),
[account?.id],
);
const modalTitle = (
<div className="account-settings-modal__title">
Account settings for{' '}
<span className="account-settings-modal__title-account-id">
{account?.id}
</span>
</div>
);
return (
<SignozModal
open
title={modalTitle}
onCancel={handleClose}
onOk={handleSubmit}
okText="Save"
okButtonProps={{
disabled: isSaveDisabled,
className: 'account-settings-modal__footer-save-button',
loading: isLoading,
}}
cancelButtonProps={{
className: 'account-settings-modal__footer-close-button',
}}
width={672}
rootClassName="account-settings-modal"
>
<Form
form={form}
layout="vertical"
initialValues={{
selectedRegions,
includeAllRegions,
}}
>
<div className="account-settings-modal__body">
{renderAccountDetails()}
<Form.Item
name="selectedRegions"
rules={[
{
validator: async (): Promise<void> => {
if (selectedRegions.length === 0) {
throw new Error('Please select at least one region to monitor');
}
},
message: 'Please select at least one region to monitor',
},
]}
>
{renderRegionSelector()}
</Form.Item>
<div className="integration-detail-content">
<RemoveIntegrationAccount
accountId={account?.id}
onRemoveIntegrationAccountSuccess={handleRemoveIntegrationAccountSuccess}
/>
</div>
</div>
</Form>
</SignozModal>
);
}
export default AccountSettingsModal;

View File

@@ -1,53 +0,0 @@
import { Color } from '@signozhq/design-tokens';
import { Alert, Spin } from 'antd';
import { LoaderCircle, TriangleAlert } from 'lucide-react';
import { ModalStateEnum } from '../types';
function AlertMessage({
modalState,
}: {
modalState: ModalStateEnum;
}): JSX.Element | null {
switch (modalState) {
case ModalStateEnum.WAITING:
return (
<Alert
message={
<div className="cloud-account-setup-form__alert-message">
<Spin
indicator={
<LoaderCircle
size={14}
color={Color.BG_AMBER_400}
className="anticon anticon-loading anticon-spin ant-spin-dot"
/>
}
/>
Waiting for connection, retrying in{' '}
<span className="retry-time">10</span> secs...
</div>
}
className="cloud-account-setup-form__alert"
type="warning"
/>
);
case ModalStateEnum.ERROR:
return (
<Alert
message={
<div className="cloud-account-setup-form__alert-message">
<TriangleAlert type="solid" size={15} color={Color.BG_SAKURA_400} />
{`We couldn't establish a connection to your AWS account. Please try again`}
</div>
}
type="error"
className="cloud-account-setup-form__alert"
/>
);
default:
return null;
}
}
export default AlertMessage;

View File

@@ -1,221 +0,0 @@
.cloud-account-setup-modal {
.account-setup-modal-footer {
&__confirm-button {
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
font-size: 12px;
font-weight: 500;
}
&__confirm-selection-count {
font-family: 'Geist Mono';
}
&__close-button {
background: var(--bg-slate-400);
border-radius: 2px;
color: var(--bg-vanilla-100);
font-family: 'Inter';
font-size: 12px;
font-weight: 500;
}
}
.cloud-account-setup-form {
.disabled {
opacity: 0.4;
}
&,
&__content {
display: flex;
flex-direction: column;
gap: 38px;
}
&__alert {
&.ant-alert {
padding: 12px;
border-radius: 6px;
font-size: 14px;
line-height: 22px; /* 157.143% */
letter-spacing: -0.07px;
}
&.ant-alert-error {
color: var(--bg-sakura-400);
border: 1px solid rgba(242, 71, 105, 0.1);
background: rgba(242, 71, 105, 0.1);
}
&.ant-alert-warning {
color: var(--bg-amber-400);
border: 1px solid rgba(255, 205, 86, 0.1);
background: rgba(255, 205, 86, 0.1);
}
&-message {
display: flex;
align-items: center;
gap: 8px;
.retry-time {
font-family: 'Geist Mono';
font-size: 14px;
font-weight: 600;
line-height: 22px;
letter-spacing: -0.07px;
}
}
}
&__form-group {
display: flex;
flex-direction: column;
gap: 12px;
}
&__title {
color: var(--bg-vanilla-100);
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.07px;
}
&__description {
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 400;
line-height: 18px;
letter-spacing: -0.06px;
}
&__select {
.ant-select-selection-item {
color: var(--bg-vanilla-100);
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
}
}
&__form-item {
margin: 0;
}
&__include-all-regions-switch {
display: flex;
align-items: center;
gap: 10px;
color: var(--bg-vanilla-400);
font-size: 12px;
line-height: 18px;
letter-spacing: -0.06px;
margin-bottom: 12px;
&-label {
background-color: transparent;
border: none;
color: var(--bg-vanilla-400);
font-size: 12px;
line-height: 18px;
letter-spacing: -0.06px;
cursor: pointer;
}
}
&__note {
padding: 12px;
color: var(--bg-robin-400);
font-size: 12px;
line-height: 22px;
letter-spacing: -0.06px;
border-radius: 4px;
border: 1px solid rgba(78, 116, 248, 0.1);
background: rgba(78, 116, 248, 0.1);
}
&__submit-button {
border-radius: 2px;
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
font-size: 14px;
font-weight: 500;
line-height: 20px;
&-content {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
&:disabled {
opacity: 0.4;
}
}
}
}
.lightMode {
.cloud-account-setup-modal {
.account-setup-modal-footer {
&__confirm-button {
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
}
&__close-button {
background: var(--bg-vanilla-100);
border: 1px solid var(--bg-vanilla-300);
color: var(--bg-ink-400);
&:hover {
border-color: var(--bg-vanilla-400);
color: var(--bg-ink-500);
}
}
}
.cloud-account-setup-form {
&__title {
color: var(--bg-ink-500);
}
&__description {
color: var(--bg-ink-400);
}
&__select {
.ant-select-selection-item {
color: var(--bg-ink-500);
}
}
&__include-all-regions-switch {
color: var(--bg-ink-400);
&-label {
color: var(--bg-ink-400);
&:hover {
color: var(--bg-ink-500);
}
}
}
&__note {
color: var(--bg-robin-500);
border: 1px solid rgba(78, 116, 248, 0.2);
background: rgba(78, 116, 248, 0.1);
}
&__submit-button {
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
}
&__alert {
&.ant-alert-error {
color: var(--bg-cherry-500);
border: 1px solid rgba(242, 71, 105, 0.2);
background: rgba(242, 71, 105, 0.1);
}
&.ant-alert-warning {
color: var(--bg-amber-500);
border: 1px solid rgba(255, 205, 86, 0.2);
background: rgba(255, 205, 86, 0.1);
}
&-message {
.retry-time {
color: var(--bg-ink-500);
}
}
}
}
}
}

View File

@@ -1,183 +0,0 @@
import './CloudAccountSetupModal.style.scss';
import { Color } from '@signozhq/design-tokens';
import SignozModal from 'components/SignozModal/SignozModal';
import { useIntegrationModal } from 'hooks/integrations/aws/useIntegrationModal';
import { SquareArrowOutUpRight } from 'lucide-react';
import { useCallback } from 'react';
import {
ActiveViewEnum,
IntegrationModalProps,
ModalStateEnum,
} from '../types';
import { RegionForm } from './RegionForm';
import { RegionSelector } from './RegionSelector';
import { SuccessView } from './SuccessView';
function CloudAccountSetupModal({
onClose,
}: IntegrationModalProps): JSX.Element {
const {
form,
modalState,
setModalState,
isLoading,
activeView,
selectedRegions,
includeAllRegions,
isGeneratingUrl,
setSelectedRegions,
setIncludeAllRegions,
handleIncludeAllRegionsChange,
handleRegionSelect,
handleSubmit,
handleClose,
setActiveView,
allRegions,
accountId,
selectedDeploymentRegion,
handleRegionChange,
connectionParams,
isConnectionParamsLoading,
} = useIntegrationModal({ onClose });
const renderContent = useCallback(() => {
if (modalState === ModalStateEnum.SUCCESS) {
return <SuccessView />;
}
if (activeView === ActiveViewEnum.SELECT_REGIONS) {
return (
<RegionSelector
selectedRegions={selectedRegions}
setSelectedRegions={setSelectedRegions}
setIncludeAllRegions={setIncludeAllRegions}
/>
);
}
return (
<RegionForm
form={form}
modalState={modalState}
setModalState={setModalState}
selectedRegions={selectedRegions}
includeAllRegions={includeAllRegions}
onIncludeAllRegionsChange={handleIncludeAllRegionsChange}
onRegionSelect={handleRegionSelect}
onSubmit={handleSubmit}
accountId={accountId}
selectedDeploymentRegion={selectedDeploymentRegion}
handleRegionChange={handleRegionChange}
connectionParams={connectionParams}
isConnectionParamsLoading={isConnectionParamsLoading}
/>
);
}, [
modalState,
activeView,
form,
setModalState,
selectedRegions,
includeAllRegions,
handleIncludeAllRegionsChange,
handleRegionSelect,
handleSubmit,
accountId,
selectedDeploymentRegion,
handleRegionChange,
connectionParams,
isConnectionParamsLoading,
setSelectedRegions,
setIncludeAllRegions,
]);
const getSelectedRegionsCount = useCallback(
(): number =>
selectedRegions.includes('all') ? allRegions.length : selectedRegions.length,
[selectedRegions, allRegions],
);
const getModalConfig = useCallback(() => {
// Handle success state first
if (modalState === ModalStateEnum.SUCCESS) {
return {
title: 'AWS Webservice Integration',
okText: (
<div className="cloud-account-setup-success-view__footer-button">
Continue
</div>
),
block: true,
onOk: handleClose,
cancelButtonProps: { style: { display: 'none' } },
disabled: false,
};
}
// Handle other views
const viewConfigs = {
[ActiveViewEnum.FORM]: {
title: 'Add AWS Account',
okText: (
<div className="cloud-account-setup-form__submit-button-content">
Launch Cloud Formation Template{' '}
<SquareArrowOutUpRight size={17} color={Color.BG_VANILLA_100} />
</div>
),
onOk: handleSubmit,
disabled:
isLoading || isGeneratingUrl || modalState === ModalStateEnum.WAITING,
cancelButtonProps: { style: { display: 'none' } },
},
[ActiveViewEnum.SELECT_REGIONS]: {
title: 'Which regions do you want to monitor?',
okText: `Confirm Selection (${getSelectedRegionsCount()})`,
onOk: (): void => setActiveView(ActiveViewEnum.FORM),
isLoading: isLoading || isGeneratingUrl,
cancelButtonProps: { style: { display: 'block' } },
disabled: false,
},
};
return viewConfigs[activeView];
}, [
modalState,
handleSubmit,
getSelectedRegionsCount,
isLoading,
isGeneratingUrl,
activeView,
handleClose,
setActiveView,
]);
const modalConfig = getModalConfig();
return (
<SignozModal
open
className="cloud-account-setup-modal"
title={modalConfig.title}
onCancel={handleClose}
onOk={modalConfig.onOk}
okText={modalConfig.okText}
okButtonProps={{
loading: isLoading,
disabled: selectedRegions.length === 0 || modalConfig.disabled,
className:
activeView === ActiveViewEnum.FORM
? 'cloud-account-setup-form__submit-button'
: 'account-setup-modal-footer__confirm-button',
block: activeView === ActiveViewEnum.FORM,
}}
cancelButtonProps={modalConfig.cancelButtonProps}
width={672}
>
{renderContent()}
</SignozModal>
);
}
export default CloudAccountSetupModal;

View File

@@ -1,137 +0,0 @@
import { Color } from '@signozhq/design-tokens';
import { Form, Select, Switch } from 'antd';
import { ChevronDown } from 'lucide-react';
import { Region } from 'utils/regions';
// Form section components
function RegionDeploymentSection({
regions,
selectedDeploymentRegion,
handleRegionChange,
isFormDisabled,
}: {
regions: Region[];
selectedDeploymentRegion: string | undefined;
handleRegionChange: (value: string) => void;
isFormDisabled: boolean;
}): JSX.Element {
return (
<div className="cloud-account-setup-form__form-group">
<div className="cloud-account-setup-form__title">
Where should we deploy the SigNoz Cloud stack?
</div>
<div className="cloud-account-setup-form__description">
Choose the AWS region for CloudFormation stack deployment
</div>
<Form.Item
name="region"
rules={[{ required: true, message: 'Please select a region' }]}
className="cloud-account-setup-form__form-item"
>
<Select
placeholder="e.g. US East (N. Virginia)"
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
className="cloud-account-setup-form__select integrations-select"
onChange={handleRegionChange}
value={selectedDeploymentRegion}
disabled={isFormDisabled}
>
{regions.flatMap((region) =>
region.subRegions.map((subRegion) => (
<Select.Option key={subRegion.id} value={subRegion.id}>
{subRegion.displayName}
</Select.Option>
)),
)}
</Select>
</Form.Item>
</div>
);
}
function MonitoringRegionsSection({
includeAllRegions,
selectedRegions,
onIncludeAllRegionsChange,
getRegionPreviewText,
onRegionSelect,
isFormDisabled,
}: {
includeAllRegions: boolean;
selectedRegions: string[];
onIncludeAllRegionsChange: (checked: boolean) => void;
getRegionPreviewText: (regions: string[]) => string[];
onRegionSelect: () => void;
isFormDisabled: boolean;
}): JSX.Element {
return (
<div className="cloud-account-setup-form__form-group">
<div className="cloud-account-setup-form__title">
Which regions do you want to monitor?
</div>
<div className="cloud-account-setup-form__description">
Choose only the regions you want SigNoz to monitor. You can enable all at
once, or pick specific ones:
</div>
<Form.Item
name="monitorRegions"
rules={[
{
validator: async (): Promise<void> => {
if (selectedRegions.length === 0) {
return Promise.reject();
}
return Promise.resolve();
},
message: 'Please select at least one region to monitor',
},
]}
className="cloud-account-setup-form__form-item"
>
<div className="cloud-account-setup-form__include-all-regions-switch">
<Switch
size="small"
checked={includeAllRegions}
onChange={onIncludeAllRegionsChange}
disabled={isFormDisabled}
/>
<button
className="cloud-account-setup-form__include-all-regions-switch-label"
type="button"
onClick={(): void =>
!isFormDisabled
? onIncludeAllRegionsChange(!includeAllRegions)
: undefined
}
>
Include all regions
</button>
</div>
<Select
suffixIcon={null}
placeholder="Select Region(s)"
className="cloud-account-setup-form__select integrations-select"
onClick={!isFormDisabled ? onRegionSelect : undefined}
mode="multiple"
maxTagCount={3}
value={getRegionPreviewText(selectedRegions)}
open={false}
/>
</Form.Item>
</div>
);
}
function ComplianceNote(): JSX.Element {
return (
<div className="cloud-account-setup-form__form-group">
<div className="cloud-account-setup-form__note">
Note: Some organizations may require the CloudFormation stack to be deployed
in the same region as their primary infrastructure for compliance or latency
reasons.
</div>
</div>
);
}
export { ComplianceNote, MonitoringRegionsSection, RegionDeploymentSection };

View File

@@ -1,102 +0,0 @@
import { Form } from 'antd';
import cx from 'classnames';
import { useAccountStatus } from 'hooks/integrations/aws/useAccountStatus';
import { useRef } from 'react';
import { AccountStatusResponse } from 'types/api/integrations/aws';
import { regions } from 'utils/regions';
import { ModalStateEnum, RegionFormProps } from '../types';
import AlertMessage from './AlertMessage';
import {
ComplianceNote,
MonitoringRegionsSection,
RegionDeploymentSection,
} from './IntegrateNowFormSections';
import RenderConnectionFields from './RenderConnectionParams';
const allRegions = (): string[] =>
regions.flatMap((r) => r.subRegions.map((sr) => sr.name));
const getRegionPreviewText = (regions: string[]): string[] => {
if (regions.includes('all')) {
return allRegions();
}
return regions;
};
export function RegionForm({
form,
modalState,
setModalState,
selectedRegions,
includeAllRegions,
onIncludeAllRegionsChange,
onRegionSelect,
onSubmit,
accountId,
selectedDeploymentRegion,
handleRegionChange,
connectionParams,
isConnectionParamsLoading,
}: RegionFormProps): JSX.Element {
const startTimeRef = useRef(Date.now());
const refetchInterval = 10 * 1000;
const errorTimeout = 5 * 60 * 1000;
const { isLoading: isAccountStatusLoading } = useAccountStatus(accountId, {
refetchInterval,
enabled: !!accountId && modalState === ModalStateEnum.WAITING,
onSuccess: (data: AccountStatusResponse) => {
if (data.data.status.integration.last_heartbeat_ts_ms !== null) {
setModalState(ModalStateEnum.SUCCESS);
} else if (Date.now() - startTimeRef.current >= errorTimeout) {
// 5 minutes in milliseconds
setModalState(ModalStateEnum.ERROR);
}
},
onError: () => {
setModalState(ModalStateEnum.ERROR);
},
});
const isFormDisabled =
modalState === ModalStateEnum.WAITING || isAccountStatusLoading;
return (
<Form
form={form}
className="cloud-account-setup-form"
layout="vertical"
onFinish={onSubmit}
>
<AlertMessage modalState={modalState} />
<div
className={cx(`cloud-account-setup-form__content`, {
disabled: isFormDisabled,
})}
>
<RegionDeploymentSection
regions={regions}
handleRegionChange={handleRegionChange}
isFormDisabled={isFormDisabled}
selectedDeploymentRegion={selectedDeploymentRegion}
/>
<MonitoringRegionsSection
includeAllRegions={includeAllRegions}
selectedRegions={selectedRegions}
onIncludeAllRegionsChange={onIncludeAllRegionsChange}
getRegionPreviewText={getRegionPreviewText}
onRegionSelect={onRegionSelect}
isFormDisabled={isFormDisabled}
/>
<ComplianceNote />
<RenderConnectionFields
isConnectionParamsLoading={isConnectionParamsLoading}
connectionParams={connectionParams}
isFormDisabled={isFormDisabled}
/>
</div>
</Form>
);
}

View File

@@ -1,21 +0,0 @@
.select-all {
margin-bottom: 20px;
}
.regions-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(145px, 1fr));
gap: 8px;
}
.region-group {
display: flex;
flex-direction: column;
gap: 4px;
}
.region-group label {
display: flex;
gap: 10px;
align-items: center;
}

View File

@@ -1,63 +0,0 @@
import './RegionSelector.style.scss';
import { Checkbox } from 'antd';
import { useRegionSelection } from 'hooks/integrations/aws/useRegionSelection';
import { Dispatch, SetStateAction } from 'react';
import { regions } from 'utils/regions';
export function RegionSelector({
selectedRegions,
setSelectedRegions,
setIncludeAllRegions,
}: {
selectedRegions: string[];
setSelectedRegions: Dispatch<SetStateAction<string[]>>;
setIncludeAllRegions: Dispatch<SetStateAction<boolean>>;
}): JSX.Element {
const {
allRegionIds,
handleSelectAll,
handleRegionSelect,
} = useRegionSelection({
selectedRegions,
setSelectedRegions,
setIncludeAllRegions,
});
return (
<div className="region-selector">
<div className="select-all">
<Checkbox
checked={selectedRegions.includes('all')}
indeterminate={
selectedRegions.length > 20 &&
selectedRegions.length < allRegionIds.length
}
onChange={(e): void => handleSelectAll(e.target.checked)}
>
Select All Regions
</Checkbox>
</div>
<div className="regions-grid">
{regions.map((region) => (
<div key={region.id} className="region-group">
<h3>{region.name}</h3>
{region.subRegions.map((subRegion) => (
<Checkbox
key={subRegion.id}
checked={
selectedRegions.includes('all') ||
selectedRegions.includes(subRegion.id)
}
onChange={(): void => handleRegionSelect(subRegion.id)}
>
{subRegion.name}
</Checkbox>
))}
</div>
))}
</div>
</div>
);
}

View File

@@ -1,48 +0,0 @@
.remove-integration-account {
display: flex;
justify-content: space-between;
padding: 16px;
border-radius: 4px;
border: 1px solid rgba(218, 85, 101, 0.2);
background: rgba(218, 85, 101, 0.06);
&__header {
display: flex;
flex-direction: column;
gap: 6px;
}
&__title {
color: var(--bg-cherry-500);
font-size: 14px;
letter-spacing: -0.07px;
}
&__subtitle {
color: var(--bg-cherry-300);
font-size: 14px;
line-height: 22px;
letter-spacing: -0.07px;
}
&__button {
display: flex;
align-items: center;
background: var(--bg-cherry-500);
border: none;
color: var(--bg-vanilla-100);
font-size: 12px;
font-weight: 500;
line-height: 13.3px; /* 110.833% */
padding: 9px 13px;
.ant-btn-icon {
margin-inline-end: 4px !important;
}
&:hover {
&.ant-btn-default {
color: var(--bg-vanilla-300) !important;
}
}
}
}

View File

@@ -1,94 +0,0 @@
import './RemoveIntegrationAccount.scss';
import { Button, Modal } from 'antd';
import logEvent from 'api/common/logEvent';
import removeAwsIntegrationAccount from 'api/Integrations/removeAwsIntegrationAccount';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { useNotifications } from 'hooks/useNotifications';
import { X } from 'lucide-react';
import { INTEGRATION_TELEMETRY_EVENTS } from 'pages/Integrations/utils';
import { useState } from 'react';
import { useMutation } from 'react-query';
function RemoveIntegrationAccount({
accountId,
onRemoveIntegrationAccountSuccess,
}: {
accountId: string;
onRemoveIntegrationAccountSuccess: () => void;
}): JSX.Element {
const { notifications } = useNotifications();
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = (): void => {
setIsModalOpen(true);
};
const {
mutate: removeIntegration,
isLoading: isRemoveIntegrationLoading,
} = useMutation(removeAwsIntegrationAccount, {
onSuccess: () => {
onRemoveIntegrationAccountSuccess?.();
setIsModalOpen(false);
},
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
});
const handleOk = (): void => {
logEvent(INTEGRATION_TELEMETRY_EVENTS.AWS_INTEGRATION_ACCOUNT_REMOVED, {
accountId,
});
removeIntegration(accountId);
};
const handleCancel = (): void => {
setIsModalOpen(false);
};
return (
<div className="remove-integration-account">
<div className="remove-integration-account__header">
<div className="remove-integration-account__title">Remove Integration</div>
<div className="remove-integration-account__subtitle">
Removing this integration won&apos;t delete any existing data but will stop
collecting new data from AWS.
</div>
</div>
<Button
className="remove-integration-account__button"
icon={<X size={14} />}
onClick={(): void => showModal()}
>
Remove
</Button>
<Modal
className="remove-integration-modal"
open={isModalOpen}
title="Remove integration"
onOk={handleOk}
onCancel={handleCancel}
okText="Remove Integration"
okButtonProps={{
danger: true,
disabled: isRemoveIntegrationLoading,
}}
>
<div className="remove-integration-modal__text">
Removing this account will remove all components created for sending
telemetry to SigNoz in your AWS account within the next ~15 minutes
(cloudformation stacks named signoz-integration-telemetry-collection in
enabled regions). <br />
<br />
After that, you can delete the cloudformation stack that was created
manually when connecting this account.
</div>
</Modal>
</div>
);
}
export default RemoveIntegrationAccount;

View File

@@ -1,71 +0,0 @@
import { Form, Input } from 'antd';
import { ConnectionParams } from 'types/api/integrations/aws';
function RenderConnectionFields({
isConnectionParamsLoading,
connectionParams,
isFormDisabled,
}: {
isConnectionParamsLoading?: boolean;
connectionParams?: ConnectionParams | null;
isFormDisabled?: boolean;
}): JSX.Element | null {
if (
isConnectionParamsLoading ||
(!!connectionParams?.ingestion_url &&
!!connectionParams?.ingestion_key &&
!!connectionParams?.signoz_api_url &&
!!connectionParams?.signoz_api_key)
) {
return null;
}
return (
<Form.Item name="connection_params">
{!connectionParams?.ingestion_url && (
<Form.Item
name="ingestion_url"
label="Ingestion URL"
rules={[{ required: true, message: 'Please enter ingestion URL' }]}
>
<Input placeholder="Enter ingestion URL" disabled={isFormDisabled} />
</Form.Item>
)}
{!connectionParams?.ingestion_key && (
<Form.Item
name="ingestion_key"
label="Ingestion Key"
rules={[{ required: true, message: 'Please enter ingestion key' }]}
>
<Input placeholder="Enter ingestion key" disabled={isFormDisabled} />
</Form.Item>
)}
{!connectionParams?.signoz_api_url && (
<Form.Item
name="signoz_api_url"
label="SigNoz API URL"
rules={[{ required: true, message: 'Please enter SigNoz API URL' }]}
>
<Input placeholder="Enter SigNoz API URL" disabled={isFormDisabled} />
</Form.Item>
)}
{!connectionParams?.signoz_api_key && (
<Form.Item
name="signoz_api_key"
label="SigNoz API KEY"
rules={[{ required: true, message: 'Please enter SigNoz API Key' }]}
>
<Input placeholder="Enter SigNoz API Key" disabled={isFormDisabled} />
</Form.Item>
)}
</Form.Item>
);
}
RenderConnectionFields.defaultProps = {
connectionParams: null,
isFormDisabled: false,
isConnectionParamsLoading: false,
};
export default RenderConnectionFields;

View File

@@ -1,156 +0,0 @@
.cloud-account-setup-success-view {
display: flex;
flex-direction: column;
gap: 40px;
text-align: center;
padding-top: 34px;
p,
h3,
h4 {
margin: 0;
}
&__content {
display: flex;
flex-direction: column;
gap: 14px;
.cloud-account-setup-success-view {
&__title {
h3 {
color: var(--bg-vanilla-100);
font-size: 20px;
font-weight: 500;
line-height: 32px;
}
}
&__description {
color: var(--bg-vanilla-400);
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.07px;
}
}
}
&__what-next {
display: flex;
flex-direction: column;
gap: 18px;
text-align: left;
&-title {
color: var(--bg-slate-50);
font-size: 11px;
font-weight: 500;
line-height: 18px;
letter-spacing: 0.88px;
text-transform: uppercase;
}
.what-next-items-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
&__item {
display: flex;
gap: 10px;
align-items: baseline;
&.ant-alert {
padding: 14px;
border-radius: 8px;
font-size: 14px;
line-height: 20px; /* 142.857% */
letter-spacing: -0.21px;
}
&.ant-alert-info {
border: 1px solid rgba(63, 94, 204, 0.5);
background: rgba(78, 116, 248, 0.2);
color: var(--bg-robin-400);
}
.what-next-item {
color: var(--bg-robin-400);
&-bullet-icon {
font-size: 20px;
line-height: 20px;
}
&-text {
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.21px;
}
}
}
}
}
&__footer {
padding-top: 18px;
.ant-btn {
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
font-size: 14px;
font-weight: 500;
line-height: 20px;
height: 36px;
}
}
}
.lottie-container {
position: absolute;
width: 743.5px;
height: 990.342px;
top: -100px;
left: -36px;
z-index: 1;
}
.lightMode {
.cloud-account-setup-success-view {
&__content {
.cloud-account-setup-success-view {
&__title {
h3 {
color: var(--bg-ink-500);
}
}
&__description {
color: var(--bg-ink-400);
}
}
}
&__what-next {
&-title {
color: var(--bg-ink-500);
}
.what-next-items-wrapper {
&__item {
&.ant-alert-info {
border: 1px solid rgba(63, 94, 204, 0.2);
background: rgba(78, 116, 248, 0.1);
color: var(--bg-robin-500);
}
.what-next-item {
color: var(--bg-robin-500);
&-text {
color: var(--bg-robin-500);
}
}
}
}
}
&__footer {
.ant-btn {
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
&:hover {
background: var(--bg-robin-400);
}
}
}
}
}

View File

@@ -1,73 +0,0 @@
import './SuccessView.style.scss';
import { Alert } from 'antd';
import integrationsSuccess from 'assets/Lotties/integrations-success.json';
import { useState } from 'react';
import Lottie from 'react-lottie';
export function SuccessView(): JSX.Element {
const [isAnimationComplete, setIsAnimationComplete] = useState(false);
const defaultOptions = {
loop: false,
autoplay: true,
animationData: integrationsSuccess,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice',
},
};
return (
<>
{!isAnimationComplete && (
<div className="lottie-container">
<Lottie
options={defaultOptions}
height={990.342}
width={743.5}
eventListeners={[
{
eventName: 'complete',
callback: (): void => setIsAnimationComplete(true),
},
]}
/>
</div>
)}
<div className="cloud-account-setup-success-view">
<div className="cloud-account-setup-success-view__icon">
<img src="Icons/solid-check-circle.svg" alt="Success" />
</div>
<div className="cloud-account-setup-success-view__content">
<div className="cloud-account-setup-success-view__title">
<h3>🎉 Success! </h3>
<h3>Your AWS Web Service integration is all set.</h3>
</div>
<div className="cloud-account-setup-success-view__description">
<p>Your observability journey is off to a great start. </p>
<p>Now that your data is flowing, heres what you can do next:</p>
</div>
</div>
<div className="cloud-account-setup-success-view__what-next">
<h4 className="cloud-account-setup-success-view__what-next-title">
WHAT NEXT
</h4>
<div className="what-next-items-wrapper">
<Alert
message={
<div className="what-next-items-wrapper__item">
<div className="what-next-item-bullet-icon"></div>
<div className="what-next-item-text">
Set up your AWS services effortlessly under your enabled account.
</div>
</div>
}
type="info"
className="what-next-items-wrapper__item"
/>
</div>
</div>
</div>
</>
);
}

View File

@@ -1,35 +0,0 @@
import { FormInstance } from 'antd';
import { Dispatch, SetStateAction } from 'react';
import { ConnectionParams } from 'types/api/integrations/aws';
export enum ActiveViewEnum {
SELECT_REGIONS = 'select-regions',
FORM = 'form',
}
export enum ModalStateEnum {
FORM = 'form',
WAITING = 'waiting',
ERROR = 'error',
SUCCESS = 'success',
}
export interface RegionFormProps {
form: FormInstance;
modalState: ModalStateEnum;
setModalState: Dispatch<SetStateAction<ModalStateEnum>>;
selectedRegions: string[];
includeAllRegions: boolean;
onIncludeAllRegionsChange: (checked: boolean) => void;
onRegionSelect: () => void;
onSubmit: () => Promise<void>;
accountId?: string;
selectedDeploymentRegion: string | undefined;
handleRegionChange: (value: string) => void;
connectionParams?: ConnectionParams;
isConnectionParamsLoading?: boolean;
}
export interface IntegrationModalProps {
onClose: () => void;
}

View File

@@ -1,50 +0,0 @@
import { Link } from 'react-router-dom';
import { ServiceData } from './types';
function DashboardItem({
dashboard,
}: {
dashboard: ServiceData['assets']['dashboards'][number];
}): JSX.Element {
const content = (
<>
<div className="cloud-service-dashboard-item__title">{dashboard.title}</div>
<div className="cloud-service-dashboard-item__preview">
<img
src={dashboard.image}
alt={dashboard.title}
className="cloud-service-dashboard-item__preview-image"
/>
</div>
</>
);
return (
<div className="cloud-service-dashboard-item">
{dashboard.url ? (
<Link to={dashboard.url} className="cloud-service-dashboard-item__link">
{content}
</Link>
) : (
content
)}
</div>
);
}
function CloudServiceDashboards({
service,
}: {
service: ServiceData;
}): JSX.Element {
return (
<>
{service.assets.dashboards.map((dashboard) => (
<DashboardItem key={dashboard.id} dashboard={dashboard} />
))}
</>
);
}
export default CloudServiceDashboards;

View File

@@ -1,87 +0,0 @@
import { Table } from 'antd';
import { ServiceData } from './types';
function CloudServiceDataCollected({
logsData,
metricsData,
}: {
logsData: ServiceData['data_collected']['logs'];
metricsData: ServiceData['data_collected']['metrics'];
}): JSX.Element {
const logsColumns = [
{
title: 'NAME',
dataIndex: 'name',
key: 'name',
width: '30%',
},
{
title: 'PATH',
dataIndex: 'path',
key: 'path',
width: '40%',
},
{
title: 'FACET TYPE',
dataIndex: 'type',
key: 'type',
width: '30%',
},
];
const metricsColumns = [
{
title: 'NAME',
dataIndex: 'name',
key: 'name',
width: '40%',
},
{
title: 'UNIT',
dataIndex: 'unit',
key: 'unit',
width: '30%',
},
{
title: 'TYPE',
dataIndex: 'type',
key: 'type',
width: '30%',
},
];
const tableProps = {
pagination: { pageSize: 20, hideOnSinglePage: true },
showHeader: true,
size: 'middle' as const,
bordered: false,
};
return (
<div className="cloud-service-data-collected">
<div className="cloud-service-data-collected__table">
<div className="cloud-service-data-collected__table-heading">Logs</div>
<Table
columns={logsColumns}
dataSource={logsData}
// eslint-disable-next-line react/jsx-props-no-spreading
{...tableProps}
className="cloud-service-data-collected__table-logs"
/>
</div>
<div className="cloud-service-data-collected__table">
<div className="cloud-service-data-collected__table-heading">Metrics</div>
<Table
columns={metricsColumns}
dataSource={metricsData}
// eslint-disable-next-line react/jsx-props-no-spreading
{...tableProps}
className="cloud-service-data-collected__table-metrics"
/>
</div>
</div>
);
}
export default CloudServiceDataCollected;

View File

@@ -1,89 +0,0 @@
.configure-service-modal {
&__body {
display: flex;
flex-direction: column;
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
padding: 14px;
&-regions-switch-switch {
display: flex;
align-items: center;
gap: 6px;
&-label {
color: var(--bg-vanilla-100);
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.07px;
}
}
&-switch-description {
margin-top: 4px;
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 400;
line-height: 18px;
letter-spacing: -0.06px;
}
&-form-item {
&:last-child {
margin-bottom: 0px;
}
}
}
.ant-modal-body {
padding-bottom: 0;
}
.ant-modal-footer {
margin: 0;
padding-bottom: 12px;
}
}
.lightMode {
.configure-service-modal {
&__body {
border-color: var(--bg-vanilla-300);
&-regions-switch-switch {
&-label {
color: var(--bg-ink-500);
}
}
&-switch-description {
color: var(--bg-ink-400);
}
}
.ant-btn {
&.ant-btn-default {
background: var(--bg-vanilla-100);
border: 1px solid var(--bg-vanilla-300);
color: var(--bg-ink-400);
&:hover {
border-color: var(--bg-vanilla-400);
color: var(--bg-ink-500);
}
}
&.ant-btn-primary {
// Keep primary button same as dark mode
background: var(--bg-robin-500);
color: var(--bg-vanilla-100);
&:hover {
background: var(--bg-robin-400);
}
&:disabled {
opacity: 0.6;
}
}
}
}
}

View File

@@ -1,199 +0,0 @@
import './ConfigureServiceModal.styles.scss';
import { Form, Switch } from 'antd';
import SignozModal from 'components/SignozModal/SignozModal';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import {
ServiceConfig,
SupportedSignals,
} from 'container/CloudIntegrationPage/ServicesSection/types';
import { useUpdateServiceConfig } from 'hooks/integrations/aws/useUpdateServiceConfig';
import { useCallback, useMemo, useState } from 'react';
import { useQueryClient } from 'react-query';
interface IConfigureServiceModalProps {
isOpen: boolean;
onClose: () => void;
serviceName: string;
serviceId: string;
cloudAccountId: string;
supportedSignals: SupportedSignals;
initialConfig?: ServiceConfig;
}
function ConfigureServiceModal({
isOpen,
onClose,
serviceName,
serviceId,
cloudAccountId,
initialConfig,
supportedSignals,
}: IConfigureServiceModalProps): JSX.Element {
const [form] = Form.useForm();
const [isLoading, setIsLoading] = useState(false);
// Track current form values
const [currentValues, setCurrentValues] = useState({
metrics: initialConfig?.metrics?.enabled || false,
logs: initialConfig?.logs?.enabled || false,
});
const {
mutate: updateServiceConfig,
isLoading: isUpdating,
} = useUpdateServiceConfig();
const queryClient = useQueryClient();
const handleSubmit = useCallback(async (): Promise<void> => {
try {
const values = await form.validateFields();
setIsLoading(true);
updateServiceConfig(
{
serviceId,
payload: {
cloud_account_id: cloudAccountId,
config: {
logs: {
enabled: values.logs,
},
metrics: {
enabled: values.metrics,
},
},
},
},
{
onSuccess: () => {
queryClient.invalidateQueries([
REACT_QUERY_KEY.AWS_SERVICE_DETAILS,
serviceId,
]);
onClose();
},
onError: (error) => {
console.error('Failed to update service config:', error);
},
},
);
} catch (error) {
console.error('Form submission failed:', error);
} finally {
setIsLoading(false);
}
}, [
form,
updateServiceConfig,
serviceId,
cloudAccountId,
queryClient,
onClose,
]);
const isSaveDisabled = useMemo(
() => currentValues.metrics === false && currentValues.logs === false,
[currentValues],
);
const handleClose = useCallback(() => {
form.resetFields();
onClose();
}, [form, onClose]);
return (
<SignozModal
title={
<div className="account-settings-modal__title">Configure {serviceName}</div>
}
centered
open={isOpen}
okText="Save"
okButtonProps={{
disabled: isSaveDisabled,
className: 'account-settings-modal__footer-save-button',
loading: isLoading || isUpdating,
}}
onCancel={handleClose}
onOk={handleSubmit}
cancelText="Close"
cancelButtonProps={{
className: 'account-settings-modal__footer-close-button',
}}
width={672}
rootClassName=" configure-service-modal"
>
<Form
form={form}
layout="vertical"
initialValues={{
metrics: initialConfig?.metrics?.enabled || false,
logs: initialConfig?.logs?.enabled || false,
}}
>
<div className=" configure-service-modal__body">
{supportedSignals.metrics && (
<Form.Item
name="metrics"
valuePropName="checked"
className="configure-service-modal__body-form-item"
>
<div className="configure-service-modal__body-regions-switch-switch">
<Switch
checked={currentValues.metrics}
onChange={(checked): void => {
setCurrentValues((prev) => ({ ...prev, metrics: checked }));
form.setFieldsValue({ metrics: checked });
}}
/>
<span className="configure-service-modal__body-regions-switch-switch-label">
Metric Collection
</span>
</div>
<div className="configure-service-modal__body-switch-description">
Metric Collection is enabled for this AWS account. We recommend keeping
this enabled, but you can disable metric collection if you do not want
to monitor your AWS infrastructure.
</div>
</Form.Item>
)}
{supportedSignals.logs && (
<Form.Item
name="logs"
valuePropName="checked"
className="configure-service-modal__body-form-item"
>
<div className="configure-service-modal__body-regions-switch-switch">
<Switch
checked={currentValues.logs}
onChange={(checked): void => {
setCurrentValues((prev) => ({ ...prev, logs: checked }));
form.setFieldsValue({ logs: checked });
}}
/>
<span className="configure-service-modal__body-regions-switch-switch-label">
Log Collection
</span>
</div>
<div className="configure-service-modal__body-switch-description">
To ingest logs from your AWS services, you must complete several steps
</div>
</Form.Item>
)}
</div>
</Form>
</SignozModal>
);
}
ConfigureServiceModal.defaultProps = {
initialConfig: {
metrics: { enabled: false },
logs: { enabled: false },
},
};
export default ConfigureServiceModal;

View File

@@ -1,158 +0,0 @@
import { Button, Tabs, TabsProps } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import Spinner from 'components/Spinner';
import CloudServiceDashboards from 'container/CloudIntegrationPage/ServicesSection/CloudServiceDashboards';
import CloudServiceDataCollected from 'container/CloudIntegrationPage/ServicesSection/CloudServiceDataCollected';
import { IServiceStatus } from 'container/CloudIntegrationPage/ServicesSection/types';
import dayjs from 'dayjs';
import { useServiceDetails } from 'hooks/integrations/aws/useServiceDetails';
import useUrlQuery from 'hooks/useUrlQuery';
import { useMemo, useState } from 'react';
import ConfigureServiceModal from './ConfigureServiceModal';
const getStatus = (
logsLastReceivedTimestamp: number | undefined,
metricsLastReceivedTimestamp: number | undefined,
): { text: string; className: string } => {
if (!logsLastReceivedTimestamp && !metricsLastReceivedTimestamp) {
return { text: 'No Data Yet', className: 'service-status--no-data' };
}
const latestTimestamp = Math.max(
logsLastReceivedTimestamp || 0,
metricsLastReceivedTimestamp || 0,
);
const isStale = dayjs().diff(dayjs(latestTimestamp), 'minute') > 30;
if (isStale) {
return { text: 'Stale Data', className: 'service-status--stale-data' };
}
return { text: 'Connected', className: 'service-status--connected' };
};
function ServiceStatus({
serviceStatus,
}: {
serviceStatus: IServiceStatus | undefined;
}): JSX.Element {
const logsLastReceivedTimestamp = serviceStatus?.logs?.last_received_ts_ms;
const metricsLastReceivedTimestamp =
serviceStatus?.metrics?.last_received_ts_ms;
const { text, className } = getStatus(
logsLastReceivedTimestamp,
metricsLastReceivedTimestamp,
);
return <div className={`service-status ${className}`}>{text}</div>;
}
function ServiceDetails(): JSX.Element | null {
const urlQuery = useUrlQuery();
const cloudAccountId = urlQuery.get('cloudAccountId');
const serviceId = urlQuery.get('service');
const [isConfigureServiceModalOpen, setIsConfigureServiceModalOpen] = useState(
false,
);
const { data: serviceDetailsData, isLoading } = useServiceDetails(
serviceId || '',
cloudAccountId || undefined,
);
// eslint-disable-next-line @typescript-eslint/naming-convention
const { config, supported_signals } = serviceDetailsData ?? {};
const totalSupportedSignals = Object.entries(supported_signals || {}).filter(
([, value]) => !!value,
).length;
const enabledSignals = useMemo(
() =>
Object.values(config || {}).filter((item) => item && item.enabled).length,
[config],
);
const isAnySignalConfigured = useMemo(
() => !!config?.logs?.enabled || !!config?.metrics?.enabled,
[config],
);
if (isLoading) {
return <Spinner size="large" height="50vh" />;
}
if (!serviceDetailsData) {
return null;
}
const tabItems: TabsProps['items'] = [
{
key: 'dashboards',
label: `Dashboards (${serviceDetailsData?.assets.dashboards.length})`,
children: <CloudServiceDashboards service={serviceDetailsData} />,
},
{
key: 'data-collected',
label: 'Data Collected',
children: (
<CloudServiceDataCollected
logsData={serviceDetailsData?.data_collected.logs || []}
metricsData={serviceDetailsData?.data_collected.metrics || []}
/>
),
},
];
return (
<div className="service-details">
<div className="service-details__title-bar">
<div className="service-details__details-title">Details</div>
<div className="service-details__right-actions">
<ServiceStatus serviceStatus={serviceDetailsData.status} />
{!!cloudAccountId && isAnySignalConfigured ? (
<Button
className="configure-button configure-button--default"
onClick={(): void => setIsConfigureServiceModalOpen(true)}
>
Configure ({enabledSignals}/{totalSupportedSignals})
</Button>
) : (
<Button
type="primary"
className="configure-button configure-button--primary"
onClick={(): void => setIsConfigureServiceModalOpen(true)}
>
Enable Service
</Button>
)}
</div>
</div>
<div className="service-details__overview">
<MarkdownRenderer
variables={{}}
markdownContent={serviceDetailsData?.overview}
/>
</div>
<div className="service-details__tabs">
<Tabs items={tabItems} />
</div>
{isConfigureServiceModalOpen && (
<ConfigureServiceModal
isOpen
onClose={(): void => setIsConfigureServiceModalOpen(false)}
serviceName={serviceDetailsData.title}
serviceId={serviceId || ''}
cloudAccountId={cloudAccountId || ''}
initialConfig={serviceDetailsData.config}
supportedSignals={serviceDetailsData.supported_signals || {}}
/>
)}
</div>
);
}
export default ServiceDetails;

View File

@@ -1,39 +0,0 @@
import cx from 'classnames';
import LineClampedText from 'periscope/components/LineClampedText/LineClampedText';
import { Service } from './types';
function ServiceItem({
service,
onClick,
isActive,
}: {
service: Service;
onClick: (serviceName: string) => void;
isActive?: boolean;
}): JSX.Element {
return (
<button
className={cx('service-item', { active: isActive })}
onClick={(): void => onClick(service.id)}
type="button"
>
<div className="service-item__icon-wrapper">
<img
src={service.icon}
alt={service.title}
className="service-item__icon"
/>
</div>
<div className="service-item__title">
<LineClampedText text={service.title} />
</div>
</button>
);
}
ServiceItem.defaultProps = {
isActive: false,
};
export default ServiceItem;

View File

@@ -1,66 +0,0 @@
import Spinner from 'components/Spinner';
import { useGetAccountServices } from 'hooks/integrations/aws/useGetAccountServices';
import useUrlQuery from 'hooks/useUrlQuery';
import { useCallback, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import ServiceItem from './ServiceItem';
interface ServicesListProps {
cloudAccountId: string;
filter: 'all_services' | 'enabled' | 'available';
}
function ServicesList({
cloudAccountId,
filter,
}: ServicesListProps): JSX.Element {
const urlQuery = useUrlQuery();
const navigate = useNavigate();
const { data: services = [], isLoading } = useGetAccountServices(
cloudAccountId,
);
const activeService = urlQuery.get('service');
const handleActiveService = useCallback(
(serviceId: string): void => {
urlQuery.set('service', serviceId);
navigate({ search: urlQuery.toString() });
},
[navigate, urlQuery],
);
const filteredServices = useMemo(() => {
if (filter === 'all_services') return services;
return services.filter((service) => {
const isEnabled =
service?.config?.logs?.enabled || service?.config?.metrics?.enabled;
return filter === 'enabled' ? isEnabled : !isEnabled;
});
}, [services, filter]);
useEffect(() => {
if (activeService || !services?.length) return;
handleActiveService(services[0].id);
}, [services, activeService, handleActiveService]);
if (isLoading) return <Spinner size="large" height="25vh" />;
if (!services) return <div>No services found</div>;
return (
<div className="services-list">
{filteredServices.map((service) => (
<ServiceItem
key={service.id}
service={service}
onClick={handleActiveService}
isActive={service.id === activeService}
/>
))}
</div>
);
}
export default ServicesList;

View File

@@ -1,438 +0,0 @@
.services-tabs {
.ant-tabs-tab {
font-family: 'Inter';
padding: 16px 4px 14px;
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
font-weight: 400;
&.ant-tabs-tab-active {
.ant-tabs-tab-btn {
color: var(--bg-vanilla-100);
}
}
}
.ant-tabs-ink-bar {
background: var(--bg-robin-500);
}
}
.services-section {
display: flex;
gap: 10px;
&__sidebar {
width: 16%;
padding: 0 16px;
}
&__content {
width: 84%;
padding: 16px;
}
}
.services-filter {
padding: 16px 0;
.ant-select-selector {
background-color: var(--bg-ink-300) !important;
border: 1px solid var(--bg-slate-400) !important;
color: var(--bg-vanilla-400) !important;
font-family: Inter;
font-size: 12px;
}
.ant-select-arrow {
color: var(--bg-vanilla-400);
}
}
.service-item {
display: flex;
gap: 12px;
align-items: center;
padding: 8px;
background-color: transparent;
border: none;
width: 100%;
cursor: pointer;
&:not(:last-child) {
border-bottom: 1px solid var(--bg-slate-400);
}
&.active {
background-color: var(--bg-ink-100);
}
&__icon-wrapper {
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--bg-ink-300);
border: 1px solid var(--bg-slate-400);
border-radius: 4px;
.service-item__icon {
width: 24px;
height: 24px;
}
}
&__title {
color: var(--bg-vanilla-100);
font-size: 14px;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
text-align: left;
}
}
.service-details {
display: flex;
flex-direction: column;
gap: 10px;
&__title-bar {
display: flex;
justify-content: space-between;
align-items: center;
height: 48px;
border-bottom: 1px solid var(--bg-slate-400);
.service-details__details-title {
color: var(--bg-vanilla-400);
font-size: 14px;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
text-align: left;
}
.service-details__right-actions {
display: flex;
align-items: center;
gap: 8px;
.service-status {
display: flex;
align-items: center;
padding: 2px 8px;
font-family: 'Geist Mono';
font-size: 12px;
font-weight: 500;
letter-spacing: 0.54px;
border-radius: 2px;
line-height: normal;
&--connected {
border: 1px solid rgba(37, 225, 146, 0.1);
background: rgba(37, 225, 146, 0.1);
color: var(--bg-forest-400);
}
&--stale-data {
background: rgba(255, 215, 120, 0.1);
border: 1px solid rgba(255, 215, 120, 0.1);
color: var(--bg-amber-400);
}
&--no-data {
border: 1px solid rgba(234, 109, 113, 0.1);
background: rgba(234, 109, 113, 0.1);
color: var(--bg-cherry-400);
}
}
.configure-button {
border-radius: 2px;
font-size: 12px;
letter-spacing: 0.12px;
font-weight: 500;
width: 116px;
box-shadow: none;
&--default {
color: var(--bg-vanilla-400);
background: var(--bg-slate-400);
border: 1px solid var(--bg-slate-400);
}
&--primary {
background-color: var(--bg-robin-500);
color: var(--bg-vanilla-100);
font-weight: 500;
color: var(--Vanilla-100, #fff);
}
}
}
}
&__overview {
color: var(--bg-vanilla-400);
font-size: 14px;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
width: 800px;
}
&__tabs {
.ant-tabs {
&-ink-bar {
background-color: transparent;
}
&-nav {
padding: 8px 0 18px;
&-wrap {
padding: 0;
}
&::before {
border-bottom: none !important;
}
}
&-tab {
margin-left: 0 !important;
padding: 0;
&-btn {
padding: 10px 24px;
color: var(--text-vanilla-400) !important;
font-family: Inter;
font-size: 12px;
font-weight: 400;
line-height: 18px; /* 150% */
letter-spacing: -0.06px;
&[aria-selected='true'] {
color: var(--text-vanilla-100) !important;
}
}
&-active {
background: var(--bg-slate-400, #1d212d);
}
}
&-extra-content {
padding: 0 16px 16px;
}
&-nav-list {
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-400);
border-radius: 2px;
}
}
}
.cloud-service {
&-dashboard-item {
display: flex;
flex-direction: column;
gap: 10px;
&__title {
color: var(--bg-vanilla-100);
font-size: 18px;
font-weight: 500;
line-height: 20px; /* 111.111% */
letter-spacing: -0.09px;
}
&__preview-image {
width: 100%;
}
}
&-data-collected {
display: flex;
flex-direction: column;
gap: 24px;
&__table {
display: flex;
flex-direction: column;
gap: 8px;
.ant-table {
background: transparent;
border: 1px solid var(--bg-slate-400);
.ant-table-thead {
> tr > th {
&::before {
display: none;
}
background: transparent;
border: none;
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 600;
line-height: 18px; /* 150% */
letter-spacing: 0.6px;
text-transform: uppercase;
}
}
.ant-table-tbody {
> tr {
&:nth-child(odd),
&:hover > td {
background: rgba(255, 255, 255, 0.01) !important;
}
> td {
&:first-child {
font-weight: 500;
}
border: none;
color: var(--bg-vanilla-400);
font-size: 14px;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
}
}
}
&__table-heading {
color: var(--bg-slate-50);
font-size: 11px;
font-weight: 500;
line-height: 18px; /* 163.636% */
letter-spacing: 0.88px;
text-transform: uppercase;
}
}
}
}
.lightMode {
.services-tabs {
.ant-tabs-tab {
&.ant-tabs-tab-active {
.ant-tabs-tab-btn {
color: var(--bg-ink-500);
}
}
}
}
.services-filter {
.ant-select-selector {
background-color: var(--bg-vanilla-100) !important;
border-color: var(--bg-vanilla-300) !important;
color: var(--bg-ink-400) !important;
}
.ant-select-arrow {
color: var(--bg-ink-400);
}
}
.service-item {
&:not(:last-child) {
border-bottom: 1px solid var(--bg-vanilla-300);
}
&.active {
background-color: var(--bg-vanilla-300);
}
&__icon-wrapper {
background-color: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-300);
}
&__title {
color: var(--bg-ink-500);
}
}
.service-details {
&__title-bar {
border-bottom: 1px solid var(--bg-vanilla-300);
.service-details__details-title {
color: var(--bg-ink-400);
}
.configure-button {
color: var(--bg-ink-400);
background: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-300);
&:hover {
border-color: var(--bg-vanilla-400);
color: var(--bg-ink-500);
}
}
.service-status {
&--connected {
border: 1px solid rgba(37, 225, 146, 0.2);
background: rgba(37, 225, 146, 0.1);
color: var(--bg-forest-500);
}
&--stale-data {
border: 1px solid rgba(255, 215, 120, 0.2);
background: rgba(255, 215, 120, 0.1);
color: var(--bg-amber-500);
}
&--no-data {
border: 1px solid rgba(234, 109, 113, 0.2);
background: rgba(234, 109, 113, 0.1);
color: var(--bg-cherry-500);
}
}
}
&__overview {
color: var(--bg-ink-400);
}
&__tabs {
.ant-tabs {
&-tab {
&-btn {
color: var(--bg-ink-400) !important;
&[aria-selected='true'] {
color: var(--bg-ink-500) !important;
}
}
&-active {
background: var(--bg-vanilla-300);
}
}
&-nav-list {
border-color: var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
}
}
.cloud-service {
&-dashboard-item {
&__title {
color: var(--bg-ink-500);
}
}
&-data-collected {
&__table {
.ant-table {
border-color: var(--bg-vanilla-300);
.ant-table-thead {
> tr > th {
color: var(--bg-ink-400);
}
}
.ant-table-tbody {
> tr {
&:nth-child(odd),
&:hover > td {
background: var(--bg-vanilla-100) !important;
}
> td {
color: var(--bg-ink-400);
}
}
}
}
}
&__table-heading {
color: var(--bg-ink-500);
}
}
}
}
}

View File

@@ -1,118 +0,0 @@
import './ServicesTabs.style.scss';
import { Color } from '@signozhq/design-tokens';
import type { SelectProps, TabsProps } from 'antd';
import { Select, Tabs } from 'antd';
import { getAwsServices } from 'api/integrations/aws';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import useUrlQuery from 'hooks/useUrlQuery';
import { ChevronDown } from 'lucide-react';
import { useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import ServiceDetails from './ServiceDetails';
import ServicesList from './ServicesList';
export enum ServiceFilterType {
ALL_SERVICES = 'all_services',
ENABLED = 'enabled',
AVAILABLE = 'available',
}
interface ServicesFilterProps {
cloudAccountId: string;
onFilterChange: (value: ServiceFilterType) => void;
}
function ServicesFilter({
cloudAccountId,
onFilterChange,
}: ServicesFilterProps): JSX.Element | null {
const { data: services, isLoading } = useQuery(
[REACT_QUERY_KEY.AWS_SERVICES, cloudAccountId],
() => getAwsServices(cloudAccountId),
);
const { enabledCount, availableCount } = useMemo(() => {
if (!services) return { enabledCount: 0, availableCount: 0 };
return services.reduce(
(acc, service) => {
const isEnabled =
service?.config?.logs?.enabled || service?.config?.metrics?.enabled;
return {
enabledCount: acc.enabledCount + (isEnabled ? 1 : 0),
availableCount: acc.availableCount + (isEnabled ? 0 : 1),
};
},
{ enabledCount: 0, availableCount: 0 },
);
}, [services]);
const selectOptions: SelectProps['options'] = useMemo(
() => [
{ value: 'all_services', label: `All Services (${services?.length || 0})` },
{ value: 'enabled', label: `Enabled (${enabledCount})` },
{ value: 'available', label: `Available (${availableCount})` },
],
[services, enabledCount, availableCount],
);
if (isLoading) return null;
if (!services?.length) return null;
return (
<div className="services-filter">
<Select
style={{ width: '100%' }}
defaultValue={ServiceFilterType.ALL_SERVICES}
options={selectOptions}
className="services-sidebar__select"
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
onChange={onFilterChange}
/>
</div>
);
}
function ServicesSection(): JSX.Element {
const urlQuery = useUrlQuery();
const cloudAccountId = urlQuery.get('cloudAccountId') || '';
const [activeFilter, setActiveFilter] = useState<
'all_services' | 'enabled' | 'available'
>('all_services');
return (
<div className="services-section">
<div className="services-section__sidebar">
<ServicesFilter
cloudAccountId={cloudAccountId}
onFilterChange={setActiveFilter}
/>
<ServicesList cloudAccountId={cloudAccountId} filter={activeFilter} />
</div>
<div className="services-section__content">
<ServiceDetails />
</div>
</div>
);
}
function ServicesTabs(): JSX.Element {
const tabItems: TabsProps['items'] = [
{
key: 'services',
label: 'Services For Integration',
children: <ServicesSection />,
},
];
return (
<div className="services-tabs">
<Tabs defaultActiveKey="services" items={tabItems} />
</div>
);
}
export default ServicesTabs;

View File

@@ -1,135 +0,0 @@
interface Service {
id: string;
title: string;
icon: string;
config: ServiceConfig;
}
interface Dashboard {
id: string;
url: string;
title: string;
description: string;
image: string;
}
interface LogField {
name: string;
path: string;
type: string;
}
interface Metric {
name: string;
type: string;
unit: string;
}
interface ConfigStatus {
enabled: boolean;
}
interface DataStatus {
last_received_ts_ms: number;
last_received_from: string;
}
interface ServiceConfig {
logs: ConfigStatus;
metrics: ConfigStatus;
}
interface IServiceStatus {
logs: DataStatus | null;
metrics: DataStatus | null;
}
interface SupportedSignals {
metrics: boolean;
logs: boolean;
}
interface ServiceData {
id: string;
title: string;
icon: string;
overview: string;
supported_signals: SupportedSignals;
assets: {
dashboards: Dashboard[];
};
data_collected: {
logs?: LogField[];
metrics: Metric[];
};
config?: ServiceConfig;
status?: IServiceStatus;
}
interface ServiceDetailsResponse {
status: 'success';
data: ServiceData;
}
interface CloudAccountConfig {
regions: string[];
}
interface IntegrationStatus {
last_heartbeat_ts_ms: number;
}
interface AccountStatus {
integration: IntegrationStatus;
}
interface CloudAccount {
id: string;
cloud_account_id: string;
config: CloudAccountConfig;
status: AccountStatus;
}
interface CloudAccountsData {
accounts: CloudAccount[];
}
interface UpdateServiceConfigPayload {
cloud_account_id: string;
config: {
logs: {
enabled: boolean;
};
metrics: {
enabled: boolean;
};
};
}
interface UpdateServiceConfigResponse {
status: string;
data: {
id: string;
config: {
logs: {
enabled: boolean;
};
metrics: {
enabled: boolean;
};
};
};
}
export type {
CloudAccount,
CloudAccountsData,
IServiceStatus,
Service,
ServiceConfig,
ServiceData,
ServiceDetailsResponse,
SupportedSignals,
UpdateServiceConfigPayload,
UpdateServiceConfigResponse,
};

View File

@@ -25,10 +25,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import ExportPanelContainer from 'container/ExportPanel/ExportPanelContainer';
import { useOptionsMenu } from 'container/OptionsMenu';
import {
defaultLogsSelectedColumns,
defaultTraceSelectedColumns,
} from 'container/OptionsMenu/constants';
import { defaultTraceSelectedColumns } from 'container/OptionsMenu/constants';
import { OptionsQuery } from 'container/OptionsMenu/types';
import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -411,14 +408,6 @@ function ExplorerOptions({
const handleClearSelect = (): void => {
removeCurrentViewFromLocalStorage();
handleOptionsChange({
...options,
selectColumns:
sourcepage === DataSource.TRACES
? defaultTraceSelectedColumns
: defaultLogsSelectedColumns,
});
history.replace(DATASOURCE_VS_ROUTES[sourcepage]);
};

View File

@@ -48,7 +48,6 @@ function WidgetGraphComponent({
openTracesButton,
onOpenTraceBtnClick,
customSeries,
customErrorMessage,
}: WidgetGraphComponentProps): JSX.Element {
const [deleteModal, setDeleteModal] = useState(false);
const [hovered, setHovered] = useState(false);
@@ -318,13 +317,6 @@ function WidgetGraphComponent({
setSearchTerm={setSearchTerm}
/>
</div>
{queryResponse.error && customErrorMessage && (
<div className="error-message-container">
<Typography.Text type="warning">{customErrorMessage}</Typography.Text>
</div>
)}
{queryResponse.isLoading && widget.panelTypes !== PANEL_TYPES.LIST && (
<Skeleton />
)}

View File

@@ -41,15 +41,9 @@ function GridCardGraph({
openTracesButton,
onOpenTraceBtnClick,
customSeries,
customErrorMessage,
start,
end,
}: GridCardGraphProps): JSX.Element {
const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>();
const [isInternalServerError, setIsInternalServerError] = useState<boolean>(
false,
);
const {
toScrollWidgetId,
setToScrollWidgetId,
@@ -184,8 +178,6 @@ function GridCardGraph({
variables: getDashboardVariables(variables),
selectedTime: widget.timePreferance || 'GLOBAL_TIME',
globalSelectedInterval,
start,
end,
},
version || DEFAULT_ENTITY_VERSION,
{
@@ -215,11 +207,6 @@ function GridCardGraph({
refetchOnMount: false,
onError: (error) => {
setErrorMessage(error.message);
if (customErrorMessage) {
setIsInternalServerError(
String(error.message).includes('API responded with 500'),
);
}
setDashboardQueryRangeCalled(true);
},
onSettled: (data) => {
@@ -269,7 +256,6 @@ function GridCardGraph({
openTracesButton={openTracesButton}
onOpenTraceBtnClick={onOpenTraceBtnClick}
customSeries={customSeries}
customErrorMessage={isInternalServerError ? customErrorMessage : undefined}
/>
)}
</div>

View File

@@ -37,7 +37,6 @@ export interface WidgetGraphComponentProps {
openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void;
customSeries?: (data: QueryData[]) => uPlot.Series[];
customErrorMessage?: string;
}
export interface GridCardGraphProps {
@@ -55,9 +54,6 @@ export interface GridCardGraphProps {
openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void;
customSeries?: (data: QueryData[]) => uPlot.Series[];
customErrorMessage?: string;
start?: number;
end?: number;
}
export interface GetGraphVisibilityStateOnLegendClickProps {

View File

@@ -106,16 +106,6 @@
}
}
.error-message-container {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
padding-top: 0;
padding-bottom: 32px;
}
.row-settings {
.ant-popover-inner {
width: 191px;

View File

@@ -1,19 +1,18 @@
import './InfraMonitoring.styles.scss';
import { initialQueriesMap } from 'constants/queryBuilder';
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useCallback, useMemo } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
function HostsListControls({
handleFiltersChange,
}: {
handleFiltersChange: (value: IBuilderQuery['filters']) => void;
}): JSX.Element {
const currentQuery = initialQueriesMap[DataSource.METRICS];
const { currentQuery } = useQueryBuilder();
const updatedCurrentQuery = useMemo(
() => ({
...currentQuery,

View File

@@ -141,9 +141,13 @@ function ClusterDetails({
[cluster?.meta.k8s_cluster_name],
);
const [logsAndTracesFilters, setLogsAndTracesFilters] = useState<
IBuilderQuery['filters']
>(initialFilters);
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters,
@@ -157,7 +161,8 @@ function ClusterDetails({
}, []);
useEffect(() => {
setLogsAndTracesFilters(initialFilters);
setLogFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]);
@@ -176,10 +181,6 @@ function ClusterDetails({
const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value);
logEvent('Infra Monitoring: Clusters list details tab changed', {
cluster: cluster?.clusterUID,
view: e.target.value,
});
};
const handleTimeChange = useCallback(
@@ -203,7 +204,6 @@ function ClusterDetails({
logEvent('Infra Monitoring: Clusters list details time updated', {
cluster: cluster?.clusterUID,
interval,
view: selectedView,
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -212,7 +212,7 @@ function ClusterDetails({
const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => {
setLogsAndTracesFilters((prevFilters) => {
setLogFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
);
@@ -244,7 +244,7 @@ function ClusterDetails({
const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => {
setLogsAndTracesFilters((prevFilters) => {
setTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
);
@@ -320,8 +320,8 @@ function ClusterDetails({
if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = {
...logsAndTracesFilters,
items: logsAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
...logFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
};
const compositeQuery = {
@@ -355,7 +355,7 @@ function ClusterDetails({
{
...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP,
filters: logsAndTracesFilters,
filters: tracesFilters,
},
],
},
@@ -519,7 +519,7 @@ function ClusterDetails({
isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters}
logFilters={logsAndTracesFilters}
logFilters={logFilters}
selectedInterval={selectedInterval}
queryKey="clusterLogs"
category={K8sCategory.CLUSTERS}
@@ -532,10 +532,9 @@ function ClusterDetails({
isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={logsAndTracesFilters}
tracesFilters={tracesFilters}
selectedInterval={selectedInterval}
queryKey="clusterTraces"
queryKeyFilters={[QUERY_KEYS.K8S_CLUSTER_NAME]}
/>
)}
{selectedView === VIEW_TYPES.EVENTS && (

View File

@@ -201,11 +201,6 @@ function K8sClustersList({
[groupedByRowData, groupBy],
);
const nestedClustersData = useMemo(() => {
if (!selectedRowData || !groupedByRowData?.payload?.data.records) return [];
return groupedByRowData?.payload?.data?.records || [];
}, [groupedByRowData, selectedRowData]);
const { data, isFetching, isLoading, isError } = useGetK8sClustersList(
query as K8sClustersListPayload,
{
@@ -240,11 +235,6 @@ function K8sClustersList({
}
}, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sClustersRowData>['onChange'] = useCallback(
(
pagination: TablePaginationConfig,
@@ -255,11 +245,6 @@ function K8sClustersList({
): void => {
if (pagination.current) {
setCurrentPage(pagination.current);
logEvent('Infra Monitoring: K8s clusters list page number changed', {
page: pagination.current,
pageSize,
numberOfPages,
});
}
if ('field' in sorter && sorter.order) {
@@ -271,7 +256,7 @@ function K8sClustersList({
setOrderBy(null);
}
},
[numberOfPages, pageSize],
[],
);
const { handleChangeQueryData } = useQueryOperations({
@@ -285,32 +270,25 @@ function K8sClustersList({
handleChangeQueryData('filters', value);
setCurrentPage(1);
logEvent('Infra Monitoring: K8s clusters list filters applied', {});
logEvent('Infra Monitoring: K8s list filters applied', {
filters: value,
});
},
[handleChangeQueryData],
);
useEffect(() => {
logEvent('Infra Monitoring: K8s clusters list page visited', {});
logEvent('Infra Monitoring: K8s list page visited', {});
}, []);
const selectedClusterData = useMemo(() => {
if (!selectedClusterName) return null;
if (groupBy.length > 0) {
// If grouped by, return the cluster from the formatted grouped by clusters data
return (
nestedClustersData.find(
(cluster) => cluster.meta.k8s_cluster_name === selectedClusterName,
) || null
);
}
// If not grouped by, return the cluster from the clusters data
return (
clustersData.find(
(cluster) => cluster.meta.k8s_cluster_name === selectedClusterName,
) || null
);
}, [selectedClusterName, groupBy.length, clustersData, nestedClustersData]);
}, [selectedClusterName, clustersData]);
const handleRowClick = (record: K8sClustersRowData): void => {
if (groupBy.length === 0) {
@@ -363,10 +341,6 @@ function K8sClustersList({
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
showHeader={false}
onRow={(record): { onClick: () => void; className: string } => ({
onClick: (): void => setselectedClusterName(record.clusterUID),
className: 'expanded-clickable-row',
})}
/>
{groupedByRowData?.payload?.data?.total &&
@@ -450,7 +424,6 @@ function K8sClustersList({
setCurrentPage(1);
setGroupBy(groupBy);
setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s clusters list group by changed', {});
},
[groupByFiltersData],
);
@@ -466,16 +439,6 @@ function K8sClustersList({
}
}, [groupByFiltersData]);
const onPaginationChange = (page: number, pageSize: number): void => {
setCurrentPage(page);
setPageSize(pageSize);
logEvent('Infra Monitoring: K8s clusters list page number changed', {
page,
pageSize,
numberOfPages,
});
};
return (
<div className="k8s-list">
<K8sHeader
@@ -501,7 +464,10 @@ function K8sClustersList({
total: totalCount,
showSizeChanger: true,
hideOnSinglePage: false,
onChange: onPaginationChange,
onChange: (page, pageSize): void => {
setCurrentPage(page);
setPageSize(pageSize);
},
}}
scroll={{ x: true }}
loading={{

View File

@@ -155,9 +155,13 @@ function DaemonSetDetails({
[daemonSet?.meta.k8s_daemonset_name],
);
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
IBuilderQuery['filters']
>(initialFilters);
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters,
@@ -171,7 +175,8 @@ function DaemonSetDetails({
}, []);
useEffect(() => {
setLogAndTracesFilters(initialFilters);
setLogFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]);
@@ -190,10 +195,6 @@ function DaemonSetDetails({
const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value);
logEvent('Infra Monitoring: DaemonSets list details tab changed', {
daemonSet: daemonSet?.daemonSetName,
view: e.target.value,
});
};
const handleTimeChange = useCallback(
@@ -217,7 +218,6 @@ function DaemonSetDetails({
logEvent('Infra Monitoring: DaemonSets list details time updated', {
daemonSet: daemonSet?.daemonSetName,
interval,
view: selectedView,
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -226,7 +226,7 @@ function DaemonSetDetails({
const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => {
setLogAndTracesFilters((prevFilters) => {
setLogFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '',
@@ -259,7 +259,7 @@ function DaemonSetDetails({
const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => {
setLogAndTracesFilters((prevFilters) => {
setTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '',
@@ -339,8 +339,8 @@ function DaemonSetDetails({
if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = {
...logAndTracesFilters,
items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
...logFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
};
const compositeQuery = {
@@ -374,7 +374,7 @@ function DaemonSetDetails({
{
...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP,
filters: logAndTracesFilters,
filters: tracesFilters,
},
],
},
@@ -551,7 +551,7 @@ function DaemonSetDetails({
isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters}
logFilters={logAndTracesFilters}
logFilters={logFilters}
selectedInterval={selectedInterval}
category={K8sCategory.DAEMONSETS}
queryKey="daemonsetLogs"
@@ -567,13 +567,9 @@ function DaemonSetDetails({
isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={logAndTracesFilters}
tracesFilters={tracesFilters}
selectedInterval={selectedInterval}
queryKey="daemonsetTraces"
queryKeyFilters={[
QUERY_KEYS.K8S_DAEMON_SET_NAME,
QUERY_KEYS.K8S_NAMESPACE_NAME,
]}
/>
)}
{selectedView === VIEW_TYPES.EVENTS && (

View File

@@ -220,11 +220,6 @@ function K8sDaemonSetsList({
[daemonSetsData, groupBy],
);
const nestedDaemonSetsData = useMemo(() => {
if (!selectedRowData || !groupedByRowData?.payload?.data.records) return [];
return groupedByRowData?.payload?.data?.records || [];
}, [groupedByRowData, selectedRowData]);
const columns = useMemo(() => getK8sDaemonSetsListColumns(groupBy), [groupBy]);
const handleGroupByRowClick = (record: K8sDaemonSetsRowData): void => {
@@ -243,11 +238,6 @@ function K8sDaemonSetsList({
}
}, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sDaemonSetsRowData>['onChange'] = useCallback(
(
pagination: TablePaginationConfig,
@@ -258,11 +248,6 @@ function K8sDaemonSetsList({
): void => {
if (pagination.current) {
setCurrentPage(pagination.current);
logEvent('Infra Monitoring: K8s daemonSets list page number changed', {
page: pagination.current,
pageSize,
numberOfPages,
});
}
if ('field' in sorter && sorter.order) {
@@ -274,7 +259,7 @@ function K8sDaemonSetsList({
setOrderBy(null);
}
},
[numberOfPages, pageSize],
[],
);
const { handleChangeQueryData } = useQueryOperations({
@@ -288,36 +273,25 @@ function K8sDaemonSetsList({
handleChangeQueryData('filters', value);
setCurrentPage(1);
logEvent('Infra Monitoring: K8s daemonSets list filters applied', {});
logEvent('Infra Monitoring: K8s list filters applied', {
filters: value,
});
},
[handleChangeQueryData],
);
useEffect(() => {
logEvent('Infra Monitoring: K8s daemonSets list page visited', {});
logEvent('Infra Monitoring: K8s list page visited', {});
}, []);
const selectedDaemonSetData = useMemo(() => {
if (groupBy.length > 0) {
// If grouped by, return the daemonset from the formatted grouped by data
return (
nestedDaemonSetsData.find(
(daemonSet) => daemonSet.daemonSetName === selectedDaemonSetUID,
) || null
);
}
// If not grouped by, return the daemonset from the daemonsets data
if (!selectedDaemonSetUID) return null;
return (
daemonSetsData.find(
(daemonSet) => daemonSet.daemonSetName === selectedDaemonSetUID,
) || null
);
}, [
selectedDaemonSetUID,
groupBy.length,
daemonSetsData,
nestedDaemonSetsData,
]);
}, [selectedDaemonSetUID, daemonSetsData]);
const handleRowClick = (record: K8sDaemonSetsRowData): void => {
if (groupBy.length === 0) {
@@ -370,10 +344,6 @@ function K8sDaemonSetsList({
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
showHeader={false}
onRow={(record): { onClick: () => void; className: string } => ({
onClick: (): void => setselectedDaemonSetUID(record.daemonsetUID),
className: 'expanded-clickable-row',
})}
/>
{groupedByRowData?.payload?.data?.total &&
@@ -456,8 +426,6 @@ function K8sDaemonSetsList({
setCurrentPage(1);
setGroupBy(groupBy);
setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s daemonSets list group by changed', {});
},
[groupByFiltersData],
);
@@ -473,16 +441,6 @@ function K8sDaemonSetsList({
}
}, [groupByFiltersData]);
const onPaginationChange = (page: number, pageSize: number): void => {
setCurrentPage(page);
setPageSize(pageSize);
logEvent('Infra Monitoring: K8s daemonSets list page number changed', {
page,
pageSize,
numberOfPages,
});
};
return (
<div className="k8s-list">
<K8sHeader
@@ -510,7 +468,10 @@ function K8sDaemonSetsList({
total: totalCount,
showSizeChanger: true,
hideOnSinglePage: false,
onChange: onPaginationChange,
onChange: (page, pageSize): void => {
setCurrentPage(page);
setPageSize(pageSize);
},
}}
scroll={{ x: true }}
loading={{

View File

@@ -157,9 +157,13 @@ function DeploymentDetails({
[deployment?.meta.k8s_deployment_name],
);
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
IBuilderQuery['filters']
>(initialFilters);
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters,
@@ -173,7 +177,8 @@ function DeploymentDetails({
}, []);
useEffect(() => {
setLogAndTracesFilters(initialFilters);
setLogFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]);
@@ -192,10 +197,6 @@ function DeploymentDetails({
const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value);
logEvent('Infra Monitoring: Deployments list details tab changed', {
deployment: deployment?.deploymentName,
view: e.target.value,
});
};
const handleTimeChange = useCallback(
@@ -219,7 +220,6 @@ function DeploymentDetails({
logEvent('Infra Monitoring: Deployments list details time updated', {
deployment: deployment?.deploymentName,
interval,
view: selectedView,
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -228,7 +228,7 @@ function DeploymentDetails({
const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => {
setLogAndTracesFilters((prevFilters) => {
setLogFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '',
@@ -266,7 +266,7 @@ function DeploymentDetails({
const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => {
setLogAndTracesFilters((prevFilters) => {
setTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '',
@@ -350,8 +350,8 @@ function DeploymentDetails({
if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = {
...logAndTracesFilters,
items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
...logFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
};
const compositeQuery = {
@@ -385,7 +385,7 @@ function DeploymentDetails({
{
...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP,
filters: logAndTracesFilters,
filters: tracesFilters,
},
],
},
@@ -562,7 +562,7 @@ function DeploymentDetails({
isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters}
logFilters={logAndTracesFilters}
logFilters={logFilters}
selectedInterval={selectedInterval}
queryKeyFilters={[
QUERY_KEYS.K8S_DEPLOYMENT_NAME,
@@ -578,13 +578,9 @@ function DeploymentDetails({
isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={logAndTracesFilters}
tracesFilters={tracesFilters}
selectedInterval={selectedInterval}
queryKey="deploymentTraces"
queryKeyFilters={[
QUERY_KEYS.K8S_DEPLOYMENT_NAME,
QUERY_KEYS.K8S_NAMESPACE_NAME,
]}
/>
)}
{selectedView === VIEW_TYPES.EVENTS && (

View File

@@ -220,11 +220,6 @@ function K8sDeploymentsList({
[deploymentsData, groupBy],
);
const nestedDeploymentsData = useMemo(() => {
if (!selectedRowData || !groupedByRowData?.payload?.data.records) return [];
return groupedByRowData?.payload?.data?.records || [];
}, [groupedByRowData, selectedRowData]);
const columns = useMemo(() => getK8sDeploymentsListColumns(groupBy), [
groupBy,
]);
@@ -245,11 +240,6 @@ function K8sDeploymentsList({
}
}, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sDeploymentsRowData>['onChange'] = useCallback(
(
pagination: TablePaginationConfig,
@@ -260,11 +250,6 @@ function K8sDeploymentsList({
): void => {
if (pagination.current) {
setCurrentPage(pagination.current);
logEvent('Infra Monitoring: K8s deployments list page number changed', {
page: pagination.current,
pageSize,
numberOfPages,
});
}
if ('field' in sorter && sorter.order) {
@@ -276,7 +261,7 @@ function K8sDeploymentsList({
setOrderBy(null);
}
},
[numberOfPages, pageSize],
[],
);
const { handleChangeQueryData } = useQueryOperations({
@@ -290,37 +275,25 @@ function K8sDeploymentsList({
handleChangeQueryData('filters', value);
setCurrentPage(1);
logEvent('Infra Monitoring: K8s deployments list filters applied', {});
logEvent('Infra Monitoring: K8s list filters applied', {
filters: value,
});
},
[handleChangeQueryData],
);
useEffect(() => {
logEvent('Infra Monitoring: K8s deployments list page visited', {});
logEvent('Infra Monitoring: K8s list page visited', {});
}, []);
const selectedDeploymentData = useMemo(() => {
if (!selectedDeploymentUID) return null;
if (groupBy.length > 0) {
// If grouped by, return the deployment from the formatted grouped by deployments data
return (
nestedDeploymentsData.find(
(deployment) => deployment.deploymentName === selectedDeploymentUID,
) || null
);
}
// If not grouped by, return the deployment from the deployments data
return (
deploymentsData.find(
(deployment) => deployment.deploymentName === selectedDeploymentUID,
) || null
);
}, [
selectedDeploymentUID,
groupBy.length,
deploymentsData,
nestedDeploymentsData,
]);
}, [selectedDeploymentUID, deploymentsData]);
const handleRowClick = (record: K8sDeploymentsRowData): void => {
if (groupBy.length === 0) {
@@ -373,10 +346,6 @@ function K8sDeploymentsList({
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
showHeader={false}
onRow={(record): { onClick: () => void; className: string } => ({
onClick: (): void => setselectedDeploymentUID(record.deploymentUID),
className: 'expanded-clickable-row',
})}
/>
{groupedByRowData?.payload?.data?.total &&
@@ -460,8 +429,6 @@ function K8sDeploymentsList({
setCurrentPage(1);
setGroupBy(groupBy);
setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s deployments list group by changed', {});
},
[groupByFiltersData],
);
@@ -477,16 +444,6 @@ function K8sDeploymentsList({
}
}, [groupByFiltersData]);
const onPaginationChange = (page: number, pageSize: number): void => {
setCurrentPage(page);
setPageSize(pageSize);
logEvent('Infra Monitoring: K8s deployments list page number changed', {
page,
pageSize,
numberOfPages,
});
};
return (
<div className="k8s-list">
<K8sHeader
@@ -514,7 +471,10 @@ function K8sDeploymentsList({
total: totalCount,
showSizeChanger: true,
hideOnSinglePage: false,
onChange: onPaginationChange,
onChange: (page, pageSize): void => {
setCurrentPage(page);
setPageSize(pageSize);
},
}}
scroll={{ x: true }}
loading={{

View File

@@ -12,7 +12,6 @@ import { useMemo } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { filterOutPrimaryFilters } from '../utils';
import EntityLogs from './EntityLogs';
interface Props {
@@ -59,14 +58,14 @@ function EntityLogsDetailedView({
...currentQuery.builder.queryData[0].aggregateAttribute,
},
filters: {
items: filterOutPrimaryFilters(logFilters.items, queryKeyFilters),
items: [],
op: 'AND',
},
},
],
},
}),
[currentQuery, logFilters.items, queryKeyFilters],
[currentQuery],
);
const query = updatedCurrentQuery?.builder?.queryData[0] || null;

View File

@@ -26,7 +26,6 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import {
filterOutPrimaryFilters,
getEntityTracesQueryPayload,
selectedEntityTracesColumns,
} from '../utils';
@@ -45,7 +44,6 @@ interface Props {
tracesFilters: IBuilderQuery['filters'];
selectedInterval: Time;
queryKey: string;
queryKeyFilters: string[];
}
function EntityTraces({
@@ -56,7 +54,6 @@ function EntityTraces({
tracesFilters,
selectedInterval,
queryKey,
queryKeyFilters,
}: Props): JSX.Element {
const [traces, setTraces] = useState<any[]>([]);
const [offset] = useState<number>(0);
@@ -76,14 +73,14 @@ function EntityTraces({
...currentQuery.builder.queryData[0].aggregateAttribute,
},
filters: {
items: filterOutPrimaryFilters(tracesFilters.items, queryKeyFilters),
items: [],
op: 'AND',
},
},
],
},
}),
[currentQuery, queryKeyFilters, tracesFilters.items],
[currentQuery],
);
const query = updatedCurrentQuery?.builder?.queryData[0] || null;

View File

@@ -7,10 +7,7 @@ import {
BaseAutocompleteData,
DataTypes,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
IBuilderQuery,
TagFilterItem,
} from 'types/api/queryBuilder/queryBuilderData';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
import { nanoToMilli } from 'utils/timeUtils';
@@ -304,12 +301,3 @@ export const getEntityTracesQueryPayload = (
],
},
});
export const filterOutPrimaryFilters = (
filters: TagFilterItem[],
primaryKeys: string[],
): TagFilterItem[] =>
filters.filter(
(filter) =>
!primaryKeys.includes(filter.key?.key ?? '') && filter.key?.key !== 'id',
);

View File

@@ -884,7 +884,3 @@
.ant-table-content {
margin-bottom: 60px !important;
}
.expanded-clickable-row {
cursor: pointer;
}

View File

@@ -4,7 +4,6 @@ import { VerticalAlignTopOutlined } from '@ant-design/icons';
import * as Sentry from '@sentry/react';
import type { CollapseProps } from 'antd';
import { Collapse, Tooltip, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource } from 'components/QuickFilters/types';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -69,11 +68,6 @@ export default function InfraMonitoringK8s(): JSX.Element {
// 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());
logEvent(
`Infra Monitoring: K8s ${selectedCategory} list quick filters applied`,
{},
);
};
const items: CollapseProps['items'] = [

Some files were not shown because too many files have changed in this diff Show More