Compare commits

..

2 Commits

Author SHA1 Message Date
Swapnil Nakade
10b55bb00f Merge branch 'main' into refactor/update-cloud-integration-version 2026-01-08 19:57:01 +05:30
Swapnil Nakade
0f6cce2c24 chore: update cloud integration agent version to v0.0.8 2026-01-08 16:10:23 +05:30
279 changed files with 3875 additions and 10507 deletions

33
.github/CODEOWNERS vendored
View File

@@ -1,13 +1,10 @@
# CODEOWNERS info: https://help.github.com/en/articles/about-code-owners
# Owners are automatically requested for review for PRs that changes code
# that they own.
/frontend/ @SigNoz/frontend-maintainers
# Onboarding
/frontend/src/container/OnboardingV2Container/onboarding-configs/onboarding-config-with-links.json @makeavish
/frontend/src/container/OnboardingV2Container/AddDataSource/AddDataSource.tsx @makeavish
@@ -15,7 +12,6 @@
.github @SigNoz/devops
# Scaffold Owners
/pkg/config/ @therealpandey
/pkg/errors/ @therealpandey
/pkg/factory/ @therealpandey
@@ -25,26 +21,22 @@
.golangci.yml @therealpandey
# Zeus Owners
/pkg/zeus/ @vikrantgupta25
/ee/zeus/ @vikrantgupta25
/pkg/licensing/ @vikrantgupta25
/ee/licensing/ @vikrantgupta25
# SQL Owners
/pkg/sqlmigration/ @vikrantgupta25
/ee/sqlmigration/ @vikrantgupta25
/pkg/sqlschema/ @vikrantgupta25
/ee/sqlschema/ @vikrantgupta25
# Analytics Owners
/pkg/analytics/ @vikrantgupta25
/pkg/statsreporter/ @vikrantgupta25
# Querier Owners
/pkg/querier/ @srikanthccv
/pkg/variables/ @srikanthccv
/pkg/types/querybuildertypes/ @srikanthccv
@@ -54,31 +46,8 @@
/pkg/telemetrymetrics/ @srikanthccv
/pkg/telemetrytraces/ @srikanthccv
# AuthN / AuthZ Owners
# AuthN / AuthZ Owners
/pkg/authz/ @vikrantgupta25 @therealpandey
# Integration tests
/tests/integration/ @therealpandey
# Dashboard Owners
/frontend/src/hooks/dashboard/ @SigNoz/pulse-frontend
## Dashboard List
/frontend/src/pages/DashboardsListPage/ @SigNoz/pulse-frontend
/frontend/src/container/ListOfDashboard/ @SigNoz/pulse-frontend
## Dashboard Page
/frontend/src/pages/DashboardPage/ @SigNoz/pulse-frontend
/frontend/src/container/DashboardContainer/ @SigNoz/pulse-frontend
/frontend/src/container/GridCardLayout/ @SigNoz/pulse-frontend
/frontend/src/container/NewWidget/ @SigNoz/pulse-frontend
## Public Dashboard Page
/frontend/src/pages/PublicDashboard/ @SigNoz/pulse-frontend
/frontend/src/container/PublicDashboardContainer/ @SigNoz/pulse-frontend

View File

@@ -21,11 +21,11 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: 3.13
- name: uv
uses: astral-sh/setup-uv@v4
- name: install
- name: poetry
run: |
cd tests/integration && uv sync
python -m pip install poetry==2.1.2
python -m poetry config virtualenvs.in-project true
cd tests/integration && poetry install --no-root
- name: fmt
run: |
make py-fmt
@@ -67,11 +67,11 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: 3.13
- name: uv
uses: astral-sh/setup-uv@v4
- name: install
- name: poetry
run: |
cd tests/integration && uv sync
python -m pip install poetry==2.1.2
python -m poetry config virtualenvs.in-project true
cd tests/integration && poetry install --no-root
- name: webdriver
run: |
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
@@ -89,7 +89,7 @@ jobs:
- name: run
run: |
cd tests/integration && \
uv run pytest \
poetry run pytest \
--basetemp=./tmp/ \
src/${{matrix.src}} \
--sqlstore-provider ${{matrix.sqlstore-provider}} \

2
.gitignore vendored
View File

@@ -222,6 +222,8 @@ cython_debug/
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/

View File

@@ -1,10 +0,0 @@
# Link to template variables: https://pkg.go.dev/github.com/vektra/mockery/v3/config#TemplateData
template: testify
packages:
github.com/SigNoz/signoz/pkg/alertmanager:
config:
all: true
dir: '{{.InterfaceDir}}/mocks'
filename: "mocks.go"
structname: 'Mock{{.InterfaceName}}'
pkgname: '{{.SrcPackageName}}mock'

View File

@@ -202,25 +202,25 @@ docker-buildx-enterprise: go-build-enterprise js-build
##############################################################
.PHONY: py-fmt
py-fmt: ## Run black for integration tests
@cd tests/integration && uv run black .
@cd tests/integration && poetry run black .
.PHONY: py-lint
py-lint: ## Run lint for integration tests
@cd tests/integration && uv run isort .
@cd tests/integration && uv run autoflake .
@cd tests/integration && uv run pylint .
@cd tests/integration && poetry run isort .
@cd tests/integration && poetry run autoflake .
@cd tests/integration && poetry run pylint .
.PHONY: py-test-setup
py-test-setup: ## Runs integration tests
@cd tests/integration && uv run pytest --basetemp=./tmp/ -vv --reuse --capture=no src/bootstrap/setup.py::test_setup
@cd tests/integration && poetry run pytest --basetemp=./tmp/ -vv --reuse --capture=no src/bootstrap/setup.py::test_setup
.PHONY: py-test-teardown
py-test-teardown: ## Runs integration tests with teardown
@cd tests/integration && uv run pytest --basetemp=./tmp/ -vv --teardown --capture=no src/bootstrap/setup.py::test_teardown
@cd tests/integration && poetry run pytest --basetemp=./tmp/ -vv --teardown --capture=no src/bootstrap/setup.py::test_teardown
.PHONY: py-test
py-test: ## Runs integration tests
@cd tests/integration && uv run pytest --basetemp=./tmp/ -vv --capture=no src/
@cd tests/integration && poetry run pytest --basetemp=./tmp/ -vv --capture=no src/
.PHONY: py-clean
py-clean: ## Clear all pycache and pytest cache from tests directory recursively

View File

@@ -9,7 +9,7 @@ SigNoz uses integration tests to verify that different components work together
Before running integration tests, ensure you have the following installed:
- Python 3.13+
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
- Poetry (for dependency management)
- Docker (for containerized services)
### Initial Setup
@@ -19,19 +19,17 @@ Before running integration tests, ensure you have the following installed:
cd tests/integration
```
2. Install dependencies using uv:
2. Install dependencies using Poetry:
```bash
uv sync
poetry install --no-root
```
> **_NOTE:_** the build backend could throw an error while installing `psycopg2`, pleae see https://www.psycopg.org/docs/install.html#build-prerequisites
### Starting the Test Environment
To spin up all the containers necessary for writing integration tests and keep them running:
```bash
uv run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/setup.py::test_setup
poetry run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/setup.py::test_setup
```
This command will:
@@ -44,7 +42,7 @@ This command will:
When you're done writing integration tests, clean up the environment:
```bash
uv run pytest --basetemp=./tmp/ -vv --teardown -s src/bootstrap/setup.py::test_teardown
poetry run pytest --basetemp=./tmp/ -vv --teardown -s src/bootstrap/setup.py::test_teardown
```
This will destroy the running integration test setup and clean up resources.
@@ -74,20 +72,20 @@ Python and pytest form the foundation of the integration testing framework. Test
│ ├── sqlite.py
│ ├── types.py
│ └── zookeeper.py
├── uv.lock
├── poetry.lock
├── pyproject.toml
└── src
└── bootstrap
├── __init__.py
├── 01_database.py
├── 02_register.py
└── 03_license.py
├── a_database.py
├── b_register.py
└── c_license.py
```
Each test suite follows some important principles:
1. **Organization**: Test suites live under `src/` in self-contained packages. Fixtures (a pytest concept) live inside `fixtures/`.
2. **Execution Order**: Files are prefixed with two-digit numbers (`01_`, `02_`, `03_`) to ensure sequential execution.
2. **Execution Order**: Files are prefixed with `a_`, `b_`, `c_` to ensure sequential execution.
3. **Time Constraints**: Each suite should complete in under 10 minutes (setup takes ~4 mins).
### Test Suite Design
@@ -109,7 +107,7 @@ Other test suites can be **pipelines, auth, querier.**
## How to write an integration test?
Now start writing an integration test. Create a new file `src/bootstrap/05_version.py` and paste the following:
Now start writing an integration test. Create a new file `src/bootstrap/e_version.py` and paste the following:
```python
import requests
@@ -127,7 +125,7 @@ def test_version(signoz: types.SigNoz) -> None:
We have written a simple test which calls the `version` endpoint of the container in step 1. In **order to just run this function, run the following command:**
```bash
uv run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/05_version.py::test_version
poetry run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/e_version.py::test_version
```
> Note: The `--reuse` flag is used to reuse the environment if it is already running. Always use this flag when writing and running integration tests. If you don't use this flag, the environment will be destroyed and recreated every time you run the test.
@@ -155,7 +153,7 @@ def test_user_registration(signoz: types.SigNoz) -> None:
},
timeout=2,
)
assert response.status_code == HTTPStatus.OK
assert response.json()["setupCompleted"] is True
```
@@ -165,27 +163,27 @@ def test_user_registration(signoz: types.SigNoz) -> None:
### Running All Tests
```bash
uv run pytest --basetemp=./tmp/ -vv --reuse src/
poetry run pytest --basetemp=./tmp/ -vv --reuse src/
```
### Running Specific Test Categories
```bash
uv run pytest --basetemp=./tmp/ -vv --reuse src/<suite>
poetry run pytest --basetemp=./tmp/ -vv --reuse src/<suite>
# Run querier tests
uv run pytest --basetemp=./tmp/ -vv --reuse src/querier/
poetry run pytest --basetemp=./tmp/ -vv --reuse src/querier/
# Run auth tests
uv run pytest --basetemp=./tmp/ -vv --reuse src/auth/
poetry run pytest --basetemp=./tmp/ -vv --reuse src/auth/
```
### Running Individual Tests
```bash
uv run pytest --basetemp=./tmp/ -vv --reuse src/<suite>/<file>.py::test_name
poetry run pytest --basetemp=./tmp/ -vv --reuse src/<suite>/<file>.py::test_name
# Run test_register in file 01_register.py in passwordauthn suite
uv run pytest --basetemp=./tmp/ -vv --reuse src/passwordauthn/01_register.py::test_register
# Run test_register in file a_register.py in auth suite
poetry run pytest --basetemp=./tmp/ -vv --reuse src/auth/a_register.py::test_register
```
## How to configure different options for integration tests?
@@ -199,7 +197,7 @@ Tests can be configured using pytest options:
Example:
```bash
uv run pytest --basetemp=./tmp/ -vv --reuse --sqlstore-provider=postgres --postgres-version=14 src/auth/
poetry run pytest --basetemp=./tmp/ -vv --reuse --sqlstore-provider=postgres --postgres-version=14 src/auth/
```
@@ -207,7 +205,7 @@ uv run pytest --basetemp=./tmp/ -vv --reuse --sqlstore-provider=postgres --postg
- **Always use the `--reuse` flag** when setting up the environment to keep containers running
- **Use the `--teardown` flag** when cleaning up to avoid resource leaks
- **Follow the naming convention** with two-digit numeric prefixes (`01_`, `02_`) for test execution order
- **Follow the naming convention** with alphabetical prefixes for test execution order
- **Use proper timeouts** in HTTP requests to avoid hanging tests
- **Clean up test data** between tests to avoid interference
- **Use descriptive test names** that clearly indicate what is being tested

View File

@@ -163,7 +163,7 @@ func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.U
}
err = module.store.RunInTx(ctx, func(ctx context.Context) error {
err := module.deletePublic(ctx, orgID, id)
err := module.DeletePublic(ctx, orgID, id)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return err
}
@@ -263,35 +263,3 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error {
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, role, lock)
}
func (module *module) deletePublic(ctx context.Context, orgID valuer.UUID, dashboardID valuer.UUID) error {
publicDashboard, err := module.store.GetPublic(ctx, dashboardID.String())
if err != nil {
return err
}
role, err := module.role.GetOrCreate(ctx, roletypes.NewRole(roletypes.AnonymousUserRoleName, roletypes.AnonymousUserRoleDescription, roletypes.RoleTypeManaged.StringValue(), orgID))
if err != nil {
return err
}
deletionObject := authtypes.MustNewObject(
authtypes.Resource{
Name: dashboardtypes.TypeableMetaResourcePublicDashboard.Name(),
Type: authtypes.TypeMetaResource,
},
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
)
err = module.role.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject})
if err != nil {
return err
}
err = module.store.DeletePublic(ctx, dashboardID.StringValue())
if err != nil {
return err
}
return nil
}

View File

