mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-10 15:00:47 +01:00
Compare commits
2 Commits
issue_2899
...
chore/remo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a896731c5d | ||
|
|
5b8067eeac |
@@ -1,19 +0,0 @@
|
||||
---
|
||||
paths:
|
||||
- "tests/**/*.py"
|
||||
---
|
||||
|
||||
# pytest conventions
|
||||
|
||||
For the Python integration suite under `tests/`. Setup, running, and suite layout live in [`docs/contributing/tests/integration.md`](../../docs/contributing/tests/integration.md).
|
||||
|
||||
- **No `_`-prefixed helper functions in test modules — this is the rule that matters most.** A reader must be able to see what a test does in its body alone, without chasing private helpers that scatter the meaning across the file. Inline the logic: an expression, a comprehension, a few repeated lines are all fine — repetition across tests is cheaper than indirection. When several tests genuinely share non-trivial setup or assertions, that is what fixtures are for — in `tests/fixtures/`, see the next rule. A module-level `_helper()` is never the answer.
|
||||
- **Fixtures live in `tests/fixtures/` — never under `integration/tests/`.** Not in test modules, not in suite `conftest.py` files. `tests/fixtures/` is the shared library (auth, signoz, clickhouse, logs/metrics/traces seeding, …): reuse what's there before writing anything new; when a new fixture is genuinely needed, add it to the matching `tests/fixtures/` module and register new modules in `tests/conftest.py` `pytest_plugins`. **The one exception: SigNoz-level fixtures in a suite's `conftest.py`.** A suite that needs its own SigNoz spun up with different envs (`create_signoz`/`create_migrator` with `env_overrides` + `cache_key` — e.g. basepath, metricreduction, querier_json_body) keeps that in its `conftest.py`; that is always okay.
|
||||
- **Fixture only when there is a lifecycle; otherwise a plain function.** A fixture earns its indirection by owning setup/teardown (`yield` + cleanup — `insert_metrics` truncating on teardown) or by provisioning a resource (containers, SigNoz instances). A stateless action or lookup (`create_saved_view`, `find_saved_view_by_name`, wiping a resource list) is a plain importable function in the matching `tests/fixtures/` module, taking `signoz`/`token` as ordinary arguments — never wrap a plain callable in a fixture-factory just to inject `signoz`.
|
||||
- **Fixtures own their cleanup.** When a test needs seeded state, put the seed + cleanup pair in a fixture (`yield`, then tear down) so tests in the same suite don't interfere — the pattern `insert_metrics` sets: yield a callable, truncate on teardown.
|
||||
- **Fixture-factory over indirect parametrization.** A fixture that yields a callable (e.g. `insert_metrics(metrics)`) is clearer than `@pytest.mark.parametrize(..., indirect=True)` + `request.param` — the value is an explicit argument, not resolved by magic.
|
||||
- **Skip at collection, not inside the test body.** Use `pytest.param(..., marks=pytest.mark.skip(reason="…"))` so a skipped case shows as SKIPPED-with-reason **and** short-circuits before its fixtures run (no environment spin-up for a test that won't execute).
|
||||
- **Test config comes from explicit `--flags`, not the environment.** Wire configuration as pytest options declared in `tests/conftest.py` (`pytest_addoption` — e.g. `--sqlstore-provider`, `--clickhouse-version`); do **not** add `os.environ` fallbacks inside tests or fixtures.
|
||||
- **snake_case parametrize ids.** `ids=["fill_gaps", "fill_zero"]`, not camelCase.
|
||||
- **Name suite files with the two-digit prefix (`NN_*.py`).** `pyproject.toml` restricts collection to `[0-9][0-9]_*.py` (plus the bootstrap `setup.py` / `run.py`) — a file that doesn't match is silently never collected.
|
||||
- **Always run pytest from `tests/`.** `--import-mode=importlib` is what allows same-basename files across suites (`querier/01_logs.py` vs `rawexportdata/01_logs.py`), but it disables pytest's implicit `sys.path` injection — `import fixtures` only resolves via `pythonpath = ["."]` from that rootdir.
|
||||
@@ -4212,21 +4212,6 @@ components:
|
||||
- missingOptionalMetrics
|
||||
- missingRequiredAttributes
|
||||
type: object
|
||||
InframonitoringtypesClusterFilter:
|
||||
properties:
|
||||
expression:
|
||||
type: string
|
||||
filterByNodeReadiness:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNodeCondition'
|
||||
nullable: true
|
||||
type: array
|
||||
filterByPodStatus:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodStatus'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
InframonitoringtypesClusterRecord:
|
||||
properties:
|
||||
clusterCPU:
|
||||
@@ -4364,16 +4349,6 @@ components:
|
||||
- containerCannotRun
|
||||
- unknown
|
||||
type: object
|
||||
InframonitoringtypesContainerFilter:
|
||||
properties:
|
||||
expression:
|
||||
type: string
|
||||
filterByContainerStatus:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesContainerStatus'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
InframonitoringtypesContainerReady:
|
||||
enum:
|
||||
- ready
|
||||
@@ -4473,16 +4448,6 @@ components:
|
||||
- total
|
||||
- endTimeBeforeRetention
|
||||
type: object
|
||||
InframonitoringtypesDaemonSetFilter:
|
||||
properties:
|
||||
expression:
|
||||
type: string
|
||||
filterByPodStatus:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodStatus'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
InframonitoringtypesDaemonSetRecord:
|
||||
properties:
|
||||
currentNodes:
|
||||
@@ -4555,16 +4520,6 @@ components:
|
||||
- total
|
||||
- endTimeBeforeRetention
|
||||
type: object
|
||||
InframonitoringtypesDeploymentFilter:
|
||||
properties:
|
||||
expression:
|
||||
type: string
|
||||
filterByPodStatus:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodStatus'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
InframonitoringtypesDeploymentRecord:
|
||||
properties:
|
||||
availablePods:
|
||||
@@ -4706,16 +4661,6 @@ components:
|
||||
- total
|
||||
- endTimeBeforeRetention
|
||||
type: object
|
||||
InframonitoringtypesJobFilter:
|
||||
properties:
|
||||
expression:
|
||||
type: string
|
||||
filterByPodStatus:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodStatus'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
InframonitoringtypesJobRecord:
|
||||
properties:
|
||||
activePods:
|
||||
@@ -4839,16 +4784,6 @@ components:
|
||||
- message
|
||||
- documentationLink
|
||||
type: object
|
||||
InframonitoringtypesNamespaceFilter:
|
||||
properties:
|
||||
expression:
|
||||
type: string
|
||||
filterByPodStatus:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodStatus'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
InframonitoringtypesNamespaceRecord:
|
||||
properties:
|
||||
counts:
|
||||
@@ -4930,21 +4865,6 @@ components:
|
||||
- ready
|
||||
- notReady
|
||||
type: object
|
||||
InframonitoringtypesNodeFilter:
|
||||
properties:
|
||||
expression:
|
||||
type: string
|
||||
filterByNodeReadiness:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNodeCondition'
|
||||
nullable: true
|
||||
type: array
|
||||
filterByPodStatus:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodStatus'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
InframonitoringtypesNodeRecord:
|
||||
properties:
|
||||
condition:
|
||||
@@ -5061,16 +4981,6 @@ components:
|
||||
- shutdown
|
||||
- unexpectedAdmissionError
|
||||
type: object
|
||||
InframonitoringtypesPodFilter:
|
||||
properties:
|
||||
expression:
|
||||
type: string
|
||||
filterByPodStatus:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodStatus'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
InframonitoringtypesPodRecord:
|
||||
properties:
|
||||
meta:
|
||||
@@ -5170,7 +5080,7 @@ components:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/InframonitoringtypesClusterFilter'
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
@@ -5196,7 +5106,7 @@ components:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/InframonitoringtypesContainerFilter'
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
@@ -5222,7 +5132,7 @@ components:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/InframonitoringtypesDaemonSetFilter'
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
@@ -5248,7 +5158,7 @@ components:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/InframonitoringtypesDeploymentFilter'
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
@@ -5300,7 +5210,7 @@ components:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/InframonitoringtypesJobFilter'
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
@@ -5326,7 +5236,7 @@ components:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNamespaceFilter'
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
@@ -5352,7 +5262,7 @@ components:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNodeFilter'
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
@@ -5378,7 +5288,7 @@ components:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodFilter'
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
@@ -5404,7 +5314,7 @@ components:
|
||||
format: int64
|
||||
type: integer
|
||||
filter:
|
||||
$ref: '#/components/schemas/InframonitoringtypesStatefulSetFilter'
|
||||
$ref: '#/components/schemas/Querybuildertypesv5Filter'
|
||||
groupBy:
|
||||
items:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5GroupByKey'
|
||||
@@ -5455,16 +5365,6 @@ components:
|
||||
- list
|
||||
- grouped_list
|
||||
type: string
|
||||
InframonitoringtypesStatefulSetFilter:
|
||||
properties:
|
||||
expression:
|
||||
type: string
|
||||
filterByPodStatus:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodStatus'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
InframonitoringtypesStatefulSetRecord:
|
||||
properties:
|
||||
currentPods:
|
||||
@@ -8938,6 +8838,15 @@ components:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
TypesPostableBulkInviteRequest:
|
||||
properties:
|
||||
invites:
|
||||
items:
|
||||
$ref: '#/components/schemas/TypesPostableInvite'
|
||||
type: array
|
||||
required:
|
||||
- invites
|
||||
type: object
|
||||
TypesPostableForgotPassword:
|
||||
properties:
|
||||
email:
|
||||
@@ -11545,6 +11454,57 @@ paths:
|
||||
summary: Create invite
|
||||
tags:
|
||||
- users
|
||||
/api/v1/invite/bulk:
|
||||
post:
|
||||
deprecated: true
|
||||
description: This endpoint creates a bulk invite for a user
|
||||
operationId: CreateBulkInvite
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesPostableBulkInviteRequest'
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"409":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Conflict
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Create bulk invite
|
||||
tags:
|
||||
- users
|
||||
/api/v1/llm_pricing_rules:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -12198,9 +12158,9 @@ paths:
|
||||
- dashboard
|
||||
/api/v1/resetPassword:
|
||||
post:
|
||||
deprecated: true
|
||||
deprecated: false
|
||||
description: This endpoint resets the password by token
|
||||
operationId: ResetPasswordDeprecated
|
||||
operationId: ResetPassword
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
@@ -14468,6 +14428,177 @@ paths:
|
||||
summary: List users
|
||||
tags:
|
||||
- users
|
||||
/api/v1/user/{id}:
|
||||
delete:
|
||||
deprecated: true
|
||||
description: This endpoint deletes the user by id
|
||||
operationId: DeleteUserDeprecated
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Delete user
|
||||
tags:
|
||||
- users
|
||||
get:
|
||||
deprecated: true
|
||||
description: This endpoint returns the user by id
|
||||
operationId: GetUserDeprecated
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesDeprecatedUser'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Get user
|
||||
tags:
|
||||
- users
|
||||
put:
|
||||
deprecated: true
|
||||
description: This endpoint updates the user by id
|
||||
operationId: UpdateUserDeprecated
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesDeprecatedUser'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesDeprecatedUser'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Update user
|
||||
tags:
|
||||
- users
|
||||
/api/v1/user/me:
|
||||
get:
|
||||
deprecated: true
|
||||
@@ -15567,41 +15698,6 @@ paths:
|
||||
summary: Forgot password
|
||||
tags:
|
||||
- users
|
||||
/api/v2/factor_password/reset:
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint resets the password using a single use reset password
|
||||
token
|
||||
operationId: ResetPassword
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesPostableResetPassword'
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
summary: Reset password
|
||||
tags:
|
||||
- users
|
||||
/api/v2/features:
|
||||
get:
|
||||
deprecated: false
|
||||
|
||||
@@ -98,14 +98,6 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
featureSet[idx].Active = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ah.Respond(w, featureSet)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,15 +17,3 @@ func GetOrDefaultEnv(key string, fallback string) string {
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// constant functions that override env vars
|
||||
|
||||
const DotMetricsEnabled = "DOT_METRICS_ENABLED"
|
||||
|
||||
var IsDotMetricsEnabled = false
|
||||
|
||||
func init() {
|
||||
if GetOrDefaultEnv(DotMetricsEnabled, "true") == "true" {
|
||||
IsDotMetricsEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,13 +527,6 @@ const routes: AppRoutes[] = [
|
||||
key: 'AI_OBSERVABILITY_OVERVIEW',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.AI_OBSERVABILITY_EXPLORER,
|
||||
exact: true,
|
||||
component: LLMObservabilityPage,
|
||||
key: 'AI_OBSERVABILITY_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.AI_OBSERVABILITY_CONFIGURATION,
|
||||
exact: true,
|
||||
|
||||
@@ -5648,47 +5648,6 @@ export interface InframonitoringtypesChecksDTO {
|
||||
type: InframonitoringtypesCheckTypeDTO;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesNodeConditionDTO {
|
||||
ready = 'ready',
|
||||
not_ready = 'not_ready',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
export enum InframonitoringtypesPodStatusDTO {
|
||||
pending = 'pending',
|
||||
running = 'running',
|
||||
failed = 'failed',
|
||||
unknown = 'unknown',
|
||||
crashloopbackoff = 'crashloopbackoff',
|
||||
imagepullbackoff = 'imagepullbackoff',
|
||||
errimagepull = 'errimagepull',
|
||||
createcontainerconfigerror = 'createcontainerconfigerror',
|
||||
containercreating = 'containercreating',
|
||||
oomkilled = 'oomkilled',
|
||||
completed = 'completed',
|
||||
error = 'error',
|
||||
containercannotrun = 'containercannotrun',
|
||||
evicted = 'evicted',
|
||||
nodeaffinity = 'nodeaffinity',
|
||||
nodelost = 'nodelost',
|
||||
shutdown = 'shutdown',
|
||||
unexpectedadmissionerror = 'unexpectedadmissionerror',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
export interface InframonitoringtypesClusterFilterDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByNodeReadiness?: InframonitoringtypesNodeConditionDTO[] | null;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByPodStatus?: InframonitoringtypesPodStatusDTO[] | null;
|
||||
}
|
||||
|
||||
export type InframonitoringtypesClusterRecordDTOCounts = {
|
||||
/**
|
||||
* @type integer
|
||||
@@ -5964,6 +5923,21 @@ export interface InframonitoringtypesContainerCountsByStatusDTO {
|
||||
waiting: number;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesContainerReadyDTO {
|
||||
ready = 'ready',
|
||||
not_ready = 'not_ready',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
export type InframonitoringtypesContainerRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesContainerRecordDTOMeta =
|
||||
InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
|
||||
|
||||
export enum InframonitoringtypesContainerStatusDTO {
|
||||
running = 'running',
|
||||
waiting = 'waiting',
|
||||
@@ -5980,32 +5954,6 @@ export enum InframonitoringtypesContainerStatusDTO {
|
||||
unknown = 'unknown',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
export interface InframonitoringtypesContainerFilterDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByContainerStatus?: InframonitoringtypesContainerStatusDTO[] | null;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesContainerReadyDTO {
|
||||
ready = 'ready',
|
||||
not_ready = 'not_ready',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
export type InframonitoringtypesContainerRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesContainerRecordDTOMeta =
|
||||
InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesContainerRecordDTO {
|
||||
containerCountsByReady: InframonitoringtypesContainerCountsByReadyDTO;
|
||||
containerCountsByStatus: InframonitoringtypesContainerCountsByStatusDTO;
|
||||
@@ -6077,17 +6025,6 @@ export interface InframonitoringtypesContainersDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesDaemonSetFilterDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByPodStatus?: InframonitoringtypesPodStatusDTO[] | null;
|
||||
}
|
||||
|
||||
export type InframonitoringtypesDaemonSetRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -6173,17 +6110,6 @@ export interface InframonitoringtypesDaemonSetsDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesDeploymentFilterDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByPodStatus?: InframonitoringtypesPodStatusDTO[] | null;
|
||||
}
|
||||
|
||||
export type InframonitoringtypesDeploymentRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -6346,17 +6272,6 @@ export interface InframonitoringtypesHostsDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesJobFilterDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByPodStatus?: InframonitoringtypesPodStatusDTO[] | null;
|
||||
}
|
||||
|
||||
export type InframonitoringtypesJobRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -6442,17 +6357,6 @@ export interface InframonitoringtypesJobsDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesNamespaceFilterDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByPodStatus?: InframonitoringtypesPodStatusDTO[] | null;
|
||||
}
|
||||
|
||||
export type InframonitoringtypesNamespaceRecordDTOCounts = {
|
||||
/**
|
||||
* @type integer
|
||||
@@ -6529,21 +6433,11 @@ export interface InframonitoringtypesNamespacesDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesNodeFilterDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByNodeReadiness?: InframonitoringtypesNodeConditionDTO[] | null;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByPodStatus?: InframonitoringtypesPodStatusDTO[] | null;
|
||||
export enum InframonitoringtypesNodeConditionDTO {
|
||||
ready = 'ready',
|
||||
not_ready = 'not_ready',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
|
||||
export type InframonitoringtypesNodeRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -6605,17 +6499,6 @@ export interface InframonitoringtypesNodesDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPodFilterDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByPodStatus?: InframonitoringtypesPodStatusDTO[] | null;
|
||||
}
|
||||
|
||||
export type InframonitoringtypesPodRecordDTOMetaAnyOf = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -6626,6 +6509,27 @@ export type InframonitoringtypesPodRecordDTOMetaAnyOf = {
|
||||
export type InframonitoringtypesPodRecordDTOMeta =
|
||||
InframonitoringtypesPodRecordDTOMetaAnyOf | null;
|
||||
|
||||
export enum InframonitoringtypesPodStatusDTO {
|
||||
pending = 'pending',
|
||||
running = 'running',
|
||||
failed = 'failed',
|
||||
unknown = 'unknown',
|
||||
crashloopbackoff = 'crashloopbackoff',
|
||||
imagepullbackoff = 'imagepullbackoff',
|
||||
errimagepull = 'errimagepull',
|
||||
createcontainerconfigerror = 'createcontainerconfigerror',
|
||||
containercreating = 'containercreating',
|
||||
oomkilled = 'oomkilled',
|
||||
completed = 'completed',
|
||||
error = 'error',
|
||||
containercannotrun = 'containercannotrun',
|
||||
evicted = 'evicted',
|
||||
nodeaffinity = 'nodeaffinity',
|
||||
nodelost = 'nodelost',
|
||||
shutdown = 'shutdown',
|
||||
unexpectedadmissionerror = 'unexpectedadmissionerror',
|
||||
no_data = 'no_data',
|
||||
}
|
||||
export interface InframonitoringtypesPodRecordDTO {
|
||||
/**
|
||||
* @type object,null
|
||||
@@ -6702,7 +6606,7 @@ export interface InframonitoringtypesPostableClustersDTO {
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: InframonitoringtypesClusterFilterDTO;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
@@ -6729,7 +6633,7 @@ export interface InframonitoringtypesPostableContainersDTO {
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: InframonitoringtypesContainerFilterDTO;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
@@ -6756,7 +6660,7 @@ export interface InframonitoringtypesPostableDaemonSetsDTO {
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: InframonitoringtypesDaemonSetFilterDTO;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
@@ -6783,7 +6687,7 @@ export interface InframonitoringtypesPostableDeploymentsDTO {
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: InframonitoringtypesDeploymentFilterDTO;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
@@ -6837,7 +6741,7 @@ export interface InframonitoringtypesPostableJobsDTO {
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: InframonitoringtypesJobFilterDTO;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
@@ -6864,7 +6768,7 @@ export interface InframonitoringtypesPostableNamespacesDTO {
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: InframonitoringtypesNamespaceFilterDTO;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
@@ -6891,7 +6795,7 @@ export interface InframonitoringtypesPostableNodesDTO {
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: InframonitoringtypesNodeFilterDTO;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
@@ -6918,7 +6822,7 @@ export interface InframonitoringtypesPostablePodsDTO {
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: InframonitoringtypesPodFilterDTO;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
@@ -6939,24 +6843,13 @@ export interface InframonitoringtypesPostablePodsDTO {
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesStatefulSetFilterDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
expression?: string;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
filterByPodStatus?: InframonitoringtypesPodStatusDTO[] | null;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPostableStatefulSetsDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: InframonitoringtypesStatefulSetFilterDTO;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array,null
|
||||
*/
|
||||
@@ -10057,21 +9950,6 @@ export interface TypesOrganizationDTO {
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface TypesPostableForgotPasswordDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
frontendBaseURL?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
}
|
||||
|
||||
export interface TypesPostableInviteDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -10091,6 +9969,28 @@ export interface TypesPostableInviteDTO {
|
||||
role?: string;
|
||||
}
|
||||
|
||||
export interface TypesPostableBulkInviteRequestDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
invites: TypesPostableInviteDTO[];
|
||||
}
|
||||
|
||||
export interface TypesPostableForgotPasswordDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
frontendBaseURL?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
}
|
||||
|
||||
export interface TypesPostableResetPasswordDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -11201,6 +11101,31 @@ export type ListUsersDeprecated200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteUserDeprecatedPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetUserDeprecatedPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetUserDeprecated200 = {
|
||||
data: TypesDeprecatedUserDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type UpdateUserDeprecatedPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type UpdateUserDeprecated200 = {
|
||||
data: TypesDeprecatedUserDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GetMyUserDeprecated200 = {
|
||||
data: TypesDeprecatedUserDTO;
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,7 @@ import type {
|
||||
CreateResetPasswordTokenPathParameters,
|
||||
CreateUser201,
|
||||
CreateUserRole201,
|
||||
DeleteUserDeprecatedPathParameters,
|
||||
DeleteUserPathParameters,
|
||||
DeleteUserRolePathParameters,
|
||||
GetMyUser200,
|
||||
@@ -36,6 +37,8 @@ import type {
|
||||
GetRolesByUserID200,
|
||||
GetRolesByUserIDPathParameters,
|
||||
GetUser200,
|
||||
GetUserDeprecated200,
|
||||
GetUserDeprecatedPathParameters,
|
||||
GetUserPathParameters,
|
||||
GetUserRole200,
|
||||
GetUserRolePathParameters,
|
||||
@@ -47,12 +50,16 @@ import type {
|
||||
RenderErrorResponseDTO,
|
||||
SetRoleByUserIDPathParameters,
|
||||
TypesChangePasswordRequestDTO,
|
||||
TypesDeprecatedUserDTO,
|
||||
TypesPostableBulkInviteRequestDTO,
|
||||
TypesPostableForgotPasswordDTO,
|
||||
TypesPostableInviteDTO,
|
||||
TypesPostableResetPasswordDTO,
|
||||
TypesPostableRoleDTO,
|
||||
TypesPostableVerifyResetPasswordTokenDTO,
|
||||
TypesUpdatableUserDTO,
|
||||
UpdateUserDeprecated200,
|
||||
UpdateUserDeprecatedPathParameters,
|
||||
UpdateUserPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
@@ -254,11 +261,95 @@ export const useCreateInvite = <
|
||||
return useMutation(getCreateInviteMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint resets the password by token
|
||||
* This endpoint creates a bulk invite for a user
|
||||
* @deprecated
|
||||
* @summary Create bulk invite
|
||||
*/
|
||||
export const createBulkInvite = (
|
||||
typesPostableBulkInviteRequestDTO?: BodyType<TypesPostableBulkInviteRequestDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/invite/bulk`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesPostableBulkInviteRequestDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateBulkInviteMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableBulkInviteRequestDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableBulkInviteRequestDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createBulkInvite'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>,
|
||||
{ data?: BodyType<TypesPostableBulkInviteRequestDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createBulkInvite(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateBulkInviteMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>
|
||||
>;
|
||||
export type CreateBulkInviteMutationBody =
|
||||
| BodyType<TypesPostableBulkInviteRequestDTO>
|
||||
| undefined;
|
||||
export type CreateBulkInviteMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Create bulk invite
|
||||
*/
|
||||
export const useCreateBulkInvite = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableBulkInviteRequestDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createBulkInvite>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableBulkInviteRequestDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getCreateBulkInviteMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint resets the password by token
|
||||
* @summary Reset password
|
||||
*/
|
||||
export const resetPasswordDeprecated = (
|
||||
export const resetPassword = (
|
||||
typesPostableResetPasswordDTO?: BodyType<TypesPostableResetPasswordDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
@@ -271,23 +362,23 @@ export const resetPasswordDeprecated = (
|
||||
});
|
||||
};
|
||||
|
||||
export const getResetPasswordDeprecatedMutationOptions = <
|
||||
export const getResetPasswordMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetPasswordDeprecated>>,
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetPasswordDeprecated>>,
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['resetPasswordDeprecated'];
|
||||
const mutationKey = ['resetPassword'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
@@ -297,47 +388,45 @@ export const getResetPasswordDeprecatedMutationOptions = <
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof resetPasswordDeprecated>>,
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
{ data?: BodyType<TypesPostableResetPasswordDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return resetPasswordDeprecated(data);
|
||||
return resetPassword(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ResetPasswordDeprecatedMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof resetPasswordDeprecated>>
|
||||
export type ResetPasswordMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof resetPassword>>
|
||||
>;
|
||||
export type ResetPasswordDeprecatedMutationBody =
|
||||
export type ResetPasswordMutationBody =
|
||||
| BodyType<TypesPostableResetPasswordDTO>
|
||||
| undefined;
|
||||
export type ResetPasswordDeprecatedMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
export type ResetPasswordMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Reset password
|
||||
*/
|
||||
export const useResetPasswordDeprecated = <
|
||||
export const useResetPassword = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetPasswordDeprecated>>,
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof resetPasswordDeprecated>>,
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getResetPasswordDeprecatedMutationOptions(options));
|
||||
return useMutation(getResetPasswordMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint lists all users
|
||||
@@ -426,6 +515,295 @@ export const invalidateListUsersDeprecated = async (
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint deletes the user by id
|
||||
* @deprecated
|
||||
* @summary Delete user
|
||||
*/
|
||||
export const deleteUserDeprecated = (
|
||||
{ id }: DeleteUserDeprecatedPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/user/${id}`,
|
||||
method: 'DELETE',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteUserDeprecatedMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteUserDeprecated>>,
|
||||
TError,
|
||||
{ pathParams: DeleteUserDeprecatedPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteUserDeprecated>>,
|
||||
TError,
|
||||
{ pathParams: DeleteUserDeprecatedPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteUserDeprecated'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof deleteUserDeprecated>>,
|
||||
{ pathParams: DeleteUserDeprecatedPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteUserDeprecated(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteUserDeprecatedMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteUserDeprecated>>
|
||||
>;
|
||||
|
||||
export type DeleteUserDeprecatedMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Delete user
|
||||
*/
|
||||
export const useDeleteUserDeprecated = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteUserDeprecated>>,
|
||||
TError,
|
||||
{ pathParams: DeleteUserDeprecatedPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteUserDeprecated>>,
|
||||
TError,
|
||||
{ pathParams: DeleteUserDeprecatedPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getDeleteUserDeprecatedMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the user by id
|
||||
* @deprecated
|
||||
* @summary Get user
|
||||
*/
|
||||
export const getUserDeprecated = (
|
||||
{ id }: GetUserDeprecatedPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetUserDeprecated200>({
|
||||
url: `/api/v1/user/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetUserDeprecatedQueryKey = ({
|
||||
id,
|
||||
}: GetUserDeprecatedPathParameters) => {
|
||||
return [`/api/v1/user/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetUserDeprecatedQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getUserDeprecated>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetUserDeprecatedPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getUserDeprecated>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetUserDeprecatedQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getUserDeprecated>>
|
||||
> = ({ signal }) => getUserDeprecated({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getUserDeprecated>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetUserDeprecatedQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getUserDeprecated>>
|
||||
>;
|
||||
export type GetUserDeprecatedQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Get user
|
||||
*/
|
||||
|
||||
export function useGetUserDeprecated<
|
||||
TData = Awaited<ReturnType<typeof getUserDeprecated>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetUserDeprecatedPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getUserDeprecated>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetUserDeprecatedQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Get user
|
||||
*/
|
||||
export const invalidateGetUserDeprecated = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetUserDeprecatedPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetUserDeprecatedQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint updates the user by id
|
||||
* @deprecated
|
||||
* @summary Update user
|
||||
*/
|
||||
export const updateUserDeprecated = (
|
||||
{ id }: UpdateUserDeprecatedPathParameters,
|
||||
typesDeprecatedUserDTO?: BodyType<TypesDeprecatedUserDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<UpdateUserDeprecated200>({
|
||||
url: `/api/v1/user/${id}`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesDeprecatedUserDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateUserDeprecatedMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateUserDeprecated>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateUserDeprecatedPathParameters;
|
||||
data?: BodyType<TypesDeprecatedUserDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateUserDeprecated>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateUserDeprecatedPathParameters;
|
||||
data?: BodyType<TypesDeprecatedUserDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateUserDeprecated'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateUserDeprecated>>,
|
||||
{
|
||||
pathParams: UpdateUserDeprecatedPathParameters;
|
||||
data?: BodyType<TypesDeprecatedUserDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateUserDeprecated(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateUserDeprecatedMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateUserDeprecated>>
|
||||
>;
|
||||
export type UpdateUserDeprecatedMutationBody =
|
||||
| BodyType<TypesDeprecatedUserDTO>
|
||||
| undefined;
|
||||
export type UpdateUserDeprecatedMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Update user
|
||||
*/
|
||||
export const useUpdateUserDeprecated = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateUserDeprecated>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateUserDeprecatedPathParameters;
|
||||
data?: BodyType<TypesDeprecatedUserDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateUserDeprecated>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateUserDeprecatedPathParameters;
|
||||
data?: BodyType<TypesDeprecatedUserDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getUpdateUserDeprecatedMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the user I belong to
|
||||
* @deprecated
|
||||
@@ -596,89 +974,6 @@ export const useForgotPassword = <
|
||||
> => {
|
||||
return useMutation(getForgotPasswordMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint resets the password using a single use reset password token
|
||||
* @summary Reset password
|
||||
*/
|
||||
export const resetPassword = (
|
||||
typesPostableResetPasswordDTO?: BodyType<TypesPostableResetPasswordDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v2/factor_password/reset`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesPostableResetPasswordDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getResetPasswordMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['resetPassword'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
{ data?: BodyType<TypesPostableResetPasswordDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return resetPassword(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ResetPasswordMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof resetPassword>>
|
||||
>;
|
||||
export type ResetPasswordMutationBody =
|
||||
| BodyType<TypesPostableResetPasswordDTO>
|
||||
| undefined;
|
||||
export type ResetPasswordMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Reset password
|
||||
*/
|
||||
export const useResetPassword = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof resetPassword>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableResetPasswordDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getResetPasswordMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint verifies whether a reset password token exists and is not expired
|
||||
* @summary Verify a reset password token
|
||||
|
||||
@@ -24,19 +24,3 @@ export const Logout = async (): Promise<void> => {
|
||||
window.dispatchEvent(new CustomEvent('LOGOUT'));
|
||||
history.push(ROUTES.LOGIN);
|
||||
};
|
||||
|
||||
export const UnderscoreToDotMap: Record<string, string> = {
|
||||
k8s_cluster_name: 'k8s.cluster.name',
|
||||
k8s_cluster_uid: 'k8s.cluster.uid',
|
||||
k8s_namespace_name: 'k8s.namespace.name',
|
||||
k8s_node_name: 'k8s.node.name',
|
||||
k8s_node_uid: 'k8s.node.uid',
|
||||
k8s_pod_name: 'k8s.pod.name',
|
||||
k8s_pod_uid: 'k8s.pod.uid',
|
||||
k8s_deployment_name: 'k8s.deployment.name',
|
||||
k8s_daemonset_name: 'k8s.daemonset.name',
|
||||
k8s_statefulset_name: 'k8s.statefulset.name',
|
||||
k8s_cronjob_name: 'k8s.cronjob.name',
|
||||
k8s_job_name: 'k8s.job.name',
|
||||
k8s_persistentvolumeclaim_name: 'k8s.persistentvolumeclaim.name',
|
||||
};
|
||||
|
||||
28
frontend/src/api/v1/invite/bulk/create.ts
Normal file
28
frontend/src/api/v1/invite/bulk/create.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { UsersProps } from 'types/api/user/inviteUsers';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateBulkInvite` hook (or `createBulkInvite` fetcher) from
|
||||
* `api/generated/services/users` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const inviteUsers = async (
|
||||
users: UsersProps,
|
||||
): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.post(`/invite/bulk`, users);
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: null,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default inviteUsers;
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 128 128"><path fill="#ea4535" d="M80.6 40.3h.4l-.2-.2 14-14v-.3c-11.8-10.4-28.1-14-43.2-9.5C36.5 20.8 24.9 32.8 20.7 48c.2-.1.5-.2.8-.2 5.2-3.4 11.4-5.4 17.9-5.4 2.2 0 4.3.2 6.4.6.1-.1.2-.1.3-.1 9-9.9 24.2-11.1 34.6-2.6h-.1z"/><path fill="#557ebf" d="M108.1 47.8c-2.3-8.5-7.1-16.2-13.8-22.1L80 39.9c6 4.9 9.5 12.3 9.3 20v2.5c16.9 0 16.9 25.2 0 25.2H63.9v20h-.1l.1.2h25.4c14.6.1 27.5-9.3 31.8-23.1 4.3-13.8-1-28.8-13-36.9z"/><path fill="#36a852" d="M39 107.9h26.3V87.7H39c-1.9 0-3.7-.4-5.4-1.1l-15.2 14.6v.2c6 4.3 13.2 6.6 20.7 6.6z"/><path fill="#f9bc15" d="M40.2 41.9c-14.9.1-28.1 9.3-32.9 22.8-4.8 13.6 0 28.5 11.8 37.3l15.6-14.9c-8.6-3.7-10.6-14.5-4-20.8 6.6-6.4 17.8-4.4 21.7 3.8L68 55.2C61.4 46.9 51.1 42 40.2 42.1z"/></svg>
|
||||
|
Before Width: | Height: | Size: 805 B |
@@ -23,13 +23,6 @@
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
|
||||
.cloud-service-data-collected-table-heading-info {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--l3-foreground);
|
||||
cursor: help;
|
||||
}
|
||||
}
|
||||
|
||||
.cloud-service-data-collected-table-logs {
|
||||
@@ -39,9 +32,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cloud-service-data-collected-table-tooltip {
|
||||
max-width: 280px;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@@ -3,19 +3,16 @@ import {
|
||||
CloudintegrationtypesCollectedLogAttributeDTO,
|
||||
CloudintegrationtypesCollectedMetricDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { BarChart, Info, ScrollText } from '@signozhq/icons';
|
||||
import { TooltipProvider, TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { BarChart, ScrollText } from '@signozhq/icons';
|
||||
|
||||
import './CloudServiceDataCollected.styles.scss';
|
||||
|
||||
function CloudServiceDataCollected({
|
||||
logsData,
|
||||
metricsData,
|
||||
metricsInfoTooltip,
|
||||
}: {
|
||||
logsData: CloudintegrationtypesCollectedLogAttributeDTO[] | null | undefined;
|
||||
metricsData: CloudintegrationtypesCollectedMetricDTO[] | null | undefined;
|
||||
metricsInfoTooltip?: string;
|
||||
}): JSX.Element {
|
||||
const logsColumns = [
|
||||
{
|
||||
@@ -87,25 +84,6 @@ function CloudServiceDataCollected({
|
||||
<div className="cloud-service-data-collected-table-heading">
|
||||
<BarChart size={14} />
|
||||
Metrics
|
||||
{metricsInfoTooltip && (
|
||||
<TooltipProvider>
|
||||
<TooltipSimple
|
||||
title={metricsInfoTooltip}
|
||||
side="top"
|
||||
tooltipContentProps={{
|
||||
className: 'cloud-service-data-collected-table-tooltip',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="cloud-service-data-collected-table-heading-info"
|
||||
aria-label="About the metrics listed below"
|
||||
data-testid="data-collected-metrics-info"
|
||||
>
|
||||
<Info size={12} />
|
||||
</span>
|
||||
</TooltipSimple>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</div>
|
||||
<Table
|
||||
columns={metricsColumns}
|
||||
@@ -119,8 +97,4 @@ function CloudServiceDataCollected({
|
||||
);
|
||||
}
|
||||
|
||||
CloudServiceDataCollected.defaultProps = {
|
||||
metricsInfoTooltip: undefined,
|
||||
};
|
||||
|
||||
export default CloudServiceDataCollected;
|
||||
|
||||
@@ -80,7 +80,7 @@ function InviteMembers({
|
||||
weight="semibold"
|
||||
className={styles.headerCellRole}
|
||||
>
|
||||
Roles
|
||||
Role
|
||||
</Typography.Text>
|
||||
<div className={styles.headerCellAction} />
|
||||
</div>
|
||||
@@ -108,10 +108,11 @@ function InviteMembers({
|
||||
|
||||
<div className={styles.cellRole}>
|
||||
<RolesSelect
|
||||
mode="multiple"
|
||||
value={row.roleIds}
|
||||
onChange={(roleIds): void => updateRole(row.id, roleIds)}
|
||||
placeholder="Select roles"
|
||||
mode="single"
|
||||
value={row.roleId || undefined}
|
||||
onChange={(roleId): void => updateRole(row.id, roleId)}
|
||||
placeholder="Select role"
|
||||
allowClear={false}
|
||||
id={`invite-role-${row.id}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -68,8 +68,8 @@ describe('InviteMembers - Edge Cases', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], VALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
await expect(
|
||||
@@ -100,8 +100,8 @@ describe('InviteMembers - Edge Cases', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], VALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
await expect(
|
||||
@@ -132,17 +132,17 @@ describe('InviteMembers - Edge Cases', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], VALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
await expect(
|
||||
screen.findByTestId('invite-api-error'),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
const viewerElements = screen.getAllByTitle('Viewer');
|
||||
const viewerElements = screen.getAllByText('Viewer');
|
||||
await user.click(viewerElements[0]);
|
||||
const editorOptions = await screen.findAllByTitle('Editor');
|
||||
const editorOptions = await screen.findAllByText('Editor');
|
||||
await user.click(editorOptions[editorOptions.length - 1]);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -189,8 +189,8 @@ describe('InviteMembers - Edge Cases', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], VALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
const submitBtn = screen.getByTestId('submit-btn');
|
||||
await user.click(submitBtn);
|
||||
@@ -226,8 +226,8 @@ describe('InviteMembers - Edge Cases', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], ' alice@signoz.io ');
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
|
||||
|
||||
@@ -32,14 +32,14 @@ describe('InviteMembers - Rendering', () => {
|
||||
render(<InviteMembers />);
|
||||
|
||||
expect(screen.getByText('Email address')).toBeInTheDocument();
|
||||
expect(screen.getByText('Roles')).toBeInTheDocument();
|
||||
expect(screen.getByText('Role')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hides header when showHeader is false', () => {
|
||||
render(<InviteMembers showHeader={false} />);
|
||||
|
||||
expect(screen.queryByText('Email address')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Roles')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Role')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders add button by default', () => {
|
||||
@@ -89,7 +89,7 @@ describe('InviteMembers - Rendering', () => {
|
||||
it('renders role select for each row', () => {
|
||||
render(<InviteMembers initialRowCount={2} />);
|
||||
|
||||
const roleSelects = screen.getAllByText('Select roles');
|
||||
const roleSelects = screen.getAllByText('Select role');
|
||||
expect(roleSelects).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,8 +40,8 @@ describe('InviteMembers - Submission', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], 'alice@signoz.io');
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
|
||||
@@ -73,17 +73,17 @@ describe('InviteMembers - Submission', () => {
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
|
||||
await user.type(emailInputs[0], 'alice@signoz.io');
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.type(emailInputs[1], 'bob@signoz.io');
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
const editorOptions = await screen.findAllByTitle('Editor');
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
const editorOptions = await screen.findAllByText('Editor');
|
||||
await user.click(editorOptions[editorOptions.length - 1]);
|
||||
|
||||
await user.type(emailInputs[2], 'charlie@signoz.io');
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
const adminOptions = await screen.findAllByTitle('Admin');
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
const adminOptions = await screen.findAllByText('Admin');
|
||||
await user.click(adminOptions[adminOptions.length - 1]);
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
@@ -125,8 +125,8 @@ describe('InviteMembers - Submission', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], VALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
|
||||
@@ -154,8 +154,8 @@ describe('InviteMembers - Submission', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], VALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
|
||||
@@ -218,12 +218,12 @@ describe('InviteMembers - Submission', () => {
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
|
||||
await user.type(emailInputs[0], 'alice@signoz.io');
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.type(emailInputs[1], 'bob@signoz.io');
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
const editorOptions = await screen.findAllByTitle('Editor');
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
const editorOptions = await screen.findAllByText('Editor');
|
||||
await user.click(editorOptions[editorOptions.length - 1]);
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
@@ -276,8 +276,8 @@ describe('InviteMembers - Submission', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], VALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
|
||||
@@ -303,8 +303,8 @@ describe('InviteMembers - Submission', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], VALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ describe('InviteMembers - Validation', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], INVALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
|
||||
@@ -60,8 +60,8 @@ describe('InviteMembers - Validation', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], INVALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
|
||||
@@ -85,8 +85,8 @@ describe('InviteMembers - Validation', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], INVALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
|
||||
await expect(
|
||||
@@ -149,8 +149,8 @@ describe('InviteMembers - Validation', () => {
|
||||
screen.findByText('Please select roles for team members'),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
@@ -204,8 +204,8 @@ describe('InviteMembers - Validation', () => {
|
||||
|
||||
const emailInputs = screen.getAllByPlaceholderText('e.g. john@signoz.io');
|
||||
await user.type(emailInputs[0], VALID_EMAIL);
|
||||
await user.click(screen.getAllByText('Select roles')[0]);
|
||||
await user.click(await screen.findByTitle('Viewer'));
|
||||
await user.click(screen.getAllByText('Select role')[0]);
|
||||
await user.click(await screen.findByText('Viewer'));
|
||||
|
||||
await user.click(screen.getByTestId('submit-btn'));
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ReactNode } from 'react';
|
||||
export interface InviteMemberRow {
|
||||
id: string;
|
||||
email: string;
|
||||
roleIds: string[];
|
||||
roleId: string;
|
||||
}
|
||||
|
||||
export interface InviteResult {
|
||||
@@ -38,7 +38,7 @@ export interface UseInviteMembersReturn {
|
||||
addRow: () => void;
|
||||
removeRow: (id: string) => void;
|
||||
updateEmail: (id: string, email: string) => void;
|
||||
updateRole: (id: string, roleIds: string[]) => void;
|
||||
updateRole: (id: string, roleId: string | undefined) => void;
|
||||
reset: () => void;
|
||||
submit: () => Promise<InviteResult[]>;
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ import {
|
||||
const createEmptyRow = (): InviteMemberRow => ({
|
||||
id: uuid(),
|
||||
email: '',
|
||||
roleIds: [],
|
||||
roleId: '',
|
||||
});
|
||||
|
||||
const isRowTouched = (row: InviteMemberRow): boolean =>
|
||||
row.email.trim() !== '' || row.roleIds.length > 0;
|
||||
row.email.trim() !== '' || row.roleId !== '';
|
||||
|
||||
export function useInviteMembers(
|
||||
options: UseInviteMembersOptions = {},
|
||||
@@ -78,7 +78,7 @@ export function useInviteMembers(
|
||||
|
||||
touched.forEach((row) => {
|
||||
const emailValid = EMAIL_REGEX.test(row.email);
|
||||
const roleValid = row.roleIds.length > 0;
|
||||
const roleValid = row.roleId !== '';
|
||||
|
||||
if (!emailValid || !row.email) {
|
||||
isValid = false;
|
||||
@@ -139,12 +139,12 @@ export function useInviteMembers(
|
||||
);
|
||||
|
||||
const updateRole = useCallback(
|
||||
(id: string, roleIds: string[]): void => {
|
||||
(id: string, roleId: string | undefined): void => {
|
||||
setRows((prev) => {
|
||||
const updated = cloneDeep(prev);
|
||||
const row = updated.find((r) => r.id === id);
|
||||
if (row) {
|
||||
row.roleIds = roleIds;
|
||||
row.roleId = roleId ?? '';
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
@@ -187,7 +187,7 @@ export function useInviteMembers(
|
||||
await createUser({
|
||||
email: row.email.trim(),
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
userRoles: row.roleIds.map((id) => ({ id })),
|
||||
userRoles: [{ id: row.roleId }],
|
||||
});
|
||||
results.push({ email: row.email, success: true });
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tooltipContent {
|
||||
--tooltip-z-index: 2100;
|
||||
}
|
||||
|
||||
.dropdownContent {
|
||||
--dropdown-menu-content-z-index: 2100;
|
||||
}
|
||||
|
||||
.leftSection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-family: 'Geist Mono', monospace;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-normal);
|
||||
color: var(--l1-foreground);
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.arrows {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { DropdownMenuSimple as Dropdown } from '@signozhq/ui/dropdown-menu';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Compass,
|
||||
Copy,
|
||||
Ellipsis,
|
||||
Link,
|
||||
} from '@signozhq/icons';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { MouseEvent, MouseEventHandler } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
import styles from './LogDetailsHeader.module.scss';
|
||||
|
||||
const TOOLTIP_CONTENT_PROPS = { className: styles.tooltipContent };
|
||||
|
||||
interface LogDetailsHeaderProps {
|
||||
log: ILog;
|
||||
onNavigatePrev: () => void;
|
||||
onNavigateNext: () => void;
|
||||
isPrevDisabled: boolean;
|
||||
isNextDisabled: boolean;
|
||||
showOpenInExplorer?: boolean;
|
||||
onOpenInExplorer?: MouseEventHandler;
|
||||
}
|
||||
|
||||
function LogDetailsHeader({
|
||||
log,
|
||||
onNavigatePrev,
|
||||
onNavigateNext,
|
||||
isPrevDisabled,
|
||||
isNextDisabled,
|
||||
showOpenInExplorer = false,
|
||||
onOpenInExplorer,
|
||||
}: LogDetailsHeaderProps): JSX.Element {
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
const { onLogCopy } = useCopyLogLink(log?.id);
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const handleCopyLog = (): void => {
|
||||
copyToClipboard(aggregateAttributesResourcesToString(log));
|
||||
toast.success('Copied to clipboard', { position: 'bottom-right' });
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
key: 'copy-log',
|
||||
label: 'Copy log',
|
||||
icon: <Copy size={14} />,
|
||||
onClick: handleCopyLog,
|
||||
},
|
||||
{
|
||||
key: 'copy-link',
|
||||
label: 'Copy link to log',
|
||||
icon: <Link size={14} />,
|
||||
onClick: (): void => onLogCopy(),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.header} data-log-detail-ignore="true">
|
||||
<div className={styles.leftSection}>
|
||||
<Divider type="vertical" className={styles.divider} />
|
||||
<Typography.Text
|
||||
className={styles.timestamp}
|
||||
data-testid="log-details-header-timestamp"
|
||||
>
|
||||
{formatTimezoneAdjustedTimestamp(
|
||||
log.date ?? log.timestamp,
|
||||
DATE_TIME_FORMATS.DASH_DATETIME,
|
||||
)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
{showOpenInExplorer && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<Compass size={16} />}
|
||||
onClick={onOpenInExplorer}
|
||||
>
|
||||
Open in Explorer
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Dropdown
|
||||
menu={{ items: menuItems }}
|
||||
align="end"
|
||||
className={styles.dropdownContent}
|
||||
onClick={(e: MouseEvent): void => e.stopPropagation()}
|
||||
>
|
||||
<Button
|
||||
variant="link"
|
||||
color="secondary"
|
||||
prefix={<Ellipsis size={16} />}
|
||||
data-testid="log-details-header-menu"
|
||||
/>
|
||||
</Dropdown>
|
||||
|
||||
<div className={styles.arrows}>
|
||||
<TooltipSimple
|
||||
title="Move to previous log"
|
||||
side="top"
|
||||
open={isPrevDisabled ? false : undefined}
|
||||
tooltipContentProps={TOOLTIP_CONTENT_PROPS}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronUp size={14} />}
|
||||
disabled={isPrevDisabled}
|
||||
onClick={onNavigatePrev}
|
||||
data-testid="log-details-header-prev"
|
||||
/>
|
||||
</TooltipSimple>
|
||||
<TooltipSimple
|
||||
title="Move to next log"
|
||||
side="top"
|
||||
open={isNextDisabled ? false : undefined}
|
||||
tooltipContentProps={TOOLTIP_CONTENT_PROPS}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronDown size={14} />}
|
||||
disabled={isNextDisabled}
|
||||
onClick={onNavigateNext}
|
||||
data-testid="log-details-header-next"
|
||||
/>
|
||||
</TooltipSimple>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LogDetailsHeader.defaultProps = {
|
||||
showOpenInExplorer: false,
|
||||
onOpenInExplorer: undefined,
|
||||
};
|
||||
|
||||
export default LogDetailsHeader;
|
||||
@@ -1,52 +0,0 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
interface UseLogNavigationParams {
|
||||
logs?: ILog[];
|
||||
activeLogId: string;
|
||||
onNavigateLog?: (log: ILog) => void;
|
||||
onScrollToLog?: (id: string) => void;
|
||||
}
|
||||
|
||||
interface UseLogNavigationReturn {
|
||||
goToPrev: () => void;
|
||||
goToNext: () => void;
|
||||
isPrevDisabled: boolean;
|
||||
isNextDisabled: boolean;
|
||||
}
|
||||
|
||||
export function useLogNavigation({
|
||||
logs,
|
||||
activeLogId,
|
||||
onNavigateLog,
|
||||
onScrollToLog,
|
||||
}: UseLogNavigationParams): UseLogNavigationReturn {
|
||||
const currentIndex = useMemo(
|
||||
() => logs?.findIndex((l) => l.id === activeLogId) ?? -1,
|
||||
[logs, activeLogId],
|
||||
);
|
||||
|
||||
const canNavigate = !!logs?.length && !!onNavigateLog && currentIndex !== -1;
|
||||
const isPrevDisabled = !canNavigate || currentIndex <= 0;
|
||||
const isNextDisabled = !canNavigate || currentIndex >= (logs?.length ?? 0) - 1;
|
||||
|
||||
const goToPrev = useCallback((): void => {
|
||||
if (isPrevDisabled || !logs) {
|
||||
return;
|
||||
}
|
||||
const prev = logs[currentIndex - 1];
|
||||
onNavigateLog?.(prev);
|
||||
onScrollToLog?.(prev.id);
|
||||
}, [isPrevDisabled, logs, currentIndex, onNavigateLog, onScrollToLog]);
|
||||
|
||||
const goToNext = useCallback((): void => {
|
||||
if (isNextDisabled || !logs) {
|
||||
return;
|
||||
}
|
||||
const next = logs[currentIndex + 1];
|
||||
onNavigateLog?.(next);
|
||||
onScrollToLog?.(next.id);
|
||||
}, [isNextDisabled, logs, currentIndex, onNavigateLog, onScrollToLog]);
|
||||
|
||||
return { goToPrev, goToNext, isPrevDisabled, isNextDisabled };
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import LogDetail from '..';
|
||||
import { VIEW_TYPES } from '../constants';
|
||||
import { LogDetailProps } from '../LogDetail.interfaces';
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
}));
|
||||
|
||||
// The flag to be removed later
|
||||
jest.mock('../constants', () => ({
|
||||
...jest.requireActual('../constants'),
|
||||
isLogDetailsV2: true,
|
||||
}));
|
||||
|
||||
const mockLog: ILog = {
|
||||
id: 'log-1',
|
||||
timestamp: '2024-01-15T09:45:30Z',
|
||||
date: '2024-01-15T09:45:30Z',
|
||||
body: 'test log body',
|
||||
severityText: 'INFO',
|
||||
severityNumber: 9,
|
||||
traceFlags: 0,
|
||||
traceId: '',
|
||||
spanID: '',
|
||||
attributesString: {},
|
||||
attributesInt: {},
|
||||
attributesFloat: {},
|
||||
resources_string: {},
|
||||
scope_string: {},
|
||||
attributes_string: {},
|
||||
severity_text: 'INFO',
|
||||
severity_number: 9,
|
||||
};
|
||||
|
||||
const makeLog = (id: string): ILog => ({ ...mockLog, id });
|
||||
|
||||
function renderDrawer(props: Partial<LogDetailProps> = {}): void {
|
||||
render(
|
||||
<LogDetail
|
||||
log={mockLog}
|
||||
selectedTab={VIEW_TYPES.OVERVIEW}
|
||||
onAddToQuery={jest.fn()}
|
||||
onClickActionItem={jest.fn()}
|
||||
onClose={jest.fn()}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('LogDetail drawer — header (isLogDetailsV2)', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('renders the revamped header when a log is provided', () => {
|
||||
renderDrawer();
|
||||
|
||||
expect(screen.getByTestId('log-details-header-menu')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('log-details-header-prev')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('log-details-header-next')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the log timestamp formatted (DASH_DATETIME) in the header', () => {
|
||||
// Pin the timezone to UTC so the formatted output is deterministic across
|
||||
// machines/CI (Jest doesn't fix a TZ).
|
||||
localStorage.setItem(LOCALSTORAGE.PREFERRED_TIMEZONE, 'UTC');
|
||||
|
||||
renderDrawer();
|
||||
|
||||
// mockLog date is 2024-01-15T09:45:30Z → DASH_DATETIME in UTC.
|
||||
expect(screen.getByTestId('log-details-header-timestamp')).toHaveTextContent(
|
||||
'Jan 15, 2024 ⎯ 09:45:30',
|
||||
);
|
||||
});
|
||||
|
||||
it('copies the log link from the ⋯ menu', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
renderDrawer();
|
||||
|
||||
await user.click(screen.getByTestId('log-details-header-menu'));
|
||||
await user.click(await screen.findByText('Copy link to log'));
|
||||
|
||||
expect(toast.success).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('copies the log from the ⋯ menu', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
renderDrawer();
|
||||
|
||||
await user.click(screen.getByTestId('log-details-header-menu'));
|
||||
await user.click(await screen.findByText('Copy log'));
|
||||
|
||||
expect(toast.success).toHaveBeenCalledWith('Copied to clipboard', {
|
||||
position: 'bottom-right',
|
||||
});
|
||||
});
|
||||
|
||||
it('shows "Open in Explorer" when a handleOpenInExplorer handler is provided', () => {
|
||||
renderDrawer({ handleOpenInExplorer: jest.fn() });
|
||||
|
||||
expect(screen.getByText('Open in Explorer')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hides "Open in Explorer" when no handleOpenInExplorer handler is provided', () => {
|
||||
renderDrawer();
|
||||
|
||||
expect(screen.queryByText('Open in Explorer')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('navigates to the next / previous log with the Down / Up arrow keys', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const logs = [makeLog('log-0'), makeLog('log-1'), makeLog('log-2')];
|
||||
const onNavigateLog = jest.fn();
|
||||
const onScrollToLog = jest.fn();
|
||||
|
||||
// Active log is the middle one so both directions are available.
|
||||
renderDrawer({ log: logs[1], logs, onNavigateLog, onScrollToLog });
|
||||
|
||||
await user.keyboard('{ArrowDown}');
|
||||
expect(onNavigateLog).toHaveBeenLastCalledWith(logs[2]);
|
||||
expect(onScrollToLog).toHaveBeenLastCalledWith('log-2');
|
||||
|
||||
await user.keyboard('{ArrowUp}');
|
||||
expect(onNavigateLog).toHaveBeenLastCalledWith(logs[0]);
|
||||
expect(onScrollToLog).toHaveBeenLastCalledWith('log-0');
|
||||
});
|
||||
|
||||
it('does not navigate past the first log on ArrowUp', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const logs = [makeLog('log-0'), makeLog('log-1')];
|
||||
const onNavigateLog = jest.fn();
|
||||
|
||||
renderDrawer({ log: logs[0], logs, onNavigateLog });
|
||||
|
||||
await user.keyboard('{ArrowUp}');
|
||||
expect(onNavigateLog).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('navigates via the header up / down buttons and disables them at boundaries', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const logs = [makeLog('log-0'), makeLog('log-1')];
|
||||
const onNavigateLog = jest.fn();
|
||||
|
||||
// Active log is the first one.
|
||||
renderDrawer({ log: logs[0], logs, onNavigateLog });
|
||||
|
||||
expect(screen.getByTestId('log-details-header-prev')).toBeDisabled();
|
||||
expect(screen.getByTestId('log-details-header-next')).toBeEnabled();
|
||||
|
||||
await user.click(screen.getByTestId('log-details-header-next'));
|
||||
expect(onNavigateLog).toHaveBeenLastCalledWith(logs[1]);
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,3 @@
|
||||
import getLocalStorage from 'api/browser/localstorage/get';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
|
||||
// Temp feature flag before actual roll-out
|
||||
export const isLogDetailsV2 =
|
||||
getLocalStorage(LOCALSTORAGE.LOG_DETAILS_V2) === 'true';
|
||||
|
||||
export const VIEW_TYPES = {
|
||||
OVERVIEW: 'OVERVIEW',
|
||||
JSON: 'JSON',
|
||||
|
||||
@@ -8,9 +8,7 @@ import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import LogStateIndicator, {
|
||||
LogType,
|
||||
} from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
|
||||
import { convertExpressionToFilters } from 'components/QueryBuilderV2/utils';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
@@ -25,7 +23,6 @@ import {
|
||||
} from 'container/LogDetailedView/utils';
|
||||
import useInitialQuery from 'container/LogsExplorerContext/useInitialQuery';
|
||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
@@ -51,10 +48,8 @@ import { ILogBody } from 'types/api/logs/log';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
|
||||
import { isLogDetailsV2, RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
|
||||
import { RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
|
||||
import { LogDetailInnerProps, LogDetailProps } from './LogDetail.interfaces';
|
||||
import LogDetailsHeader from './LogDetailsHeader/LogDetailsHeader';
|
||||
import { useLogNavigation } from './LogDetailsHeader/useLogNavigation';
|
||||
|
||||
import './LogDetails.styles.scss';
|
||||
|
||||
@@ -101,8 +96,7 @@ function LogDetailInner({
|
||||
target.closest('[data-log-detail-ignore="true"]') ||
|
||||
target.closest('.cm-tooltip-autocomplete') ||
|
||||
target.closest('.drawer-popover') ||
|
||||
target.closest('.query-status-popover') ||
|
||||
target.closest('[data-radix-popper-content-wrapper]')
|
||||
target.closest('.query-status-popover')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -118,30 +112,49 @@ function LogDetailInner({
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
const { goToPrev, goToNext, isPrevDisabled, isNextDisabled } =
|
||||
useLogNavigation({
|
||||
logs,
|
||||
activeLogId: log.id,
|
||||
onNavigateLog,
|
||||
onScrollToLog,
|
||||
});
|
||||
|
||||
// Keyboard navigation - handle up/down arrow keys. Only listen in the OVERVIEW
|
||||
// tab so we don't hijack arrow keys from the JSON editor / context view.
|
||||
// Keyboard navigation - handle up/down arrow keys
|
||||
// Only listen when in OVERVIEW tab
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
useEffect(() => {
|
||||
if (selectedView !== VIEW_TYPES.OVERVIEW) {
|
||||
return undefined;
|
||||
if (
|
||||
!logs ||
|
||||
!onNavigateLog ||
|
||||
logs.length === 0 ||
|
||||
selectedView !== VIEW_TYPES.OVERVIEW
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent): void => {
|
||||
const currentIndex = logs.findIndex((l) => l.id === log.id);
|
||||
if (currentIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
goToPrev();
|
||||
// Navigate to previous log
|
||||
if (currentIndex > 0) {
|
||||
const prevLog = logs[currentIndex - 1];
|
||||
onNavigateLog(prevLog);
|
||||
// Trigger scroll to the log element
|
||||
if (onScrollToLog) {
|
||||
onScrollToLog(prevLog.id);
|
||||
}
|
||||
}
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
goToNext();
|
||||
// Navigate to next log
|
||||
if (currentIndex < logs.length - 1) {
|
||||
const nextLog = logs[currentIndex + 1];
|
||||
onNavigateLog(nextLog);
|
||||
// Trigger scroll to the log element
|
||||
if (onScrollToLog) {
|
||||
onScrollToLog(nextLog.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -149,7 +162,7 @@ function LogDetailInner({
|
||||
return (): void => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [selectedView, goToPrev, goToNext]);
|
||||
}, [log.id, logs, onNavigateLog, onScrollToLog, selectedView]);
|
||||
|
||||
const listQuery = useMemo(() => {
|
||||
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) {
|
||||
@@ -290,6 +303,33 @@ function LogDetailInner({
|
||||
};
|
||||
|
||||
const logType = log?.attributes_string?.log_level || LogType.INFO;
|
||||
const currentLogIndex = logs ? logs.findIndex((l) => l.id === log.id) : -1;
|
||||
const isPrevDisabled =
|
||||
!logs || !onNavigateLog || logs.length === 0 || currentLogIndex <= 0;
|
||||
const isNextDisabled =
|
||||
!logs ||
|
||||
!onNavigateLog ||
|
||||
logs.length === 0 ||
|
||||
currentLogIndex === logs.length - 1;
|
||||
|
||||
type HandleNavigateLogParams = {
|
||||
direction: 'next' | 'previous';
|
||||
};
|
||||
|
||||
const handleNavigateLog = ({ direction }: HandleNavigateLogParams): void => {
|
||||
if (!logs || !onNavigateLog || currentLogIndex === -1) {
|
||||
return;
|
||||
}
|
||||
if (direction === 'previous' && !isPrevDisabled) {
|
||||
const prevLog = logs[currentLogIndex - 1];
|
||||
onNavigateLog(prevLog);
|
||||
onScrollToLog?.(prevLog.id);
|
||||
} else if (direction === 'next' && !isNextDisabled) {
|
||||
const nextLog = logs[currentLogIndex + 1];
|
||||
onNavigateLog(nextLog);
|
||||
onScrollToLog?.(nextLog.id);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
@@ -298,69 +338,57 @@ function LogDetailInner({
|
||||
maskClosable={false}
|
||||
getContainer={getContainer}
|
||||
title={
|
||||
isLogDetailsV2 ? (
|
||||
<LogDetailsHeader
|
||||
log={log}
|
||||
onNavigatePrev={goToPrev}
|
||||
onNavigateNext={goToNext}
|
||||
isPrevDisabled={isPrevDisabled}
|
||||
isNextDisabled={isNextDisabled}
|
||||
showOpenInExplorer={!!handleOpenInExplorer}
|
||||
onOpenInExplorer={handleOpenInExplorer}
|
||||
/>
|
||||
) : (
|
||||
<div className="log-detail-drawer__title" data-log-detail-ignore="true">
|
||||
<div className="log-detail-drawer__title-left">
|
||||
<Divider type="vertical" className={cx('log-type-indicator', LogType)} />
|
||||
<Typography.Text className="title">Log details</Typography.Text>
|
||||
</div>
|
||||
<div className="log-detail-drawer__title-right">
|
||||
<div className="log-arrows">
|
||||
<Tooltip
|
||||
title={isPrevDisabled ? '' : 'Move to previous log'}
|
||||
placement="top"
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronUp size={14} />}
|
||||
className="log-arrow-btn log-arrow-btn-up"
|
||||
disabled={isPrevDisabled}
|
||||
onClick={goToPrev}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={isNextDisabled ? '' : 'Move to next log'}
|
||||
placement="top"
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronDown size={14} />}
|
||||
className="log-arrow-btn log-arrow-btn-down"
|
||||
disabled={isNextDisabled}
|
||||
onClick={goToNext}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{handleOpenInExplorer && (
|
||||
<div>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<Compass size={16} />}
|
||||
className="open-in-explorer-btn"
|
||||
onClick={handleOpenInExplorer}
|
||||
>
|
||||
Open in Explorer
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="log-detail-drawer__title" data-log-detail-ignore="true">
|
||||
<div className="log-detail-drawer__title-left">
|
||||
<Divider type="vertical" className={cx('log-type-indicator', LogType)} />
|
||||
<Typography.Text className="title">Log details</Typography.Text>
|
||||
</div>
|
||||
)
|
||||
<div className="log-detail-drawer__title-right">
|
||||
<div className="log-arrows">
|
||||
<Tooltip
|
||||
title={isPrevDisabled ? '' : 'Move to previous log'}
|
||||
placement="top"
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronUp size={14} />}
|
||||
className="log-arrow-btn log-arrow-btn-up"
|
||||
disabled={isPrevDisabled}
|
||||
onClick={(): void => handleNavigateLog({ direction: 'previous' })}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={isNextDisabled ? '' : 'Move to next log'}
|
||||
placement="top"
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<ChevronDown size={14} />}
|
||||
className="log-arrow-btn log-arrow-btn-down"
|
||||
disabled={isNextDisabled}
|
||||
onClick={(): void => handleNavigateLog({ direction: 'next' })}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{handleOpenInExplorer && (
|
||||
<div>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<Compass size={16} />}
|
||||
className="open-in-explorer-btn"
|
||||
onClick={handleOpenInExplorer}
|
||||
>
|
||||
Open in Explorer
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
placement="right"
|
||||
onClose={drawerCloseHandler}
|
||||
@@ -379,15 +407,7 @@ function LogDetailInner({
|
||||
data-testid="log-detail-drawer"
|
||||
>
|
||||
<div className="log-detail-drawer__log">
|
||||
{isLogDetailsV2 ? (
|
||||
<LogStateIndicator
|
||||
severityText={log.severity_text}
|
||||
severityNumber={log.severity_number}
|
||||
fontSize={options?.fontSize ?? FontSize.MEDIUM}
|
||||
/>
|
||||
) : (
|
||||
<Divider type="vertical" className={cx('log-type-indicator', logType)} />
|
||||
)}
|
||||
<Divider type="vertical" className={cx('log-type-indicator', logType)} />
|
||||
<Tooltip
|
||||
title={removeEscapeCharacters(logBody)}
|
||||
placement="left"
|
||||
@@ -463,25 +483,22 @@ function LogDetailInner({
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* V2 moves copy actions into the header ⋯ menu */}
|
||||
{!isLogDetailsV2 && (
|
||||
<Tooltip
|
||||
title={selectedView === VIEW_TYPES.JSON ? 'Copy JSON' : 'Copy Log Link'}
|
||||
placement="topLeft"
|
||||
aria-label={
|
||||
selectedView === VIEW_TYPES.JSON ? 'Copy JSON' : 'Copy Log Link'
|
||||
}
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
variant="link"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={<Copy size={12} />}
|
||||
onClick={selectedView === VIEW_TYPES.JSON ? handleJSONCopy : onLogCopy}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip
|
||||
title={selectedView === VIEW_TYPES.JSON ? 'Copy JSON' : 'Copy Log Link'}
|
||||
placement="topLeft"
|
||||
aria-label={
|
||||
selectedView === VIEW_TYPES.JSON ? 'Copy JSON' : 'Copy Log Link'
|
||||
}
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<Button
|
||||
variant="link"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={<Copy size={12} />}
|
||||
onClick={selectedView === VIEW_TYPES.JSON ? handleJSONCopy : onLogCopy}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{isFilterVisible && contextQuery?.builder.queryData[0] && (
|
||||
|
||||
@@ -7,7 +7,6 @@ export enum FeatureKeys {
|
||||
GATEWAY = 'gateway',
|
||||
PREMIUM_SUPPORT = 'premium_support',
|
||||
ANOMALY_DETECTION = 'anomaly_detection',
|
||||
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
|
||||
USE_JSON_BODY = 'use_json_body',
|
||||
ENABLE_AI_OBSERVABILITY = 'enable_ai_observability',
|
||||
ENABLE_METRICS_REDUCTION = 'enable_metrics_reduction',
|
||||
|
||||
@@ -13,7 +13,6 @@ export enum LOCALSTORAGE {
|
||||
TRACES_LIST_COLUMNS = 'TRACES_LIST_COLUMNS',
|
||||
LOGS_LIST_COLUMNS = 'LOGS_LIST_COLUMNS',
|
||||
LOGS_LIST_COLUMN_SIZING = 'LOGS_LIST_COLUMN_SIZING',
|
||||
LOG_DETAILS_V2 = 'LOG_DETAILS_V2',
|
||||
LOGGED_IN_USER_NAME = 'LOGGED_IN_USER_NAME',
|
||||
LOGGED_IN_USER_EMAIL = 'LOGGED_IN_USER_EMAIL',
|
||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||
|
||||
@@ -92,7 +92,6 @@ const ROUTES = {
|
||||
AI_OBSERVABILITY_ATTRIBUTE_MAPPING: '/ai-observability/attribute-mapping',
|
||||
AI_OBSERVABILITY_BASE: '/ai-observability',
|
||||
AI_OBSERVABILITY_OVERVIEW: '/ai-observability/overview',
|
||||
AI_OBSERVABILITY_EXPLORER: '/ai-observability/explorer',
|
||||
AI_OBSERVABILITY_CONFIGURATION: '/ai-observability/configuration',
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Exception, PayloadProps } from 'types/api/errors/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import { FilterDropdownExtendsProps } from './types';
|
||||
import {
|
||||
extractFilterValues,
|
||||
@@ -418,11 +416,6 @@ function AllErrors(): JSX.Element {
|
||||
},
|
||||
];
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const onChangeHandler: TableProps<Exception>['onChange'] = useCallback(
|
||||
(
|
||||
paginations: TablePaginationConfig,
|
||||
@@ -458,7 +451,7 @@ function AllErrors(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!isUndefined(errorCountResponse.data?.payload)) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('Exception: List page visited', {
|
||||
|
||||
@@ -35,7 +35,6 @@ import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { DOCS_LINKS } from '../constants';
|
||||
import { columns, TIME_PICKER_OPTIONS } from './constants';
|
||||
|
||||
@@ -212,19 +211,13 @@ function ServiceMetrics({
|
||||
|
||||
const topLevelOperations = useMemo(() => Object.entries(data || {}), [data]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryRangeRequestData = useMemo(
|
||||
() =>
|
||||
getQueryRangeRequestData({
|
||||
topLevelOperations,
|
||||
globalSelectedInterval,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
[globalSelectedInterval, topLevelOperations, dotMetricsEnabled],
|
||||
[globalSelectedInterval, topLevelOperations],
|
||||
);
|
||||
|
||||
const dataQueries = useGetQueriesRange(
|
||||
|
||||
@@ -82,7 +82,7 @@ export function getHostMetricsQueryPayload(
|
||||
start: number,
|
||||
end: number,
|
||||
): ReturnType<typeof getHostQueryPayload> {
|
||||
return getHostQueryPayload(host.hostName, start, end, true);
|
||||
return getHostQueryPayload(host.hostName, start, end);
|
||||
}
|
||||
|
||||
export { hostWidgetInfo };
|
||||
|
||||
@@ -121,12 +121,6 @@ jest.spyOn(appContextHooks, 'useAppContext').mockReturnValue({
|
||||
plan_version: 'test-plan-version',
|
||||
},
|
||||
},
|
||||
featureFlags: [
|
||||
{
|
||||
name: 'DOT_METRICS_ENABLED',
|
||||
active: false,
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
|
||||
const mockEntity = {
|
||||
|
||||
@@ -17,12 +17,9 @@ import { ChevronDown, Dot, PencilLine, Plug, Plus } from '@signozhq/icons';
|
||||
|
||||
import AzureCloudAccountSetupModal from '../../AzureCloudServices/AddNewAccount/CloudAccountSetupModal';
|
||||
import AzureAccountSettingsModal from '../../AzureCloudServices/EditAccount/AccountSettingsModal';
|
||||
import GcpCloudAccountSetupDrawer from '../../GoogleCloudPlatform/AddNewAccount/CloudAccountSetupDrawer';
|
||||
import GcpAccountSettingsDrawer from '../../GoogleCloudPlatform/EditAccount/AccountSettingsDrawer';
|
||||
import {
|
||||
mapAccountDtoToAwsCloudAccount,
|
||||
mapAccountDtoToAzureCloudAccount,
|
||||
mapAccountDtoToGcpCloudAccount,
|
||||
} from '../../mapCloudAccountFromDto';
|
||||
import AwsCloudAccountSetupModal from '../AddNewAccount/CloudAccountSetupModal';
|
||||
import AwsAccountSettingsModal from '../EditAccount/AccountSettingsModal';
|
||||
@@ -159,18 +156,6 @@ function AccountActions({ type }: { type: IntegrationType }): JSX.Element {
|
||||
});
|
||||
}
|
||||
|
||||
if (type === IntegrationType.GCP_SERVICES) {
|
||||
raw.forEach((account) => {
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
const mapped = mapAccountDtoToGcpCloudAccount(account);
|
||||
if (mapped) {
|
||||
mappedAccounts.push(mapped);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mappedAccounts;
|
||||
}, [listAccountsResponse, type]);
|
||||
|
||||
@@ -222,23 +207,13 @@ function AccountActions({ type }: { type: IntegrationType }): JSX.Element {
|
||||
// log telemetry event when an account is viewed.
|
||||
useEffect(() => {
|
||||
if (activeAccount) {
|
||||
const { config } = activeAccount;
|
||||
let enabledRegions: string[];
|
||||
if ('regions' in config) {
|
||||
// AWS
|
||||
enabledRegions = config.regions;
|
||||
} else if ('resource_groups' in config) {
|
||||
// Azure
|
||||
enabledRegions = config.resource_groups;
|
||||
} else {
|
||||
// GCP
|
||||
enabledRegions = config.project_ids;
|
||||
}
|
||||
|
||||
logEvent(`${type} Integration: Account viewed`, {
|
||||
cloudAccountId: activeAccount?.cloud_account_id,
|
||||
status: activeAccount?.status,
|
||||
enabledRegions,
|
||||
enabledRegions:
|
||||
'regions' in activeAccount.config
|
||||
? activeAccount.config.regions
|
||||
: activeAccount.config.resource_groups,
|
||||
});
|
||||
}
|
||||
}, [activeAccount, type]);
|
||||
@@ -285,11 +260,6 @@ function AccountActions({ type }: { type: IntegrationType }): JSX.Element {
|
||||
onClose={(): void => setIsIntegrationModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{type === IntegrationType.GCP_SERVICES && (
|
||||
<GcpCloudAccountSetupDrawer
|
||||
onClose={(): void => setIsIntegrationModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -311,13 +281,6 @@ function AccountActions({ type }: { type: IntegrationType }): JSX.Element {
|
||||
setActiveAccount={setActiveAccount}
|
||||
/>
|
||||
)}
|
||||
{type === IntegrationType.GCP_SERVICES && (
|
||||
<GcpAccountSettingsDrawer
|
||||
onClose={(): void => setIsAccountSettingsModalOpen(false)}
|
||||
account={activeAccount}
|
||||
setActiveAccount={setActiveAccount}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -46,34 +46,14 @@ const EMPTY_FORM_VALUES: ServiceConfigFormValues = {
|
||||
s3BucketsByRegion: {},
|
||||
};
|
||||
|
||||
const GCP_METRICS_INFO_TOOLTIP =
|
||||
'These are suggested metrics for your OpenTelemetry Collector Configuration. The metrics you actually receive may vary based on the metrics listed in your collector config.';
|
||||
|
||||
function getIntegrationServiceConfig(
|
||||
type: IntegrationType,
|
||||
serviceDetailsData?: ServiceDetailsData,
|
||||
):
|
||||
| { logs?: { enabled?: boolean }; metrics?: { enabled?: boolean } }
|
||||
| undefined {
|
||||
const config = serviceDetailsData?.cloudIntegrationService?.config;
|
||||
|
||||
if (type === IntegrationType.AWS_SERVICES) {
|
||||
return config?.aws;
|
||||
}
|
||||
if (type === IntegrationType.GCP_SERVICES) {
|
||||
return config?.gcp;
|
||||
}
|
||||
return config?.azure;
|
||||
}
|
||||
|
||||
function getInitialFormValues(
|
||||
type: IntegrationType,
|
||||
serviceDetailsData?: ServiceDetailsData,
|
||||
): ServiceConfigFormValues {
|
||||
const integrationConfig = getIntegrationServiceConfig(
|
||||
type,
|
||||
serviceDetailsData,
|
||||
);
|
||||
const integrationConfig =
|
||||
type === IntegrationType.AWS_SERVICES
|
||||
? serviceDetailsData?.cloudIntegrationService?.config?.aws
|
||||
: serviceDetailsData?.cloudIntegrationService?.config?.azure;
|
||||
|
||||
return {
|
||||
logsEnabled: integrationConfig?.logs?.enabled || false,
|
||||
@@ -118,21 +98,16 @@ function getServiceConfigPayload({
|
||||
};
|
||||
}
|
||||
|
||||
// Azure and GCP share the same simple logs/metrics enable-flag shape.
|
||||
const signalConfig = {
|
||||
logs: {
|
||||
enabled: isLogsSupported ? logsEnabled : false,
|
||||
},
|
||||
metrics: {
|
||||
enabled: isMetricsSupported ? metricsEnabled : false,
|
||||
return {
|
||||
azure: {
|
||||
logs: {
|
||||
enabled: isLogsSupported ? logsEnabled : false,
|
||||
},
|
||||
metrics: {
|
||||
enabled: isMetricsSupported ? metricsEnabled : false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (type === IntegrationType.GCP_SERVICES) {
|
||||
return { gcp: signalConfig };
|
||||
}
|
||||
|
||||
return { azure: signalConfig };
|
||||
}
|
||||
|
||||
function ServiceDetails({
|
||||
@@ -187,10 +162,10 @@ function ServiceDetails({
|
||||
? isAccountServiceLoading
|
||||
: isReadOnlyServiceLoading;
|
||||
|
||||
const integrationConfig = getIntegrationServiceConfig(
|
||||
type,
|
||||
serviceDetailsData,
|
||||
);
|
||||
const integrationConfig =
|
||||
type === IntegrationType.AWS_SERVICES
|
||||
? serviceDetailsData?.cloudIntegrationService?.config?.aws
|
||||
: serviceDetailsData?.cloudIntegrationService?.config?.azure;
|
||||
const isServiceEnabledInPersistedConfig =
|
||||
Boolean(integrationConfig?.logs?.enabled) ||
|
||||
Boolean(integrationConfig?.metrics?.enabled);
|
||||
@@ -502,11 +477,6 @@ function ServiceDetails({
|
||||
<CloudServiceDataCollected
|
||||
logsData={serviceDetailsData?.dataCollected?.logs || []}
|
||||
metricsData={serviceDetailsData?.dataCollected?.metrics || []}
|
||||
metricsInfoTooltip={
|
||||
type === IntegrationType.GCP_SERVICES
|
||||
? GCP_METRICS_INFO_TOOLTIP
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -36,12 +36,8 @@ function AccountSettingsModal({
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// `account.config` is the shared per-provider union (Azure | AWS | GCP).
|
||||
// Narrow to Azure by `resource_groups` (Azure-only) rather than
|
||||
// `deployment_region`, which GCP also has — so it no longer identifies
|
||||
// Azure uniquely.
|
||||
const azureConfig = useMemo(
|
||||
() => ('resource_groups' in account.config ? account.config : null),
|
||||
() => ('deployment_region' in account.config ? account.config : null),
|
||||
[account.config],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
.setupDrawer {
|
||||
--dialog-header-padding: var(--spacing-10) var(--spacing-12);
|
||||
--dialog-footer-padding: var(--spacing-8) var(--spacing-12);
|
||||
|
||||
// Input and ComboboxSimple default to --border borders, inherited text and
|
||||
// --muted-foreground placeholders; the drawer wants the dimmer --l2-border with
|
||||
// brighter values and duller placeholders. Backgrounds are left alone — both
|
||||
// components default to transparent and every field sits on an --l2-background
|
||||
// surface already. Focus borders stay at their per-component defaults.
|
||||
--input-border-color: var(--l2-border);
|
||||
--input-hover-border-color: var(--l2-border);
|
||||
--input-foreground: var(--l1-foreground);
|
||||
--input-placeholder-color: var(--l3-foreground);
|
||||
--combobox-trigger-border-color: var(--l2-border);
|
||||
|
||||
// Bounded flex column so the header/footer stay put and only the body
|
||||
// scrolls when content overflows.
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
[data-slot='drawer-header'],
|
||||
[data-slot='drawer-footer'] {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// The drawer body renders inside [data-slot='drawer-description'] — this is
|
||||
// the only region allowed to scroll.
|
||||
[data-slot='drawer-description'] {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-10);
|
||||
min-height: 0;
|
||||
padding: var(--spacing-10) var(--spacing-12);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
[data-slot='select-content'] {
|
||||
width: var(--radix-select-trigger-width);
|
||||
}
|
||||
|
||||
[data-slot='combobox-content'] {
|
||||
z-index: 5;
|
||||
background: var(--l1-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
border-radius: var(--radius-2);
|
||||
}
|
||||
|
||||
// Selected region value: bright, like every other field's text.
|
||||
[data-slot='combobox-value'] {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
// Empty trigger (.regionEmpty, set from the RHF value): dull the placeholder text.
|
||||
.regionEmpty [data-slot='combobox-value'] {
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: var(--periscope-font-size-medium);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
}
|
||||
|
||||
.footerContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.drawerSection {
|
||||
composes: drawerSection from './shared.module.scss';
|
||||
}
|
||||
|
||||
.mono {
|
||||
composes: mono from './shared.module.scss';
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fieldError {
|
||||
composes: fieldError from './shared.module.scss';
|
||||
}
|
||||
|
||||
.projectIdsSelect {
|
||||
:global(.ant-select-selector) {
|
||||
min-height: 36px;
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l2-border) !important;
|
||||
}
|
||||
|
||||
&:hover :global(.ant-select-selector),
|
||||
&:global(.ant-select-focused) :global(.ant-select-selector) {
|
||||
border-color: var(--l2-border);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
// antd defaults to 14px; pin to 13px to line up with the Input/Combobox fields.
|
||||
:global(.ant-select-selection-placeholder),
|
||||
:global(.ant-select-selection-search-input),
|
||||
:global(.ant-select-selection-item) {
|
||||
font-size: var(--periscope-font-size-base);
|
||||
}
|
||||
|
||||
:global(.ant-select-selection-placeholder) {
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
|
||||
:global(.ant-select-selection-search-input) {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
:global(.ant-select-selection-item) {
|
||||
color: var(--l1-foreground);
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
border-radius: var(--radius-2);
|
||||
}
|
||||
|
||||
:global(.ant-select-selection-item-remove) {
|
||||
color: var(--l3-foreground);
|
||||
|
||||
&:hover {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Callout } from '@signozhq/ui/callout';
|
||||
import { ComboboxSimple } from '@signozhq/ui/combobox';
|
||||
import { DrawerWrapper } from '@signozhq/ui/drawer';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Select } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { GCP_REGIONS } from 'container/Integrations/constants';
|
||||
import { IntegrationModalProps } from 'container/Integrations/HeroSection/types';
|
||||
import { useCloudAccountSetupDrawer } from 'hooks/integration/gcp/useCloudAccountSetupDrawer';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import ConnectionSecretsFields from './ConnectionSecretsFields';
|
||||
import FieldLabel from './FieldLabel';
|
||||
import FlowSelector from './FlowSelector';
|
||||
import SetupGuideCallout from './SetupGuideCallout';
|
||||
import { GcpSetupFormValues, SetupFlow } from './types';
|
||||
|
||||
import styles from './CloudAccountSetupDrawer.module.scss';
|
||||
|
||||
const REGION_ITEMS = GCP_REGIONS.map((region) => ({
|
||||
value: region.value,
|
||||
label: `${region.label} (${region.value})`,
|
||||
}));
|
||||
|
||||
const DEFAULT_VALUES: GcpSetupFormValues = {
|
||||
accountName: '',
|
||||
deploymentProjectId: '',
|
||||
deploymentRegion: '',
|
||||
projectIds: [],
|
||||
sigNozApiUrl: '',
|
||||
sigNozApiKey: '',
|
||||
ingestionUrl: '',
|
||||
ingestionKey: '',
|
||||
};
|
||||
|
||||
function CloudAccountSetupDrawer({
|
||||
onClose,
|
||||
}: IntegrationModalProps): JSX.Element {
|
||||
const {
|
||||
isLoading,
|
||||
connectAccount,
|
||||
handleClose,
|
||||
connectionParams,
|
||||
isConnectionParamsLoading,
|
||||
submitError,
|
||||
clearSubmitError,
|
||||
} = useCloudAccountSetupDrawer({ onClose });
|
||||
|
||||
const { control, handleSubmit, setValue } = useForm<GcpSetupFormValues>({
|
||||
defaultValues: DEFAULT_VALUES,
|
||||
});
|
||||
|
||||
const [flow, setFlow] = useState<SetupFlow>('manual');
|
||||
|
||||
// Pre-fill the deployment/ingestion fields with the fetched credentials.
|
||||
useEffect(() => {
|
||||
if (!connectionParams) {
|
||||
return;
|
||||
}
|
||||
setValue('sigNozApiUrl', connectionParams.sigNozApiUrl);
|
||||
setValue('sigNozApiKey', connectionParams.sigNozApiKey);
|
||||
setValue('ingestionUrl', connectionParams.ingestionUrl);
|
||||
setValue('ingestionKey', connectionParams.ingestionKey);
|
||||
}, [connectionParams, setValue]);
|
||||
|
||||
const footer = (
|
||||
<div className={styles.footerContainer}>
|
||||
{submitError && (
|
||||
<Callout
|
||||
type="error"
|
||||
size="small"
|
||||
showIcon
|
||||
action="dismissible"
|
||||
onClick={clearSubmitError}
|
||||
title="Failed to connect GCP account"
|
||||
testId="gcp-connect-error"
|
||||
>
|
||||
{submitError}
|
||||
</Callout>
|
||||
)}
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={handleClose}
|
||||
testId="gcp-cancel-btn"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
onClick={handleSubmit(connectAccount)}
|
||||
loading={isLoading}
|
||||
disabled={isConnectionParamsLoading}
|
||||
testId="gcp-connect-account-btn"
|
||||
>
|
||||
Connect Account
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<DrawerWrapper
|
||||
open={true}
|
||||
className={styles.setupDrawer}
|
||||
onOpenChange={(open): void => {
|
||||
if (!open) {
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
showCloseButton
|
||||
title="Connect Google Cloud Platform"
|
||||
width="base"
|
||||
footer={footer}
|
||||
drawerHeaderProps={{ className: styles.title }}
|
||||
>
|
||||
<FlowSelector value={flow} onChange={setFlow} />
|
||||
<SetupGuideCallout />
|
||||
<div className={styles.drawerSection}>
|
||||
<FieldLabel
|
||||
htmlFor="gcp-account-name-input"
|
||||
label="Account Name"
|
||||
tooltip="A label to identify this group of GCP projects (org ID, billing email, or any descriptive name)"
|
||||
required
|
||||
/>
|
||||
<Controller
|
||||
name="accountName"
|
||||
control={control}
|
||||
rules={{ required: 'Please enter an account name' }}
|
||||
render={({ field, fieldState }): JSX.Element => (
|
||||
<>
|
||||
<Input
|
||||
id="gcp-account-name-input"
|
||||
className={styles.fullWidth}
|
||||
placeholder="e.g. my-org or billing@company.com"
|
||||
value={field.value}
|
||||
onChange={(e): void => field.onChange(e.target.value)}
|
||||
testId="gcp-account-name-input"
|
||||
/>
|
||||
{fieldState.error && (
|
||||
<Typography.Text
|
||||
as="span"
|
||||
size="small"
|
||||
role="alert"
|
||||
className={styles.fieldError}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.drawerSection}>
|
||||
<FieldLabel
|
||||
htmlFor="gcp-deployment-project-id-input"
|
||||
label="Deployment Project ID"
|
||||
tooltip="The GCP project that hosts your OTel Collector deployment — often separate from the projects you actually monitor"
|
||||
required
|
||||
/>
|
||||
<Controller
|
||||
name="deploymentProjectId"
|
||||
control={control}
|
||||
rules={{ required: 'Please enter the deployment project ID' }}
|
||||
render={({ field, fieldState }): JSX.Element => (
|
||||
<>
|
||||
<Input
|
||||
id="gcp-deployment-project-id-input"
|
||||
className={cx(styles.fullWidth, styles.mono)}
|
||||
placeholder="e.g. my-deployment-project-123"
|
||||
value={field.value}
|
||||
onChange={(e): void => field.onChange(e.target.value)}
|
||||
testId="gcp-deployment-project-id-input"
|
||||
/>
|
||||
{fieldState.error && (
|
||||
<Typography.Text
|
||||
as="span"
|
||||
size="small"
|
||||
role="alert"
|
||||
className={styles.fieldError}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.drawerSection}>
|
||||
<FieldLabel
|
||||
htmlFor="gcp-deployment-region-select"
|
||||
label="Deployment Region"
|
||||
tooltip="The GCP region where your OTel Collector will be deployed"
|
||||
required
|
||||
/>
|
||||
<Controller
|
||||
name="deploymentRegion"
|
||||
control={control}
|
||||
rules={{ required: 'Please select a region' }}
|
||||
render={({ field, fieldState }): JSX.Element => (
|
||||
<>
|
||||
<ComboboxSimple
|
||||
id="gcp-deployment-region-select"
|
||||
className={cx(styles.fullWidth, {
|
||||
[styles.regionEmpty]: !field.value,
|
||||
})}
|
||||
items={REGION_ITEMS}
|
||||
value={field.value}
|
||||
onChange={(value): void => field.onChange(value as string)}
|
||||
placeholder="Select a region..."
|
||||
inputPlaceholder="Search regions…"
|
||||
withPortal={false}
|
||||
testId="gcp-deployment-region-select"
|
||||
/>
|
||||
{fieldState.error && (
|
||||
<Typography.Text
|
||||
as="span"
|
||||
size="small"
|
||||
role="alert"
|
||||
className={styles.fieldError}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.drawerSection}>
|
||||
<FieldLabel
|
||||
htmlFor="gcp-project-ids-select"
|
||||
label="Projects to Monitor"
|
||||
tooltip="Enter each GCP project ID then press Enter"
|
||||
required
|
||||
/>
|
||||
<Controller
|
||||
name="projectIds"
|
||||
control={control}
|
||||
rules={{
|
||||
validate: (value): true | string =>
|
||||
value.length > 0 || 'Please add at least one project ID',
|
||||
}}
|
||||
render={({ field, fieldState }): JSX.Element => (
|
||||
<>
|
||||
<Select
|
||||
id="gcp-project-ids-select"
|
||||
className={cx(styles.fullWidth, styles.projectIdsSelect)}
|
||||
mode="tags"
|
||||
value={field.value}
|
||||
onChange={(value): void => field.onChange(value)}
|
||||
placeholder="Add project IDs…"
|
||||
tokenSeparators={[',', ' ']}
|
||||
notFoundContent={null}
|
||||
suffixIcon={null}
|
||||
getPopupContainer={popupContainer}
|
||||
data-testid="gcp-project-ids-select"
|
||||
/>
|
||||
{fieldState.error && (
|
||||
<Typography.Text
|
||||
as="span"
|
||||
size="small"
|
||||
role="alert"
|
||||
className={styles.fieldError}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<ConnectionSecretsFields
|
||||
control={control}
|
||||
isLoading={isConnectionParamsLoading}
|
||||
connectionParams={connectionParams}
|
||||
/>
|
||||
</DrawerWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default CloudAccountSetupDrawer;
|
||||
@@ -1,71 +0,0 @@
|
||||
.drawerSurface {
|
||||
composes: drawerSurface from './shared.module.scss';
|
||||
}
|
||||
|
||||
.drawerSurfaceHead {
|
||||
composes: drawerSurfaceHead from './shared.module.scss';
|
||||
}
|
||||
|
||||
.drawerSection {
|
||||
composes: drawerSection from './shared.module.scss';
|
||||
}
|
||||
|
||||
.headLabel {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
.mono {
|
||||
composes: mono from './shared.module.scss';
|
||||
}
|
||||
|
||||
.fieldError {
|
||||
composes: fieldError from './shared.module.scss';
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.secretsBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-6);
|
||||
}
|
||||
|
||||
.skeletonLabel :global(.ant-skeleton-input) {
|
||||
width: 120px;
|
||||
min-width: 120px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.skeletonInput :global(.ant-skeleton-input) {
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.readonlyField {
|
||||
display: flex;
|
||||
gap: var(--spacing-2);
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
padding: 0 var(--spacing-2) 0 var(--spacing-4);
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
border-radius: var(--radius-2);
|
||||
}
|
||||
|
||||
.readonlyValue {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
// Match the other fields' value text: 13px and the brighter --l1-foreground.
|
||||
font-size: var(--periscope-font-size-base);
|
||||
color: var(--l2-foreground);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
import { Lock } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Skeleton } from 'antd';
|
||||
import { CloudintegrationtypesCredentialsDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import cx from 'classnames';
|
||||
import CopyButton from 'periscope/components/CopyButton/CopyButton';
|
||||
import { Control, Controller } from 'react-hook-form';
|
||||
|
||||
import FieldLabel from './FieldLabel';
|
||||
import { GcpSetupFormValues } from './types';
|
||||
import { SecretFieldType, validateSecretValue } from './validators';
|
||||
import styles from './ConnectionSecretsFields.module.scss';
|
||||
|
||||
type CredentialField = keyof CloudintegrationtypesCredentialsDTO;
|
||||
|
||||
interface FieldConfig {
|
||||
name: CredentialField;
|
||||
label: string;
|
||||
tooltip: string;
|
||||
placeholder: string;
|
||||
testId: string;
|
||||
type: SecretFieldType;
|
||||
}
|
||||
|
||||
const FIELDS: FieldConfig[] = [
|
||||
{
|
||||
name: 'sigNozApiUrl',
|
||||
label: 'SigNoz API URL',
|
||||
tooltip: 'Base URL of your SigNoz instance the collector reports to',
|
||||
placeholder: 'https://<tenant>.signoz.cloud',
|
||||
testId: 'gcp-signoz-api-url-input',
|
||||
type: 'url',
|
||||
},
|
||||
{
|
||||
name: 'sigNozApiKey',
|
||||
label: 'SigNoz API Key',
|
||||
tooltip: 'API key used to authenticate with your SigNoz instance',
|
||||
placeholder: 'Enter SigNoz API key',
|
||||
testId: 'gcp-signoz-api-key-input',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'ingestionUrl',
|
||||
label: 'Ingestion URL',
|
||||
tooltip: 'OTLP ingestion endpoint your OTel Collector sends telemetry to',
|
||||
placeholder: 'https://ingest.<region>.signoz.cloud',
|
||||
testId: 'gcp-ingestion-url-input',
|
||||
type: 'url',
|
||||
},
|
||||
{
|
||||
name: 'ingestionKey',
|
||||
label: 'Ingestion Key',
|
||||
tooltip: 'Ingestion key that authorizes telemetry sent to SigNoz',
|
||||
placeholder: 'Enter ingestion key',
|
||||
testId: 'gcp-ingestion-key-input',
|
||||
type: 'text',
|
||||
},
|
||||
];
|
||||
|
||||
interface ConnectionSecretsFieldsProps {
|
||||
control: Control<GcpSetupFormValues>;
|
||||
isLoading: boolean;
|
||||
connectionParams?: CloudintegrationtypesCredentialsDTO;
|
||||
}
|
||||
|
||||
function ConnectionSecretsFields({
|
||||
control,
|
||||
isLoading,
|
||||
connectionParams,
|
||||
}: ConnectionSecretsFieldsProps): JSX.Element {
|
||||
const hasMissingValue = FIELDS.some(
|
||||
(field) => !connectionParams?.[field.name],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.drawerSurface}>
|
||||
<div className={styles.drawerSurfaceHead}>
|
||||
<Typography.Text weight="bold" size="base">
|
||||
Deployment details & ingestion secrets
|
||||
</Typography.Text>
|
||||
{!hasMissingValue && (
|
||||
<div className={styles.headLabel}>
|
||||
<Lock size={12} />
|
||||
<Typography.Text as="span" size="small" className={styles.headLabel}>
|
||||
Auto-filled by SigNoz
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className={styles.secretsBody} data-testid="gcp-secrets-skeleton">
|
||||
{FIELDS.map((field) => (
|
||||
<div key={field.name} className={styles.drawerSection}>
|
||||
<Skeleton.Input active size="small" className={styles.skeletonLabel} />
|
||||
<Skeleton.Input active block className={styles.skeletonInput} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.secretsBody}>
|
||||
{FIELDS.map((field) => {
|
||||
// Backend-provided values are read-only — the user can't edit them, so
|
||||
// show a truncated value with a copy button. Missing values (enterprise)
|
||||
// stay editable inputs with no copy button.
|
||||
const providedValue = connectionParams?.[field.name];
|
||||
if (providedValue) {
|
||||
return (
|
||||
<div key={field.name} className={styles.drawerSection}>
|
||||
<FieldLabel
|
||||
htmlFor={field.testId}
|
||||
label={field.label}
|
||||
tooltip={field.tooltip}
|
||||
/>
|
||||
<div className={styles.readonlyField}>
|
||||
<Typography.Text
|
||||
as="span"
|
||||
id={field.testId}
|
||||
className={cx(styles.readonlyValue, styles.mono)}
|
||||
title={providedValue}
|
||||
testId={field.testId}
|
||||
>
|
||||
{providedValue}
|
||||
</Typography.Text>
|
||||
<CopyButton
|
||||
value={providedValue}
|
||||
size={12}
|
||||
ariaLabel={`Copy ${field.label}`}
|
||||
testId={`${field.testId}-copy`}
|
||||
onCopy={(): void => {
|
||||
toast.success(`${field.label} copied to clipboard`, {
|
||||
position: 'bottom-right',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={field.name} className={styles.drawerSection}>
|
||||
<FieldLabel
|
||||
htmlFor={field.testId}
|
||||
label={field.label}
|
||||
tooltip={field.tooltip}
|
||||
/>
|
||||
<Controller
|
||||
name={field.name}
|
||||
control={control}
|
||||
rules={{
|
||||
validate: (value): true | string =>
|
||||
validateSecretValue(field.label, field.type, value),
|
||||
}}
|
||||
render={({ field: rhfField, fieldState }): JSX.Element => (
|
||||
<>
|
||||
<Input
|
||||
id={field.testId}
|
||||
className={cx(styles.fullWidth, styles.mono)}
|
||||
placeholder={field.placeholder}
|
||||
value={rhfField.value}
|
||||
onChange={(e): void => rhfField.onChange(e.target.value)}
|
||||
testId={field.testId}
|
||||
/>
|
||||
{fieldState.error && (
|
||||
<Typography.Text
|
||||
as="span"
|
||||
size="small"
|
||||
role="alert"
|
||||
className={styles.fieldError}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ConnectionSecretsFields.defaultProps = {
|
||||
connectionParams: undefined,
|
||||
};
|
||||
|
||||
export default ConnectionSecretsFields;
|
||||
@@ -1,22 +0,0 @@
|
||||
.fieldLabel {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
.required {
|
||||
composes: required from './shared.module.scss';
|
||||
}
|
||||
|
||||
.infoTrigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--l3-foreground);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.tooltipContent {
|
||||
max-width: 240px;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Info } from '@signozhq/icons';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
|
||||
import styles from './FieldLabel.module.scss';
|
||||
|
||||
interface FieldLabelProps {
|
||||
htmlFor: string;
|
||||
label: string;
|
||||
tooltip: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
function FieldLabel({
|
||||
htmlFor,
|
||||
label,
|
||||
tooltip,
|
||||
required,
|
||||
}: FieldLabelProps): JSX.Element {
|
||||
return (
|
||||
<label className={styles.fieldLabel} htmlFor={htmlFor}>
|
||||
{label}
|
||||
|
||||
<TooltipSimple
|
||||
title={tooltip}
|
||||
side="top"
|
||||
tooltipContentProps={{ className: styles.tooltipContent }}
|
||||
>
|
||||
<span
|
||||
className={styles.infoTrigger}
|
||||
aria-label={`${label} help`}
|
||||
data-testid={`${htmlFor}-tooltip`}
|
||||
>
|
||||
<Info size={12} />
|
||||
</span>
|
||||
</TooltipSimple>
|
||||
{required && (
|
||||
<span className={styles.required} aria-hidden="true">
|
||||
*
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
FieldLabel.defaultProps = {
|
||||
required: false,
|
||||
};
|
||||
|
||||
export default FieldLabel;
|
||||
@@ -1,84 +0,0 @@
|
||||
.drawerSection {
|
||||
composes: drawerSection from './shared.module.scss';
|
||||
}
|
||||
|
||||
.drawerSurface {
|
||||
composes: drawerSurface from './shared.module.scss';
|
||||
}
|
||||
|
||||
.drawerSurfaceHead {
|
||||
composes: drawerSurfaceHead from './shared.module.scss';
|
||||
}
|
||||
|
||||
.flowRadioGroup {
|
||||
--radio-group-item-border-color: var(--l2-border);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
width: 100%;
|
||||
|
||||
.flowRadio {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: var(--spacing-5);
|
||||
width: 100%;
|
||||
padding: var(--spacing-5) var(--spacing-6);
|
||||
margin: 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-2);
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.12s ease,
|
||||
border-color 0.12s ease;
|
||||
|
||||
> button[role='radio'] {
|
||||
flex: 0 0 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
> label {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: block;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&.flowRadioManual:has(button[data-state='checked']) {
|
||||
background: color-mix(in srgb, var(--accent-primary) 10%, transparent);
|
||||
border-color: color-mix(in srgb, var(--accent-primary) 30%, transparent);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--l3-background-hover);
|
||||
}
|
||||
|
||||
&:has(button[disabled]) {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
|
||||
&:hover {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flowRadioTitle {
|
||||
display: flex;
|
||||
gap: var(--spacing-2);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flowRadioDesc {
|
||||
margin-top: var(--spacing-2);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
|
||||
import { SetupFlow } from './types';
|
||||
import styles from './FlowSelector.module.scss';
|
||||
|
||||
interface FlowSelectorProps {
|
||||
value: SetupFlow;
|
||||
onChange: (flow: SetupFlow) => void;
|
||||
}
|
||||
|
||||
function FlowSelector({ value, onChange }: FlowSelectorProps): JSX.Element {
|
||||
return (
|
||||
<div className={cx(styles.drawerSection, styles.drawerSurface)}>
|
||||
<div className={styles.drawerSurfaceHead}>
|
||||
<Typography.Text weight="bold" size="base">
|
||||
Connection method
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
<RadioGroup
|
||||
value={value}
|
||||
onChange={(next): void => onChange(next as SetupFlow)}
|
||||
className={styles.flowRadioGroup}
|
||||
>
|
||||
<RadioGroupItem
|
||||
value="manual"
|
||||
containerClassName={cx(styles.flowRadio, styles.flowRadioManual)}
|
||||
testId="gcp-flow-manual"
|
||||
>
|
||||
<div className={styles.flowRadioTitle}>
|
||||
<Typography.Text weight="semibold" size="base">
|
||||
Connect Manually
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Typography.Text
|
||||
as="p"
|
||||
size="small"
|
||||
color="muted"
|
||||
className={styles.flowRadioDesc}
|
||||
>
|
||||
Deploy your own OTel Collector.
|
||||
</Typography.Text>
|
||||
</RadioGroupItem>
|
||||
|
||||
<RadioGroupItem
|
||||
value="agent"
|
||||
containerClassName={styles.flowRadio}
|
||||
testId="gcp-flow-agent"
|
||||
disabled
|
||||
>
|
||||
<div className={styles.flowRadioTitle}>
|
||||
<Typography.Text weight="semibold" size="base">
|
||||
Connect via Agent
|
||||
</Typography.Text>
|
||||
<Badge color="robin" variant="default">
|
||||
Soon
|
||||
</Badge>
|
||||
</div>
|
||||
<Typography.Text
|
||||
as="p"
|
||||
size="small"
|
||||
color="muted"
|
||||
className={styles.flowRadioDesc}
|
||||
>
|
||||
SigNoz deploys and manages the collector for you.
|
||||
</Typography.Text>
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FlowSelector;
|
||||
@@ -1,13 +0,0 @@
|
||||
.guideLink {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
color: var(--callout-primary-title);
|
||||
font-size: var(--periscope-font-size-base);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--accent-primary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { ArrowUpRight, KeyRound } from '@signozhq/icons';
|
||||
import { Callout } from '@signozhq/ui/callout';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import styles from './SetupGuideCallout.module.scss';
|
||||
|
||||
const GCP_INTEGRATION_DOCS_URL =
|
||||
'https://signoz.io/docs/integrations/gcp/gcp-integration/';
|
||||
|
||||
function SetupGuideCallout(): JSX.Element {
|
||||
return (
|
||||
<Callout icon={<KeyRound />} testId="gcp-setup-guide-callout">
|
||||
<Typography.Text as="span" size="base">
|
||||
Please go through our GCP integration guide, which covers all prerequisites
|
||||
— service account, IAM roles, and resource setup.
|
||||
</Typography.Text>
|
||||
<a
|
||||
className={styles.guideLink}
|
||||
href={GCP_INTEGRATION_DOCS_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-testid="gcp-setup-guide-link"
|
||||
>
|
||||
GCP integration guide
|
||||
<ArrowUpRight size={12} />
|
||||
</a>
|
||||
</Callout>
|
||||
);
|
||||
}
|
||||
|
||||
export default SetupGuideCallout;
|
||||
@@ -1,36 +0,0 @@
|
||||
.drawerSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-3);
|
||||
}
|
||||
|
||||
.drawerSection > label {
|
||||
font-size: var(--periscope-font-size-normal);
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.required {
|
||||
color: var(--accent-cherry);
|
||||
}
|
||||
|
||||
.fieldError {
|
||||
font-size: var(--periscope-font-size-small);
|
||||
color: var(--accent-cherry);
|
||||
}
|
||||
|
||||
.drawerSurface {
|
||||
padding: var(--spacing-7);
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--l2-border);
|
||||
}
|
||||
|
||||
.drawerSurfaceHead {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--spacing-5);
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: var(--font-family-mono);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
export type SetupFlow = 'manual' | 'agent';
|
||||
|
||||
export interface GcpSetupFormValues {
|
||||
accountName: string;
|
||||
deploymentProjectId: string;
|
||||
deploymentRegion: string;
|
||||
projectIds: string[];
|
||||
sigNozApiUrl: string;
|
||||
sigNozApiKey: string;
|
||||
ingestionUrl: string;
|
||||
ingestionKey: string;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
export type SecretFieldType = 'url' | 'text';
|
||||
|
||||
export function isValidUrl(value: string): boolean {
|
||||
try {
|
||||
return Boolean(new URL(value));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function validateSecretValue(
|
||||
label: string,
|
||||
type: SecretFieldType,
|
||||
value: string | undefined,
|
||||
): true | string {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) {
|
||||
return `Please enter the ${label}`;
|
||||
}
|
||||
if (type === 'url' && !isValidUrl(trimmed)) {
|
||||
return `Please enter a valid URL for ${label}`;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 17px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.connectedAccountDetails {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.connectedAccountDetailsTitle {
|
||||
color: var(--l1-foreground);
|
||||
font-size: 14px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: var(--line-height-20);
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.accountId {
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
|
||||
.accountIdValue {
|
||||
font-family: 'Geist Mono';
|
||||
font-size: 12px;
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
|
||||
.regionSelector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
.regionSelectorTitle {
|
||||
color: var(--l1-foreground);
|
||||
font-size: 14px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: var(--line-height-20);
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.regionSelectorDescription {
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-5);
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import { Dispatch, SetStateAction, useMemo } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Save } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { DrawerWrapper } from '@signozhq/ui/drawer';
|
||||
import { Form, Select } from 'antd';
|
||||
import { invalidateListAccounts } from 'api/generated/services/cloudintegration';
|
||||
import { INTEGRATION_TYPES } from 'container/Integrations/constants';
|
||||
import { CloudAccount } from 'container/Integrations/types';
|
||||
import { useAccountSettingsDrawer } from 'hooks/integration/gcp/useAccountSettingsDrawer';
|
||||
|
||||
import RemoveIntegrationAccount from '../../RemoveAccount/RemoveIntegrationAccount';
|
||||
|
||||
import styles from './AccountSettingsDrawer.module.scss';
|
||||
|
||||
interface AccountSettingsDrawerProps {
|
||||
onClose: () => void;
|
||||
account: CloudAccount;
|
||||
setActiveAccount: Dispatch<SetStateAction<CloudAccount | null>>;
|
||||
}
|
||||
|
||||
function AccountSettingsDrawer({
|
||||
onClose,
|
||||
account,
|
||||
setActiveAccount,
|
||||
}: AccountSettingsDrawerProps): JSX.Element {
|
||||
const {
|
||||
form,
|
||||
isLoading,
|
||||
projectIds,
|
||||
isSaveDisabled,
|
||||
setProjectIds,
|
||||
handleSubmit,
|
||||
handleClose,
|
||||
} = useAccountSettingsDrawer({ onClose, account, setActiveAccount });
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const gcpConfig = useMemo(
|
||||
() => ('project_ids' in account.config ? account.config : null),
|
||||
[account.config],
|
||||
);
|
||||
|
||||
return (
|
||||
<DrawerWrapper
|
||||
open={true}
|
||||
title="Account Settings"
|
||||
direction="right"
|
||||
showCloseButton
|
||||
onOpenChange={(open): void => {
|
||||
if (!open) {
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
width="wide"
|
||||
footer={
|
||||
<div className={styles.footer}>
|
||||
<RemoveIntegrationAccount
|
||||
accountId={account?.id}
|
||||
onRemoveIntegrationAccountSuccess={(): void => {
|
||||
void invalidateListAccounts(queryClient, {
|
||||
cloudProvider: INTEGRATION_TYPES.GCP,
|
||||
});
|
||||
setActiveAccount(null);
|
||||
handleClose();
|
||||
}}
|
||||
cloudProvider={INTEGRATION_TYPES.GCP}
|
||||
/>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
disabled={isSaveDisabled}
|
||||
onClick={handleSubmit}
|
||||
loading={isLoading}
|
||||
prefix={<Save size={14} />}
|
||||
data-testid="gcp-update-account-btn"
|
||||
>
|
||||
Update Changes
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
projectIds: gcpConfig?.project_ids || [],
|
||||
}}
|
||||
>
|
||||
<div className={styles.body}>
|
||||
<div className={styles.connectedAccountDetails}>
|
||||
<div className={styles.connectedAccountDetailsTitle}>
|
||||
Connected Account details
|
||||
</div>
|
||||
<div className={styles.accountId}>
|
||||
Account Name:{' '}
|
||||
<span className={styles.accountIdValue}>
|
||||
{account?.providerAccountId}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{gcpConfig?.deployment_project_id && (
|
||||
<div className={styles.regionSelector}>
|
||||
<div className={styles.regionSelectorTitle}>Deployment project ID</div>
|
||||
<div className={styles.regionSelectorDescription}>
|
||||
{gcpConfig.deployment_project_id}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{gcpConfig?.deployment_region && (
|
||||
<div className={styles.regionSelector}>
|
||||
<div className={styles.regionSelectorTitle}>Deployment region</div>
|
||||
<div className={styles.regionSelectorDescription}>
|
||||
{gcpConfig.deployment_region}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.regionSelector}>
|
||||
<div className={styles.regionSelectorTitle}>Projects to monitor</div>
|
||||
<div className={styles.regionSelectorDescription}>
|
||||
Update the GCP project IDs that should be monitored.
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
name="projectIds"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
type: 'array',
|
||||
min: 1,
|
||||
message: 'Please add at least one project ID',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
value={projectIds}
|
||||
tokenSeparators={[',']}
|
||||
onChange={(values): void => {
|
||||
setProjectIds(values);
|
||||
form.setFieldValue('projectIds', values);
|
||||
}}
|
||||
data-testid="gcp-edit-project-ids-select"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</DrawerWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountSettingsDrawer;
|
||||
@@ -1,230 +0,0 @@
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest, RestRequest } from 'msw';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
|
||||
import AccountSettingsDrawer from '../EditAccount/AccountSettingsDrawer';
|
||||
import {
|
||||
GCP_ACCOUNT_ID,
|
||||
GCP_ACCOUNT_URL,
|
||||
GCP_ACCOUNTS_URL,
|
||||
gcpAccount,
|
||||
gcpAccountConfig,
|
||||
listAccountsResponse,
|
||||
} from './mockData';
|
||||
|
||||
// `useAccountSettingsDrawer` imports logEvent by relative path, which the
|
||||
// jest.config moduleNameMapper (keyed on the `api/common/logEvent` alias) does
|
||||
// not intercept — so mock the resolved module directly.
|
||||
jest.mock('../../../../../api/common/logEvent', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
...jest.requireActual('@signozhq/ui/sonner'),
|
||||
toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const onClose = jest.fn();
|
||||
const setActiveAccount = jest.fn();
|
||||
|
||||
const renderDrawer = (): void => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<TooltipProvider>
|
||||
<AccountSettingsDrawer
|
||||
onClose={onClose}
|
||||
account={gcpAccount}
|
||||
setActiveAccount={setActiveAccount}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
/** The antd tags Select renders its text input inside the testId wrapper. */
|
||||
const getProjectIdsInput = (): HTMLElement =>
|
||||
within(screen.getByTestId('gcp-edit-project-ids-select')).getByRole(
|
||||
'combobox',
|
||||
);
|
||||
|
||||
/** Each selected tag carries an antd "close" icon that removes it. */
|
||||
const getProjectIdTagRemoveButtons = (): HTMLElement[] =>
|
||||
within(screen.getByTestId('gcp-edit-project-ids-select')).queryAllByLabelText(
|
||||
'close',
|
||||
);
|
||||
|
||||
describe('GCP AccountSettingsDrawer', () => {
|
||||
let updatePayload: Record<string, unknown> | null;
|
||||
|
||||
beforeEach(() => {
|
||||
updatePayload = null;
|
||||
|
||||
server.use(
|
||||
rest.get(GCP_ACCOUNTS_URL, (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(listAccountsResponse)),
|
||||
),
|
||||
rest.put(GCP_ACCOUNT_URL, async (req: RestRequest, res, ctx) => {
|
||||
updatePayload = await req.json();
|
||||
return res(ctx.status(204));
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('renders the connected account details and existing project IDs', () => {
|
||||
renderDrawer();
|
||||
|
||||
expect(screen.getByText(gcpAccount.providerAccountId)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(gcpAccountConfig.deployment_project_id),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(gcpAccountConfig.deployment_region),
|
||||
).toBeInTheDocument();
|
||||
|
||||
gcpAccountConfig.project_ids.forEach((projectId) => {
|
||||
expect(screen.getByTitle(projectId)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps save disabled until the project IDs actually change', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDrawer();
|
||||
|
||||
expect(screen.getByTestId('gcp-update-account-btn')).toBeDisabled();
|
||||
|
||||
await user.type(getProjectIdsInput(), 'project-c,');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('gcp-update-account-btn')).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('sends the updated project IDs while preserving the immutable deployment fields', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDrawer();
|
||||
|
||||
await user.type(getProjectIdsInput(), 'project-c,');
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('gcp-update-account-btn')).toBeEnabled();
|
||||
});
|
||||
await user.click(screen.getByTestId('gcp-update-account-btn'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(updatePayload).not.toBeNull();
|
||||
});
|
||||
|
||||
expect(updatePayload).toStrictEqual({
|
||||
config: {
|
||||
gcp: {
|
||||
deploymentRegion: gcpAccountConfig.deployment_region,
|
||||
deploymentProjectId: gcpAccountConfig.deployment_project_id,
|
||||
projectIds: ['project-a', 'project-b', 'project-c'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(setActiveAccount).toHaveBeenCalledWith({
|
||||
...gcpAccount,
|
||||
config: {
|
||||
deployment_region: gcpAccountConfig.deployment_region,
|
||||
deployment_project_id: gcpAccountConfig.deployment_project_id,
|
||||
project_ids: ['project-a', 'project-b', 'project-c'],
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
expect(toast.success).toHaveBeenCalledWith(
|
||||
'Account settings updated successfully',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('blocks the update and shows a validation error when every project ID is removed', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDrawer();
|
||||
|
||||
// Strip every tag via its remove icon; the list shrinks as we go.
|
||||
while (getProjectIdTagRemoveButtons().length > 0) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await user.click(getProjectIdTagRemoveButtons()[0]);
|
||||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('gcp-update-account-btn')).toBeEnabled();
|
||||
});
|
||||
await user.click(screen.getByTestId('gcp-update-account-btn'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText('Please add at least one project ID'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(updatePayload).toBeNull();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('surfaces a toast and keeps the drawer open when the update fails', async () => {
|
||||
server.use(
|
||||
rest.put(GCP_ACCOUNT_URL, (_req, res, ctx) =>
|
||||
res(
|
||||
ctx.status(500),
|
||||
ctx.json({ status: 'error', error: { message: 'update failed' } }),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
renderDrawer();
|
||||
|
||||
await user.type(getProjectIdsInput(), 'project-c,');
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('gcp-update-account-btn')).toBeEnabled();
|
||||
});
|
||||
await user.click(screen.getByTestId('gcp-update-account-btn'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalledWith(
|
||||
'Failed to update account settings',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
expect(setActiveAccount).not.toHaveBeenCalled();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('disconnects the account with the GCP-specific confirmation copy', async () => {
|
||||
let disconnectedId: string | null = null;
|
||||
server.use(
|
||||
rest.delete(`${GCP_ACCOUNTS_URL}/:id`, (req, res, ctx) => {
|
||||
disconnectedId = req.params.id as string;
|
||||
return res(ctx.status(204));
|
||||
}),
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
renderDrawer();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /disconnect/i }));
|
||||
|
||||
await expect(
|
||||
screen.findByText(/manually tear down/i),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /remove account/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(disconnectedId).toBe(GCP_ACCOUNT_ID);
|
||||
});
|
||||
expect(setActiveAccount).toHaveBeenCalledWith(null);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,210 +0,0 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest, RestRequest } from 'msw';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
|
||||
import CloudAccountSetupDrawer from '../AddNewAccount/CloudAccountSetupDrawer';
|
||||
import {
|
||||
checkInResponse,
|
||||
CLOUD_INTEGRATION_ID,
|
||||
connectionCredentials,
|
||||
connectionCredentialsResponse,
|
||||
createAccountResponse,
|
||||
GCP_ACCOUNTS_URL,
|
||||
GCP_CHECK_IN_URL,
|
||||
GCP_CREDENTIALS_URL,
|
||||
} from './mockData';
|
||||
|
||||
// `useCloudAccountSetupDrawer` imports logEvent by relative path, which the
|
||||
// jest.config moduleNameMapper (keyed on the `api/common/logEvent` alias) does
|
||||
// not intercept — so mock the resolved module directly.
|
||||
jest.mock('../../../../../api/common/logEvent', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
const onClose = jest.fn();
|
||||
|
||||
const renderDrawer = (): void => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<TooltipProvider>
|
||||
<CloudAccountSetupDrawer onClose={onClose} />
|
||||
</TooltipProvider>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
describe('GCP CloudAccountSetupDrawer', () => {
|
||||
let createAccountPayload: Record<string, unknown> | null;
|
||||
let checkInPayload: Record<string, unknown> | null;
|
||||
|
||||
beforeEach(() => {
|
||||
createAccountPayload = null;
|
||||
checkInPayload = null;
|
||||
|
||||
server.use(
|
||||
rest.get(GCP_CREDENTIALS_URL, (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(connectionCredentialsResponse)),
|
||||
),
|
||||
// check_in is registered first — it is a more specific path than
|
||||
// /accounts and msw matches handlers in registration order.
|
||||
rest.post(GCP_CHECK_IN_URL, async (req: RestRequest, res, ctx) => {
|
||||
checkInPayload = await req.json();
|
||||
return res(ctx.status(200), ctx.json(checkInResponse));
|
||||
}),
|
||||
rest.post(GCP_ACCOUNTS_URL, async (req: RestRequest, res, ctx) => {
|
||||
createAccountPayload = await req.json();
|
||||
return res(ctx.status(201), ctx.json(createAccountResponse));
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('renders SigNoz-provided credentials as read-only fields', async () => {
|
||||
renderDrawer();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('gcp-signoz-api-url-input')).toHaveTextContent(
|
||||
connectionCredentials.sigNozApiUrl,
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('gcp-signoz-api-key-input')).toHaveTextContent(
|
||||
connectionCredentials.sigNozApiKey,
|
||||
);
|
||||
expect(screen.getByTestId('gcp-ingestion-url-input')).toHaveTextContent(
|
||||
connectionCredentials.ingestionUrl,
|
||||
);
|
||||
expect(screen.getByTestId('gcp-ingestion-key-input')).toHaveTextContent(
|
||||
connectionCredentials.ingestionKey,
|
||||
);
|
||||
expect(screen.getByText('Auto-filled by SigNoz')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('blocks submission and surfaces validation errors when the form is empty', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDrawer();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('gcp-connect-account-btn')).toBeEnabled();
|
||||
});
|
||||
|
||||
await user.click(screen.getByTestId('gcp-connect-account-btn'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Please enter an account name')).toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
screen.getByText('Please enter the deployment project ID'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Please select a region')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Please add at least one project ID'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(createAccountPayload).toBeNull();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates the account, checks the agent in, and closes the drawer', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDrawer();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('gcp-connect-account-btn')).toBeEnabled();
|
||||
});
|
||||
|
||||
await user.type(
|
||||
screen.getByTestId('gcp-account-name-input'),
|
||||
'billing@company.com',
|
||||
);
|
||||
await user.type(
|
||||
screen.getByTestId('gcp-deployment-project-id-input'),
|
||||
'my-deployment-project-123',
|
||||
);
|
||||
|
||||
await user.click(screen.getByTestId('gcp-deployment-region-select'));
|
||||
await user.click(await screen.findByText('Mumbai (asia-south1)'));
|
||||
|
||||
const projectIdsInput = document.querySelector(
|
||||
'#gcp-project-ids-select',
|
||||
) as HTMLInputElement;
|
||||
await user.type(projectIdsInput, 'project-a,project-b,');
|
||||
|
||||
await user.click(screen.getByTestId('gcp-connect-account-btn'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createAccountPayload).not.toBeNull();
|
||||
});
|
||||
|
||||
expect(createAccountPayload).toStrictEqual({
|
||||
config: {
|
||||
gcp: {
|
||||
deploymentRegion: 'asia-south1',
|
||||
deploymentProjectId: 'my-deployment-project-123',
|
||||
projectIds: ['project-a', 'project-b'],
|
||||
},
|
||||
},
|
||||
// Backend-provided credentials win over anything in the form.
|
||||
credentials: connectionCredentials,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(checkInPayload).toStrictEqual({
|
||||
providerAccountId: 'billing@company.com',
|
||||
cloudIntegrationId: CLOUD_INTEGRATION_ID,
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the backend error inline when account creation fails', async () => {
|
||||
server.use(
|
||||
rest.post(GCP_ACCOUNTS_URL, (_req, res, ctx) =>
|
||||
res(
|
||||
ctx.status(400),
|
||||
ctx.json({
|
||||
status: 'error',
|
||||
error: { message: 'deployment project id is not accessible' },
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
renderDrawer();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('gcp-connect-account-btn')).toBeEnabled();
|
||||
});
|
||||
|
||||
await user.type(screen.getByTestId('gcp-account-name-input'), 'my-org');
|
||||
await user.type(
|
||||
screen.getByTestId('gcp-deployment-project-id-input'),
|
||||
'my-deployment-project-123',
|
||||
);
|
||||
await user.click(screen.getByTestId('gcp-deployment-region-select'));
|
||||
await user.click(await screen.findByText('Mumbai (asia-south1)'));
|
||||
|
||||
const projectIdsInput = document.querySelector(
|
||||
'#gcp-project-ids-select',
|
||||
) as HTMLInputElement;
|
||||
await user.type(projectIdsInput, 'project-a,');
|
||||
|
||||
await user.click(screen.getByTestId('gcp-connect-account-btn'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('gcp-connect-error')).toHaveTextContent(
|
||||
'deployment project id is not accessible',
|
||||
);
|
||||
});
|
||||
expect(checkInPayload).toBeNull();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,65 +0,0 @@
|
||||
import { CloudintegrationtypesCredentialsDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
CloudAccount,
|
||||
GCPCloudAccountConfig,
|
||||
} from 'container/Integrations/types';
|
||||
|
||||
export const GCP_CREDENTIALS_URL =
|
||||
'http://localhost/api/v1/cloud_integrations/gcp/credentials';
|
||||
export const GCP_ACCOUNTS_URL =
|
||||
'http://localhost/api/v1/cloud_integrations/gcp/accounts';
|
||||
export const GCP_CHECK_IN_URL =
|
||||
'http://localhost/api/v1/cloud_integrations/gcp/accounts/check_in';
|
||||
|
||||
export const CLOUD_INTEGRATION_ID = 'ci-gcp-1234';
|
||||
|
||||
export const GCP_ACCOUNT_ID = 'acc-gcp-1';
|
||||
export const GCP_ACCOUNT_URL = `${GCP_ACCOUNTS_URL}/${GCP_ACCOUNT_ID}`;
|
||||
|
||||
export const gcpAccountConfig: GCPCloudAccountConfig = {
|
||||
deployment_region: 'asia-south1',
|
||||
deployment_project_id: 'my-deployment-project-123',
|
||||
project_ids: ['project-a', 'project-b'],
|
||||
};
|
||||
|
||||
export const gcpAccount: CloudAccount = {
|
||||
id: GCP_ACCOUNT_ID,
|
||||
cloud_account_id: 'gcp-cloud-1',
|
||||
providerAccountId: 'billing@company.com',
|
||||
config: gcpAccountConfig,
|
||||
status: { integration: { last_heartbeat_ts_ms: 1_700_000_000_000 } },
|
||||
};
|
||||
|
||||
export const listAccountsResponse = {
|
||||
status: 'success',
|
||||
data: { accounts: [] },
|
||||
};
|
||||
|
||||
/**
|
||||
* Credentials the backend hands out on SigNoz Cloud. When present the drawer
|
||||
* renders them read-only and sends them back verbatim on submit.
|
||||
*/
|
||||
export const connectionCredentials: CloudintegrationtypesCredentialsDTO = {
|
||||
sigNozApiUrl: 'https://tenant.signoz.cloud',
|
||||
sigNozApiKey: 'signoz-api-key-abc',
|
||||
ingestionUrl: 'https://ingest.us.signoz.cloud',
|
||||
ingestionKey: 'ingestion-key-xyz',
|
||||
};
|
||||
|
||||
export const connectionCredentialsResponse = {
|
||||
status: 'success',
|
||||
data: connectionCredentials,
|
||||
};
|
||||
|
||||
export const createAccountResponse = {
|
||||
status: 'success',
|
||||
data: {
|
||||
id: CLOUD_INTEGRATION_ID,
|
||||
connectionArtifact: {},
|
||||
},
|
||||
};
|
||||
|
||||
export const checkInResponse = {
|
||||
status: 'success',
|
||||
data: {},
|
||||
};
|
||||
@@ -60,44 +60,6 @@ function RemoveIntegrationAccount({
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
let modalDescription: JSX.Element;
|
||||
if (cloudProvider === INTEGRATION_TYPES.AWS) {
|
||||
modalDescription = (
|
||||
<>
|
||||
Removing this account will remove all components created for sending
|
||||
telemetry to SigNoz in your AWS account within the next ~15 minutes
|
||||
(cloudformation stacks named signoz-integration-telemetry-collection in
|
||||
enabled regions). <br />
|
||||
<br />
|
||||
After that, you can delete the cloudformation stack that was created
|
||||
manually when connecting this account.
|
||||
</>
|
||||
);
|
||||
} else if (cloudProvider === INTEGRATION_TYPES.GCP) {
|
||||
modalDescription = (
|
||||
<>
|
||||
Removing this account will stop SigNoz from monitoring it. <br />
|
||||
<br />
|
||||
Since you manage the GCP resources yourself, remember to manually tear down
|
||||
the OTel collector and Pub/Sub resources you created for this integration if
|
||||
you no longer need them.
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
modalDescription = (
|
||||
<>
|
||||
Removing this account will remove all components created for sending
|
||||
telemetry to SigNoz in your Azure subscription within the next ~15 minutes
|
||||
(deployment stack named signoz-integration-telemetry will be deleted
|
||||
automatically). <br />
|
||||
<br />
|
||||
After that, you have to manually delete 'signoz-integration'
|
||||
deployment stack that was created while connecting this account (Takes ~20
|
||||
minutes to delete).
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="remove-integration-account-container">
|
||||
<Button
|
||||
@@ -122,7 +84,28 @@ function RemoveIntegrationAccount({
|
||||
loading: isRemoveIntegrationLoading,
|
||||
}}
|
||||
>
|
||||
{modalDescription}
|
||||
{cloudProvider === INTEGRATION_TYPES.AWS ? (
|
||||
<>
|
||||
Removing this account will remove all components created for sending
|
||||
telemetry to SigNoz in your AWS account within the next ~15 minutes
|
||||
(cloudformation stacks named signoz-integration-telemetry-collection in
|
||||
enabled regions). <br />
|
||||
<br />
|
||||
After that, you can delete the cloudformation stack that was created
|
||||
manually when connecting this account.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Removing this account will remove all components created for sending
|
||||
telemetry to SigNoz in your Azure subscription within the next ~15 minutes
|
||||
(deployment stack named signoz-integration-telemetry will be deleted
|
||||
automatically). <br />
|
||||
<br />
|
||||
After that, you have to manually delete 'signoz-integration'
|
||||
deployment stack that was created while connecting this account (Takes ~20
|
||||
minutes to delete).
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -47,27 +47,3 @@ export function mapAccountDtoToAzureCloudAccount(
|
||||
providerAccountId: account.providerAccountId,
|
||||
};
|
||||
}
|
||||
|
||||
export function mapAccountDtoToGcpCloudAccount(
|
||||
account: CloudintegrationtypesAccountDTO,
|
||||
): IntegrationCloudAccount | null {
|
||||
if (!account.providerAccountId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: account.id,
|
||||
cloud_account_id: account.id,
|
||||
config: {
|
||||
deployment_region: account.config?.gcp?.deploymentRegion ?? '',
|
||||
deployment_project_id: account.config?.gcp?.deploymentProjectId ?? '',
|
||||
project_ids: account.config?.gcp?.projectIds ?? [],
|
||||
},
|
||||
status: {
|
||||
integration: {
|
||||
last_heartbeat_ts_ms: account.agentReport?.timestampMillis ?? 0,
|
||||
},
|
||||
},
|
||||
providerAccountId: account.providerAccountId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import { ArrowLeft, MoveUpRight, RotateCw } from '@signozhq/icons';
|
||||
import awwSnapUrl from '@/assets/Icons/awwSnap.svg';
|
||||
|
||||
import CloudIntegration from '../CloudIntegration/CloudIntegration';
|
||||
import { IntegrationType } from '../types';
|
||||
import { INTEGRATION_TYPES } from '../constants';
|
||||
import { IntegrationType } from '../types';
|
||||
import { handleContactSupport } from '../utils';
|
||||
import IntegrationDetailContent from './IntegrationDetailContent';
|
||||
import IntegrationDetailHeader from './IntegrationDetailHeader';
|
||||
@@ -24,12 +24,6 @@ import { getConnectionStatesFromConnectionStatus } from './utils';
|
||||
|
||||
import './IntegrationDetailPage.styles.scss';
|
||||
|
||||
const cloudIntegrationTypeById: Record<string, IntegrationType> = {
|
||||
[INTEGRATION_TYPES.AWS]: IntegrationType.AWS_SERVICES,
|
||||
[INTEGRATION_TYPES.AZURE]: IntegrationType.AZURE_SERVICES,
|
||||
[INTEGRATION_TYPES.GCP]: IntegrationType.GCP_SERVICES,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function IntegrationDetailPage(): JSX.Element {
|
||||
const history = useHistory();
|
||||
@@ -61,8 +55,19 @@ function IntegrationDetailPage(): JSX.Element {
|
||||
),
|
||||
);
|
||||
|
||||
if (integrationId && cloudIntegrationTypeById[integrationId]) {
|
||||
return <CloudIntegration type={cloudIntegrationTypeById[integrationId]} />;
|
||||
if (
|
||||
integrationId === INTEGRATION_TYPES.AWS ||
|
||||
integrationId === INTEGRATION_TYPES.AZURE
|
||||
) {
|
||||
return (
|
||||
<CloudIntegration
|
||||
type={
|
||||
integrationId === INTEGRATION_TYPES.AWS
|
||||
? IntegrationType.AWS_SERVICES
|
||||
: IntegrationType.AZURE_SERVICES
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import awsDarkLogo from '@/assets/Logos/aws-dark.svg';
|
||||
import azureOpenaiLogo from '@/assets/Logos/azure-openai.svg';
|
||||
import gcpLogo from '@/assets/Logos/gcp.svg';
|
||||
|
||||
import { AzureRegion, GCPRegion } from './types';
|
||||
import { AzureRegion } from './types';
|
||||
|
||||
export const INTEGRATION_TELEMETRY_EVENTS = {
|
||||
INTEGRATIONS_LIST_VISITED: 'Integrations Page: Visited the list page',
|
||||
@@ -22,7 +21,6 @@ export const INTEGRATION_TELEMETRY_EVENTS = {
|
||||
export const INTEGRATION_TYPES = {
|
||||
AWS: 'aws',
|
||||
AZURE: 'azure',
|
||||
GCP: 'gcp',
|
||||
};
|
||||
|
||||
export const AWS_INTEGRATION = {
|
||||
@@ -55,26 +53,7 @@ export const AZURE_INTEGRATION = {
|
||||
is_new: true,
|
||||
};
|
||||
|
||||
export const GCP_INTEGRATION = {
|
||||
id: INTEGRATION_TYPES.GCP,
|
||||
title: 'Google Cloud Platform',
|
||||
description: 'Setup for GCP monitoring with SigNoz',
|
||||
author: {
|
||||
name: 'SigNoz',
|
||||
email: 'integrations@signoz.io',
|
||||
homepage: 'https://signoz.io',
|
||||
},
|
||||
icon: gcpLogo,
|
||||
icon_alt: 'gcp-logo',
|
||||
is_installed: false,
|
||||
is_new: true,
|
||||
};
|
||||
|
||||
export const ONE_CLICK_INTEGRATIONS = [
|
||||
AWS_INTEGRATION,
|
||||
AZURE_INTEGRATION,
|
||||
GCP_INTEGRATION,
|
||||
];
|
||||
export const ONE_CLICK_INTEGRATIONS = [AWS_INTEGRATION, AZURE_INTEGRATION];
|
||||
|
||||
export const AZURE_REGIONS: AzureRegion[] = [
|
||||
{
|
||||
@@ -186,66 +165,3 @@ export const AZURE_REGIONS: AzureRegion[] = [
|
||||
{ label: 'West US 2', value: 'westus2', geography: 'United States' },
|
||||
{ label: 'West US 3', value: 'westus3', geography: 'United States' },
|
||||
];
|
||||
|
||||
// Source of truth: pkg/types/cloudintegrationtypes/regions.go (GCP regions).
|
||||
export const GCP_REGIONS: GCPRegion[] = [
|
||||
{ label: 'Johannesburg', value: 'africa-south1', geography: 'Africa' },
|
||||
{ label: 'Changhua County', value: 'asia-east1', geography: 'APAC' },
|
||||
{ label: 'Hong Kong', value: 'asia-east2', geography: 'APAC' },
|
||||
{ label: 'Tokyo', value: 'asia-northeast1', geography: 'APAC' },
|
||||
{ label: 'Osaka', value: 'asia-northeast2', geography: 'APAC' },
|
||||
{ label: 'Seoul', value: 'asia-northeast3', geography: 'APAC' },
|
||||
{ label: 'Mumbai', value: 'asia-south1', geography: 'APAC' },
|
||||
{ label: 'Delhi', value: 'asia-south2', geography: 'APAC' },
|
||||
{ label: 'Singapore', value: 'asia-southeast1', geography: 'APAC' },
|
||||
{ label: 'Jakarta', value: 'asia-southeast2', geography: 'APAC' },
|
||||
{ label: 'Bangkok', value: 'asia-southeast3', geography: 'APAC' },
|
||||
{ label: 'Sydney', value: 'australia-southeast1', geography: 'APAC' },
|
||||
{ label: 'Melbourne', value: 'australia-southeast2', geography: 'APAC' },
|
||||
{ label: 'Warsaw', value: 'europe-central2', geography: 'Europe' },
|
||||
{ label: 'Hamina', value: 'europe-north1', geography: 'Europe' },
|
||||
{ label: 'Stockholm', value: 'europe-north2', geography: 'Europe' },
|
||||
{ label: 'Madrid', value: 'europe-southwest1', geography: 'Europe' },
|
||||
{ label: 'St. Ghislain', value: 'europe-west1', geography: 'Europe' },
|
||||
{ label: 'London', value: 'europe-west2', geography: 'Europe' },
|
||||
{ label: 'Frankfurt', value: 'europe-west3', geography: 'Europe' },
|
||||
{ label: 'Eemshaven', value: 'europe-west4', geography: 'Europe' },
|
||||
{ label: 'Zurich', value: 'europe-west6', geography: 'Europe' },
|
||||
{ label: 'Milan', value: 'europe-west8', geography: 'Europe' },
|
||||
{ label: 'Paris', value: 'europe-west9', geography: 'Europe' },
|
||||
{ label: 'Berlin', value: 'europe-west10', geography: 'Europe' },
|
||||
{ label: 'Turin', value: 'europe-west12', geography: 'Europe' },
|
||||
{ label: 'Doha', value: 'me-central1', geography: 'Middle East' },
|
||||
{ label: 'Dammam', value: 'me-central2', geography: 'Middle East' },
|
||||
{ label: 'Tel Aviv', value: 'me-west1', geography: 'Middle East' },
|
||||
{
|
||||
label: 'Montréal',
|
||||
value: 'northamerica-northeast1',
|
||||
geography: 'North America',
|
||||
},
|
||||
{
|
||||
label: 'Toronto',
|
||||
value: 'northamerica-northeast2',
|
||||
geography: 'North America',
|
||||
},
|
||||
{
|
||||
label: 'Querétaro',
|
||||
value: 'northamerica-south1',
|
||||
geography: 'North America',
|
||||
},
|
||||
{
|
||||
label: 'São Paulo',
|
||||
value: 'southamerica-east1',
|
||||
geography: 'South America',
|
||||
},
|
||||
{ label: 'Santiago', value: 'southamerica-west1', geography: 'South America' },
|
||||
{ label: 'Council Bluffs', value: 'us-central1', geography: 'North America' },
|
||||
{ label: 'Moncks Corner', value: 'us-east1', geography: 'North America' },
|
||||
{ label: 'Ashburn', value: 'us-east4', geography: 'North America' },
|
||||
{ label: 'Columbus', value: 'us-east5', geography: 'North America' },
|
||||
{ label: 'Dallas', value: 'us-south1', geography: 'North America' },
|
||||
{ label: 'The Dalles', value: 'us-west1', geography: 'North America' },
|
||||
{ label: 'Los Angeles', value: 'us-west2', geography: 'North America' },
|
||||
{ label: 'Salt Lake City', value: 'us-west3', geography: 'North America' },
|
||||
{ label: 'Las Vegas', value: 'us-west4', geography: 'North America' },
|
||||
];
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
export enum IntegrationType {
|
||||
AWS_SERVICES = 'aws',
|
||||
AZURE_SERVICES = 'azure',
|
||||
GCP_SERVICES = 'gcp',
|
||||
}
|
||||
|
||||
interface LogField {
|
||||
@@ -88,10 +87,7 @@ export interface ServiceData {
|
||||
export interface CloudAccount {
|
||||
id: string;
|
||||
cloud_account_id: string;
|
||||
config:
|
||||
| AzureCloudAccountConfig
|
||||
| AWSCloudAccountConfig
|
||||
| GCPCloudAccountConfig;
|
||||
config: AzureCloudAccountConfig | AWSCloudAccountConfig;
|
||||
status: AccountStatus | IServiceStatus;
|
||||
providerAccountId: string;
|
||||
}
|
||||
@@ -101,12 +97,6 @@ export interface AzureCloudAccountConfig {
|
||||
resource_groups: string[];
|
||||
}
|
||||
|
||||
export interface GCPCloudAccountConfig {
|
||||
deployment_region: string;
|
||||
deployment_project_id: string;
|
||||
project_ids: string[];
|
||||
}
|
||||
|
||||
export interface AccountStatus {
|
||||
integration: IntegrationStatus;
|
||||
}
|
||||
@@ -121,12 +111,6 @@ export interface AzureRegion {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface GCPRegion {
|
||||
label: string;
|
||||
geography: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface UpdateServiceConfigPayload {
|
||||
cloud_account_id: string;
|
||||
config: AzureServicesConfig;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
.explorer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
padding: var(--spacing-2) var(--spacing-0);
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: var(--l2-foreground);
|
||||
font-size: var(--periscope-font-size-base);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import styles from './Explorer.module.scss';
|
||||
|
||||
// Shell for the AI Observability Explorer tab. Owns the
|
||||
// /ai-observability/explorer route and is intentionally empty for now: the
|
||||
// query builder + results surface land in a follow-up.
|
||||
function Explorer(): JSX.Element {
|
||||
return (
|
||||
<div className={styles.explorer} data-testid="llm-observability-explorer">
|
||||
<div className={styles.placeholder}>Explorer coming soon.</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Explorer;
|
||||
@@ -44,7 +44,6 @@ describe('LLMObservability (integration)', () => {
|
||||
expect(screen.getByTestId('llm-observability-overview')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('llm-overview-dashboard')).toBeInTheDocument();
|
||||
expect(screen.getByRole('tab', { name: 'Overview' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('tab', { name: 'Explorer' })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('tab', { name: 'Model pricing' }),
|
||||
).toBeInTheDocument();
|
||||
@@ -79,27 +78,6 @@ describe('LLMObservability (integration)', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('navigates to the explorer route when the Explorer tab is clicked', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<LLMObservability />, undefined, {
|
||||
initialRoute: ROUTES.AI_OBSERVABILITY_OVERVIEW,
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('tab', { name: 'Explorer' }));
|
||||
|
||||
expect(safeNavigateMock).toHaveBeenCalledWith(
|
||||
ROUTES.AI_OBSERVABILITY_EXPLORER,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders the explorer panel on the explorer route', () => {
|
||||
render(<LLMObservability />, undefined, {
|
||||
initialRoute: ROUTES.AI_OBSERVABILITY_EXPLORER,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('llm-observability-explorer')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the attribute mapping page on the attribute mapping route', () => {
|
||||
render(<LLMObservability />, undefined, {
|
||||
initialRoute: ROUTES.AI_OBSERVABILITY_ATTRIBUTE_MAPPING,
|
||||
|
||||
@@ -5,12 +5,10 @@ import ROUTES from 'constants/routes';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
|
||||
import LLMObservabilityAttributeMapping from '../AttributeMapping/LLMObservabilityAttributeMapping';
|
||||
import Explorer from '../Explorer/Explorer';
|
||||
import Overview from '../Overview/Overview';
|
||||
import LLMObservabilityModelPricing from '../Settings/ModelPricing/LLMObservabilityModelPricing';
|
||||
|
||||
const OVERVIEW_KEY = ROUTES.AI_OBSERVABILITY_OVERVIEW;
|
||||
const EXPLORER_KEY = ROUTES.AI_OBSERVABILITY_EXPLORER;
|
||||
const CONFIGURATION_KEY = ROUTES.AI_OBSERVABILITY_CONFIGURATION;
|
||||
const ATTRIBUTE_MAPPING_KEY = ROUTES.AI_OBSERVABILITY_ATTRIBUTE_MAPPING;
|
||||
|
||||
@@ -33,8 +31,6 @@ export function useLLMObservabilityTabs(): UseLLMObservabilityTabsResult {
|
||||
activeTab = CONFIGURATION_KEY;
|
||||
} else if (pathname.startsWith(ATTRIBUTE_MAPPING_KEY)) {
|
||||
activeTab = ATTRIBUTE_MAPPING_KEY;
|
||||
} else if (pathname.startsWith(EXPLORER_KEY)) {
|
||||
activeTab = EXPLORER_KEY;
|
||||
}
|
||||
|
||||
const onTabChange = useCallback(
|
||||
@@ -50,11 +46,6 @@ export function useLLMObservabilityTabs(): UseLLMObservabilityTabsResult {
|
||||
label: 'Overview',
|
||||
children: <Overview />,
|
||||
},
|
||||
{
|
||||
key: EXPLORER_KEY,
|
||||
label: 'Explorer',
|
||||
children: <Explorer />,
|
||||
},
|
||||
{
|
||||
key: CONFIGURATION_KEY,
|
||||
label: 'Model pricing',
|
||||
|
||||
@@ -17,8 +17,6 @@ import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import {
|
||||
getHostQueryPayload,
|
||||
getNodeQueryPayload,
|
||||
@@ -53,23 +51,12 @@ function NodeMetrics({
|
||||
};
|
||||
}, [timestamp]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryPayloads = useMemo(() => {
|
||||
if (nodeName) {
|
||||
return getNodeQueryPayload(
|
||||
clusterName,
|
||||
nodeName,
|
||||
start,
|
||||
end,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
return getNodeQueryPayload(clusterName, nodeName, start, end);
|
||||
}
|
||||
return getHostQueryPayload(hostName, start, end, dotMetricsEnabled);
|
||||
}, [nodeName, hostName, clusterName, start, end, dotMetricsEnabled]);
|
||||
return getHostQueryPayload(hostName, start, end);
|
||||
}, [nodeName, hostName, clusterName, start, end]);
|
||||
|
||||
const widgetInfo = nodeName ? nodeWidgetInfo : hostWidgetInfo;
|
||||
const queries = useQueries(
|
||||
|
||||
@@ -12,13 +12,11 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { getPodQueryPayload, podWidgetInfo } from './constants';
|
||||
|
||||
function PodMetrics({
|
||||
@@ -54,14 +52,9 @@ function PodMetrics({
|
||||
scrollLeft: 0,
|
||||
});
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryPayloads = useMemo(
|
||||
() => getPodQueryPayload(clusterName, podName, start, end, dotMetricsEnabled),
|
||||
[clusterName, end, podName, start, dotMetricsEnabled],
|
||||
() => getPodQueryPayload(clusterName, podName, start, end),
|
||||
[clusterName, end, podName, start],
|
||||
);
|
||||
const queries = useQueries(
|
||||
queryPayloads.map((payload) => ({
|
||||
|
||||
@@ -9,48 +9,21 @@ export const getPodQueryPayload = (
|
||||
podName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sClusterNameKey = dotMetricsEnabled
|
||||
? 'k8s.cluster.name'
|
||||
: 'k8s_cluster_name';
|
||||
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
|
||||
const containerCpuUtilKey = dotMetricsEnabled
|
||||
? 'container.cpu.usage'
|
||||
: 'container_cpu_usage';
|
||||
const containerMemUsageKey = dotMetricsEnabled
|
||||
? 'container.memory.usage'
|
||||
: 'container_memory_usage';
|
||||
const k8sContainerCpuReqKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_request'
|
||||
: 'k8s_container_cpu_request';
|
||||
const k8sContainerCpuLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_limit'
|
||||
: 'k8s_container_cpu_limit';
|
||||
const k8sContainerMemReqKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_request'
|
||||
: 'k8s_container_memory_request';
|
||||
const k8sContainerMemLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_limit'
|
||||
: 'k8s_container_memory_limit';
|
||||
const k8sPodFsAvailKey = dotMetricsEnabled
|
||||
? 'k8s.pod.filesystem.available'
|
||||
: 'k8s_pod_filesystem_available';
|
||||
const k8sPodFsCapKey = dotMetricsEnabled
|
||||
? 'k8s.pod.filesystem.capacity'
|
||||
: 'k8s_pod_filesystem_capacity';
|
||||
const k8sPodNetIoKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.io'
|
||||
: 'k8s_pod_network_io';
|
||||
const podLegendTemplate = dotMetricsEnabled
|
||||
? '{{k8s.pod.name}}'
|
||||
: '{{k8s_pod_name}}';
|
||||
const podLegendUsage = dotMetricsEnabled
|
||||
? 'usage - {{k8s.pod.name}}'
|
||||
: 'usage - {{k8s_pod_name}}';
|
||||
const podLegendLimit = dotMetricsEnabled
|
||||
? 'limit - {{k8s.pod.name}}'
|
||||
: 'limit - {{k8s_pod_name}}';
|
||||
const k8sClusterNameKey = 'k8s.cluster.name';
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
const containerCpuUtilKey = 'container.cpu.usage';
|
||||
const containerMemUsageKey = 'container.memory.usage';
|
||||
const k8sContainerCpuReqKey = 'k8s.container.cpu_request';
|
||||
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
|
||||
const k8sContainerMemReqKey = 'k8s.container.memory_request';
|
||||
const k8sContainerMemLimitKey = 'k8s.container.memory_limit';
|
||||
const k8sPodFsAvailKey = 'k8s.pod.filesystem.available';
|
||||
const k8sPodFsCapKey = 'k8s.pod.filesystem.capacity';
|
||||
const k8sPodNetIoKey = 'k8s.pod.network.io';
|
||||
const podLegendTemplate = '{{k8s.pod.name}}';
|
||||
const podLegendUsage = 'usage - {{k8s.pod.name}}';
|
||||
const podLegendLimit = 'limit - {{k8s.pod.name}}';
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -1027,36 +1000,17 @@ export const getNodeQueryPayload = (
|
||||
nodeName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sClusterNameKey = dotMetricsEnabled
|
||||
? 'k8s.cluster.name'
|
||||
: 'k8s_cluster_name';
|
||||
const k8sNodeNameKey = dotMetricsEnabled ? 'k8s.node.name' : 'k8s_node_name';
|
||||
const k8sNodeCpuTimeKey = dotMetricsEnabled
|
||||
? 'k8s.node.cpu.time'
|
||||
: 'k8s_node_cpu_time';
|
||||
const k8sNodeAllocCpuKey = dotMetricsEnabled
|
||||
? 'k8s.node.allocatable_cpu'
|
||||
: 'k8s_node_allocatable_cpu';
|
||||
const k8sNodeMemWsKey = dotMetricsEnabled
|
||||
? 'k8s.node.memory.working_set'
|
||||
: 'k8s_node_memory_working_set';
|
||||
const k8sNodeAllocMemKey = dotMetricsEnabled
|
||||
? 'k8s.node.allocatable_memory'
|
||||
: 'k8s_node_allocatable_memory';
|
||||
const k8sNodeNetIoKey = dotMetricsEnabled
|
||||
? 'k8s.node.network.io'
|
||||
: 'k8s_node_network_io';
|
||||
const k8sNodeFsAvailKey = dotMetricsEnabled
|
||||
? 'k8s.node.filesystem.available'
|
||||
: 'k8s_node_filesystem_available';
|
||||
const k8sNodeFsCapKey = dotMetricsEnabled
|
||||
? 'k8s.node.filesystem.capacity'
|
||||
: 'k8s_node_filesystem_capacity';
|
||||
const podLegend = dotMetricsEnabled
|
||||
? '{{k8s.node.name}}'
|
||||
: '{{k8s_node_name}}';
|
||||
const k8sClusterNameKey = 'k8s.cluster.name';
|
||||
const k8sNodeNameKey = 'k8s.node.name';
|
||||
const k8sNodeCpuTimeKey = 'k8s.node.cpu.time';
|
||||
const k8sNodeAllocCpuKey = 'k8s.node.allocatable_cpu';
|
||||
const k8sNodeMemWsKey = 'k8s.node.memory.working_set';
|
||||
const k8sNodeAllocMemKey = 'k8s.node.allocatable_memory';
|
||||
const k8sNodeNetIoKey = 'k8s.node.network.io';
|
||||
const k8sNodeFsAvailKey = 'k8s.node.filesystem.available';
|
||||
const k8sNodeFsCapKey = 'k8s.node.filesystem.capacity';
|
||||
const podLegend = '{{k8s.node.name}}';
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -1586,48 +1540,23 @@ export const getHostQueryPayload = (
|
||||
hostName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const hostNameKey = dotMetricsEnabled ? 'host.name' : 'host_name';
|
||||
const cpuTimeKey = dotMetricsEnabled ? 'system.cpu.time' : 'system_cpu_time';
|
||||
const memUsageKey = dotMetricsEnabled
|
||||
? 'system.memory.usage'
|
||||
: 'system_memory_usage';
|
||||
const load1mKey = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.1m'
|
||||
: 'system_cpu_load_average_1m';
|
||||
const load5mKey = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.5m'
|
||||
: 'system_cpu_load_average_5m';
|
||||
const load15mKey = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.15m'
|
||||
: 'system_cpu_load_average_15m';
|
||||
const netIoKey = dotMetricsEnabled ? 'system.network.io' : 'system_network_io';
|
||||
const netPktsKey = dotMetricsEnabled
|
||||
? 'system.network.packets'
|
||||
: 'system_network_packets';
|
||||
const netErrKey = dotMetricsEnabled
|
||||
? 'system.network.errors'
|
||||
: 'system_network_errors';
|
||||
const netDropKey = dotMetricsEnabled
|
||||
? 'system.network.dropped'
|
||||
: 'system_network_dropped';
|
||||
const netConnKey = dotMetricsEnabled
|
||||
? 'system.network.connections'
|
||||
: 'system_network_connections';
|
||||
const diskIoKey = dotMetricsEnabled ? 'system.disk.io' : 'system_disk_io';
|
||||
const diskOpTimeKey = dotMetricsEnabled
|
||||
? 'system.disk.operation_time'
|
||||
: 'system_disk_operation_time';
|
||||
const diskOpsKey = dotMetricsEnabled
|
||||
? 'system.disk.operations'
|
||||
: 'system_disk_operations';
|
||||
const diskPendingKey = dotMetricsEnabled
|
||||
? 'system.disk.pending_operations'
|
||||
: 'system_disk_pending_operations';
|
||||
const fsUsageKey = dotMetricsEnabled
|
||||
? 'system.filesystem.usage'
|
||||
: 'system_filesystem_usage';
|
||||
const hostNameKey = 'host.name';
|
||||
const cpuTimeKey = 'system.cpu.time';
|
||||
const memUsageKey = 'system.memory.usage';
|
||||
const load1mKey = 'system.cpu.load_average.1m';
|
||||
const load5mKey = 'system.cpu.load_average.5m';
|
||||
const load15mKey = 'system.cpu.load_average.15m';
|
||||
const netIoKey = 'system.network.io';
|
||||
const netPktsKey = 'system.network.packets';
|
||||
const netErrKey = 'system.network.errors';
|
||||
const netDropKey = 'system.network.dropped';
|
||||
const netConnKey = 'system.network.connections';
|
||||
const diskIoKey = 'system.disk.io';
|
||||
const diskOpTimeKey = 'system.disk.operation_time';
|
||||
const diskOpsKey = 'system.disk.operations';
|
||||
const diskPendingKey = 'system.disk.pending_operations';
|
||||
const fsUsageKey = 'system.filesystem.usage';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -21,7 +21,6 @@ export const databaseCallsRPS = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: DatabaseCallsRPSProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
@@ -33,7 +32,7 @@ export const databaseCallsRPS = ({
|
||||
const groupBy: BaseAutocompleteData[] = [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled ? WidgetKeys.Db_system : WidgetKeys.Db_system_norm,
|
||||
key: WidgetKeys.DbSystem,
|
||||
type: 'tag',
|
||||
},
|
||||
];
|
||||
@@ -42,9 +41,7 @@ export const databaseCallsRPS = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -75,7 +72,6 @@ export const databaseCallsRPS = ({
|
||||
export const databaseCallsAvgDuration = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: DatabaseCallProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozDbLatencySum,
|
||||
@@ -92,9 +88,7 @@ export const databaseCallsAvgDuration = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
|
||||
@@ -32,7 +32,6 @@ export const externalCallErrorPercent = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozExternalCallLatencyCount,
|
||||
@@ -49,9 +48,7 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -61,7 +58,7 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.Int64,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -74,9 +71,7 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -120,7 +115,6 @@ export const externalCallErrorPercent = ({
|
||||
export const externalCallDuration = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
dataType: DataTypes.Float64,
|
||||
@@ -141,9 +135,7 @@ export const externalCallDuration = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -183,7 +175,6 @@ export const externalCallRpsByAddress = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
@@ -198,9 +189,7 @@ export const externalCallRpsByAddress = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -231,7 +220,6 @@ export const externalCallDurationByAddress = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
dataType: DataTypes.Float64,
|
||||
@@ -251,9 +239,7 @@ export const externalCallDurationByAddress = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
|
||||
@@ -37,15 +37,10 @@ export const latency = ({
|
||||
tagFilterItems,
|
||||
isSpanMetricEnable = false,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}: LatencyProps): QueryBuilderData => {
|
||||
const signozLatencyBucketMetrics = dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm;
|
||||
const signozLatencyBucketMetrics = WidgetKeys.SignozLatencyBucket;
|
||||
|
||||
const signozMetricsServiceName = dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm;
|
||||
const signozMetricsServiceName = WidgetKeys.OTelServiceName;
|
||||
const newAutoCompleteData: BaseAutocompleteData = {
|
||||
key: isSpanMetricEnable
|
||||
? signozLatencyBucketMetrics
|
||||
@@ -287,28 +282,21 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
threashold,
|
||||
delta,
|
||||
metricsBuckets,
|
||||
dotMetricsEnabled,
|
||||
}: ApDexMetricsQueryBuilderQueriesProps): QueryBuilderData => {
|
||||
const autoCompleteDataA: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
|
||||
const autoCompleteDataB: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
|
||||
const autoCompleteDataC: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -317,9 +305,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -343,7 +329,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -363,9 +349,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -399,7 +383,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -409,9 +393,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -474,13 +456,10 @@ export const operationPerSec = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations,
|
||||
dotMetricsEnabled,
|
||||
}: OperationPerSecProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
},
|
||||
@@ -491,9 +470,7 @@ export const operationPerSec = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -534,7 +511,6 @@ export const errorPercentage = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations,
|
||||
dotMetricsEnabled,
|
||||
}: OperationPerSecProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozCallsTotal,
|
||||
@@ -553,9 +529,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -575,7 +549,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.Int64,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -589,9 +563,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
|
||||
@@ -21,12 +21,9 @@ import { getQueryBuilderQuerieswithFormula } from './MetricsPageQueriesFactory';
|
||||
|
||||
export const topOperationQueries = ({
|
||||
servicename,
|
||||
dotMetricsEnabled,
|
||||
}: TopOperationQueryFactoryProps): QueryBuilderData => {
|
||||
const latencyAutoCompleteData: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -38,9 +35,7 @@ export const topOperationQueries = ({
|
||||
};
|
||||
|
||||
const numOfCallAutoCompleteData: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -49,9 +44,7 @@ export const topOperationQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -65,9 +58,7 @@ export const topOperationQueries = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -77,7 +68,7 @@ export const topOperationQueries = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.Int64,
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
|
||||
@@ -28,8 +28,6 @@ import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import {
|
||||
GraphTitle,
|
||||
MENU_ITEMS,
|
||||
@@ -89,12 +87,7 @@ function DBCall(): JSX.Element {
|
||||
[queries],
|
||||
);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const legend = dotMetricsEnabled ? '{{db.system}}' : '{{db_system}}';
|
||||
const legend = '{{db.system}}';
|
||||
|
||||
const databaseCallsRPSWidget = useMemo(
|
||||
() =>
|
||||
@@ -106,7 +99,6 @@ function DBCall(): JSX.Element {
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -117,7 +109,7 @@ function DBCall(): JSX.Element {
|
||||
id: SERVICE_CHART_ID.dbCallsRPS,
|
||||
fillSpans: false,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled, legend],
|
||||
[servicename, tagFilterItems, legend],
|
||||
);
|
||||
const databaseCallsAverageDurationWidget = useMemo(
|
||||
() =>
|
||||
@@ -128,7 +120,6 @@ function DBCall(): JSX.Element {
|
||||
builder: databaseCallsAvgDuration({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -139,7 +130,7 @@ function DBCall(): JSX.Element {
|
||||
id: GraphTitle.DATABASE_CALLS_AVG_DURATION,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const stepInterval = useMemo(
|
||||
@@ -157,7 +148,7 @@ function DBCall(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
|
||||
@@ -30,8 +30,6 @@ import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import {
|
||||
GraphTitle,
|
||||
legend,
|
||||
@@ -84,10 +82,6 @@ function External(): JSX.Element {
|
||||
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
|
||||
[queries],
|
||||
);
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const externalCallErrorWidget = useMemo(
|
||||
() =>
|
||||
@@ -99,7 +93,6 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -109,7 +102,7 @@ function External(): JSX.Element {
|
||||
yAxisUnit: '%',
|
||||
id: GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const selectedTraceTags = useMemo(
|
||||
@@ -126,7 +119,6 @@ function External(): JSX.Element {
|
||||
builder: externalCallDuration({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -137,7 +129,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_DURATION,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const errorApmToTraceQuery = useGetAPMToTracesQueries({
|
||||
@@ -171,7 +163,7 @@ function External(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
@@ -194,7 +186,6 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -205,7 +196,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const externalCallDurationAddressWidget = useMemo(
|
||||
@@ -218,7 +209,6 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -229,7 +219,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const apmToTraceQuery = useGetAPMToTracesQueries({
|
||||
|
||||
@@ -93,15 +93,12 @@ function Application(): JSX.Element {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[handleSetTimeStamp],
|
||||
);
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const logEventCalledRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
@@ -159,7 +156,6 @@ function Application(): JSX.Element {
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -169,7 +165,7 @@ function Application(): JSX.Element {
|
||||
yAxisUnit: 'ops',
|
||||
id: SERVICE_CHART_ID.rps,
|
||||
}),
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
|
||||
const errorPercentageWidget = useMemo(
|
||||
@@ -182,7 +178,6 @@ function Application(): JSX.Element {
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -193,7 +188,7 @@ function Application(): JSX.Element {
|
||||
id: SERVICE_CHART_ID.errorPercentage,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
|
||||
const stepInterval = useMemo(
|
||||
|
||||
@@ -22,8 +22,6 @@ import { apDexMetricsQueryBuilderQueries } from 'container/MetricsApplication/Me
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../../../constants/features';
|
||||
import { useAppContext } from '../../../../../providers/App/App';
|
||||
import { IServiceName } from '../../types';
|
||||
import { ApDexMetricsProps } from './types';
|
||||
|
||||
@@ -38,10 +36,6 @@ function ApDexMetrics({
|
||||
}: ApDexMetricsProps): JSX.Element {
|
||||
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
||||
const servicename = decodeURIComponent(encodedServiceName);
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
const apDexMetricsWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -55,7 +49,6 @@ function ApDexMetrics({
|
||||
threashold: thresholdValue || 0,
|
||||
delta: delta || false,
|
||||
metricsBuckets: metricsBuckets || [],
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -81,7 +74,6 @@ function ApDexMetrics({
|
||||
tagFilterItems,
|
||||
thresholdValue,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import Spinner from 'components/Spinner';
|
||||
import { useGetMetricMeta } from 'hooks/apDex/useGetMetricMeta';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
|
||||
import { FeatureKeys } from '../../../../../constants/features';
|
||||
import { useAppContext } from '../../../../../providers/App/App';
|
||||
import { WidgetKeys } from '../../../constant';
|
||||
import { IServiceName } from '../../types';
|
||||
import ApDexMetrics from './ApDexMetrics';
|
||||
@@ -20,17 +18,8 @@ function ApDexMetricsApplication({
|
||||
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
||||
const servicename = decodeURIComponent(encodedServiceName);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const signozLatencyBucketMetrics = dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm;
|
||||
|
||||
const { data, isLoading, error } = useGetMetricMeta(
|
||||
signozLatencyBucketMetrics,
|
||||
WidgetKeys.SignozLatencyBucket,
|
||||
servicename,
|
||||
);
|
||||
useErrorNotification(error);
|
||||
|
||||
@@ -56,10 +56,6 @@ function ServiceOverview({
|
||||
[isSpanMetricEnable, queries],
|
||||
);
|
||||
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const latencyWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -71,7 +67,6 @@ function ServiceOverview({
|
||||
tagFilterItems,
|
||||
isSpanMetricEnable,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -81,13 +76,7 @@ function ServiceOverview({
|
||||
yAxisUnit: 'ns',
|
||||
id: SERVICE_CHART_ID.latency,
|
||||
}),
|
||||
[
|
||||
isSpanMetricEnable,
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
[isSpanMetricEnable, servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
|
||||
const isQueryEnabled =
|
||||
|
||||
@@ -19,8 +19,6 @@ import { EQueryType } from 'types/common/dashboard';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
import { IServiceName } from '../types';
|
||||
import { title } from './config';
|
||||
import ColumnWithLink from './TableRenderer/ColumnWithLink';
|
||||
@@ -44,11 +42,6 @@ function TopOperationMetrics(): JSX.Element {
|
||||
convertRawQueriesToTraceSelectedTags(queries) || [],
|
||||
);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const keyOperationWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -57,14 +50,13 @@ function TopOperationMetrics(): JSX.Element {
|
||||
promql: [],
|
||||
builder: topOperationQueries({
|
||||
servicename,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
panelTypes: PANEL_TYPES.TABLE,
|
||||
}),
|
||||
[servicename, dotMetricsEnabled],
|
||||
[servicename],
|
||||
);
|
||||
|
||||
const updatedQuery = updateStepInterval(keyOperationWidget.query);
|
||||
|
||||
@@ -10,7 +10,6 @@ export interface IServiceName {
|
||||
|
||||
export interface TopOperationQueryFactoryProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
|
||||
@@ -20,7 +19,6 @@ export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
|
||||
export interface ExternalCallProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface BuilderQueriesProps {
|
||||
@@ -52,7 +50,6 @@ export interface OperationPerSecProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
topLevelOperations: string[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface LatencyProps {
|
||||
@@ -60,7 +57,6 @@ export interface LatencyProps {
|
||||
tagFilterItems: TagFilterItem[];
|
||||
isSpanMetricEnable?: boolean;
|
||||
topLevelOperationsRoute: string[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface ApDexProps {
|
||||
@@ -78,5 +74,4 @@ export interface TableRendererProps {
|
||||
export interface ApDexMetricsQueryBuilderQueriesProps extends ApDexProps {
|
||||
delta: boolean;
|
||||
metricsBuckets: number[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -85,14 +85,11 @@ export enum WidgetKeys {
|
||||
HasError = 'hasError',
|
||||
Address = 'address',
|
||||
DurationNano = 'durationNano',
|
||||
StatusCodeNorm = 'status_code',
|
||||
StatusCode = 'status.code',
|
||||
Operation = 'operation',
|
||||
OperationName = 'operationName',
|
||||
Service_name_norm = 'service_name',
|
||||
Service_name = 'service.name',
|
||||
OTelServiceName = 'service.name',
|
||||
ServiceName = 'serviceName',
|
||||
SignozLatencyCountNorm = 'signoz_latency_count',
|
||||
SignozLatencyCount = 'signoz_latency.count',
|
||||
SignozDBLatencyCount = 'signoz_db_latency_count',
|
||||
DatabaseCallCount = 'signoz_database_call_count',
|
||||
@@ -101,10 +98,8 @@ export enum WidgetKeys {
|
||||
SignozCallsTotal = 'signoz_calls_total',
|
||||
SignozExternalCallLatencyCount = 'signoz_external_call_latency_count',
|
||||
SignozExternalCallLatencySum = 'signoz_external_call_latency_sum',
|
||||
Signoz_latency_bucket_norm = 'signoz_latency_bucket',
|
||||
Signoz_latency_bucket = 'signoz_latency.bucket',
|
||||
Db_system = 'db.system',
|
||||
Db_system_norm = 'db_system',
|
||||
SignozLatencyBucket = 'signoz_latency.bucket',
|
||||
DbSystem = 'db.system',
|
||||
}
|
||||
|
||||
export const topOperationMetricsDownloadOptions: DownloadOptions = {
|
||||
|
||||
@@ -32,5 +32,4 @@ export interface DatabaseCallsRPSProps extends DatabaseCallProps {
|
||||
export interface DatabaseCallProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import './InviteTeamMembers.styles.scss';
|
||||
|
||||
interface TeamMember {
|
||||
email: string;
|
||||
roles: string[];
|
||||
role: string;
|
||||
name: string;
|
||||
frontendBaseUrl: string;
|
||||
id: string;
|
||||
@@ -45,7 +45,7 @@ function InviteTeamMembers({
|
||||
const toTeamMembers = (rows: InviteMemberRow[]): TeamMember[] =>
|
||||
rows.map((row) => ({
|
||||
email: row.email,
|
||||
roles: row.roleIds.map((roleId) => roleIdToName[roleId] ?? roleId),
|
||||
role: roleIdToName[row.roleId] ?? row.roleId,
|
||||
name: '',
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
id: row.id,
|
||||
|
||||
@@ -166,8 +166,8 @@ describe('InviteTeamMembers', () => {
|
||||
{ email: 'user2@test.com', success: true },
|
||||
];
|
||||
const mockRows: InviteMemberRow[] = [
|
||||
{ id: 'row-1', email: 'user1@test.com', roleIds: ['role-viewer-id'] },
|
||||
{ id: 'row-2', email: 'user2@test.com', roleIds: ['role-editor-id'] },
|
||||
{ id: 'row-1', email: 'user1@test.com', roleId: 'role-viewer-id' },
|
||||
{ id: 'row-2', email: 'user2@test.com', roleId: 'role-editor-id' },
|
||||
];
|
||||
mockInviteMembersProps?.onSuccess?.(mockResults, mockRows);
|
||||
|
||||
@@ -177,14 +177,14 @@ describe('InviteTeamMembers', () => {
|
||||
teamMembers: [
|
||||
{
|
||||
email: 'user1@test.com',
|
||||
roles: ['VIEWER'],
|
||||
role: 'VIEWER',
|
||||
name: '',
|
||||
frontendBaseUrl: 'http://localhost:3301',
|
||||
id: 'row-1',
|
||||
},
|
||||
{
|
||||
email: 'user2@test.com',
|
||||
roles: ['EDITOR'],
|
||||
role: 'EDITOR',
|
||||
name: '',
|
||||
frontendBaseUrl: 'http://localhost:3301',
|
||||
id: 'row-2',
|
||||
@@ -211,8 +211,8 @@ describe('InviteTeamMembers', () => {
|
||||
{ email: 'user2@test.com', success: false, error: 'Already exists' },
|
||||
];
|
||||
const mockRows: InviteMemberRow[] = [
|
||||
{ id: 'row-1', email: 'user1@test.com', roleIds: ['role-viewer-id'] },
|
||||
{ id: 'row-2', email: 'user2@test.com', roleIds: ['role-admin-id'] },
|
||||
{ id: 'row-1', email: 'user1@test.com', roleId: 'role-viewer-id' },
|
||||
{ id: 'row-2', email: 'user2@test.com', roleId: 'role-admin-id' },
|
||||
];
|
||||
mockInviteMembersProps?.onPartialSuccess?.(mockResults, mockRows);
|
||||
|
||||
@@ -222,14 +222,14 @@ describe('InviteTeamMembers', () => {
|
||||
teamMembers: [
|
||||
{
|
||||
email: 'user1@test.com',
|
||||
roles: ['VIEWER'],
|
||||
role: 'VIEWER',
|
||||
name: '',
|
||||
frontendBaseUrl: 'http://localhost:3301',
|
||||
id: 'row-1',
|
||||
},
|
||||
{
|
||||
email: 'user2@test.com',
|
||||
roles: ['ADMIN'],
|
||||
role: 'ADMIN',
|
||||
name: '',
|
||||
frontendBaseUrl: 'http://localhost:3301',
|
||||
id: 'row-2',
|
||||
@@ -252,8 +252,8 @@ describe('InviteTeamMembers', () => {
|
||||
{ email: 'user2@test.com', success: false, error: 'Error 2' },
|
||||
];
|
||||
const mockRows: InviteMemberRow[] = [
|
||||
{ id: 'row-1', email: 'user1@test.com', roleIds: ['role-editor-id'] },
|
||||
{ id: 'row-2', email: 'user2@test.com', roleIds: ['role-viewer-id'] },
|
||||
{ id: 'row-1', email: 'user1@test.com', roleId: 'role-editor-id' },
|
||||
{ id: 'row-2', email: 'user2@test.com', roleId: 'role-viewer-id' },
|
||||
];
|
||||
mockInviteMembersProps?.onAllFailed?.(mockResults, mockRows);
|
||||
|
||||
@@ -263,14 +263,14 @@ describe('InviteTeamMembers', () => {
|
||||
teamMembers: [
|
||||
{
|
||||
email: 'user1@test.com',
|
||||
roles: ['EDITOR'],
|
||||
role: 'EDITOR',
|
||||
name: '',
|
||||
frontendBaseUrl: 'http://localhost:3301',
|
||||
id: 'row-1',
|
||||
},
|
||||
{
|
||||
email: 'user2@test.com',
|
||||
roles: ['VIEWER'],
|
||||
role: 'VIEWER',
|
||||
name: '',
|
||||
frontendBaseUrl: 'http://localhost:3301',
|
||||
id: 'row-2',
|
||||
|
||||
@@ -53,8 +53,6 @@ import { getUserOperatingSystem, UserOperatingSystem } from 'utils/getUserOS';
|
||||
import { useSelectPopupContainer } from 'utils/selectPopupContainer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
import { selectStyle } from './config';
|
||||
import { PLACEHOLDER } from './constant';
|
||||
import ExampleQueriesRendererForLogs from './ExampleQueriesRendererForLogs';
|
||||
@@ -104,11 +102,6 @@ function QueryBuilderSearch({
|
||||
|
||||
const [isEditingTag, setIsEditingTag] = useState(false);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const {
|
||||
updateTag,
|
||||
handleClearTag,
|
||||
@@ -128,7 +121,6 @@ function QueryBuilderSearch({
|
||||
exampleQueries,
|
||||
} = useAutoComplete(
|
||||
query,
|
||||
dotMetricsEnabled,
|
||||
whereClauseConfig,
|
||||
isLogsExplorerPage,
|
||||
isInfraMonitoring,
|
||||
@@ -146,7 +138,6 @@ function QueryBuilderSearch({
|
||||
const { sourceKeys, handleRemoveSourceKey } = useFetchKeysAndValues(
|
||||
searchValue,
|
||||
query,
|
||||
dotMetricsEnabled,
|
||||
searchKey,
|
||||
isLogsExplorerPage,
|
||||
isInfraMonitoring,
|
||||
|
||||
@@ -14,8 +14,6 @@ import { SelectOption } from 'types/common/select';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import QueryChip from './components/QueryChip';
|
||||
import { QueryChipItem, SearchContainer } from './styles';
|
||||
|
||||
@@ -42,12 +40,7 @@ function ResourceAttributesFilter({
|
||||
SelectOption<string, string>[]
|
||||
>([]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const resourceDeploymentKey = getResourceDeploymentKeys(dotMetricsEnabled);
|
||||
const resourceDeploymentKey = getResourceDeploymentKeys();
|
||||
|
||||
const [selectedEnvironments, setSelectedEnvironments] = useState<string[]>([]);
|
||||
|
||||
@@ -73,14 +66,14 @@ function ResourceAttributesFilter({
|
||||
}, [queries, resourceDeploymentKey]);
|
||||
|
||||
useEffect(() => {
|
||||
getEnvironmentTagKeys(dotMetricsEnabled).then((tagKeys) => {
|
||||
getEnvironmentTagKeys().then((tagKeys) => {
|
||||
if (tagKeys && Array.isArray(tagKeys) && tagKeys.length > 0) {
|
||||
getEnvironmentTagValues(dotMetricsEnabled).then((tagValues) => {
|
||||
getEnvironmentTagValues().then((tagValues) => {
|
||||
setEnvironments(tagValues);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [dotMetricsEnabled]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="resourceAttributesFilter-container">
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
getResourceDeploymentKeys,
|
||||
} from 'hooks/useResourceAttribute/utils';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
import { QueryChipContainer, QueryChipItem } from '../../styles';
|
||||
import { IQueryChipProps } from './types';
|
||||
|
||||
@@ -13,13 +11,7 @@ function QueryChip({ queryData, onClose }: IQueryChipProps): JSX.Element {
|
||||
onClose(queryData.id);
|
||||
};
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const isClosable =
|
||||
queryData.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled);
|
||||
const isClosable = queryData.tagKey !== getResourceDeploymentKeys();
|
||||
|
||||
return (
|
||||
<QueryChipContainer>
|
||||
|
||||
@@ -4,8 +4,6 @@ import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { ServiceMetricsProps } from '../types';
|
||||
import { getQueryRangeRequestData } from '../utils';
|
||||
import ServiceMetricTable from './ServiceMetricTable';
|
||||
@@ -18,19 +16,13 @@ function ServiceMetricsApplication({
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryRangeRequestData = useMemo(
|
||||
() =>
|
||||
getQueryRangeRequestData({
|
||||
topLevelOperations,
|
||||
globalSelectedInterval,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
[globalSelectedInterval, topLevelOperations, dotMetricsEnabled],
|
||||
[globalSelectedInterval, topLevelOperations],
|
||||
);
|
||||
return (
|
||||
<ServiceMetricTable
|
||||
|
||||
@@ -19,13 +19,10 @@ import {
|
||||
|
||||
export const serviceMetricsQuery = (
|
||||
topLevelOperation: [keyof ServiceDataProps, string[]],
|
||||
dotMetricsEnabled: boolean,
|
||||
): QueryBuilderData => {
|
||||
const p99AutoCompleteData: BaseAutocompleteData = {
|
||||
dataType: DataTypes.Float64,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
type: '',
|
||||
};
|
||||
|
||||
@@ -53,9 +50,7 @@ export const serviceMetricsQuery = (
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -78,9 +73,7 @@ export const serviceMetricsQuery = (
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -90,7 +83,7 @@ export const serviceMetricsQuery = (
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.Int64,
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -113,9 +106,7 @@ export const serviceMetricsQuery = (
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -138,9 +129,7 @@ export const serviceMetricsQuery = (
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -193,9 +182,7 @@ export const serviceMetricsQuery = (
|
||||
const groupBy: BaseAutocompleteData[] = [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -17,8 +17,6 @@ import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import SkipOnBoardingModal from '../SkipOnBoardModal';
|
||||
import ServiceTraceTable from './ServiceTracesTable';
|
||||
|
||||
@@ -40,11 +38,6 @@ function ServiceTraces(): JSX.Element {
|
||||
selectedTags,
|
||||
});
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
useErrorNotification(error);
|
||||
|
||||
const services = data || [];
|
||||
@@ -62,7 +55,7 @@ function ServiceTraces(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current && !isUndefined(data)) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
const rps = data.reduce((total, service) => total + service.callRate, 0);
|
||||
|
||||
@@ -26,7 +26,6 @@ export interface ServiceMetricsTableProps {
|
||||
export interface GetQueryRangeRequestDataProps {
|
||||
topLevelOperations: [keyof ServiceDataProps, string[]][];
|
||||
globalSelectedInterval: Time | CustomTimeType;
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface GetServiceListFromQueryProps {
|
||||
|
||||
@@ -26,7 +26,6 @@ export function getSeriesValue(
|
||||
export const getQueryRangeRequestData = ({
|
||||
topLevelOperations,
|
||||
globalSelectedInterval,
|
||||
dotMetricsEnabled,
|
||||
}: GetQueryRangeRequestDataProps): GetQueryResultsProps[] => {
|
||||
const requestData: GetQueryResultsProps[] = [];
|
||||
topLevelOperations.forEach((operation) => {
|
||||
@@ -34,7 +33,7 @@ export const getQueryRangeRequestData = ({
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
builder: serviceMetricsQuery(operation, dotMetricsEnabled),
|
||||
builder: serviceMetricsQuery(operation),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
|
||||
@@ -206,7 +206,6 @@ export const routesToSkip = [
|
||||
ROUTES.AI_OBSERVABILITY_OVERVIEW,
|
||||
ROUTES.AI_OBSERVABILITY_CONFIGURATION,
|
||||
ROUTES.AI_OBSERVABILITY_ATTRIBUTE_MAPPING,
|
||||
ROUTES.AI_OBSERVABILITY_EXPLORER,
|
||||
];
|
||||
|
||||
export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS];
|
||||
|
||||
@@ -10,8 +10,7 @@ import {
|
||||
export function isOneClickIntegration(integrationId: string): boolean {
|
||||
return (
|
||||
integrationId === INTEGRATION_TYPES.AWS ||
|
||||
integrationId === INTEGRATION_TYPES.AZURE ||
|
||||
integrationId === INTEGRATION_TYPES.GCP
|
||||
integrationId === INTEGRATION_TYPES.AZURE
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,12 +39,8 @@ export function useAccountSettingsModal({
|
||||
}: UseAccountSettingsModalProps): UseAccountSettingsModal {
|
||||
const [form] = Form.useForm();
|
||||
const { mutate: updateAccount, isLoading } = useUpdateAccount();
|
||||
// `account.config` is the shared per-provider union (Azure | AWS | GCP).
|
||||
// Narrow to Azure by `resource_groups` (Azure-only) rather than
|
||||
// `deployment_region`, which GCP also has — so it no longer identifies
|
||||
// Azure uniquely.
|
||||
const accountConfig = useMemo(
|
||||
() => ('resource_groups' in account.config ? account.config : null),
|
||||
() => ('deployment_region' in account.config ? account.config : null),
|
||||
[account.config],
|
||||
);
|
||||
const [resourceGroups, setResourceGroups] = useState<string[]>(
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { Form } from 'antd';
|
||||
import { FormInstance } from 'antd/lib';
|
||||
import { useUpdateAccount } from 'api/generated/services/cloudintegration';
|
||||
import { INTEGRATION_TYPES } from 'container/Integrations/constants';
|
||||
import { CloudAccount } from 'container/Integrations/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import logEvent from '../../../api/common/logEvent';
|
||||
|
||||
interface UseAccountSettingsDrawerProps {
|
||||
onClose: () => void;
|
||||
account: CloudAccount;
|
||||
setActiveAccount: Dispatch<SetStateAction<CloudAccount | null>>;
|
||||
}
|
||||
|
||||
interface UseAccountSettingsDrawer {
|
||||
form: FormInstance;
|
||||
isLoading: boolean;
|
||||
projectIds: string[];
|
||||
isSaveDisabled: boolean;
|
||||
setProjectIds: Dispatch<SetStateAction<string[]>>;
|
||||
handleSubmit: () => Promise<void>;
|
||||
handleClose: () => void;
|
||||
}
|
||||
|
||||
export function useAccountSettingsDrawer({
|
||||
onClose,
|
||||
account,
|
||||
setActiveAccount,
|
||||
}: UseAccountSettingsDrawerProps): UseAccountSettingsDrawer {
|
||||
const [form] = Form.useForm();
|
||||
const { mutate: updateAccount, isLoading } = useUpdateAccount();
|
||||
const accountConfig = useMemo(
|
||||
() => ('project_ids' in account.config ? account.config : null),
|
||||
[account.config],
|
||||
);
|
||||
const [projectIds, setProjectIds] = useState<string[]>(
|
||||
accountConfig?.project_ids || [],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!accountConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.setFieldsValue({
|
||||
projectIds: accountConfig.project_ids,
|
||||
});
|
||||
setProjectIds(accountConfig.project_ids);
|
||||
}, [accountConfig, form]);
|
||||
|
||||
const handleSubmit = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
|
||||
if (!accountConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateAccount(
|
||||
{
|
||||
pathParams: {
|
||||
cloudProvider: INTEGRATION_TYPES.GCP,
|
||||
id: account?.id || '',
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
gcp: {
|
||||
// Deployment region & project ID are immutable in the UI, but the
|
||||
// Updatable GCP DTO requires all three fields to be sent.
|
||||
deploymentRegion: accountConfig.deployment_region,
|
||||
deploymentProjectId: accountConfig.deployment_project_id,
|
||||
projectIds: values.projectIds || [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
const nextConfig = {
|
||||
deployment_region: accountConfig.deployment_region,
|
||||
deployment_project_id: accountConfig.deployment_project_id,
|
||||
project_ids: values.projectIds || [],
|
||||
};
|
||||
|
||||
setActiveAccount({
|
||||
...account,
|
||||
config: nextConfig,
|
||||
});
|
||||
onClose();
|
||||
|
||||
toast.success('Account settings updated successfully', {
|
||||
position: 'bottom-right',
|
||||
});
|
||||
|
||||
void logEvent('GCP Integration: Account settings updated', {
|
||||
cloudAccountId: account.cloud_account_id,
|
||||
deploymentRegion: nextConfig.deployment_region,
|
||||
projectIds: nextConfig.project_ids,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error('Failed to update account settings', {
|
||||
description: error?.message,
|
||||
position: 'bottom-right',
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Form submission failed:', error);
|
||||
}
|
||||
}, [form, updateAccount, account, accountConfig, setActiveAccount, onClose]);
|
||||
|
||||
const isSaveDisabled = useMemo(() => {
|
||||
if (!accountConfig) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isEqual(
|
||||
[...(projectIds || [])].sort(),
|
||||
[...accountConfig.project_ids].sort(),
|
||||
);
|
||||
}, [accountConfig, projectIds]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
return {
|
||||
form,
|
||||
isLoading,
|
||||
projectIds,
|
||||
isSaveDisabled,
|
||||
setProjectIds,
|
||||
handleSubmit,
|
||||
handleClose,
|
||||
};
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import {
|
||||
CreateAccountMutationResult,
|
||||
GetConnectionCredentialsQueryResult,
|
||||
invalidateListAccounts,
|
||||
useAgentCheckIn,
|
||||
useCreateAccount,
|
||||
useGetConnectionCredentials,
|
||||
} from 'api/generated/services/cloudintegration';
|
||||
import {
|
||||
CloudintegrationtypesCredentialsDTO,
|
||||
CloudintegrationtypesPostableAccountDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { ErrorType } from 'api/generatedAPIInstance';
|
||||
import { INTEGRATION_TYPES } from 'container/Integrations/constants';
|
||||
import { GcpSetupFormValues } from 'container/Integrations/CloudIntegration/GoogleCloudPlatform/AddNewAccount/types';
|
||||
import useAxiosError from 'hooks/useAxiosError';
|
||||
import { toAPIError } from 'utils/errorUtils';
|
||||
|
||||
import logEvent from '../../../api/common/logEvent';
|
||||
|
||||
interface UseCloudAccountSetupDrawerProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface UseCloudAccountSetupDrawer {
|
||||
isLoading: boolean;
|
||||
connectAccount: (values: GcpSetupFormValues) => Promise<void>;
|
||||
handleClose: () => void;
|
||||
connectionParams?: CloudintegrationtypesCredentialsDTO;
|
||||
isConnectionParamsLoading: boolean;
|
||||
submitError: string | null;
|
||||
clearSubmitError: () => void;
|
||||
}
|
||||
|
||||
export function useCloudAccountSetupDrawer({
|
||||
onClose,
|
||||
}: UseCloudAccountSetupDrawerProps): UseCloudAccountSetupDrawer {
|
||||
const queryClient = useQueryClient();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||
|
||||
const clearSubmitError = useCallback((): void => {
|
||||
setSubmitError(null);
|
||||
}, []);
|
||||
|
||||
const { mutateAsync: createAccount } = useCreateAccount();
|
||||
const { mutateAsync: checkIn } = useAgentCheckIn();
|
||||
const handleError = useAxiosError();
|
||||
|
||||
const { data: connectionParams, isLoading: isConnectionParamsLoading } =
|
||||
useGetConnectionCredentials<GetConnectionCredentialsQueryResult>(
|
||||
{
|
||||
cloudProvider: INTEGRATION_TYPES.GCP,
|
||||
},
|
||||
{
|
||||
query: {
|
||||
onError: handleError,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const handleClose = useCallback((): void => {
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
const handleConnectionSuccess = useCallback(
|
||||
(payload: {
|
||||
cloudIntegrationId: string;
|
||||
providerAccountId: string;
|
||||
}): void => {
|
||||
void logEvent('GCP Integration: Account connected', {
|
||||
cloudIntegrationId: payload.cloudIntegrationId,
|
||||
providerAccountId: payload.providerAccountId,
|
||||
});
|
||||
toast.success('GCP account connected successfully', {
|
||||
position: 'bottom-right',
|
||||
});
|
||||
void invalidateListAccounts(queryClient, {
|
||||
cloudProvider: INTEGRATION_TYPES.GCP,
|
||||
});
|
||||
handleClose();
|
||||
},
|
||||
[handleClose, queryClient],
|
||||
);
|
||||
|
||||
const connectAccount = useCallback(
|
||||
async (values: GcpSetupFormValues): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setSubmitError(null);
|
||||
|
||||
const payload: CloudintegrationtypesPostableAccountDTO = {
|
||||
config: {
|
||||
gcp: {
|
||||
deploymentRegion: values.deploymentRegion,
|
||||
deploymentProjectId: values.deploymentProjectId,
|
||||
projectIds: values.projectIds || [],
|
||||
},
|
||||
},
|
||||
credentials: {
|
||||
// Cloud users can't edit these — the backend-provided credentials are
|
||||
// authoritative. Enterprise users have no backend defaults and enter
|
||||
// their own (validated non-empty), so their form values are used.
|
||||
ingestionUrl: connectionParams?.data?.ingestionUrl || values.ingestionUrl,
|
||||
ingestionKey: connectionParams?.data?.ingestionKey || values.ingestionKey,
|
||||
sigNozApiUrl: connectionParams?.data?.sigNozApiUrl || values.sigNozApiUrl,
|
||||
sigNozApiKey: connectionParams?.data?.sigNozApiKey || values.sigNozApiKey,
|
||||
},
|
||||
};
|
||||
|
||||
// Step 1: create the integration account.
|
||||
const createResponse: CreateAccountMutationResult = await createAccount({
|
||||
pathParams: { cloudProvider: INTEGRATION_TYPES.GCP },
|
||||
data: payload,
|
||||
});
|
||||
|
||||
const cloudIntegrationId = createResponse.data.id;
|
||||
const providerAccountId = values.accountName;
|
||||
|
||||
void logEvent('GCP Integration: Account created', {
|
||||
id: cloudIntegrationId,
|
||||
});
|
||||
|
||||
// Step 2: mimic the agent by checking in from the frontend (manual flow).
|
||||
await checkIn({
|
||||
pathParams: { cloudProvider: INTEGRATION_TYPES.GCP },
|
||||
data: {
|
||||
providerAccountId,
|
||||
cloudIntegrationId,
|
||||
data: {},
|
||||
},
|
||||
});
|
||||
|
||||
handleConnectionSuccess({ cloudIntegrationId, providerAccountId });
|
||||
} catch (error) {
|
||||
// Surface the backend's message inline in the drawer instead of a
|
||||
// generic failure string.
|
||||
const message = toAPIError(
|
||||
error as ErrorType<RenderErrorResponseDTO>,
|
||||
'Failed to connect GCP account',
|
||||
).getErrorMessage();
|
||||
setSubmitError(message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[connectionParams, createAccount, checkIn, handleConnectionSuccess],
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
connectAccount,
|
||||
handleClose,
|
||||
connectionParams: connectionParams?.data as
|
||||
| CloudintegrationtypesCredentialsDTO
|
||||
| undefined,
|
||||
isConnectionParamsLoading,
|
||||
submitError,
|
||||
clearSubmitError,
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import { MouseEventHandler } from 'react';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
|
||||
@@ -11,7 +11,7 @@ export type UseCopyLogLink = {
|
||||
isHighlighted: boolean;
|
||||
isLogsExplorerPage: boolean;
|
||||
activeLogId: string | null;
|
||||
onLogCopy: (event?: MouseEvent<HTMLElement>) => void;
|
||||
onLogCopy: MouseEventHandler<HTMLElement>;
|
||||
onClearActiveLog: () => void;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { MouseEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
MouseEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
@@ -40,14 +46,14 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
|
||||
[pathname],
|
||||
);
|
||||
|
||||
const onLogCopy = useCallback(
|
||||
(event?: MouseEvent<HTMLElement>): void => {
|
||||
const onLogCopy: MouseEventHandler<HTMLElement> = useCallback(
|
||||
(event) => {
|
||||
if (!logId) {
|
||||
return;
|
||||
}
|
||||
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
urlQuery.delete(QueryParams.activeLogId);
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
@@ -60,7 +66,7 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
|
||||
|
||||
setCopy(link);
|
||||
|
||||
toast.success('Copied to clipboard', { position: 'bottom-right' });
|
||||
toast.success('Copied to clipboard', { position: 'top-right' });
|
||||
},
|
||||
[logId, urlQuery, minTime, maxTime, pathname, setCopy],
|
||||
);
|
||||
|
||||
@@ -27,7 +27,6 @@ export type WhereClauseConfig = {
|
||||
|
||||
export const useAutoComplete = (
|
||||
query: IBuilderQuery,
|
||||
dotMetricsEnabled: boolean,
|
||||
whereClauseConfig?: WhereClauseConfig,
|
||||
shouldUseSuggestions?: boolean,
|
||||
isInfraMonitoring?: boolean,
|
||||
@@ -40,7 +39,6 @@ export const useAutoComplete = (
|
||||
const { keys, results, isFetching, exampleQueries } = useFetchKeysAndValues(
|
||||
searchValue,
|
||||
query,
|
||||
dotMetricsEnabled,
|
||||
searchKey,
|
||||
shouldUseSuggestions,
|
||||
isInfraMonitoring,
|
||||
|
||||
@@ -48,7 +48,6 @@ type IuseFetchKeysAndValues = {
|
||||
export const useFetchKeysAndValues = (
|
||||
searchValue: string,
|
||||
query: IBuilderQuery,
|
||||
dotMetricsEnabled: boolean,
|
||||
searchKey: string,
|
||||
shouldUseSuggestions?: boolean,
|
||||
isInfraMonitoring?: boolean,
|
||||
|
||||
@@ -6,8 +6,6 @@ import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { encode } from 'js-base64';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import { whilelistedKeys } from './config';
|
||||
import { ResourceContext } from './context';
|
||||
import {
|
||||
@@ -58,11 +56,6 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const dispatchQueries = useCallback(
|
||||
(queries: IResourceAttribute[]): void => {
|
||||
urlQuery.set(
|
||||
@@ -78,7 +71,7 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
|
||||
const loadTagKeys = (): void => {
|
||||
handleLoading(true);
|
||||
GetTagKeys(dotMetricsEnabled)
|
||||
GetTagKeys()
|
||||
.then((tagKeys) => {
|
||||
const options = mappingWithRoutesAndKeys(pathname, tagKeys);
|
||||
setOptionsData({ options, mode: undefined });
|
||||
@@ -161,15 +154,15 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
|
||||
setSelectedQueries([...value]);
|
||||
},
|
||||
[optionsData.mode, step, staging, dotMetricsEnabled, pathname],
|
||||
[optionsData.mode, step, staging, pathname],
|
||||
);
|
||||
|
||||
const handleEnvironmentChange = useCallback(
|
||||
(environments: string[]): void => {
|
||||
const staging = [getResourceDeploymentKeys(dotMetricsEnabled), 'IN'];
|
||||
const staging = [getResourceDeploymentKeys(), 'IN'];
|
||||
|
||||
const queriesCopy = queries.filter(
|
||||
(query) => query.tagKey !== getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(query) => query.tagKey !== getResourceDeploymentKeys(),
|
||||
);
|
||||
|
||||
if (environments && Array.isArray(environments) && environments.length > 0) {
|
||||
@@ -184,7 +177,7 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
|
||||
setStep('Idle');
|
||||
},
|
||||
[dispatchQueries, dotMetricsEnabled, queries],
|
||||
[dispatchQueries, queries],
|
||||
);
|
||||
|
||||
const handleClose = useCallback(
|
||||
|
||||
@@ -2,13 +2,9 @@ import { ReactNode } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { createMemoryHistory, MemoryHistory } from 'history';
|
||||
import { encode } from 'js-base64';
|
||||
import { AppContext } from 'providers/App/App';
|
||||
import { IAppContext } from 'providers/App/types';
|
||||
import { getAppContextMock } from 'tests/test-utils';
|
||||
|
||||
import ResourceProvider from '../ResourceProvider';
|
||||
import useResourceAttribute from '../useResourceAttribute';
|
||||
@@ -55,10 +51,8 @@ const mockTagValues = getResourceAttributesTagValues as jest.MockedFunction<
|
||||
|
||||
function createWrapper({
|
||||
routerHistory,
|
||||
appContextOverrides,
|
||||
}: {
|
||||
routerHistory: MemoryHistory;
|
||||
appContextOverrides?: Partial<IAppContext>;
|
||||
}): ({ children }: { children: ReactNode }) => JSX.Element {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
@@ -66,13 +60,9 @@ function createWrapper({
|
||||
return function Wrapper({ children }: { children: ReactNode }): JSX.Element {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppContext.Provider
|
||||
value={getAppContextMock('ADMIN', appContextOverrides)}
|
||||
>
|
||||
<Router history={routerHistory}>
|
||||
<ResourceProvider>{children}</ResourceProvider>
|
||||
</Router>
|
||||
</AppContext.Provider>
|
||||
<Router history={routerHistory}>
|
||||
<ResourceProvider>{children}</ResourceProvider>
|
||||
</Router>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
@@ -411,7 +401,7 @@ describe('ResourceProvider', () => {
|
||||
});
|
||||
|
||||
describe('handleEnvironmentChange', () => {
|
||||
it('adds an environment query when envs are provided', async () => {
|
||||
it('adds a dotted environment query when envs are provided', async () => {
|
||||
const routerHistory = createMemoryHistory({ initialEntries: ['/'] });
|
||||
const { result } = renderHook(() => useResourceAttribute(), {
|
||||
wrapper: createWrapper({ routerHistory }),
|
||||
@@ -424,7 +414,7 @@ describe('ResourceProvider', () => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.queries).toHaveLength(1);
|
||||
expect(result.current.queries[0]).toMatchObject({
|
||||
tagKey: 'resource_deployment_environment',
|
||||
tagKey: 'resource_deployment.environment',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
});
|
||||
@@ -435,7 +425,7 @@ describe('ResourceProvider', () => {
|
||||
const seeded = [
|
||||
{
|
||||
id: 'env',
|
||||
tagKey: 'resource_deployment_environment',
|
||||
tagKey: 'resource_deployment.environment',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
},
|
||||
@@ -459,7 +449,7 @@ describe('ResourceProvider', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const tagKeys = result.current.queries.map((q) => q.tagKey);
|
||||
expect(tagKeys).not.toContain('resource_deployment_environment');
|
||||
expect(tagKeys).not.toContain('resource_deployment.environment');
|
||||
expect(tagKeys).toContain('resource_service_name');
|
||||
});
|
||||
});
|
||||
@@ -468,7 +458,7 @@ describe('ResourceProvider', () => {
|
||||
const seeded = [
|
||||
{
|
||||
id: 'env',
|
||||
tagKey: 'resource_deployment_environment',
|
||||
tagKey: 'resource_deployment.environment',
|
||||
operator: 'IN',
|
||||
tagValue: ['production'],
|
||||
},
|
||||
@@ -486,43 +476,13 @@ describe('ResourceProvider', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const envQueries = result.current.queries.filter(
|
||||
(q) => q.tagKey === 'resource_deployment_environment',
|
||||
(q) => q.tagKey === 'resource_deployment.environment',
|
||||
);
|
||||
expect(envQueries).toHaveLength(1);
|
||||
expect(envQueries[0].tagValue).toStrictEqual(['staging']);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the dotted deployment env key when DOT_METRICS_ENABLED is active', async () => {
|
||||
const routerHistory = createMemoryHistory({ initialEntries: ['/'] });
|
||||
const { result } = renderHook(() => useResourceAttribute(), {
|
||||
wrapper: createWrapper({
|
||||
routerHistory,
|
||||
appContextOverrides: {
|
||||
featureFlags: [
|
||||
{
|
||||
name: FeatureKeys.DOT_METRICS_ENABLED,
|
||||
active: true,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.handleEnvironmentChange(['production']);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.queries[0].tagKey).toBe(
|
||||
'resource_deployment.environment',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves unrelated query params when dispatching', async () => {
|
||||
const routerHistory = createMemoryHistory({
|
||||
initialEntries: ['/?tab=overview'],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user