@@ -12,7 +12,6 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler/rulestore/sqlrulestore"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
"go.opentelemetry.io/otel/propagation"
@@ -105,7 +104,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
signoz.Alertmanager,
signoz.SQLStore,
signoz.TelemetryStore,
signoz.TelemetryMetadataStore,
signoz.Prometheus,
signoz.Modules.OrgGetter,
signoz.Querier,
@@ -357,13 +355,12 @@ func (s *Server) Stop(ctx context.Context) error {
return nil
}
func makeRulesManager(ch baseint.Reader, cache cache.Cache, alertmanager alertmanager.Alertmanager, sqlstore sqlstore.SQLStore, telemetryStore telemetrystore.TelemetryStore, metadataStore telemetrytypes.MetadataStore, prometheus prometheus.Prometheus, orgGetter organization.Getter, querier querier.Querier, providerSettings factory.ProviderSettings, queryParser queryparser.QueryParser) (*baserules.Manager, error) {
func makeRulesManager(ch baseint.Reader, cache cache.Cache, alertmanager alertmanager.Alertmanager, sqlstore sqlstore.SQLStore, telemetryStore telemetrystore.TelemetryStore, prometheus prometheus.Prometheus, orgGetter organization.Getter, querier querier.Querier, providerSettings factory.ProviderSettings, queryParser queryparser.QueryParser) (*baserules.Manager, error) {
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
maintenanceStore := sqlrulestore.NewMaintenanceStore(sqlstore)
// create manager opts
managerOpts := &baserules.ManagerOptions{
TelemetryStore: telemetryStore,
MetadataStore: metadataStore,
Prometheus: prometheus,
Context: context.Background(),
Logger: zap.L(),
@@ -379,7 +376,6 @@ func makeRulesManager(ch baseint.Reader, cache cache.Cache, alertmanager alertma
RuleStore: ruleStore,
MaintenanceStore: maintenanceStore,
SqlStore: sqlstore,
QueryParser: queryParser,
}
// create Manager

View File

@@ -247,8 +247,7 @@ func (r *AnomalyRule) buildAndRunQuery(ctx context.Context, orgID valuer.UUID, t
}
}
results, err := r.Threshold.Eval(*series, r.Unit(), ruletypes.EvalData{
ActiveAlerts: r.ActiveAlertsLabelFP(),
SendUnmatched: r.ShouldSendUnmatched(),
ActiveAlerts: r.ActiveAlertsLabelFP(),
})
if err != nil {
return nil, err
@@ -292,19 +291,7 @@ func (r *AnomalyRule) buildAndRunQueryV5(ctx context.Context, orgID valuer.UUID,
scoresJSON, _ := json.Marshal(queryResult.AnomalyScores)
r.logger.InfoContext(ctx, "anomaly scores", "scores", string(scoresJSON))
// Filter out new series if newGroupEvalDelay is configured
seriesToProcess := queryResult.AnomalyScores
if r.ShouldSkipNewGroups() {
filteredSeries, filterErr := r.BaseRule.FilterNewSeries(ctx, ts, seriesToProcess)
// In case of error we log the error and continue with the original series
if filterErr != nil {
r.logger.ErrorContext(ctx, "Error filtering new series, ", "error", filterErr, "rule_name", r.Name())
} else {
seriesToProcess = filteredSeries
}
}
for _, series := range seriesToProcess {
for _, series := range queryResult.AnomalyScores {
if r.Condition().RequireMinPoints {
if len(series.Points) < r.Condition().RequiredNumPoints {
r.logger.InfoContext(ctx, "not enough data points to evaluate series, skipping", "ruleid", r.ID(), "numPoints", len(series.Points), "requiredPoints", r.Condition().RequiredNumPoints)
@@ -312,8 +299,7 @@ func (r *AnomalyRule) buildAndRunQueryV5(ctx context.Context, orgID valuer.UUID,
}
}
results, err := r.Threshold.Eval(*series, r.Unit(), ruletypes.EvalData{
ActiveAlerts: r.ActiveAlertsLabelFP(),
SendUnmatched: r.ShouldSendUnmatched(),
ActiveAlerts: r.ActiveAlertsLabelFP(),
})
if err != nil {
return nil, err

View File

@@ -37,8 +37,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
opts.SLogger,
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
)
if err != nil {
@@ -61,8 +59,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
opts.Reader,
opts.ManagerOpts.Prometheus,
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
)
if err != nil {
@@ -86,8 +82,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
opts.Cache,
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
)
if err != nil {
return task, err
@@ -146,8 +140,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
baserules.WithSendAlways(),
baserules.WithSendUnmatched(),
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
)
if err != nil {
@@ -168,8 +160,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
baserules.WithSendAlways(),
baserules.WithSendUnmatched(),
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
)
if err != nil {
@@ -189,8 +179,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
baserules.WithSendAlways(),
baserules.WithSendUnmatched(),
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
)
if err != nil {
zap.L().Error("failed to prepare a new anomaly rule for test", zap.String("name", alertname), zap.Error(err))

View File

@@ -1,283 +0,0 @@
package rules
import (
"context"
"encoding/json"
"math"
"strconv"
"testing"
"time"
"github.com/SigNoz/signoz/pkg/alertmanager"
alertmanagermock "github.com/SigNoz/signoz/pkg/alertmanager/mocks"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/prometheus/prometheustest"
"github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstoretest"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
cmock "github.com/srikanthccv/ClickHouse-go-mock"
)
func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
target := 10.0
recovery := 5.0
for _, tc := range rules.TcTestNotiSendUnmatchedThresholdRule {
t.Run(tc.Name, func(t *testing.T) {
rule := rules.ThresholdRuleAtLeastOnceValueAbove(target, &recovery)
// Marshal rule to JSON as TestNotification expects
ruleBytes, err := json.Marshal(rule)
require.NoError(t, err)
orgID := valuer.GenerateUUID()
// for saving temp alerts that are triggered via TestNotification
triggeredTestAlerts := []map[*alertmanagertypes.PostableAlert][]string{}
// Create manager using test factory with hooks
mgr := rules.NewTestManager(t, &rules.TestManagerOptions{
AlertmanagerHook: func(am alertmanager.Alertmanager) {
fAlert := am.(*alertmanagermock.MockAlertmanager)
// mock set notification config
fAlert.On("SetNotificationConfig", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
// for saving temp alerts that are triggered via TestNotification
if tc.ExpectAlerts > 0 {
fAlert.On("TestAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
triggeredTestAlerts = append(triggeredTestAlerts, args.Get(3).(map[*alertmanagertypes.PostableAlert][]string))
}).Return(nil).Times(tc.ExpectAlerts)
}
},
ManagerOptionsHook: func(opts *rules.ManagerOptions) {
opts.PrepareTestRuleFunc = TestNotification
},
SqlStoreHook: func(store sqlstore.SQLStore) {
mockStore := store.(*sqlstoretest.Provider)
// Mock the organizations query that SendAlerts makes
// Bun generates: SELECT id FROM organizations LIMIT 1 (or SELECT "id" FROM "organizations" LIMIT 1)
orgRows := mockStore.Mock().NewRows([]string{"id"}).AddRow(orgID.StringValue())
// Match bun's generated query pattern - bun may quote identifiers
mockStore.Mock().ExpectQuery("SELECT (.+) FROM (.+)organizations(.+) LIMIT (.+)").WillReturnRows(orgRows)
},
TelemetryStoreHook: func(store telemetrystore.TelemetryStore) {
telemetryStore := store.(*telemetrystoretest.Provider)
// Set up mock data for telemetry store
cols := make([]cmock.ColumnType, 0)
cols = append(cols, cmock.ColumnType{Name: "value", Type: "Float64"})
cols = append(cols, cmock.ColumnType{Name: "attr", Type: "String"})
cols = append(cols, cmock.ColumnType{Name: "ts", Type: "DateTime"})
alertDataRows := cmock.NewRows(cols, tc.Values)
mock := telemetryStore.Mock()
// Generate query arguments for the metric query
evalTime := time.Now().UTC()
evalWindow := 5 * time.Minute
evalDelay := time.Duration(0)
queryArgs := rules.GenerateMetricQueryCHArgs(
evalTime,
evalWindow,
evalDelay,
"probe_success",
metrictypes.Unspecified,
)
mock.ExpectQuery("*WITH __temporal_aggregation_cte*").
WithArgs(queryArgs...).
WillReturnRows(alertDataRows)
},
})
count, apiErr := mgr.TestNotification(context.Background(), orgID, string(ruleBytes))
if apiErr != nil {
t.Logf("TestNotification error: %v, type: %s", apiErr.Err, apiErr.Typ)
}
require.Nil(t, apiErr)
assert.Equal(t, tc.ExpectAlerts, count)
if tc.ExpectAlerts > 0 {
// check if the alert has been triggered
require.Len(t, triggeredTestAlerts, 1)
var gotAlerts []*alertmanagertypes.PostableAlert
for a := range triggeredTestAlerts[0] {
gotAlerts = append(gotAlerts, a)
}
require.Len(t, gotAlerts, tc.ExpectAlerts)
// check if the alert has triggered with correct threshold value
if tc.ExpectValue != 0 {
assert.Equal(t, strconv.FormatFloat(tc.ExpectValue, 'f', -1, 64), gotAlerts[0].Annotations["value"])
}
} else {
// check if no alerts have been triggered
assert.Empty(t, triggeredTestAlerts)
}
})
}
}
func TestManager_TestNotification_SendUnmatched_PromRule(t *testing.T) {
target := 10.0
for _, tc := range rules.TcTestNotificationSendUnmatchedPromRule {
t.Run(tc.Name, func(t *testing.T) {
// Capture base time once per test case to ensure consistent timestamps
baseTime := time.Now().UTC()
rule := rules.BuildPromAtLeastOnceValueAbove(target, nil)
// Marshal rule to JSON as TestNotification expects
ruleBytes, err := json.Marshal(rule)
require.NoError(t, err)
orgID := valuer.GenerateUUID()
// for saving temp alerts that are triggered via TestNotification
triggeredTestAlerts := []map[*alertmanagertypes.PostableAlert][]string{}
// Variable to store promProvider for cleanup
var promProvider *prometheustest.Provider
// Create manager using test factory with hooks
mgr := rules.NewTestManager(t, &rules.TestManagerOptions{
AlertmanagerHook: func(am alertmanager.Alertmanager) {
mockAM := am.(*alertmanagermock.MockAlertmanager)
// mock set notification config
mockAM.On("SetNotificationConfig", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
// for saving temp alerts that are triggered via TestNotification
if tc.ExpectAlerts > 0 {
mockAM.On("TestAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
triggeredTestAlerts = append(triggeredTestAlerts, args.Get(3).(map[*alertmanagertypes.PostableAlert][]string))
}).Return(nil).Times(tc.ExpectAlerts)
}
},
SqlStoreHook: func(store sqlstore.SQLStore) {
mockStore := store.(*sqlstoretest.Provider)
// Mock the organizations query that SendAlerts makes
orgRows := mockStore.Mock().NewRows([]string{"id"}).AddRow(orgID.StringValue())
mockStore.Mock().ExpectQuery("SELECT (.+) FROM (.+)organizations(.+) LIMIT (.+)").WillReturnRows(orgRows)
},
TelemetryStoreHook: func(store telemetrystore.TelemetryStore) {
mockStore := store.(*telemetrystoretest.Provider)
// Set up Prometheus-specific mock data
// Fingerprint columns for Prometheus queries
fingerprintCols := []cmock.ColumnType{
{Name: "fingerprint", Type: "UInt64"},
{Name: "any(labels)", Type: "String"},
}
// Samples columns for Prometheus queries
samplesCols := []cmock.ColumnType{
{Name: "metric_name", Type: "String"},
{Name: "fingerprint", Type: "UInt64"},
{Name: "unix_milli", Type: "Int64"},
{Name: "value", Type: "Float64"},
{Name: "flags", Type: "UInt32"},
}
// Calculate query time range similar to Prometheus rule tests
// TestNotification uses time.Now().UTC() for evaluation
// We calculate the query window based on current time to match what the actual evaluation will use
evalTime := baseTime
evalWindowMs := int64(5 * 60 * 1000) // 5 minutes in ms
evalTimeMs := evalTime.UnixMilli()
queryStart := ((evalTimeMs-2*evalWindowMs)/60000)*60000 + 1 // truncate to minute + 1ms
queryEnd := (evalTimeMs / 60000) * 60000 // truncate to minute
// Create fingerprint data
fingerprint := uint64(12345)
labelsJSON := `{"__name__":"test_metric"}`
fingerprintData := [][]interface{}{
{fingerprint, labelsJSON},
}
fingerprintRows := cmock.NewRows(fingerprintCols, fingerprintData)
// Create samples data from test case values, calculating timestamps relative to baseTime
validSamplesData := make([][]interface{}, 0)
for _, v := range tc.Values {
// Skip NaN and Inf values in the samples data
if math.IsNaN(v.Value) || math.IsInf(v.Value, 0) {
continue
}
// Calculate timestamp relative to baseTime
sampleTimestamp := baseTime.Add(v.Offset).UnixMilli()
validSamplesData = append(validSamplesData, []interface{}{
"test_metric",
fingerprint,
sampleTimestamp,
v.Value,
uint32(0), // flags - 0 means normal value
})
}
samplesRows := cmock.NewRows(samplesCols, validSamplesData)
mock := mockStore.Mock()
// Mock the fingerprint query (for Prometheus label matching)
mock.ExpectQuery("SELECT fingerprint, any").
WithArgs("test_metric", "__name__", "test_metric").
WillReturnRows(fingerprintRows)
// Mock the samples query (for Prometheus metric data)
mock.ExpectQuery("SELECT metric_name, fingerprint, unix_milli").
WithArgs(
"test_metric",
"test_metric",
"__name__",
"test_metric",
queryStart,
queryEnd,
).
WillReturnRows(samplesRows)
// Create Prometheus provider for this test
promProvider = prometheustest.New(context.Background(), instrumentationtest.New().ToProviderSettings(), prometheus.Config{}, store)
},
ManagerOptionsHook: func(opts *rules.ManagerOptions) {
// Set Prometheus provider for PromQL queries
if promProvider != nil {
opts.Prometheus = promProvider
}
opts.PrepareTestRuleFunc = TestNotification
},
})
count, apiErr := mgr.TestNotification(context.Background(), orgID, string(ruleBytes))
if apiErr != nil {
t.Logf("TestNotification error: %v, type: %s", apiErr.Err, apiErr.Typ)
}
require.Nil(t, apiErr)
assert.Equal(t, tc.ExpectAlerts, count)
if tc.ExpectAlerts > 0 {
// check if the alert has been triggered
require.Len(t, triggeredTestAlerts, 1)
var gotAlerts []*alertmanagertypes.PostableAlert
for a := range triggeredTestAlerts[0] {
gotAlerts = append(gotAlerts, a)
}
require.Len(t, gotAlerts, tc.ExpectAlerts)
// check if the alert has triggered with correct threshold value
if tc.ExpectValue != 0 && !math.IsNaN(tc.ExpectValue) && !math.IsInf(tc.ExpectValue, 0) {
assert.Equal(t, strconv.FormatFloat(tc.ExpectValue, 'f', -1, 64), gotAlerts[0].Annotations["value"])
}
} else {
// check if no alerts have been triggered
assert.Empty(t, triggeredTestAlerts)
}
promProvider.Close()
})
}
}

View File

@@ -45,7 +45,6 @@
"DEFAULT": "Open source Observability Platform | SigNoz",
"ALERT_HISTORY": "SigNoz | Alert Rule History",
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview",
"ALERT_TYPE_SELECTION": "SigNoz | Select Alert Type",
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring",
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring",
"METER_EXPLORER": "SigNoz | Meter Explorer",

View File

@@ -93,15 +93,13 @@ export const OnboardingV2 = Loadable(
() => import(/* webpackChunkName: "Onboarding V2" */ 'pages/OnboardingPageV2'),
);
export const DashboardsListPage = Loadable(
export const DashboardPage = Loadable(
() =>
import(
/* webpackChunkName: "DashboardsListPage" */ 'pages/DashboardsListPage'
),
import(/* webpackChunkName: "DashboardPage" */ 'pages/DashboardsListPage'),
);
export const DashboardPage = Loadable(
() => import(/* webpackChunkName: "DashboardPage" */ 'pages/DashboardPage'),
export const NewDashboardPage = Loadable(
() => import(/* webpackChunkName: "New DashboardPage" */ 'pages/NewDashboard'),
);
export const DashboardWidget = Loadable(

View File

@@ -1,5 +1,4 @@
import ROUTES from 'constants/routes';
import AlertTypeSelectionPage from 'pages/AlertTypeSelection';
import MessagingQueues from 'pages/MessagingQueues';
import MeterExplorer from 'pages/MeterExplorer';
import { RouteProps } from 'react-router-dom';
@@ -13,7 +12,6 @@ import {
CreateAlertChannelAlerts,
CreateNewAlerts,
DashboardPage,
DashboardsListPage,
DashboardWidget,
EditRulesPage,
ErrorDetails,
@@ -29,6 +27,7 @@ import {
LogsIndexToFields,
LogsSaveViews,
MetricsExplorer,
NewDashboardPage,
OldLogsExplorer,
Onboarding,
OnboardingV2,
@@ -160,14 +159,14 @@ const routes: AppRoutes[] = [
{
path: ROUTES.ALL_DASHBOARD,
exact: true,
component: DashboardsListPage,
component: DashboardPage,
isPrivate: true,
key: 'ALL_DASHBOARD',
},
{
path: ROUTES.DASHBOARD,
exact: true,
component: DashboardPage,
component: NewDashboardPage,
isPrivate: true,
key: 'DASHBOARD',
},
@@ -199,13 +198,6 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'LIST_ALL_ALERT',
},
{
path: ROUTES.ALERT_TYPE_SELECTION,
exact: true,
component: AlertTypeSelectionPage,
isPrivate: true,
key: 'ALERT_TYPE_SELECTION',
},
{
path: ROUTES.ALERTS_NEW,
exact: true,

View File

@@ -16,7 +16,7 @@ import {
QueryRangePayloadV5,
} from 'types/api/v5/queryRange';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { prepareQueryRangePayloadV5 } from './prepareQueryRangePayloadV5';
@@ -41,7 +41,7 @@ describe('prepareQueryRangePayloadV5', () => {
temporality: '',
timeAggregation: 'sum',
spaceAggregation: 'avg',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
timeAggregation: 'sum',
@@ -62,7 +62,7 @@ describe('prepareQueryRangePayloadV5', () => {
limit: null,
stepInterval: 600,
orderBy: [],
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
legend: 'Legend A',
...overrides,
});
@@ -416,7 +416,7 @@ describe('prepareQueryRangePayloadV5', () => {
metricName: 'cpu_usage',
timeAggregation: 'sum',
spaceAggregation: 'avg',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
temporality: undefined,
}),
],
@@ -569,7 +569,7 @@ describe('prepareQueryRangePayloadV5', () => {
},
],
legend: '{{service.name}}',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
offset: 0,
pageSize: 100,
},

View File

@@ -4,7 +4,7 @@ import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsAppli
import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil';
import { Widgets } from 'types/api/dashboard/getAll';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 as uuidv4 } from 'uuid';
// dynamic step interval
@@ -57,7 +57,7 @@ export const celeryAllStateWidgetData = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: 'rate',
@@ -121,7 +121,7 @@ export const celeryRetryStateWidgetData = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: 'count',
@@ -181,7 +181,7 @@ export const celeryFailedStateWidgetData = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: 'rate',
@@ -241,7 +241,7 @@ export const celerySuccessStateWidgetData = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: 'rate',
@@ -288,7 +288,7 @@ export const celeryTasksByWorkerWidgetData = (
limit: 10,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: 'rate',
@@ -351,7 +351,7 @@ export const celeryErrorByWorkerWidgetData = (
},
],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
{
dataSource: 'traces',
@@ -385,7 +385,7 @@ export const celeryErrorByWorkerWidgetData = (
},
],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
{
queryName: 'F1',
@@ -436,7 +436,7 @@ export const celeryLatencyByWorkerWidgetData = (
limit: 10,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: 'p99',
@@ -485,7 +485,7 @@ export const celeryActiveTasksWidgetData = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'avg',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: 'latest',
@@ -539,7 +539,7 @@ export const celeryTaskLatencyWidgetData = (
},
],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: type || 'p99',
@@ -590,7 +590,7 @@ export const celerySlowestTasksTableWidgetData = getWidgetQueryBuilder(
},
],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -652,7 +652,7 @@ export const celeryRetryTasksTableWidgetData = getWidgetQueryBuilder(
},
],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -715,7 +715,7 @@ export const celeryFailedTasksTableWidgetData = getWidgetQueryBuilder(
},
],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -776,7 +776,7 @@ export const celerySuccessTasksTableWidgetData = getWidgetQueryBuilder(
},
],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -838,7 +838,7 @@ export const celeryTimeSeriesTablesWidgetData = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -877,7 +877,7 @@ export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',
@@ -926,7 +926,7 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',
@@ -975,7 +975,7 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',
@@ -1024,7 +1024,7 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',

View File

@@ -1,102 +0,0 @@
import { Calendar } from '@signozhq/calendar';
import { Button } from 'antd';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import dayjs from 'dayjs';
import { CalendarIcon, Check, X } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { DateRange } from './CustomTimePickerPopoverContent';
function CalendarContainer({
dateRange,
onSelectDateRange,
onCancel,
onApply,
}: {
dateRange: DateRange;
onSelectDateRange: (dateRange: DateRange) => void;
onCancel: () => void;
onApply: () => void;
}): JSX.Element {
const { timezone } = useTimezone();
// this is to override the default behavior of the shadcn calendar component
// if a range is already selected, clicking on a date will reset selection and set the new date as the start date
const handleSelect = (
_selected: DateRange | undefined,
clickedDate?: Date,
): void => {
if (!clickedDate) {
return;
}
// No dates selected → start new
if (!dateRange?.from) {
onSelectDateRange({ from: clickedDate });
return;
}
// Only start selected → complete the range
if (dateRange.from && !dateRange.to) {
if (clickedDate < dateRange.from) {
onSelectDateRange({ from: clickedDate, to: dateRange.from });
} else {
onSelectDateRange({ from: dateRange.from, to: clickedDate });
}
return;
}
onSelectDateRange({ from: clickedDate, to: undefined });
};
return (
<div className="calendar-container">
<div className="calendar-container-header">
<CalendarIcon size={12} />
<div className="calendar-container-header-title">
{dayjs(dateRange?.from)
.tz(timezone.value)
.format(DATE_TIME_FORMATS.MONTH_DATE_SHORT)}{' '}
-{' '}
{dayjs(dateRange?.to)
.tz(timezone.value)
.format(DATE_TIME_FORMATS.MONTH_DATE_SHORT)}
</div>
</div>
<div className="calendar-container-body">
<Calendar
mode="range"
required
defaultMonth={dateRange?.from}
selected={dateRange}
disabled={{
after: dayjs().toDate(),
}}
onSelect={handleSelect}
/>
<div className="calendar-actions">
<Button
type="primary"
className="periscope-btn secondary cancel-btn"
onClick={onCancel}
icon={<X size={12} />}
>
Cancel
</Button>
<Button
type="primary"
className="periscope-btn primary apply-btn"
onClick={onApply}
icon={<Check size={12} />}
>
Apply
</Button>
</div>
</div>
</div>
);
}
export default CalendarContainer;

View File

@@ -36,6 +36,7 @@
}
.time-selection-dropdown-content {
min-width: 172px;
width: 100%;
}
@@ -47,16 +48,18 @@
padding: 4px 8px;
padding-left: 0px !important;
input {
width: 280px;
&::placeholder {
color: white;
&.custom-time {
input:not(:focus) {
min-width: 280px;
}
}
&:focus::placeholder {
color: rgba($color: #ffffff, $alpha: 0.4);
}
input::placeholder {
color: white;
}
input:focus::placeholder {
color: rgba($color: #ffffff, $alpha: 0.4);
}
}
@@ -172,26 +175,9 @@
}
.time-input-prefix {
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
border-radius: 3px;
width: 36px;
font-size: 11px;
color: var(--bg-vanilla-400);
background-color: var(--bg-ink-200);
&.is-live {
background-color: transparent;
color: var(--bg-forest-500);
}
.live-dot-icon {
width: 8px;
height: 8px;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var(--bg-forest-500);
animation: ripple 1s infinite;
@@ -205,7 +191,7 @@
0% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4);
}
60% {
70% {
box-shadow: 0 0 0 6px rgba(245, 158, 11, 0);
}
100% {
@@ -265,11 +251,6 @@
background: rgb(179 179 179 / 15%);
}
.time-input-prefix {
background-color: var(--bg-vanilla-300);
color: var(--bg-ink-400);
}
.time-input-suffix-icon-badge {
color: var(--bg-ink-100);
background: rgb(179 179 179 / 15%);

View File

@@ -1,9 +1,8 @@
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import './CustomTimePicker.styles.scss';
import { Input, InputRef, Popover, Tooltip } from 'antd';
import { Input, Popover, Tooltip, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -14,9 +13,10 @@ import {
RelativeDurationSuggestionOptions,
} from 'container/TopNav/DateTimeSelectionV2/config';
import dayjs from 'dayjs';
import { isValidShortHandDateTimeFormat } from 'lib/getMinMax';
import { isValidTimeFormat } from 'lib/getMinMax';
import { defaultTo, isFunction, noop } from 'lodash-es';
import { ChevronDown, ChevronUp } from 'lucide-react';
import debounce from 'lodash-es/debounce';
import { CheckCircle, ChevronDown, Clock } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import {
ChangeEvent,
@@ -25,26 +25,20 @@ import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { getTimeDifference, validateEpochRange } from 'utils/epochUtils';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { popupContainer } from 'utils/selectPopupContainer';
import { TimeRangeValidationResult, validateTimeRange } from 'utils/timeUtils';
import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent';
const maxAllowedMinTimeInMonths = 15;
const maxAllowedMinTimeInMonths = 6;
type ViewType = 'datetime' | 'timezone';
const DEFAULT_VIEW: ViewType = 'datetime';
export enum CustomTimePickerInputStatus {
SUCCESS = 'success',
ERROR = 'error',
UNSET = '',
}
interface CustomTimePickerProps {
onSelect: (value: string) => void;
onError: (value: boolean) => void;
@@ -70,8 +64,6 @@ interface CustomTimePickerProps {
onExitLiveLogs?: () => void;
/** When false, hides the "Recently Used" time ranges section */
showRecentlyUsed?: boolean;
minTime: number;
maxTime: number;
}
function CustomTimePicker({
@@ -92,24 +84,23 @@ function CustomTimePicker({
onExitLiveLogs,
showLiveLogs,
showRecentlyUsed = true,
minTime,
maxTime,
}: CustomTimePickerProps): JSX.Element {
const [
selectedTimePlaceholderValue,
setSelectedTimePlaceholderValue,
] = useState('Select / Enter Time Range');
const [inputValue, setInputValue] = useState('');
const [inputStatus, setInputStatus] = useState<CustomTimePickerInputStatus>(
CustomTimePickerInputStatus.UNSET,
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const [inputErrorDetails, setInputErrorDetails] = useState<
TimeRangeValidationResult['errorDetails'] | null
>(null);
const location = useLocation();
const inputRef = useRef<InputRef>(null);
const [inputValue, setInputValue] = useState('');
const [inputStatus, setInputStatus] = useState<'' | 'error' | 'success'>('');
const [inputErrorMessage, setInputErrorMessage] = useState<string | null>(
null,
);
const location = useLocation();
const [isInputFocused, setIsInputFocused] = useState(false);
const [activeView, setActiveView] = useState<ViewType>(DEFAULT_VIEW);
@@ -132,48 +123,12 @@ function CustomTimePicker({
const [isOpenedFromFooter, setIsOpenedFromFooter] = useState(false);
// function to get selected time in Last 1m, Last 2h, Last 3d, Last 4w format
// 1m, 2h, 3d, 4w -> Last 1 minute, Last 2 hours, Last 3 days, Last 4 weeks
const getSelectedTimeRangeLabelInRelativeFormat = (
selectedTime: string,
): string => {
if (!selectedTime || selectedTime === 'custom') {
return selectedTime || '';
}
// Check if the format matches the relative time format (e.g., 1m, 2h, 3d, 4w)
const match = selectedTime.match(/^(\d+)([mhdw])$/);
if (!match) {
// If it doesn't match the format, return as is
return `Last ${selectedTime}`;
}
const value = parseInt(match[1], 10);
const unit = match[2];
// Map unit abbreviations to full words
const unitMap: Record<string, { singular: string; plural: string }> = {
m: { singular: 'minute', plural: 'minutes' },
h: { singular: 'hour', plural: 'hours' },
d: { singular: 'day', plural: 'days' },
w: { singular: 'week', plural: 'weeks' },
};
const unitLabel = value === 1 ? unitMap[unit].singular : unitMap[unit].plural;
return `Last ${value} ${unitLabel}`;
};
const getSelectedTimeRangeLabel = (
selectedTime: string,
selectedTimeValue: string,
): string => {
if (!selectedTime) {
return '';
}
if (selectedTime === 'custom') {
// TODO: if the user preference is 12 hour format, then convert the date range string to 12-hour format (pick this up while working on 12/24 hour preference feature)
// TODO(shaheer): if the user preference is 12 hour format, then convert the date range string to 12-hour format (pick this up while working on 12/24 hour preference feature)
// // Convert the date range string to 12-hour format
// const dates = selectedTimeValue.split(' - ');
// if (dates.length === 2) {
@@ -209,90 +164,42 @@ function CustomTimePicker({
}
}
if (isValidShortHandDateTimeFormat(selectedTime)) {
return getSelectedTimeRangeLabelInRelativeFormat(selectedTime);
if (isValidTimeFormat(selectedTime)) {
return selectedTime;
}
return '';
};
const resetErrorStatus = (): void => {
setInputStatus(CustomTimePickerInputStatus.UNSET);
onError(false);
setInputErrorDetails(null);
};
useEffect(() => {
if (showLiveLogs) {
setSelectedTimePlaceholderValue('Live');
setInputValue('Live');
resetErrorStatus();
} else {
const value = getSelectedTimeRangeLabel(selectedTime, selectedValue);
setSelectedTimePlaceholderValue(value);
setInputValue(value);
resetErrorStatus();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedTime, selectedValue, showLiveLogs]);
const hide = (): void => {
setOpen(false);
};
const getInputPrefix = (): JSX.Element => {
if (showLiveLogs) {
return (
<span className="time-input-prefix is-live">
<span className="live-dot-icon" />
</span>
);
}
const timeDifference = getTimeDifference(
Number(minTime / 1000_000),
Number(maxTime / 1000_000),
);
return <span className="time-input-prefix">{timeDifference}</span>;
};
const handleOpenChange = (newOpen: boolean): void => {
setOpen(newOpen);
if (!newOpen) {
setCustomDTPickerVisible?.(false);
setActiveView('datetime');
if (showLiveLogs) {
setSelectedTimePlaceholderValue('Live');
setInputValue('Live');
return;
}
// set the input value to a relative format if the selected time is not custom
const inputValue = getSelectedTimeRangeLabel(selectedTime, selectedValue);
setInputValue(inputValue);
}
};
const handleInputChange = (event: ChangeEvent<HTMLInputElement>): void => {
const inputValue = event.target.value;
setInputValue(inputValue);
resetErrorStatus();
};
const handleInputPressEnter = (): void => {
// check if the entered time is in the format of 1m, 2h, 3d, 4w
const isTimeDurationShortHandFormat = /^(\d+)([mhdw])$/.test(inputValue);
if (isTimeDurationShortHandFormat) {
setInputStatus(CustomTimePickerInputStatus.SUCCESS);
const debouncedHandleInputChange = debounce((inputValue): void => {
const isValidFormat = /^(\d+)([mhdw])$/.test(inputValue);
if (isValidFormat) {
setInputStatus('success');
onError(false);
setInputErrorDetails(null);
setInputErrorMessage(null);
const match = inputValue.match(/^(\d+)([mhdw])$/) as RegExpMatchArray;
const match = inputValue.match(/^(\d+)([mhdw])$/);
const value = parseInt(match[1], 10);
const unit = match[2];
@@ -323,13 +230,9 @@ function CustomTimePicker({
}
if (minTime && (!minTime.isValid() || minTime < maxAllowedMinTime)) {
setInputStatus(CustomTimePickerInputStatus.ERROR);
setInputStatus('error');
onError(true);
setInputErrorDetails({
message: `Please enter time less than ${maxAllowedMinTimeInMonths} months`,
code: 'TIME_LESS_THAN_MAX_ALLOWED_TIME_IN_MONTHS',
description: `Please enter time less than ${maxAllowedMinTimeInMonths} months`,
});
setInputErrorMessage('Please enter time less than 6 months');
if (isFunction(onCustomTimeStatusUpdate)) {
onCustomTimeStatusUpdate(true);
}
@@ -338,64 +241,44 @@ function CustomTimePicker({
time: [minTime, currentTime],
timeStr: inputValue,
});
setOpen(false);
}
return;
}
// parse the input value to get the start and end time
const [startTime, endTime] = inputValue.split(/\s[-]\s/);
// check if startTime and endTime are epoch format
const { isValid: isValidStartTime, range: epochRange } = validateEpochRange(
Number(startTime),
Number(endTime),
);
if (isValidStartTime && epochRange?.startTime && epochRange?.endTime) {
onCustomDateHandler?.([epochRange?.startTime, epochRange?.endTime]);
setOpen(false);
return;
}
const {
isValid: isValidTimeRange,
errorDetails,
startTimeMs,
endTimeMs,
} = validateTimeRange(
startTime,
endTime,
DATE_TIME_FORMATS.UK_DATETIME_SECONDS,
);
if (!isValidTimeRange) {
setInputStatus(CustomTimePickerInputStatus.ERROR);
} else {
setInputStatus('error');
onError(true);
setInputErrorDetails(errorDetails || null);
return;
setInputErrorMessage(null);
if (isFunction(onCustomTimeStatusUpdate)) {
onCustomTimeStatusUpdate(false);
}
}
}, 300);
const handleInputChange = (event: ChangeEvent<HTMLInputElement>): void => {
const inputValue = event.target.value;
if (inputValue.length > 0) {
setOpen(false);
} else {
setOpen(true);
}
onCustomDateHandler?.([dayjs(startTimeMs), dayjs(endTimeMs)]);
setInputValue(inputValue);
setOpen(false);
// Call the debounced function with the input value
debouncedHandleInputChange(inputValue);
};
const handleSelect = (label: string, value: string): void => {
if (value === 'custom') {
if (label === 'Custom') {
setCustomDTPickerVisible?.(true);
return;
}
onSelect(value);
setSelectedTimePlaceholderValue(label);
resetErrorStatus();
setInputStatus('');
onError(false);
setInputErrorMessage(null);
setInputValue('');
if (value !== 'custom') {
hide();
}
@@ -422,48 +305,20 @@ function CustomTimePicker({
</div>
);
const handleOpen = (e: React.SyntheticEvent): void => {
e.stopPropagation();
if (showLiveLogs) {
setOpen(true);
setSelectedTimePlaceholderValue('Live');
setInputValue('Live');
return;
}
setOpen(true);
// reset the input status and error message as we reset the time to previous correct value
resetErrorStatus();
const startTime = dayjs(minTime / 1000_000).format(
DATE_TIME_FORMATS.UK_DATETIME_SECONDS,
);
const endTime = dayjs(maxTime / 1000_000).format(
DATE_TIME_FORMATS.UK_DATETIME_SECONDS,
);
setInputValue(`${startTime} - ${endTime}`);
const handleFocus = (): void => {
setIsInputFocused(true);
setActiveView('datetime');
};
const handleClose = (e: React.MouseEvent): void => {
e.stopPropagation();
setOpen(false);
setCustomDTPickerVisible?.(false);
if (showLiveLogs) {
setInputValue('Live');
return;
}
// set the input value to a relative format if the selected time is not custom
const inputValue = getSelectedTimeRangeLabel(selectedTime, selectedValue);
setInputValue(inputValue);
const handleBlur = (): void => {
setIsInputFocused(false);
};
// this is required as TopNav component wraps the components and we need to clear the state on path change
useEffect(() => {
resetErrorStatus();
setInputStatus('');
onError(false);
setInputErrorMessage(null);
setInputValue('');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.pathname]);
@@ -480,10 +335,6 @@ function CustomTimePicker({
);
};
const handleInputBlur = (): void => {
resetErrorStatus();
};
const getTooltipTitle = (): string => {
if (selectedTime === 'custom' && inputValue === '' && !open) {
return `${dayjs(minTime / 1000_000)
@@ -498,19 +349,27 @@ function CustomTimePicker({
return '';
};
// Focus and select input text when popover opens
useEffect(() => {
if (open && inputRef.current) {
// Use setTimeout to wait for React to update the DOM and make input editable
setTimeout(() => {
const inputElement = inputRef.current?.input;
if (inputElement) {
inputElement.focus();
inputElement.select();
}
}, 0);
const getInputPrefix = (): JSX.Element => {
if (showLiveLogs) {
return (
<div className="time-input-prefix">
<div className="live-dot-icon" />
</div>
);
}
}, [open]);
return (
<div className="time-input-prefix">
{inputValue && inputStatus === 'success' ? (
<CheckCircle size={14} color="#51E7A8" />
) : (
<Tooltip title="Enter time in format (e.g., 1m, 2h, 3d, 4w)">
<Clock size={14} className="cursor-pointer" />
</Tooltip>
)}
</div>
);
};
return (
<div className="custom-time-picker">
@@ -526,10 +385,9 @@ function CustomTimePicker({
content={
newPopover ? (
<CustomTimePickerPopoverContent
isLiveLogsEnabled={!!showLiveLogs}
setIsOpen={setOpen}
setCustomDTPickerVisible={defaultTo(setCustomDTPickerVisible, noop)}
customDateTimeVisible={defaultTo(customDateTimeVisible, false)}
setCustomDTPickerVisible={defaultTo(setCustomDTPickerVisible, noop)}
onCustomDateHandler={defaultTo(onCustomDateHandler, noop)}
onSelectHandler={handleSelect}
onGoLive={defaultTo(onGoLive, noop)}
@@ -541,10 +399,6 @@ function CustomTimePicker({
setIsOpenedFromFooter={setIsOpenedFromFooter}
isOpenedFromFooter={isOpenedFromFooter}
showRecentlyUsed={showRecentlyUsed}
customDateTimeInputStatus={inputStatus}
inputErrorDetails={inputErrorDetails}
minTime={minTime}
maxTime={maxTime}
/>
) : (
content
@@ -553,32 +407,25 @@ function CustomTimePicker({
arrow={false}
trigger="click"
open={open}
destroyTooltipOnHide
onOpenChange={handleOpenChange}
style={{
padding: 0,
}}
>
<Input
ref={inputRef}
className={cx(
'timeSelection-input',
inputStatus === CustomTimePickerInputStatus.ERROR ? 'error' : '',
)}
className="timeSelection-input"
type="text"
status={
inputValue && inputStatus === CustomTimePickerInputStatus.ERROR
? 'error'
: ''
status={inputValue && inputStatus === 'error' ? 'error' : ''}
placeholder={
isInputFocused
? 'Time Format (1m or 2h or 3d or 4w)'
: selectedTimePlaceholderValue
}
readOnly={!open || showLiveLogs}
placeholder={selectedTimePlaceholderValue}
value={inputValue}
onFocus={handleOpen}
onClick={handleOpen}
onFocus={handleFocus}
onClick={handleFocus}
onBlur={handleBlur}
onChange={handleInputChange}
onPressEnter={handleInputPressEnter}
onBlur={handleInputBlur}
data-1p-ignore
prefix={getInputPrefix()}
suffix={
@@ -588,25 +435,24 @@ function CustomTimePicker({
<span>{activeTimezoneOffset}</span>
</div>
)}
{open ? (
<ChevronUp
size={14}
className="cursor-pointer time-input-suffix-icon-badge"
onClick={handleClose}
/>
) : (
<ChevronDown
size={14}
className="cursor-pointer time-input-suffix-icon-badge"
onClick={handleOpen}
/>
)}
<ChevronDown
size={14}
className="cursor-pointer time-input-suffix-icon-badge"
onClick={(e): void => {
e.stopPropagation();
handleViewChange('datetime');
}}
/>
</div>
}
/>
</Popover>
</Tooltip>
{inputStatus === 'error' && inputErrorMessage && (
<Typography.Title level={5} className="valid-format-error">
{inputErrorMessage}
</Typography.Title>
)}
</div>
);
}

View File

@@ -4,6 +4,7 @@ import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import DatePickerV2 from 'components/DatePickerV2/DatePickerV2';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
@@ -14,7 +15,7 @@ import {
RelativeDurationSuggestionOptions,
} from 'container/TopNav/DateTimeSelectionV2/config';
import dayjs from 'dayjs';
import { Clock, PenLine, TriangleAlertIcon } from 'lucide-react';
import { Clock, PenLine } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import {
Dispatch,
@@ -26,23 +27,10 @@ import {
} from 'react';
import { useLocation } from 'react-router-dom';
import { getCustomTimeRanges } from 'utils/customTimeRangeUtils';
import { TimeRangeValidationResult } from 'utils/timeUtils';
import CalendarContainer from './CalendarContainer';
import { CustomTimePickerInputStatus } from './CustomTimePicker';
import TimezonePicker from './TimezonePicker';
const TO_MILLISECONDS_FACTOR = 1000_000;
export type DateRange = {
from: Date | undefined;
to?: Date | undefined;
};
interface CustomTimePickerPopoverContentProps {
isLiveLogsEnabled: boolean;
minTime: number;
maxTime: number;
options: any[];
setIsOpen: Dispatch<SetStateAction<boolean>>;
customDateTimeVisible: boolean;
@@ -60,8 +48,6 @@ interface CustomTimePickerPopoverContentProps {
setIsOpenedFromFooter: Dispatch<SetStateAction<boolean>>;
onExitLiveLogs: () => void;
showRecentlyUsed: boolean;
customDateTimeInputStatus: CustomTimePickerInputStatus;
inputErrorDetails: TimeRangeValidationResult['errorDetails'] | null;
}
interface RecentlyUsedDateTimeRange {
@@ -72,29 +58,8 @@ interface RecentlyUsedDateTimeRange {
to: string;
}
const getDateRange = (
minTime: number,
maxTime: number,
timezone: string,
): DateRange => {
const from = dayjs(minTime / TO_MILLISECONDS_FACTOR)
.tz(timezone)
.startOf('day')
.toDate();
const to = dayjs(maxTime / TO_MILLISECONDS_FACTOR)
.tz(timezone)
.endOf('day')
.toDate();
return { from, to };
};
// eslint-disable-next-line sonarjs/cognitive-complexity
function CustomTimePickerPopoverContent({
isLiveLogsEnabled,
minTime,
maxTime,
options,
setIsOpen,
customDateTimeVisible,
@@ -109,8 +74,6 @@ function CustomTimePickerPopoverContent({
setIsOpenedFromFooter,
onExitLiveLogs,
showRecentlyUsed = true,
customDateTimeInputStatus = CustomTimePickerInputStatus.UNSET,
inputErrorDetails,
}: CustomTimePickerPopoverContentProps): JSX.Element {
const { pathname } = useLocation();
@@ -120,9 +83,6 @@ function CustomTimePickerPopoverContent({
const url = new URLSearchParams(window.location.search);
const { timezone } = useTimezone();
const activeTimezoneOffset = timezone.offset;
let panelTypeFromURL = url.get(QueryParams.panelTypes);
try {
@@ -134,9 +94,8 @@ function CustomTimePickerPopoverContent({
const isLogsListView =
panelTypeFromURL !== 'table' && panelTypeFromURL !== 'graph'; // we do not select list view in the url
const [dateRange, setDateRange] = useState<DateRange>(() =>
getDateRange(minTime, maxTime, timezone.value),
);
const { timezone } = useTimezone();
const activeTimezoneOffset = timezone.offset;
const [recentlyUsedTimeRanges, setRecentlyUsedTimeRanges] = useState<
RecentlyUsedDateTimeRange[]
@@ -218,66 +177,36 @@ function CustomTimePickerPopoverContent({
setIsOpen(false);
};
const handleSelectDateRange = (dateRange: DateRange): void => {
setDateRange(dateRange);
};
const handleCalendarRangeApply = (): void => {
if (dateRange) {
const from = dayjs(dateRange.from)
.tz(timezone.value)
.startOf('day')
.toDate();
const to = dayjs(dateRange.to).tz(timezone.value).endOf('day').toDate();
onCustomDateHandler([dayjs(from), dayjs(to)]);
}
setIsOpen(false);
};
const handleCalendarRangeCancel = (): void => {
setCustomDTPickerVisible(false);
};
return (
<>
<div className="date-time-popover">
<div className="date-time-options">
{isLogsExplorerPage && isLogsListView && (
<Button
className={cx('data-time-live', isLiveLogsEnabled ? 'active' : '')}
type="text"
onClick={handleGoLive}
>
Live
</Button>
)}
{options.map((option) => (
<Button
type="text"
key={option.label + option.value}
onClick={(e: React.MouseEvent<HTMLButtonElement>): void => {
e.stopPropagation();
e.preventDefault();
handleExitLiveLogs();
onSelectHandler(option.label, option.value);
}}
className={cx(
'date-time-options-btn',
customDateTimeVisible
? option.value === 'custom' && !isLiveLogsEnabled && 'active'
: selectedTime === option.value && !isLiveLogsEnabled && 'active',
)}
>
<span className="time-label">{option.label}</span>
{option.value !== 'custom' && option.value !== '1month' && (
<span className="time-value">{option.value}</span>
)}
</Button>
))}
</div>
{!customDateTimeVisible && (
<div className="date-time-options">
{isLogsExplorerPage && isLogsListView && (
<Button className="data-time-live" type="text" onClick={handleGoLive}>
Live
</Button>
)}
{options.map((option) => (
<Button
type="text"
key={option.label + option.value}
onClick={(): void => {
handleExitLiveLogs();
onSelectHandler(option.label, option.value);
}}
className={cx(
'date-time-options-btn',
customDateTimeVisible
? option.value === 'custom' && 'active'
: selectedTime === option.value && 'active',
)}
>
{option.label}
</Button>
))}
</div>
)}
<div
className={cx(
'relative-date-time',
@@ -285,38 +214,19 @@ function CustomTimePickerPopoverContent({
)}
>
{customDateTimeVisible ? (
<CalendarContainer
dateRange={dateRange}
onSelectDateRange={handleSelectDateRange}
onCancel={handleCalendarRangeCancel}
onApply={handleCalendarRangeApply}
<DatePickerV2
onSetCustomDTPickerVisible={setCustomDTPickerVisible}
setIsOpen={setIsOpen}
onCustomDateHandler={onCustomDateHandler}
/>
) : (
<div className="time-selector-container">
{customDateTimeInputStatus === CustomTimePickerInputStatus.ERROR &&
inputErrorDetails && (
<div className="input-error-message-container">
<div className="input-error-message-title">
<TriangleAlertIcon color={Color.BG_CHERRY_400} size={16} />
<span className="input-error-message-text">
{inputErrorDetails.message}
</span>
</div>
{inputErrorDetails.description && (
<p className="input-error-message-description">
{inputErrorDetails.description}
</p>
)}
</div>
)}
<div className="relative-times-container">
<div className="time-heading">RELATIVE TIMES</div>
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
</div>
{showRecentlyUsed && recentlyUsedTimeRanges.length > 0 && (
{showRecentlyUsed && (
<div className="recently-used-container">
<div className="time-heading">RECENTLY USED</div>
<div className="recently-used-range">

View File

@@ -0,0 +1,114 @@
.date-picker-v2-container {
display: flex;
flex-direction: row;
}
.custom-date-time-picker-v2 {
padding: 12px;
.periscope-calendar {
border-radius: 4px;
border: none !important;
background: none !important;
padding: 8px 0 !important;
}
.periscope-calendar-day {
background: none !important;
&.periscope-calendar-today {
&.text-accent-foreground {
color: var(--bg-vanilla-100) !important;
}
}
button {
&:hover {
background-color: var(--bg-robin-500) !important;
color: var(--bg-vanilla-100) !important;
}
}
}
.custom-time-selector {
display: flex;
flex-direction: row;
gap: 16px;
align-items: center;
justify-content: space-between;
.time-input {
border-radius: 4px;
border: none !important;
background: none !important;
padding: 8px 4px !important;
color: var(--bg-vanilla-100) !important;
&::-webkit-calendar-picker-indicator {
display: none !important;
-webkit-appearance: none;
appearance: none;
}
&:focus {
border: none !important;
outline: none !important;
box-shadow: none !important;
}
&:focus-visible {
border: none !important;
outline: none !important;
box-shadow: none !important;
}
}
}
.custom-date-time-picker-footer {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
justify-content: flex-end;
margin-top: 16px;
.next-btn {
width: 80px;
}
.clear-btn {
width: 80px;
}
}
}
.invalid-date-range-tooltip {
.ant-tooltip-inner {
color: var(--bg-sakura-500) !important;
}
}
.lightMode {
.custom-date-time-picker-v2 {
.periscope-calendar-day {
&.periscope-calendar-today {
&.text-accent-foreground {
color: var(--bg-ink-500) !important;
}
}
button {
&:hover {
background-color: var(--bg-robin-500) !important;
color: var(--bg-ink-500) !important;
}
}
}
.custom-time-selector {
.time-input {
color: var(--bg-ink-500) !important;
}
}
}
}

View File

@@ -0,0 +1,311 @@
import './DatePickerV2.styles.scss';
import { Calendar } from '@signozhq/calendar';
import { Input } from '@signozhq/input';
import { Button, Tooltip } from 'antd';
import cx from 'classnames';
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
import dayjs, { Dayjs } from 'dayjs';
import { CornerUpLeft, MoveRight } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { addCustomTimeRange } from 'utils/customTimeRangeUtils';
function DatePickerV2({
onSetCustomDTPickerVisible,
setIsOpen,
onCustomDateHandler,
}: {
onSetCustomDTPickerVisible: (visible: boolean) => void;
setIsOpen: (isOpen: boolean) => void;
onCustomDateHandler: (
dateTimeRange: DateTimeRangeType,
lexicalContext?: LexicalContext,
) => void;
}): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const timeInputRef = useRef<HTMLInputElement>(null);
const { timezone } = useTimezone();
const [selectedDateTimeFor, setSelectedDateTimeFor] = useState<'to' | 'from'>(
'from',
);
const [selectedFromDateTime, setSelectedFromDateTime] = useState<Dayjs | null>(
dayjs(minTime / 1000_000).tz(timezone.value),
);
const [selectedToDateTime, setSelectedToDateTime] = useState<Dayjs | null>(
dayjs(maxTime / 1000_000).tz(timezone.value),
);
const handleNext = (): void => {
if (selectedDateTimeFor === 'to') {
onCustomDateHandler([selectedFromDateTime, selectedToDateTime]);
addCustomTimeRange([selectedFromDateTime, selectedToDateTime]);
setIsOpen(false);
onSetCustomDTPickerVisible(false);
setSelectedDateTimeFor('from');
} else {
setSelectedDateTimeFor('to');
}
};
const handleDateChange = (date: Date | undefined): void => {
if (!date) {
return;
}
if (selectedDateTimeFor === 'from') {
const prevFromDateTime = selectedFromDateTime;
const newDate = dayjs(date);
const updatedFromDateTime = prevFromDateTime
? prevFromDateTime
.year(newDate.year())
.month(newDate.month())
.date(newDate.date())
: dayjs(date).tz(timezone.value);
setSelectedFromDateTime(updatedFromDateTime);
} else {
// eslint-disable-next-line sonarjs/no-identical-functions
setSelectedToDateTime((prev) => {
const newDate = dayjs(date);
// Update only the date part, keeping time from existing state
return prev
? prev.year(newDate.year()).month(newDate.month()).date(newDate.date())
: dayjs(date).tz(timezone.value);
});
}
// focus the time input
timeInputRef?.current?.focus();
};
const handleTimeChange = (time: string): void => {
// time should have format HH:mm:ss
if (!/^\d{2}:\d{2}:\d{2}$/.test(time)) {
return;
}
if (selectedDateTimeFor === 'from') {
setSelectedFromDateTime((prev) => {
if (prev) {
return prev
.set('hour', parseInt(time.split(':')[0], 10))
.set('minute', parseInt(time.split(':')[1], 10))
.set('second', parseInt(time.split(':')[2], 10));
}
return prev;
});
}
if (selectedDateTimeFor === 'to') {
// eslint-disable-next-line sonarjs/no-identical-functions
setSelectedToDateTime((prev) => {
if (prev) {
return prev
.set('hour', parseInt(time.split(':')[0], 10))
.set('minute', parseInt(time.split(':')[1], 10))
.set('second', parseInt(time.split(':')[2], 10));
}
return prev;
});
}
};
const getDefaultMonth = (): Date => {
let defaultDate = null;
if (selectedDateTimeFor === 'from') {
defaultDate = selectedFromDateTime?.toDate();
} else if (selectedDateTimeFor === 'to') {
defaultDate = selectedToDateTime?.toDate();
}
return defaultDate ?? new Date();
};
const isValidRange = (): boolean => {
if (selectedDateTimeFor === 'to') {
return selectedToDateTime?.isAfter(selectedFromDateTime) ?? false;
}
return true;
};
const handleBack = (): void => {
setSelectedDateTimeFor('from');
};
const handleHideCustomDTPicker = (): void => {
onSetCustomDTPickerVisible(false);
};
const handleSelectDateTimeFor = (selectedDateTimeFor: 'to' | 'from'): void => {
setSelectedDateTimeFor(selectedDateTimeFor);
};
return (
<div className="date-picker-v2-container">
<div className="date-time-custom-options-container">
<div
className="back-btn"
onClick={handleHideCustomDTPicker}
role="button"
tabIndex={0}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
handleHideCustomDTPicker();
}
}}
>
<CornerUpLeft size={16} />
<span>Back</span>
</div>
<div className="date-time-custom-options">
<div
role="button"
tabIndex={0}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
handleSelectDateTimeFor('from');
}
}}
className={cx(
'date-time-custom-option-from',
selectedDateTimeFor === 'from' && 'active',
)}
onClick={(): void => {
handleSelectDateTimeFor('from');
}}
>
<div className="date-time-custom-option-from-title">FROM</div>
<div className="date-time-custom-option-from-value">
{selectedFromDateTime?.format('YYYY-MM-DD HH:mm:ss')}
</div>
</div>
<div
role="button"
tabIndex={0}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
handleSelectDateTimeFor('to');
}
}}
className={cx(
'date-time-custom-option-to',
selectedDateTimeFor === 'to' && 'active',
)}
onClick={(): void => {
handleSelectDateTimeFor('to');
}}
>
<div className="date-time-custom-option-to-title">TO</div>
<div className="date-time-custom-option-to-value">
{selectedToDateTime?.format('YYYY-MM-DD HH:mm:ss')}
</div>
</div>
</div>
</div>
<div className="custom-date-time-picker-v2">
<Calendar
mode="single"
required
selected={
selectedDateTimeFor === 'from'
? selectedFromDateTime?.toDate()
: selectedToDateTime?.toDate()
}
key={selectedDateTimeFor + selectedDateTimeFor}
onSelect={handleDateChange}
defaultMonth={getDefaultMonth()}
disabled={(current): boolean => {
if (selectedDateTimeFor === 'to') {
// disable dates after today and before selectedFromDateTime
const currentDay = dayjs(current);
return currentDay.isAfter(dayjs()) || false;
}
if (selectedDateTimeFor === 'from') {
// disable dates after selectedToDateTime
return dayjs(current).isAfter(dayjs()) || false;
}
return false;
}}
className="rounded-md border"
navLayout="after"
/>
<div className="custom-time-selector">
<label className="text-xs font-normal block" htmlFor="time-picker">
Timestamp
</label>
<MoveRight size={16} />
<div className="time-input-container">
<Input
type="time"
ref={timeInputRef}
className="time-input"
value={
selectedDateTimeFor === 'from'
? selectedFromDateTime?.format('HH:mm:ss')
: selectedToDateTime?.format('HH:mm:ss')
}
onChange={(e): void => handleTimeChange(e.target.value)}
step="1"
/>
</div>
</div>
<div className="custom-date-time-picker-footer">
{selectedDateTimeFor === 'to' && (
<Button
className="periscope-btn secondary clear-btn"
type="default"
onClick={handleBack}
>
Back
</Button>
)}
<Tooltip
title={
!isValidRange() ? 'Invalid range: TO date should be after FROM date' : ''
}
overlayClassName="invalid-date-range-tooltip"
>
<Button
className="periscope-btn primary next-btn"
type="primary"
onClick={handleNext}
disabled={!isValidRange()}
>
{selectedDateTimeFor === 'from' ? 'Next' : 'Apply'}
</Button>
</Tooltip>
</div>
</div>
</div>
);
}
export default DatePickerV2;

View File

@@ -176,8 +176,6 @@ function HostMetricTraces({
onTimeChange={handleTimeChange}
defaultRelativeTime="5m"
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
</div>

View File

@@ -6,7 +6,7 @@ import {
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { nanoToMilli } from 'utils/timeUtils';
export const columns = [
@@ -121,7 +121,7 @@ export const getHostTracesQueryPayload = (
],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
queryFormulas: [],

View File

@@ -87,8 +87,6 @@ function HostMetricLogsDetailedView({
onTimeChange={handleTimeChange}
defaultRelativeTime="5m"
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
</div>

View File

@@ -3,7 +3,7 @@ import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 as uuidv4 } from 'uuid';
export const getHostLogsQueryPayload = (
@@ -45,7 +45,7 @@ export const getHostLogsQueryPayload = (
],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
offset: 0,
pageSize: 100,
},

View File

@@ -211,8 +211,6 @@ function Metrics({
defaultRelativeTime="5m"
isModalTimeSelection={isModalTimeSelection}
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
</div>

View File

@@ -7,7 +7,7 @@ import { VirtuosoMockContext } from 'react-virtuoso';
import configureStore from 'redux-mock-store';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import VariableItem from '../../../container/DashboardContainer/DashboardVariablesSelection/VariableItem';
import VariableItem from '../../../container/NewDashboard/DashboardVariablesSelection/VariableItem';
// Mock the dashboard variables query
jest.mock('api/dashboard/variables/dashboardVariablesQuery', () => ({

View File

@@ -1,5 +1,5 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { uniqueOptions } from 'container/DashboardContainer/DashboardVariablesSelection/util';
import { uniqueOptions } from 'container/NewDashboard/DashboardVariablesSelection/util';
import { OptionData } from './types';

View File

@@ -10,7 +10,7 @@ import {
import QueryAddOns from '../QueryV2/QueryAddOns/QueryAddOns';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
// Mocks: only what is required for this component to render and for us to assert handler calls
const mockHandleChangeQueryData = jest.fn();
@@ -200,7 +200,7 @@ describe('QueryAddOns', () => {
it('auto-opens reduce-to content when reduceTo is set', () => {
render(
<QueryAddOns
query={baseQuery({ reduceTo: ReduceOperators.SUM })}
query={baseQuery({ reduceTo: 'sum' })}
version="v5"
isListViewPanel={false}
showReduceTo
@@ -216,10 +216,8 @@ describe('QueryAddOns', () => {
it('calls handleSetQueryData when reduce-to value changes', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
const query = baseQuery({
reduceTo: ReduceOperators.AVG,
aggregations: [
{ id: 'a', operator: 'count', reduceTo: ReduceOperators.AVG },
],
reduceTo: 'avg',
aggregations: [{ id: 'a', operator: 'count', reduceTo: 'avg' }],
});
render(
<QueryAddOns
@@ -260,7 +258,7 @@ describe('QueryAddOns', () => {
aggregations: [
{
...(query.aggregations?.[0] as any),
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
},
],
});

View File

@@ -6,7 +6,7 @@ import {
DataTypes,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { extractQueryPairs } from 'utils/queryContextUtils';
import {
@@ -803,7 +803,7 @@ describe('convertAggregationToExpression', () => {
timeAggregation: 'avg',
spaceAggregation: 'max',
alias: 'test_alias',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
temporality: 'delta',
});
@@ -812,7 +812,7 @@ describe('convertAggregationToExpression', () => {
metricName: 'test_metric',
timeAggregation: 'avg',
spaceAggregation: 'max',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
temporality: 'delta',
},
]);

View File

@@ -34,7 +34,7 @@ function YAxisUnitSelector({
initialValue,
);
const currentUniversalUnitName = getUniversalNameFromMetricUnit(value);
return `Unit mismatch. The metric was sent with unit ${initialUniversalUnitName}, but ${currentUniversalUnitName} is selected.`;
return `Unit mismatch. Saved unit is ${initialUniversalUnitName}, but ${currentUniversalUnitName} is selected.`;
}
return '';
}, [initialValue, value, loading]);
@@ -82,7 +82,6 @@ function YAxisUnitSelector({
'warning-state': incompatibleUnitMessage,
})}
data-testid={dataTestId}
allowClear
>
{categories.map((category) => (
<Select.OptGroup key={category.name} label={category.name}>

View File

@@ -106,7 +106,7 @@ describe('YAxisUnitSelector', () => {
fireEvent.mouseOver(warningIcon);
return screen
.findByText(
'Unit mismatch. The metric was sent with unit Seconds (s), but Bytes (B) is selected.',
'Unit mismatch. Saved unit is Seconds (s), but Bytes (B) is selected.',
)
.then((el) => expect(el).toBeInTheDocument());
});

View File

@@ -12,8 +12,4 @@
.anticon {
color: var(--bg-amber-400) !important;
}
.ant-select-clear {
right: 28px;
}
}

View File

@@ -51,6 +51,4 @@ export enum QueryParams {
thresholds = 'thresholds',
selectedExplorerView = 'selectedExplorerView',
variables = 'variables',
version = 'version',
showNewCreateAlertsPage = 'showNewCreateAlertsPage',
}

View File

@@ -138,11 +138,11 @@ export const mapOfFormulaToFilters: Record<
};
export const REDUCE_TO_VALUES: SelectOption<ReduceOperators, string>[] = [
{ value: ReduceOperators.LAST, label: 'Latest of values in timeframe' },
{ value: ReduceOperators.SUM, label: 'Sum of values in timeframe' },
{ value: ReduceOperators.AVG, label: 'Average of values in timeframe' },
{ value: ReduceOperators.MAX, label: 'Max of values in timeframe' },
{ value: ReduceOperators.MIN, label: 'Min of values in timeframe' },
{ value: 'last', label: 'Latest of values in timeframe' },
{ value: 'sum', label: 'Sum of values in timeframe' },
{ value: 'avg', label: 'Average of values in timeframe' },
{ value: 'max', label: 'Max of values in timeframe' },
{ value: 'min', label: 'Min of values in timeframe' },
];
export const initialHavingValues: HavingForm = {
@@ -180,7 +180,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
temporality: '',
timeAggregation: MetricAggregateOperator.COUNT,
spaceAggregation: MetricAggregateOperator.SUM,
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
functions: [],
@@ -196,7 +196,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
orderBy: [],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
source: '',
};
@@ -228,7 +228,7 @@ export const initialQueryBuilderFormMeterValues: IBuilderQuery = {
temporality: '',
timeAggregation: MeterAggregateOperator.COUNT,
spaceAggregation: MeterAggregateOperator.SUM,
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
functions: [],
@@ -244,7 +244,7 @@ export const initialQueryBuilderFormMeterValues: IBuilderQuery = {
orderBy: [],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
};
export const initialQueryBuilderFormValuesMap: Record<

View File

@@ -27,7 +27,6 @@ const ROUTES = {
ALERTS_NEW: '/alerts/new',
ALERT_HISTORY: '/alerts/history',
ALERT_OVERVIEW: '/alerts/overview',
ALERT_TYPE_SELECTION: '/alerts/type-selection',
ALL_CHANNELS: '/settings/channels',
CHANNELS_NEW: '/settings/channels/new',
CHANNELS_EDIT: '/settings/channels/edit/:channelId',

View File

@@ -37,7 +37,7 @@ import {
} from 'types/api/v5/queryRange';
import { QueryData } from 'types/api/widgets/getQuery';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';
import { domainNameKey } from './constants';
@@ -401,7 +401,7 @@ export const getDomainMetricsQueryPayload = (
orderBy: [],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
{
dataSource: DataSource.TRACES,
@@ -429,7 +429,7 @@ export const getDomainMetricsQueryPayload = (
orderBy: [],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
{
dataSource: DataSource.TRACES,
@@ -457,7 +457,7 @@ export const getDomainMetricsQueryPayload = (
orderBy: [],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
{
dataSource: DataSource.TRACES,
@@ -485,7 +485,7 @@ export const getDomainMetricsQueryPayload = (
orderBy: [],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
queryFormulas: [
@@ -653,7 +653,7 @@ export const getEndPointsQueryPayload = (
limit: 1000,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count',
@@ -700,7 +700,7 @@ export const getEndPointsQueryPayload = (
limit: 1000,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'p99',
@@ -748,7 +748,7 @@ export const getEndPointsQueryPayload = (
limit: 1000,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -805,7 +805,7 @@ export const getEndPointsQueryPayload = (
limit: 1000,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count',
@@ -1431,7 +1431,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -1461,7 +1461,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'p99',
@@ -1491,7 +1491,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count',
@@ -1521,7 +1521,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -1551,7 +1551,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'E',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count',
@@ -1629,7 +1629,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count',
@@ -1665,7 +1665,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'p99',
@@ -1705,7 +1705,7 @@ export const getEndPointDetailsQueryPayload = (
},
],
legend: 'rate',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
queryFormulas: [],
@@ -1781,7 +1781,7 @@ export const getEndPointDetailsQueryPayload = (
type: 'attribute',
},
],
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
queryFormulas: [],
@@ -1850,7 +1850,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count',
@@ -1887,7 +1887,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'p99',
@@ -1924,7 +1924,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -1961,7 +1961,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count',
@@ -2039,7 +2039,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: null,
timeAggregation: 'rate',
@@ -2110,7 +2110,7 @@ export const getEndPointDetailsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: null,
timeAggregation: 'p99',
@@ -2208,7 +2208,7 @@ export const getEndPointZeroStateQueryPayload = (
type: 'tag',
},
],
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
queryFormulas: [],
@@ -2787,7 +2787,7 @@ export const getStatusCodeBarChartWidgetData = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -2909,7 +2909,7 @@ export const getAllEndpointsWidgetData = (
limit: 1000,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count',
@@ -2941,7 +2941,7 @@ export const getAllEndpointsWidgetData = (
limit: 1000,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'p99',
@@ -2973,7 +2973,7 @@ export const getAllEndpointsWidgetData = (
limit: 1000,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -3005,7 +3005,7 @@ export const getAllEndpointsWidgetData = (
limit: 1000,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count',
@@ -3191,7 +3191,7 @@ export const getRateOverTimeWidgetData = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: null,
timeAggregation: 'rate',
@@ -3242,7 +3242,7 @@ export const getLatencyOverTimeWidgetData = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: null,
timeAggregation: 'p99',

View File

@@ -1,12 +1,24 @@
import ROUTES from 'constants/routes';
import * as usePrefillAlertConditions from 'container/FormAlertRules/usePrefillAlertConditions';
import AlertTypeSelectionPage from 'pages/AlertTypeSelection';
import CreateAlertPage from 'pages/CreateAlert';
import { act, fireEvent, render } from 'tests/test-utils';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { ALERT_TYPE_TO_TITLE, ALERT_TYPE_URL_MAP } from './constants';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: (): { pathname: string } => ({
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.ALERTS_NEW}`,
}),
}));
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): any => ({
safeNavigate: jest.fn(),
}),
}));
jest
.spyOn(usePrefillAlertConditions, 'usePrefillAlertConditions')
.mockReturnValue({
@@ -54,20 +66,13 @@ describe('Alert rule documentation redirection', () => {
window.open = mockWindowOpen;
});
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: (): { pathname: string } => ({
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.ALERT_TYPE_SELECTION}`,
}),
}));
beforeEach(() => {
act(() => {
renderResult = render(
<AlertTypeSelectionPage />,
<CreateAlertPage />,
{},
{
initialRoute: ROUTES.ALERT_TYPE_SELECTION,
initialRoute: ROUTES.ALERTS_NEW,
},
);
});
@@ -112,20 +117,18 @@ describe('Alert rule documentation redirection', () => {
expect(mockWindowOpen).toHaveBeenCalledTimes(alertTypeCount);
});
});
describe('Create alert page redirection', () => {
Object.values(AlertTypes)
.filter((type) => type !== AlertTypes.ANOMALY_BASED_ALERT)
.forEach((alertType) => {
it(`should redirect to create alert page for ${alertType} and "Check an example alert" should redirect to the correct documentation`, () => {
const { getByRole } = render(
<CreateAlertPage />,
{},
{
initialRoute: `${ROUTES.ALERTS_NEW}?alertType=${alertType}`,
},
);
const { getByTestId, getByRole } = renderResult;
const alertTypeLink = getByTestId(`alert-type-card-${alertType}`);
act(() => {
fireEvent.click(alertTypeLink);
});
act(() => {
fireEvent.click(

View File

@@ -1,131 +0,0 @@
import { render, screen } from '@testing-library/react';
import { QueryParams } from 'constants/query';
import { initialQueriesMap } from 'constants/queryBuilder';
import * as useCompositeQueryParamHooks from 'hooks/queryBuilder/useGetCompositeQueryParam';
import * as useUrlQueryHooks from 'hooks/useUrlQuery';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { DataSource } from 'types/common/queryBuilder';
import CreateAlertRule from '../index';
jest.mock('container/FormAlertRules', () => ({
__esModule: true,
default: function MockFormAlertRules({
alertType,
}: {
alertType: AlertTypes;
}): JSX.Element {
return (
<div>
<h1>Form Alert Rules</h1>
<p>{alertType}</p>
</div>
);
},
AlertDetectionTypes: {
THRESHOLD_ALERT: 'threshold_rule',
ANOMALY_DETECTION_ALERT: 'anomaly_rule',
},
}));
jest.mock('container/CreateAlertV2', () => ({
__esModule: true,
default: function MockCreateAlertV2(): JSX.Element {
return <div>Create Alert V2</div>;
},
}));
const useCompositeQueryParamSpy = jest.spyOn(
useCompositeQueryParamHooks,
'useGetCompositeQueryParam',
);
const useUrlQuerySpy = jest.spyOn(useUrlQueryHooks, 'default');
const mockSetUrlQuery = jest.fn();
const mockToString = jest.fn();
const mockGetUrlQuery = jest.fn();
const FORM_ALERT_RULES_TEXT = 'Form Alert Rules';
const CREATE_ALERT_V2_TEXT = 'Create Alert V2';
describe('CreateAlertRule', () => {
beforeEach(() => {
jest.clearAllMocks();
useUrlQuerySpy.mockReturnValue(({
set: mockSetUrlQuery,
toString: mockToString,
get: mockGetUrlQuery,
} as Partial<URLSearchParams>) as URLSearchParams);
useCompositeQueryParamSpy.mockReturnValue(initialQueriesMap.metrics);
});
it('should render v1 flow when showNewCreateAlertsPage is false', () => {
mockGetUrlQuery.mockReturnValue(null);
render(<CreateAlertRule />);
expect(screen.getByText(FORM_ALERT_RULES_TEXT)).toBeInTheDocument();
});
it('should render v2 flow when showNewCreateAlertsPage is true', () => {
mockGetUrlQuery.mockImplementation((key: string) => {
if (key === QueryParams.showNewCreateAlertsPage) {
return 'true';
}
return null;
});
render(<CreateAlertRule />);
expect(screen.getByText(CREATE_ALERT_V2_TEXT)).toBeInTheDocument();
});
it('should render v1 flow when ruleType is anomaly_rule even if showNewCreateAlertsPage is true', () => {
mockGetUrlQuery.mockImplementation((key: string) => {
if (key === QueryParams.showNewCreateAlertsPage) {
return 'true';
}
if (key === QueryParams.ruleType) {
return 'anomaly_rule';
}
return null;
});
render(<CreateAlertRule />);
expect(screen.getByText(FORM_ALERT_RULES_TEXT)).toBeInTheDocument();
expect(screen.queryByText(CREATE_ALERT_V2_TEXT)).not.toBeInTheDocument();
});
it('should use alertType from URL when provided', () => {
mockGetUrlQuery.mockImplementation((key: string) => {
if (key === QueryParams.alertType) {
return AlertTypes.LOGS_BASED_ALERT;
}
return null;
});
render(<CreateAlertRule />);
expect(screen.getByText(FORM_ALERT_RULES_TEXT)).toBeInTheDocument();
expect(screen.getByText(AlertTypes.LOGS_BASED_ALERT)).toBeInTheDocument();
});
it('should use alertType from compositeQuery dataSource when alertType is not in URL', () => {
mockGetUrlQuery.mockReturnValue(null);
useCompositeQueryParamSpy.mockReturnValue({
...initialQueriesMap.metrics,
builder: {
...initialQueriesMap.metrics.builder,
queryData: [
{
...initialQueriesMap.metrics.builder.queryData[0],
dataSource: DataSource.TRACES,
},
],
},
});
render(<CreateAlertRule />);
expect(screen.getByText(FORM_ALERT_RULES_TEXT)).toBeInTheDocument();
expect(screen.getByText(AlertTypes.TRACES_BASED_ALERT)).toBeInTheDocument();
});
it('should default to METRICS_BASED_ALERT when no alertType and no compositeQuery', () => {
mockGetUrlQuery.mockReturnValue(null);
useCompositeQueryParamSpy.mockReturnValue(null);
render(<CreateAlertRule />);
expect(screen.getByText(FORM_ALERT_RULES_TEXT)).toBeInTheDocument();
expect(screen.getByText(AlertTypes.METRICS_BASED_ALERT)).toBeInTheDocument();
});
});

View File

@@ -1,50 +1,134 @@
import { Form } from 'antd';
import { Form, Row } from 'antd';
import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { QueryParams } from 'constants/query';
import CreateAlertV2 from 'container/CreateAlertV2';
import FormAlertRules, { AlertDetectionTypes } from 'container/FormAlertRules';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import useUrlQuery from 'hooks/useUrlQuery';
import { useMemo } from 'react';
import history from 'lib/history';
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef } from 'types/api/alerts/def';
import { ALERT_TYPE_VS_SOURCE_MAPPING } from './config';
import { ALERTS_VALUES_MAP } from './defaults';
import {
alertDefaults,
anamolyAlertDefaults,
exceptionAlertDefaults,
logAlertDefaults,
traceAlertDefaults,
} from './defaults';
import SelectAlertType from './SelectAlertType';
function CreateRules(): JSX.Element {
const [formInstance] = Form.useForm();
const [initValues, setInitValues] = useState<AlertDef | null>(null);
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const alertTypeFromURL = queryParams.get(QueryParams.ruleType);
const version = queryParams.get('version');
const alertTypeFromParams =
alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
? AlertTypes.ANOMALY_BASED_ALERT
: queryParams.get(QueryParams.alertType);
const { thresholds } = (location.state as {
thresholds: ThresholdProps[];
}) || {
thresholds: null,
};
const compositeQuery = useGetCompositeQueryParam();
const queryParams = useUrlQuery();
const ruleTypeFromURL = queryParams.get(QueryParams.ruleType);
const alertTypeFromURL = queryParams.get(QueryParams.alertType);
const version = queryParams.get(QueryParams.version);
const showNewCreateAlertsPageFlag =
queryParams.get(QueryParams.showNewCreateAlertsPage) === 'true';
const alertType = useMemo(() => {
if (ruleTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT) {
return AlertTypes.ANOMALY_BASED_ALERT;
function getAlertTypeFromDataSource(): AlertTypes | null {
if (!compositeQuery) {
return null;
}
if (!alertTypeFromURL) {
const dataSource = compositeQuery?.builder.queryData?.[0]?.dataSource;
if (dataSource) {
return ALERT_TYPE_VS_SOURCE_MAPPING[dataSource];
}
return AlertTypes.METRICS_BASED_ALERT;
}
return alertTypeFromURL as AlertTypes;
}, [alertTypeFromURL, ruleTypeFromURL, compositeQuery?.builder.queryData]);
const dataSource = compositeQuery?.builder?.queryData[0]?.dataSource;
const initialAlertValue: AlertDef = useMemo(
() => ({
...ALERTS_VALUES_MAP[alertType],
version: version || ENTITY_VERSION_V5,
}),
[alertType, version],
return ALERT_TYPE_VS_SOURCE_MAPPING[dataSource];
}
const [alertType, setAlertType] = useState<AlertTypes>(
(alertTypeFromParams as AlertTypes) || getAlertTypeFromDataSource(),
);
const [formInstance] = Form.useForm();
const onSelectType = (typ: AlertTypes): void => {
setAlertType(typ);
switch (typ) {
case AlertTypes.LOGS_BASED_ALERT:
setInitValues(logAlertDefaults);
break;
case AlertTypes.TRACES_BASED_ALERT:
setInitValues(traceAlertDefaults);
break;
case AlertTypes.EXCEPTIONS_BASED_ALERT:
setInitValues(exceptionAlertDefaults);
break;
case AlertTypes.ANOMALY_BASED_ALERT:
setInitValues({
...anamolyAlertDefaults,
version: version || ENTITY_VERSION_V5,
ruleType: AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
});
break;
default:
setInitValues({
...alertDefaults,
version: version || ENTITY_VERSION_V5,
ruleType: AlertDetectionTypes.THRESHOLD_ALERT,
});
}
queryParams.set(
QueryParams.alertType,
typ === AlertTypes.ANOMALY_BASED_ALERT
? AlertTypes.METRICS_BASED_ALERT
: typ,
);
if (
typ === AlertTypes.ANOMALY_BASED_ALERT ||
alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
) {
queryParams.set(
QueryParams.ruleType,
AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
);
} else {
queryParams.set(QueryParams.ruleType, AlertDetectionTypes.THRESHOLD_ALERT);
}
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
history.replace(generatedUrl, {
thresholds,
});
};
useEffect(() => {
if (alertType) {
onSelectType(alertType);
} else {
logEvent('Alert: New alert data source selection page visited', {});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [alertType]);
if (!initValues) {
return (
<Row wrap={false}>
<SelectAlertType onSelect={onSelectType} />
</Row>
);
}
const showNewCreateAlertsPageFlag =
queryParams.get('showNewCreateAlertsPage') === 'true';
if (
showNewCreateAlertsPageFlag &&
alertType !== AlertTypes.ANOMALY_BASED_ALERT
@@ -56,7 +140,7 @@ function CreateRules(): JSX.Element {
<FormAlertRules
alertType={alertType}
formInstance={formInstance}
initialValue={initialAlertValue}
initialValue={initValues}
ruleId=""
/>
);

View File

@@ -1,9 +1,8 @@
import { Select, Tooltip, Typography } from 'antd';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { Info } from 'lucide-react';
import { useCallback, useMemo } from 'react';
import { useMemo } from 'react';
import { ALL_SELECTED_VALUE } from '../constants';
import { useCreateAlertState } from '../context';
function MultipleNotifications(): JSX.Element {
@@ -13,12 +12,6 @@ function MultipleNotifications(): JSX.Element {
} = useCreateAlertState();
const { currentQuery } = useQueryBuilder();
const isAllOptionSelected = useMemo(
() =>
notificationSettings.multipleNotifications?.includes(ALL_SELECTED_VALUE),
[notificationSettings.multipleNotifications],
);
const spaceAggregationOptions = useMemo(() => {
const allGroupBys = currentQuery.builder.queryData?.reduce<string[]>(
(acc, query) => {
@@ -28,60 +21,15 @@ function MultipleNotifications(): JSX.Element {
[],
);
const uniqueGroupBys = [...new Set(allGroupBys)];
const options = uniqueGroupBys.map((key) => ({
return uniqueGroupBys.map((key) => ({
label: key,
value: key,
disabled: isAllOptionSelected,
'data-testid': 'multiple-notifications-select-option',
}));
if (options.length > 0) {
return [
{
label: 'All',
value: ALL_SELECTED_VALUE,
'data-testid': 'multiple-notifications-select-option',
},
...options,
];
}
return options;
}, [currentQuery.builder.queryData, isAllOptionSelected]);
}, [currentQuery.builder.queryData]);
const isMultipleNotificationsEnabled = spaceAggregationOptions.length > 0;
const onSelectChange = useCallback(
(newSelectedOptions: string[]): void => {
const currentSelectedOptions = notificationSettings.multipleNotifications;
const allOptionLastSelected =
!currentSelectedOptions?.includes(ALL_SELECTED_VALUE) &&
newSelectedOptions.includes(ALL_SELECTED_VALUE);
if (allOptionLastSelected) {
setNotificationSettings({
type: 'SET_MULTIPLE_NOTIFICATIONS',
payload: [ALL_SELECTED_VALUE],
});
} else {
setNotificationSettings({
type: 'SET_MULTIPLE_NOTIFICATIONS',
payload: newSelectedOptions,
});
}
},
[setNotificationSettings, notificationSettings.multipleNotifications],
);
const groupByDescription = useMemo(() => {
if (isAllOptionSelected) {
return 'All = grouping of alerts is disabled';
}
if (notificationSettings.multipleNotifications?.length) {
return `Alerts with same ${notificationSettings.multipleNotifications?.join(
', ',
)} will be grouped`;
}
return 'Empty = all matching alerts combined into one notification';
}, [isAllOptionSelected, notificationSettings.multipleNotifications]);
const multipleNotificationsInput = useMemo(() => {
const placeholder = isMultipleNotificationsEnabled
? 'Select fields to group by (optional)'
@@ -90,7 +38,12 @@ function MultipleNotifications(): JSX.Element {
<div>
<Select
options={spaceAggregationOptions}
onChange={onSelectChange}
onChange={(value): void => {
setNotificationSettings({
type: 'SET_MULTIPLE_NOTIFICATIONS',
payload: value,
});
}}
value={notificationSettings.multipleNotifications}
mode="multiple"
placeholder={placeholder}
@@ -101,7 +54,11 @@ function MultipleNotifications(): JSX.Element {
/>
{isMultipleNotificationsEnabled && (
<Typography.Paragraph className="multiple-notifications-select-description">
{groupByDescription}
{notificationSettings.multipleNotifications?.length
? `Alerts with same ${notificationSettings.multipleNotifications?.join(
', ',
)} will be grouped`
: 'Empty = all matching alerts combined into one notification'}
</Typography.Paragraph>
)}
</div>
@@ -115,10 +72,9 @@ function MultipleNotifications(): JSX.Element {
}
return input;
}, [
groupByDescription,
isMultipleNotificationsEnabled,
notificationSettings.multipleNotifications,
onSelectChange,
setNotificationSettings,
spaceAggregationOptions,
]);

View File

@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ALL_SELECTED_VALUE } from 'container/CreateAlertV2/constants';
import * as createAlertContext from 'container/CreateAlertV2/context';
import {
INITIAL_ALERT_THRESHOLD_STATE,
@@ -29,7 +28,6 @@ jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
}));
const TEST_QUERY = 'test-query';
const TEST_QUERY_2 = 'test-query-2';
const TEST_GROUP_BY_FIELDS = [{ key: 'service' }, { key: 'environment' }];
const TRUE = 'true';
const FALSE = 'false';
@@ -153,7 +151,7 @@ describe('MultipleNotifications', () => {
groupBy: [{ key: 'http.status_code' }],
},
{
queryName: TEST_QUERY_2,
queryName: 'test-query-2',
groupBy: [{ key: 'service' }],
},
],
@@ -169,99 +167,6 @@ describe('MultipleNotifications', () => {
expect(
screen.getByRole('option', { name: 'http.status_code' }),
).toBeInTheDocument();
});
it('selecting the "all" option shows correct group by description', () => {
useQueryBuilder.mockReturnValue({
currentQuery: {
builder: {
queryData: [
{
queryName: TEST_QUERY_2,
groupBy: [{ key: 'service' }],
},
],
},
},
});
jest.spyOn(createAlertContext, 'useCreateAlertState').mockReturnValue(
createMockAlertContextState({
notificationSettings: {
...INITIAL_NOTIFICATION_SETTINGS_STATE,
multipleNotifications: [ALL_SELECTED_VALUE],
},
}),
);
render(<MultipleNotifications />);
expect(
screen.getByText('All = grouping of alerts is disabled'),
).toBeInTheDocument();
});
it('selecting "all" option should disable selection of other options', async () => {
useQueryBuilder.mockReturnValue({
currentQuery: {
builder: {
queryData: [
{
queryName: TEST_QUERY_2,
groupBy: [{ key: 'service' }],
},
],
},
},
});
render(<MultipleNotifications />);
const select = screen.getByRole(COMBOBOX_ROLE);
await userEvent.click(select);
const serviceOption = screen.getAllByTestId(
'multiple-notifications-select-option',
);
expect(serviceOption).toHaveLength(2);
expect(serviceOption[0]).not.toHaveClass('ant-select-item-option-disabled');
expect(serviceOption[1]).toHaveClass('ant-select-item-option-disabled');
});
it('selecting "all" option should remove all other selected options', async () => {
useQueryBuilder.mockReturnValue({
currentQuery: {
builder: {
queryData: [
{
queryName: TEST_QUERY_2,
groupBy: [{ key: 'service' }],
},
],
},
},
});
jest.spyOn(createAlertContext, 'useCreateAlertState').mockReturnValue(
createMockAlertContextState({
notificationSettings: {
...INITIAL_NOTIFICATION_SETTINGS_STATE,
multipleNotifications: ['service'],
},
setNotificationSettings: mockSetNotificationSettings,
}),
);
render(<MultipleNotifications />);
const select = screen.getByRole(COMBOBOX_ROLE);
await userEvent.click(select);
const serviceOption = screen.getAllByTestId(
'multiple-notifications-select-option',
);
expect(serviceOption).toHaveLength(2);
await userEvent.click(serviceOption[0]);
expect(mockSetNotificationSettings).toHaveBeenCalledWith({
type: 'SET_MULTIPLE_NOTIFICATIONS',
payload: [ALL_SELECTED_VALUE],
});
expect(screen.getByRole('option', { name: 'service' })).toBeInTheDocument();
});
});

View File

@@ -35,21 +35,24 @@ function ChartPreview({ alertDef }: ChartPreviewProps): JSX.Element {
const yAxisUnit = alertState.yAxisUnit || '';
const shouldUpdateYAxisUnit =
const fetchYAxisUnit =
!isEditMode && alertType === AlertTypes.METRICS_BASED_ALERT;
const selectedQueryName = thresholdState.selectedQuery;
const { yAxisUnit: initialYAxisUnit, isLoading } = useGetYAxisUnit(
selectedQueryName,
{
enabled: fetchYAxisUnit,
},
);
// Every time a new metric is selected, set the y-axis unit to its unit value if present
// Only for metrics-based alerts in create mode
// Only for metrics-based alerts
useEffect(() => {
if (shouldUpdateYAxisUnit) {
if (fetchYAxisUnit) {
setAlertState({ type: 'SET_Y_AXIS_UNIT', payload: initialYAxisUnit });
}
}, [initialYAxisUnit, setAlertState, shouldUpdateYAxisUnit]);
}, [initialYAxisUnit, setAlertState, fetchYAxisUnit]);
const headline = (
<div className="chart-preview-headline">

View File

@@ -72,5 +72,3 @@ export const defaultPostableAlertRuleV2: PostableAlertRuleV2 = {
alert: 'TEST_ALERT',
evaluation: defaultEvaluation,
};
export const ALL_SELECTED_VALUE = '__all__';

View File

@@ -1,67 +0,0 @@
.settings-container-root {
.ant-drawer-wrapper-body {
border-left: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
.ant-drawer-header {
height: 48px;
border-bottom: 1px solid var(--bg-slate-500);
padding: 14px 14px 14px 11px;
.ant-drawer-header-title {
gap: 16px;
.ant-drawer-title {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
padding-left: 16px;
border-left: 1px solid var(--bg-slate-500);
}
.ant-drawer-close {
height: 16px;
width: 16px;
margin-inline-end: 0px !important;
}
}
}
.ant-drawer-body {
padding: 16px;
&::-webkit-scrollbar {
width: 0.1rem;
}
}
}
}
.lightMode {
.settings-container-root {
.ant-drawer-wrapper-body {
border-left: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.ant-drawer-header {
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-drawer-header-title {
.ant-drawer-title {
color: var(--bg-ink-400);
border-left: 1px solid var(--bg-vanilla-300);
}
.ant-drawer-close {
color: var(--bg-ink-300);
}
}
}
}
}
}

View File

@@ -1,34 +0,0 @@
import './SettingsDrawer.styles.scss';
import { Drawer } from 'antd';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { memo, PropsWithChildren, ReactElement } from 'react';
type SettingsDrawerProps = PropsWithChildren<{
drawerTitle: string;
isOpen: boolean;
onClose: () => void;
}>;
function SettingsDrawer({
children,
drawerTitle,
isOpen,
onClose,
}: SettingsDrawerProps): JSX.Element {
return (
<Drawer
title={drawerTitle}
placement="right"
width="50%"
onClose={onClose}
open={isOpen}
rootClassName="settings-container-root"
>
{/* Need to type cast because of OverlayScrollbar type definition. We should be good once we remove it. */}
<OverlayScrollbar>{children as ReactElement}</OverlayScrollbar>
</Drawer>
);
}
export default memo(SettingsDrawer);

View File

@@ -1,7 +0,0 @@
import { MutableRefObject } from 'react';
export interface VariablesSettingsTab {
resetState: () => void;
}
export type VariablesSettingsTabHandle = MutableRefObject<VariablesSettingsTab | null>;

View File

@@ -1,5 +1,4 @@
import { renderHook } from '@testing-library/react';
import { ReduceOperators } from 'types/common/queryBuilder';
import { usePrefillAlertConditions } from '../usePrefillAlertConditions';
@@ -56,7 +55,7 @@ const mockStagedQuery = {
builder: {
queryData: [
{
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
},
@@ -85,10 +84,10 @@ describe('usePrefillAlertConditions', () => {
builder: {
queryData: [
{
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
{
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
},
@@ -103,10 +102,10 @@ describe('usePrefillAlertConditions', () => {
builder: {
queryData: [
{
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
{
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
},
],
},

View File

@@ -6,8 +6,6 @@ import saveAlertApi from 'api/alerts/save';
import testAlertApi from 'api/alerts/testAlert';
import logEvent from 'api/common/logEvent';
import { getInvolvedQueriesInTraceOperator } from 'components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils';
import YAxisUnitSelector from 'components/YAxisUnitSelector';
import { YAxisSource } from 'components/YAxisUnitSelector/types';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
@@ -16,9 +14,9 @@ import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import ROUTES from 'constants/routes';
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import useGetYAxisUnit from 'hooks/useGetYAxisUnit';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
@@ -232,18 +230,16 @@ function FormAlertRules({
};
useEffect(() => {
if (detectionMethod) {
const ruleType =
detectionMethod === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
? AlertDetectionTypes.ANOMALY_DETECTION_ALERT
: AlertDetectionTypes.THRESHOLD_ALERT;
const ruleType =
detectionMethod === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
? AlertDetectionTypes.ANOMALY_DETECTION_ALERT
: AlertDetectionTypes.THRESHOLD_ALERT;
queryParams.set(QueryParams.ruleType, ruleType);
queryParams.set(QueryParams.ruleType, ruleType);
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
safeNavigate(generatedUrl);
}
safeNavigate(generatedUrl);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [detectionMethod]);
@@ -485,7 +481,7 @@ function FormAlertRules({
chQueries: mapQueryDataToApi(currentQuery.clickhouse_sql, 'name').data,
queryType: currentQuery.queryType,
panelType: panelType || initQuery.panelType,
unit: yAxisUnit,
unit: currentQuery.unit,
}),
},
};
@@ -504,7 +500,6 @@ function FormAlertRules({
alertType,
initQuery,
panelType,
yAxisUnit,
]);
const saveRule = useCallback(async () => {
@@ -530,7 +525,8 @@ function FormAlertRules({
if (response.statusCode === 200) {
logData = {
status: 'success',
statusMessage: isNewRule ? t('rule_created') : t('rule_edited'),
statusMessage:
!ruleId || isEmpty(ruleId) ? t('rule_created') : t('rule_edited'),
};
notifications.success({
@@ -582,7 +578,7 @@ function FormAlertRules({
dataSource: ALERTS_DATA_SOURCE_MAP[postableAlert?.alertType as AlertTypes],
channelNames: postableAlert?.preferredChannels,
broadcastToAll: postableAlert?.broadcastToAll,
isNewRule,
isNewRule: !ruleId || isEmpty(ruleId),
ruleId,
queryType: currentQuery.queryType,
alertId: postableAlert?.id,
@@ -667,7 +663,7 @@ function FormAlertRules({
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
channelNames: postableAlert?.preferredChannels,
broadcastToAll: postableAlert?.broadcastToAll,
isNewRule,
isNewRule: !ruleId || isEmpty(ruleId),
ruleId,
queryType: currentQuery.queryType,
status: statusResponse.status,
@@ -739,6 +735,8 @@ function FormAlertRules({
alertDef?.broadcastToAll ||
(alertDef.preferredChannels && alertDef.preferredChannels.length > 0);
const isRuleCreated = !ruleId || isEmpty(ruleId);
function handleRedirection(option: AlertTypes): void {
let url;
if (
@@ -753,7 +751,7 @@ function FormAlertRules({
if (url) {
logEvent('Alert: Check example alert clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
isNewRule,
isNewRule: !ruleId || isEmpty(ruleId),
ruleId,
queryType: currentQuery.queryType,
link: url,
@@ -763,7 +761,7 @@ function FormAlertRules({
}
useEffect(() => {
if (!isNewRule) {
if (!isRuleCreated) {
logEvent('Alert: Edit page visited', {
ruleId,
dataSource: ALERTS_DATA_SOURCE_MAP[alertType as AlertTypes],
@@ -788,24 +786,6 @@ function FormAlertRules({
featureFlags?.find((flag) => flag.name === FeatureKeys.ANOMALY_DETECTION)
?.active || false;
// Only update automatically when creating a new metrics-based alert rule
const shouldUpdateYAxisUnit = useMemo(
() => isNewRule && alertType === AlertTypes.METRICS_BASED_ALERT,
[isNewRule, alertType],
);
const { yAxisUnit: initialYAxisUnit, isLoading } = useGetYAxisUnit(
alertDef.condition.selectedQueryName,
);
// Every time a new metric is selected, set the y-axis unit to its unit
// Only for metrics-based alerts in create mode
useEffect(() => {
if (shouldUpdateYAxisUnit) {
setYAxisUnit(initialYAxisUnit || '');
}
}, [initialYAxisUnit, shouldUpdateYAxisUnit]);
return (
<>
{Element}
@@ -813,7 +793,7 @@ function FormAlertRules({
<div
id="top"
className={`form-alert-rules-container ${
isNewRule ? 'create-mode' : 'edit-mode'
isRuleCreated ? 'create-mode' : 'edit-mode'
}`}
>
<div className="overview-header">
@@ -861,12 +841,9 @@ function FormAlertRules({
</div>
<StepContainer>
<YAxisUnitSelector
value={yAxisUnit}
initialValue={initialYAxisUnit}
<BuilderUnitsFilter
onChange={onUnitChangeHandler}
source={YAxisSource.ALERTS}
loading={isLoading}
yAxisUnit={yAxisUnit}
/>
</StepContainer>
@@ -944,7 +921,7 @@ function FormAlertRules({
type="default"
onClick={onCancelHandler}
>
{isNewRule && t('button_cancelchanges')}
{(!ruleId || isEmpty(ruleId)) && t('button_cancelchanges')}
{ruleId && !isEmpty(ruleId) && t('button_discard')}
</ActionButton>
</ButtonContainer>

View File

@@ -4,14 +4,11 @@ import './DashboardEmptyState.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
import SettingsDrawer from 'container/DashboardContainer/DashboardDescription/SettingsDrawer';
import { VariablesSettingsTab } from 'container/DashboardContainer/DashboardDescription/types';
import DashboardSettings from 'container/DashboardContainer/DashboardSettings';
import SettingsDrawer from 'container/NewDashboard/DashboardDescription/SettingsDrawer';
import useComponentPermission from 'hooks/useComponentPermission';
import { useAppContext } from 'providers/App/App';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useCallback, useRef, useState } from 'react';
import { useCallback } from 'react';
import { ROLES, USER_ROLES } from 'types/roles';
import { ComponentTypes } from 'utils/permission';
@@ -23,11 +20,6 @@ export default function DashboardEmptyState(): JSX.Element {
setSelectedRowWidgetId,
} = useDashboard();
const variablesSettingsTabHandle = useRef<VariablesSettingsTab>(null);
const [isSettingsDrawerOpen, setIsSettingsDrawerOpen] = useState<boolean>(
false,
);
const { user } = useAppContext();
let permissions: ComponentTypes[] = ['add_panel'];
@@ -52,19 +44,6 @@ export default function DashboardEmptyState(): JSX.Element {
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [handleToggleDashboardSlider]);
const onConfigureClick = useCallback((): void => {
setIsSettingsDrawerOpen(true);
}, []);
const onSettingsDrawerClose = useCallback((): void => {
setIsSettingsDrawerOpen(false);
if (variablesSettingsTabHandle.current) {
variablesSettingsTabHandle.current.resetState();
}
}, []);
return (
<section className="dashboard-empty-state">
<div className="dashboard-content">
@@ -98,26 +77,7 @@ export default function DashboardEmptyState(): JSX.Element {
Give it a name, add description, tags and variables
</Typography.Text>
</div>
{/* This Empty State needs to be consolidated. The SettingsDrawer should be global to the
whole dashboard page instead of confined to this Empty State */}
<Button
type="text"
className="configure-button"
icon={<ConfigureIcon />}
data-testid="show-drawer"
onClick={onConfigureClick}
>
Configure
</Button>
<SettingsDrawer
drawerTitle="Dashboard Configuration"
isOpen={isSettingsDrawerOpen}
onClose={onSettingsDrawerClose}
>
<DashboardSettings
variablesSettingsTabHandle={variablesSettingsTabHandle}
/>
</SettingsDrawer>
<SettingsDrawer drawerTitle="Dashboard Configuration" />
</div>
<div className="actions-1">
<div className="actions-add-panel">

View File

@@ -3,7 +3,7 @@ import './PanelTypeSelector.scss';
import { Select, Typography } from 'antd';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import GraphTypes from 'container/DashboardContainer/ComponentsSlider/menuItems';
import GraphTypes from 'container/NewDashboard/ComponentsSlider/menuItems';
import { handleQueryChange } from 'container/NewWidget/utils';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useCallback } from 'react';

View File

@@ -8,7 +8,7 @@ import { Provider } from 'react-redux';
import store from 'store';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import {
MENUITEM_KEYS_VS_LABELS,
@@ -68,7 +68,7 @@ const mockProps: WidgetGraphComponentProps = {
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',

View File

@@ -10,7 +10,7 @@ import { ENTITY_VERSION_V5 } from 'constants/app';
import { QueryParams } from 'constants/query';
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
import { DEFAULT_ROW_NAME } from 'container/DashboardContainer/DashboardDescription/utils';
import { DEFAULT_ROW_NAME } from 'container/NewDashboard/DashboardDescription/utils';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { createDynamicVariableToWidgetsMap } from 'hooks/dashboard/utils';
import useComponentPermission from 'hooks/useComponentPermission';

View File

@@ -1,6 +1,6 @@
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { getBarStepIntervalPoints, updateBarStepInterval } from '../utils';
@@ -126,7 +126,7 @@ describe('GridCardLayout Utils', () => {
limit: null,
offset: 0,
pageSize: 0,
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
legend: '',
},
],

View File

@@ -1,5 +1,3 @@
import { ReduceOperators } from 'types/common/queryBuilder';
/* eslint-disable sonarjs/no-duplicate-string */
export const tableDataMultipleQueriesSuccessResponse = {
columns: [
@@ -133,7 +131,7 @@ export const widgetQueryWithLegend = {
},
],
legend: 'p99',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
{
dataSource: 'metrics',
@@ -160,7 +158,7 @@ export const widgetQueryWithLegend = {
orderBy: [],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
queryFormulas: [],
@@ -376,7 +374,7 @@ export const widgetQueryQBv5MultiAggregations = {
},
],
legend: 'p99',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
{
dataSource: 'metrics',
@@ -416,7 +414,7 @@ export const widgetQueryQBv5MultiAggregations = {
},
],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
{
dataSource: 'metrics',
@@ -456,7 +454,7 @@ export const widgetQueryQBv5MultiAggregations = {
},
],
legend: 'max',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
queryFormulas: [],

View File

@@ -17,7 +17,7 @@ jest.mock('lib/getMinMax', () => ({
default: jest.fn().mockImplementation(() => ({
minTime: 1713734400000,
maxTime: 1713738000000,
isValidShortHandDateTimeFormat: jest.fn().mockReturnValue(true),
isValidTimeFormat: jest.fn().mockReturnValue(true),
})),
}));
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({

View File

@@ -4,7 +4,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';
export const clusterWidgetInfo = [
@@ -177,7 +177,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -216,7 +216,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -255,7 +255,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -294,7 +294,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -367,7 +367,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -406,7 +406,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -445,7 +445,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -484,7 +484,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -570,7 +570,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -656,7 +656,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -742,7 +742,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -794,7 +794,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -892,7 +892,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -944,7 +944,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -996,7 +996,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -1048,7 +1048,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -1164,7 +1164,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'avg',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1210,7 +1210,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'avg',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1256,7 +1256,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'avg',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1366,7 +1366,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -1418,7 +1418,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -1470,7 +1470,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -1522,7 +1522,7 @@ export const getClusterMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',

View File

@@ -4,7 +4,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';
export const daemonSetWidgetInfo = [
@@ -126,7 +126,7 @@ export const getDaemonSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -176,7 +176,7 @@ export const getDaemonSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -226,7 +226,7 @@ export const getDaemonSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -310,7 +310,7 @@ export const getDaemonSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -360,7 +360,7 @@ export const getDaemonSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -410,7 +410,7 @@ export const getDaemonSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -507,7 +507,7 @@ export const getDaemonSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -604,7 +604,7 @@ export const getDaemonSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',

View File

@@ -4,7 +4,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';
export const deploymentWidgetInfo = [
@@ -111,7 +111,7 @@ export const getDeploymentMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -150,7 +150,7 @@ export const getDeploymentMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -189,7 +189,7 @@ export const getDeploymentMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -262,7 +262,7 @@ export const getDeploymentMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -301,7 +301,7 @@ export const getDeploymentMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -340,7 +340,7 @@ export const getDeploymentMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -426,7 +426,7 @@ export const getDeploymentMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -512,7 +512,7 @@ export const getDeploymentMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',

View File

@@ -266,8 +266,6 @@ export default function Events({
onTimeChange={handleTimeChange}
defaultRelativeTime="5m"
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
</div>

View File

@@ -93,8 +93,6 @@ function EntityLogsDetailedView({
onTimeChange={handleTimeChange}
defaultRelativeTime="5m"
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
</div>

View File

@@ -258,8 +258,6 @@ function EntityMetrics<T>({
defaultRelativeTime="5m"
isModalTimeSelection={isModalTimeSelection}
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
</div>

View File

@@ -188,8 +188,6 @@ function EntityTraces({
onTimeChange={handleTimeChange}
defaultRelativeTime="5m"
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
</div>

View File

@@ -12,7 +12,7 @@ import {
TagFilterItem,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { nanoToMilli } from 'utils/timeUtils';
import { v4 as uuidv4 } from 'uuid';
@@ -73,7 +73,7 @@ export const getEntityEventsOrLogsQueryPayload = (
],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
offset: 0,
pageSize: 100,
},
@@ -223,7 +223,7 @@ export const getEntityTracesQueryPayload = (
],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
},
],
queryFormulas: [],

View File

@@ -4,7 +4,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';
export const jobWidgetInfo = [
@@ -101,7 +101,7 @@ export const getJobMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -185,7 +185,7 @@ export const getJobMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -282,7 +282,7 @@ export const getJobMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -379,7 +379,7 @@ export const getJobMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',

View File

@@ -4,7 +4,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';
export const namespaceWidgetInfo = [
@@ -185,7 +185,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -224,7 +224,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -263,7 +263,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -302,7 +302,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -375,7 +375,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -414,7 +414,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -453,7 +453,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -492,7 +492,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -531,7 +531,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'E',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -570,7 +570,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'F',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -650,7 +650,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: 20,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -730,7 +730,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: 10,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -816,7 +816,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -902,7 +902,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
@@ -982,7 +982,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1028,7 +1028,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1074,7 +1074,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1160,7 +1160,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1212,7 +1212,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1292,7 +1292,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1338,7 +1338,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1384,7 +1384,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1430,7 +1430,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1510,7 +1510,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1556,7 +1556,7 @@ export const getNamespaceMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.LAST,
reduceTo: 'last',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'avg',

View File

@@ -4,7 +4,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';
export const nodeWidgetInfo = [
@@ -178,7 +178,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -217,7 +217,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -256,7 +256,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -295,7 +295,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -334,7 +334,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'E',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -407,7 +407,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -446,7 +446,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -485,7 +485,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -524,7 +524,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -563,7 +563,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'E',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -602,7 +602,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'F',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -641,7 +641,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'G',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -714,7 +714,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -753,7 +753,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -792,7 +792,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -878,7 +878,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -917,7 +917,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -956,7 +956,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1049,7 +1049,7 @@ export const getNodeMetricsQueryPayload = (
limit: 10,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1129,7 +1129,7 @@ export const getNodeMetricsQueryPayload = (
limit: 10,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1215,7 +1215,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
@@ -1301,7 +1301,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -1374,7 +1374,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1413,7 +1413,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1452,7 +1452,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1525,7 +1525,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1564,7 +1564,7 @@ export const getNodeMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',

View File

@@ -4,7 +4,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';
export const podWidgetInfo = [
@@ -228,7 +228,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -278,7 +278,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -328,7 +328,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -412,7 +412,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -462,7 +462,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -512,7 +512,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -562,7 +562,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -612,7 +612,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'E',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -662,7 +662,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'F',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -746,7 +746,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -796,7 +796,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -846,7 +846,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -930,7 +930,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -980,7 +980,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1030,7 +1030,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -1080,7 +1080,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',
@@ -1130,7 +1130,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'E',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -1180,7 +1180,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'F',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'min',
@@ -1264,7 +1264,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1314,7 +1314,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'avg',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1405,7 +1405,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1496,7 +1496,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1587,7 +1587,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1644,7 +1644,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1701,7 +1701,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -1805,7 +1805,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1862,7 +1862,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1919,7 +1919,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -2010,7 +2010,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -2067,7 +2067,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -2124,7 +2124,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
@@ -2234,7 +2234,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -2331,7 +2331,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
@@ -2415,7 +2415,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -2465,7 +2465,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -2515,7 +2515,7 @@ export const getPodMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',

View File

@@ -4,7 +4,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';
export const statefulSetWidgetInfo = [
@@ -139,7 +139,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -189,7 +189,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -239,7 +239,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -309,7 +309,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'avg',
stepInterval: 60,
timeAggregation: 'avg',
@@ -359,7 +359,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'avg',
stepInterval: 60,
timeAggregation: 'avg',
@@ -429,7 +429,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -479,7 +479,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -529,7 +529,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -599,7 +599,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'avg',
stepInterval: 60,
timeAggregation: 'avg',
@@ -649,7 +649,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'avg',
stepInterval: 60,
timeAggregation: 'avg',
@@ -732,7 +732,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -815,7 +815,7 @@ export const getStatefulSetMetricsQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',

View File

@@ -4,7 +4,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';
export const volumeWidgetInfo = [
@@ -141,7 +141,7 @@ export const getVolumeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -233,7 +233,7 @@ export const getVolumeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -324,7 +324,7 @@ export const getVolumeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -416,7 +416,7 @@ export const getVolumeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -508,7 +508,7 @@ export const getVolumeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',

View File

@@ -62,7 +62,7 @@ const setupCommonMocks = (): void => {
minTime: 1713734400000,
maxTime: 1713738000000,
})),
isValidShortHandDateTimeFormat: jest.fn().mockReturnValue(true),
isValidTimeFormat: jest.fn().mockReturnValue(true),
}));
jest.spyOn(appContextHooks, 'useAppContext').mockReturnValue({

View File

@@ -29,8 +29,8 @@ import useComponentPermission from 'hooks/useComponentPermission';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import useInterval from 'hooks/useInterval';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { useAppContext } from 'providers/App/App';
import { useCallback, useState } from 'react';
@@ -50,7 +50,6 @@ const { Search } = Input;
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
const { t } = useTranslation('common');
const { safeNavigate } = useSafeNavigate();
const { user } = useAppContext();
const [addNewAlert, action] = useComponentPermission(
['add_new_alert', 'action'],
@@ -113,8 +112,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
number: allAlertRules?.length,
layout: 'new',
});
params.set(QueryParams.showNewCreateAlertsPage, 'true');
safeNavigate(`${ROUTES.ALERT_TYPE_SELECTION}?${params.toString()}`);
history.push(`${ROUTES.ALERTS_NEW}?showNewCreateAlertsPage=true`);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -123,7 +121,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
number: allAlertRules?.length,
layout: 'classic',
});
safeNavigate(ROUTES.ALERT_TYPE_SELECTION);
history.push(ROUTES.ALERTS_NEW);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -163,7 +161,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
if (openInNewTab) {
window.open(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`, '_blank');
} else {
safeNavigate(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
history.push(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
}
};
@@ -192,7 +190,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
setTimeout(() => {
const clonedAlert = refetchData.payload[refetchData.payload.length - 1];
params.set(QueryParams.ruleId, String(clonedAlert.id));
safeNavigate(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
history.push(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
}, 2000);
}
if (status === 'error') {

View File

@@ -28,9 +28,9 @@ import cx from 'classnames';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import ROUTES from 'constants/routes';
import { sanitizeDashboardData } from 'container/DashboardContainer/DashboardDescription';
import { downloadObjectAsJson } from 'container/DashboardContainer/DashboardDescription/utils';
import { Base64Icons } from 'container/DashboardContainer/DashboardSettings/General/utils';
import { sanitizeDashboardData } from 'container/NewDashboard/DashboardDescription';
import { downloadObjectAsJson } from 'container/NewDashboard/DashboardDescription/utils';
import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/utils';
import dayjs from 'dayjs';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
import useComponentPermission from 'hooks/useComponentPermission';

View File

@@ -20,7 +20,7 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
import { ExternalLink, Github, MonitorDot, MoveRight } from 'lucide-react';
import { ExternalLink, Github, MonitorDot, MoveRight, X } from 'lucide-react';
import { useErrorModal } from 'providers/ErrorModalProvider';
// #TODO: Lucide will be removing brand icons like GitHub in the future. In that case, we can use Simple Icons. https://simpleicons.org/
// See more: https://github.com/lucide-icons/lucide/issues/94
@@ -151,10 +151,7 @@ function ImportJSON({
wrapClassName="import-json-modal"
open={isImportJSONModalVisible}
centered
closable
keyboard
maskClosable
onCancel={onCancelHandler}
closable={false}
destroyOnClose
width="60vw"
footer={
@@ -226,6 +223,8 @@ function ImportJSON({
<div className="import-json-content-container">
<div className="import-json-content-header">
<Typography.Text>{t('import_json')}</Typography.Text>
<X size={14} className="periscope-btn ghost" onClick={onCancelHandler} />
</div>
<MEditor

View File

@@ -5,7 +5,7 @@ import {
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
export const mockLog: ILog = {
id: 'test-log-id',
@@ -54,7 +54,7 @@ export const mockQuery: Query = {
op: 'AND',
},
expression: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
},
],
queryFormulas: [],

View File

@@ -3,7 +3,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
export const getPodQueryPayload = (
clusterName: string,
@@ -114,7 +114,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -191,7 +191,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -268,7 +268,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -325,7 +325,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -409,7 +409,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -466,7 +466,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -553,7 +553,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -614,7 +614,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -702,7 +702,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -763,7 +763,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'latest',
@@ -851,7 +851,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -912,7 +912,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1001,7 +1001,7 @@ export const getPodQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -1122,7 +1122,7 @@ export const getNodeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -1171,7 +1171,7 @@ export const getNodeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1260,7 +1260,7 @@ export const getNodeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1309,7 +1309,7 @@ export const getNodeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1412,7 +1412,7 @@ export const getNodeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -1493,7 +1493,7 @@ export const getNodeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1554,7 +1554,7 @@ export const getNodeQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1675,7 +1675,7 @@ export const getHostQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -1716,7 +1716,7 @@ export const getHostQueryPayload = (
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -1792,7 +1792,7 @@ export const getHostQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1853,7 +1853,7 @@ export const getHostQueryPayload = (
limit: 30,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1894,7 +1894,7 @@ export const getHostQueryPayload = (
limit: 30,
orderBy: [],
queryName: 'B',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -1935,7 +1935,7 @@ export const getHostQueryPayload = (
limit: 30,
orderBy: [],
queryName: 'C',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -2017,7 +2017,7 @@ export const getHostQueryPayload = (
limit: 30,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -2093,7 +2093,7 @@ export const getHostQueryPayload = (
limit: 30,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -2169,7 +2169,7 @@ export const getHostQueryPayload = (
limit: 30,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -2245,7 +2245,7 @@ export const getHostQueryPayload = (
limit: 30,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -2321,7 +2321,7 @@ export const getHostQueryPayload = (
limit: 30,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
@@ -2382,7 +2382,7 @@ export const getHostQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -2464,7 +2464,7 @@ export const getHostQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
@@ -2539,7 +2539,7 @@ export const getHostQueryPayload = (
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'max',

View File

@@ -92,10 +92,6 @@ function TableView({
}
});
}
// pin trace_id by default when present
if (logData?.trace_id) {
pinnedAttributes.trace_id = true;
}
setPinnedAttributes(pinnedAttributes);
}, [

View File

@@ -6,11 +6,7 @@ import { cleanup, render, screen, waitFor } from 'tests/test-utils';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query, QueryState } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import {
DataSource,
QueryBuilderContextType,
ReduceOperators,
} from 'types/common/queryBuilder';
import { DataSource, QueryBuilderContextType } from 'types/common/queryBuilder';
import { explorerViewToPanelType } from 'utils/explorerUtils';
import LogExplorerQuerySection from './index';
@@ -170,7 +166,7 @@ const createMockQuery = (filterExpression?: string): Query => ({
orderBy: [{ columnName: 'timestamp', order: 'desc' }],
pageSize: 0,
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
stepInterval: 60,
},
],

View File

@@ -66,7 +66,7 @@ describe('useInitialQuery - Priority-Based Resource Filtering', () => {
queryName: 'A',
expression: 'A',
disabled: false,
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg' as ReduceOperators,
legend: '',
},
],

View File

@@ -518,15 +518,15 @@ describe('Logs Explorer -> stage and run query', () => {
const initialStart = initialPayload.start;
const initialEnd = initialPayload.end;
// Click the Run Query button
// Click the Stage & Run Query button
const user = userEvent.setup({ pointerEventsCheck: 0 });
await user.click(
screen.getByRole('button', {
name: /run query/i,
name: /stage & run query/i,
}),
);
// Wait for additional API calls to be made after clicking Run Query
// Wait for additional API calls to be made after clicking Stage & Run Query
await waitFor(
() => {
expect(capturedPayloads.length).toBeGreaterThan(1);

View File

@@ -7,7 +7,7 @@ import {
IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import { v4 as uuid } from 'uuid';
interface GetWidgetQueryProps {
@@ -101,7 +101,7 @@ export const getTotalLogSizeWidgetData = (): Widgets =>
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
@@ -138,7 +138,7 @@ export const getTotalTraceSizeWidgetData = (): Widgets =>
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
@@ -175,7 +175,7 @@ export const getTotalMetricDatapointCountWidgetData = (): Widgets =>
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.SUM,
reduceTo: 'sum',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
@@ -212,7 +212,7 @@ export const getLogCountWidgetData = (): Widgets =>
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
@@ -249,7 +249,7 @@ export const getLogSizeWidgetData = (): Widgets =>
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
@@ -286,7 +286,7 @@ export const getSpanCountWidgetData = (): Widgets =>
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
@@ -323,7 +323,7 @@ export const getSpanSizeWidgetData = (): Widgets =>
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
@@ -360,7 +360,7 @@ export const getMetricCountWidgetData = (): Widgets =>
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',

View File

@@ -9,7 +9,6 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import {
MetricAggregateOperator,
QueryBuilderData,
ReduceOperators,
Temporality,
} from 'types/common/queryBuilder';
@@ -52,7 +51,7 @@ export const getQueryBuilderQueries = ({
items: filterItems[index],
op: 'AND',
},
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
spaceAggregation: spaceAggregateOperators[index],
timeAggregation: timeAggregateOperators[index],
dataSource,
@@ -96,7 +95,7 @@ export const getQueryBuilderQuerieswithFormula = ({
aggregateAttribute: autocompleteData[index],
queryName: alphabet[index],
expression: alphabet[index],
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
filters: {
items: additionalItems[index],
op: 'AND',

View File

@@ -4,7 +4,7 @@ import { SpaceAggregation, TimeAggregation } from 'api/v5/v5';
import { initialQueriesMap } from 'constants/queryBuilder';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
export function formatTimestampToReadableDate(timestamp: string): string {
const date = new Date(timestamp);
@@ -114,7 +114,7 @@ export function getMetricDetailsQuery(
metricName,
timeAggregation: timeAggregation as TimeAggregation,
spaceAggregation: spaceAggregation as SpaceAggregation,
reduceTo: ReduceOperators.AVG,
reduceTo: 'avg',
temporality: '',
},
],

View File

@@ -1,3 +1,47 @@
.settings-container-root {
.ant-drawer-wrapper-body {
border-left: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
.ant-drawer-header {
height: 48px;
border-bottom: 1px solid var(--bg-slate-500);
padding: 14px 14px 14px 11px;
.ant-drawer-header-title {
gap: 16px;
.ant-drawer-title {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
padding-left: 16px;
border-left: 1px solid #161922;
}
.ant-drawer-close {
height: 16px;
width: 16px;
margin-inline-end: 0px !important;
}
}
}
.ant-drawer-body {
padding: 16px;
&::-webkit-scrollbar {
width: 0.1rem;
}
}
}
}
.dashboard-description-container {
box-shadow: none;
border: none;
@@ -532,6 +576,28 @@
}
.lightMode {
.settings-container-root {
.ant-drawer-wrapper-body {
border-left: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
.ant-drawer-header {
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-drawer-header-title {
.ant-drawer-title {
color: var(--bg-ink-400);
border-left: 1px solid var(--bg-vanilla-300);
}
.ant-drawer-close {
color: var(--bg-ink-300);
}
}
}
}
}
.dashboard-description-container {
color: var(--bg-ink-400);

View File

@@ -0,0 +1,53 @@
import './Description.styles.scss';
import { Button } from 'antd';
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { useRef, useState } from 'react';
import DashboardSettingsContent from '../DashboardSettings';
import { DrawerContainer } from './styles';
function SettingsDrawer({ drawerTitle }: { drawerTitle: string }): JSX.Element {
const [visible, setVisible] = useState<boolean>(false);
const variableViewModeRef = useRef<() => void>();
const showDrawer = (): void => {
setVisible(true);
};
const handleClose = (): void => {
setVisible(false);
variableViewModeRef?.current?.();
};
return (
<>
<Button
type="text"
className="configure-button"
icon={<ConfigureIcon />}
data-testid="show-drawer"
onClick={showDrawer}
>
Configure
</Button>
<DrawerContainer
title={drawerTitle}
placement="right"
width="50%"
onClose={handleClose}
open={visible}
rootClassName="settings-container-root"
>
<OverlayScrollbar>
<DashboardSettingsContent variableViewModeRef={variableViewModeRef} />
</OverlayScrollbar>
</DrawerContainer>
</>
);
}
export default SettingsDrawer;

View File

@@ -12,7 +12,6 @@ import {
Typography,
} from 'antd';
import logEvent from 'api/common/logEvent';
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
@@ -41,7 +40,7 @@ import {
import { useAppContext } from 'providers/App/App';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { sortLayout } from 'providers/Dashboard/util';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { FullScreenHandle } from 'react-full-screen';
import { Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
@@ -53,11 +52,9 @@ import { ComponentTypes } from 'utils/permission';
import { v4 as uuid } from 'uuid';
import DashboardGraphSlider from '../ComponentsSlider';
import DashboardSettings from '../DashboardSettings';
import { Base64Icons } from '../DashboardSettings/General/utils';
import DashboardVariableSelection from '../DashboardVariablesSelection';
import SettingsDrawer from './SettingsDrawer';
import { VariablesSettingsTab } from './types';
import { DEFAULT_ROW_NAME, downloadObjectAsJson } from './utils';
interface DashboardDescriptionProps {
@@ -104,11 +101,6 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
handleDashboardLockToggle,
} = useDashboard();
const variablesSettingsTabHandle = useRef<VariablesSettingsTab>(null);
const [isSettingsDrawerOpen, setIsSettingsDrawerOpen] = useState<boolean>(
false,
);
const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
const isPublicDashboardEnabled = isCloudUser || isEnterpriseSelfHostedUser;
@@ -348,18 +340,6 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
publicDashboardResponse?.data,
]);
const onConfigureClick = useCallback((): void => {
setIsSettingsDrawerOpen(true);
}, []);
const onSettingsDrawerClose = useCallback((): void => {
setIsSettingsDrawerOpen(false);
// good use case for a state library like Jotai
if (variablesSettingsTabHandle.current) {
variablesSettingsTabHandle.current.resetState();
}
}, []);
return (
<Card className="dashboard-description-container">
<div className="dashboard-header">
@@ -524,26 +504,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
/>
</Popover>
{!isDashboardLocked && editDashboard && (
<>
<Button
type="text"
className="configure-button"
icon={<ConfigureIcon />}
data-testid="show-drawer"
onClick={onConfigureClick}
>
Configure
</Button>
<SettingsDrawer
drawerTitle="Dashboard Configuration"
isOpen={isSettingsDrawerOpen}
onClose={onSettingsDrawerClose}
>
<DashboardSettings
variablesSettingsTabHandle={variablesSettingsTabHandle}
/>
</SettingsDrawer>
</>
<SettingsDrawer drawerTitle="Dashboard Configuration" />
)}
{!isDashboardLocked && addPanelPermission && (
<Button

View File

@@ -0,0 +1,24 @@
import { Button as ButtonComponent, Drawer } from 'antd';
import styled from 'styled-components';
export const Container = styled.div`
margin-top: 0.5rem;
`;
export const Button = styled(ButtonComponent)`
&&& {
display: flex;
align-items: center;
}
`;
export const DrawerContainer = styled(Drawer)`
.ant-drawer-header {
padding: 16px;
border: none;
}
.ant-drawer-body {
padding-top: 0;
}
`;

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