mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-20 17:11:08 +01:00
Compare commits
13 Commits
refactor/p
...
chore/open
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a8cfddc9f | ||
|
|
5f3896a096 | ||
|
|
2cd7285e3e | ||
|
|
63924a0fbb | ||
|
|
9127c5abf7 | ||
|
|
806a51269c | ||
|
|
37f3cd4403 | ||
|
|
65fc5a9cbb | ||
|
|
03bdeb9ba4 | ||
|
|
e4e58e2349 | ||
|
|
6c1f12f5d0 | ||
|
|
6c6632f940 | ||
|
|
39b752c428 |
43
.github/workflows/e2eci.yaml
vendored
43
.github/workflows/e2eci.yaml
vendored
@@ -2,25 +2,13 @@ name: e2eci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- labeled
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
merge_group:
|
||||
types:
|
||||
- checks_requested
|
||||
|
||||
concurrency:
|
||||
group: e2eci-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
if: github.event_name != 'merge_group'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
e2e: ${{ steps.changes.outputs.e2e }}
|
||||
@@ -30,17 +18,21 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
LABELED: ${{ contains(github.event.pull_request.labels.*.name, 'safe-to-e2e') }}
|
||||
FORCE: ${{ contains(github.event.pull_request.labels.*.name, 'safe-to-e2e') }}
|
||||
run: |
|
||||
files=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename')
|
||||
if [ "$LABELED" = true ] || grep -qE '^(frontend|tests/e2e)/' <<< "$files"; then
|
||||
CHANGED_FILES=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename | select(startswith("tests/integration/") | not)')
|
||||
PATTERN='^(frontend/|tests/|\.github/workflows/e2eci\.yaml$|cmd/enterprise/Dockerfile\.with-web\.integration$)'
|
||||
|
||||
if [ "$FORCE" = "true" ]; then
|
||||
echo "e2e=true" >> "$GITHUB_OUTPUT"
|
||||
elif printf '%s\n' "$CHANGED_FILES" | grep -qE "$PATTERN"; then
|
||||
echo "e2e=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "e2e=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
fmtlint:
|
||||
needs: changes
|
||||
if: |
|
||||
((github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && needs.changes.outputs.e2e == 'true'
|
||||
if: needs.changes.outputs.e2e == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
@@ -70,9 +62,7 @@ jobs:
|
||||
- chromium
|
||||
shard: [1, 2, 3, 4]
|
||||
needs: changes
|
||||
if: |
|
||||
((github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && needs.changes.outputs.e2e == 'true'
|
||||
if: needs.changes.outputs.e2e == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
@@ -181,17 +171,14 @@ jobs:
|
||||
name: playwright-artifacts-${{ matrix.project }}-${{ matrix.shard }}
|
||||
path: tests/e2e/artifacts/
|
||||
retention-days: 5
|
||||
# Required status check. Fails when e2e was needed but did not pass,
|
||||
# including fork and dependabot PRs still waiting on safe-to-test.
|
||||
# Required status check. Fails when e2e was needed but did not pass.
|
||||
e2e:
|
||||
if: always()
|
||||
needs: [changes, fmtlint, test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: verdict
|
||||
if: |
|
||||
github.event_name != 'merge_group' && (needs.changes.result != 'success' ||
|
||||
(needs.changes.outputs.e2e == 'true' && (needs.fmtlint.result != 'success' || needs.test.result != 'success')))
|
||||
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
||||
run: |
|
||||
echo "::error::e2e did not pass. Fork and dependabot PRs need the safe-to-test label."
|
||||
echo "::error::e2e did not pass."
|
||||
exit 1
|
||||
|
||||
@@ -251,6 +251,7 @@ components:
|
||||
opsgenie: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelOpsgenieConfig'
|
||||
pagerduty: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelPagerdutyConfig'
|
||||
slack: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelSlackConfig'
|
||||
telegram: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfig'
|
||||
webhook: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelWebhookConfig'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
@@ -264,6 +265,7 @@ components:
|
||||
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJiraConfig'
|
||||
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJSMOpsConfig'
|
||||
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelIncidentIOConfig'
|
||||
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfig'
|
||||
type: object
|
||||
AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelEmailConfig:
|
||||
properties:
|
||||
@@ -373,6 +375,18 @@ components:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfig:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- telegram
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/AlertmanagertypesChannelTelegramConfig'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelWebhookConfig:
|
||||
properties:
|
||||
kind:
|
||||
@@ -525,6 +539,7 @@ components:
|
||||
- jira
|
||||
- jsmops
|
||||
- incidentio
|
||||
- telegram
|
||||
type: string
|
||||
AlertmanagertypesChannelListOrder:
|
||||
enum:
|
||||
@@ -729,6 +744,31 @@ components:
|
||||
- title
|
||||
- value
|
||||
type: object
|
||||
AlertmanagertypesChannelTelegramConfig:
|
||||
properties:
|
||||
apiUrl:
|
||||
type: string
|
||||
botToken:
|
||||
format: password
|
||||
type: string
|
||||
chatId:
|
||||
format: int64
|
||||
type: integer
|
||||
disableNotifications:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
messageThreadId:
|
||||
type: integer
|
||||
sendResolved:
|
||||
nullable: true
|
||||
type: boolean
|
||||
title:
|
||||
type: string
|
||||
required:
|
||||
- botToken
|
||||
- chatId
|
||||
type: object
|
||||
AlertmanagertypesChannelWebhookConfig:
|
||||
properties:
|
||||
bearerToken:
|
||||
@@ -1022,6 +1062,8 @@ components:
|
||||
- jsmops_configs
|
||||
- required:
|
||||
- incidentio_configs
|
||||
- required:
|
||||
- telegram_configs
|
||||
- required:
|
||||
- discord_configs
|
||||
- required:
|
||||
@@ -1042,8 +1084,6 @@ components:
|
||||
- victorops_configs
|
||||
- required:
|
||||
- sns_configs
|
||||
- required:
|
||||
- telegram_configs
|
||||
- required:
|
||||
- webex_configs
|
||||
- required:
|
||||
@@ -1119,7 +1159,7 @@ components:
|
||||
type: array
|
||||
telegram_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigTelegramConfig'
|
||||
$ref: '#/components/schemas/AlertmanagertypesTelegramReceiverConfig'
|
||||
type: array
|
||||
victorops_configs:
|
||||
items:
|
||||
@@ -1263,7 +1303,7 @@ components:
|
||||
type: array
|
||||
telegram_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigTelegramConfig'
|
||||
$ref: '#/components/schemas/AlertmanagertypesTelegramReceiverConfig'
|
||||
type: array
|
||||
victorops_configs:
|
||||
items:
|
||||
@@ -1334,6 +1374,34 @@ components:
|
||||
- timezone
|
||||
- startTime
|
||||
type: object
|
||||
AlertmanagertypesTelegramReceiverConfig:
|
||||
properties:
|
||||
api_url:
|
||||
$ref: '#/components/schemas/ConfigURLType2'
|
||||
chat:
|
||||
format: int64
|
||||
type: integer
|
||||
chat_file:
|
||||
type: string
|
||||
disable_notifications:
|
||||
type: boolean
|
||||
http_config:
|
||||
$ref: '#/components/schemas/ConfigHTTPClientConfig'
|
||||
message:
|
||||
type: string
|
||||
message_thread_id:
|
||||
type: integer
|
||||
parse_mode:
|
||||
type: string
|
||||
send_resolved:
|
||||
type: boolean
|
||||
title:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
token_file:
|
||||
type: string
|
||||
type: object
|
||||
AlertmanagertypesTestableNotificationChannel:
|
||||
properties:
|
||||
config:
|
||||
@@ -1374,9 +1442,13 @@ components:
|
||||
AuthtypesAuthDomainConfigGoogle:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
description: The kind of authn provider.
|
||||
spec:
|
||||
$ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
description: The google auth configuration.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -1384,9 +1456,13 @@ components:
|
||||
AuthtypesAuthDomainConfigOIDC:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
description: The kind of authn provider.
|
||||
spec:
|
||||
$ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
description: The oidc configuration.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -1394,9 +1470,13 @@ components:
|
||||
AuthtypesAuthDomainConfigSAML:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
description: The kind of authn provider.
|
||||
spec:
|
||||
$ref: '#/components/schemas/AuthtypesSamlConfig'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesSamlConfig'
|
||||
description: The saml configuration.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -8152,9 +8232,13 @@ components:
|
||||
Querybuildertypesv5BucketOptionsLinear:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
|
||||
description: How the upper bounds are spaced.
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5LinearBucketsSpec'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5LinearBucketsSpec'
|
||||
description: The evenly spaced bucket specification.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -8162,9 +8246,13 @@ components:
|
||||
Querybuildertypesv5BucketOptionsLog:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
|
||||
description: How the upper bounds are spaced.
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5LogBucketsSpec'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5LogBucketsSpec'
|
||||
description: The logarithmic bucket specification.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -8753,54 +8841,78 @@ components:
|
||||
Querybuildertypesv5QueryEnvelopeBuilder:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5BuilderQuerySpec'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5BuilderQuerySpec'
|
||||
description: The builder query specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
Querybuildertypesv5QueryEnvelopeBuilderAI:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregation'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregation'
|
||||
description: The AI builder query specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
Querybuildertypesv5QueryEnvelopeClickHouseSQL:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5ClickHouseQuery'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5ClickHouseQuery'
|
||||
description: The ClickHouse SQL query specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
Querybuildertypesv5QueryEnvelopeFormula:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderFormula'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderFormula'
|
||||
description: The formula specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
Querybuildertypesv5QueryEnvelopePromQL:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5PromQuery'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5PromQuery'
|
||||
description: The PromQL query specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
Querybuildertypesv5QueryEnvelopeTraceOperator:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderTraceOperator'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderTraceOperator'
|
||||
description: The trace operator specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
@@ -9360,9 +9472,13 @@ components:
|
||||
RuletypesEvaluationCumulative:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
description: The kind of evaluation.
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesCumulativeWindow'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesCumulativeWindow'
|
||||
description: The cumulative window evaluation specification.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -9385,9 +9501,13 @@ components:
|
||||
RuletypesEvaluationRolling:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
description: The kind of evaluation.
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesRollingWindow'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesRollingWindow'
|
||||
description: The rolling window evaluation specification.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -9768,9 +9888,13 @@ components:
|
||||
RuletypesThresholdBasic:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/RuletypesThresholdKind'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesThresholdKind'
|
||||
description: The kind of threshold.
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesBasicRuleThresholds'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesBasicRuleThresholds'
|
||||
description: The basic threshold specification (array of thresholds).
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
|
||||
@@ -277,10 +277,7 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (int, error) {
|
||||
lb.Set(ruletypes.AlertRuleIDLabel, r.ID())
|
||||
lb.Set(ruletypes.RuleSourceLabel, r.GeneratorURL())
|
||||
|
||||
annotations := make(ruletypes.Labels, 0, len(r.Annotations().Map()))
|
||||
for name, value := range r.Annotations().Map() {
|
||||
annotations = append(annotations, ruletypes.Label{Name: name, Value: expand(value)})
|
||||
}
|
||||
annotations := r.ExpandAnnotations(expand)
|
||||
if smpl.IsMissing {
|
||||
lb.Set(ruletypes.AlertNameLabel, "[No data] "+r.Name())
|
||||
lb.Set(ruletypes.NoDataLabel, "true")
|
||||
|
||||
@@ -262,23 +262,20 @@
|
||||
// React Hooks rules - Enforces Rules of Hooks (only call at top level)
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
// Warns about missing dependencies in useEffect/useMemo/useCallback
|
||||
// NOTE: The following react-hooks rules are not supported right know, follow the progress at https://github.com/oxc-project/oxc/issues/1022
|
||||
// Most of them are for React Compiler, which we don't have enabled right know
|
||||
// "react-hooks/config": "error",
|
||||
// "react-hooks/error-boundaries": "error",
|
||||
// "react-hooks/component-hook-factories": "error",
|
||||
// "react-hooks/gating": "error",
|
||||
// "react-hooks/globals": "error",
|
||||
// "react-hooks/immutability": "error",
|
||||
// "react-hooks/preserve-manual-memoization": "error",
|
||||
// "react-hooks/purity": "error",
|
||||
// "react-hooks/refs": "error",
|
||||
// "react-hooks/set-state-in-effect": "error",
|
||||
// "react-hooks/set-state-in-render": "error",
|
||||
// "react-hooks/static-components": "error",
|
||||
// "react-hooks/unsupported-syntax": "warn",
|
||||
// "react-hooks/use-memo": "error",
|
||||
// "react-hooks/incompatible-library": "warn",
|
||||
// React Compiler rules, off because we don't have React Compiler enabled
|
||||
"react/error-boundaries": "off",
|
||||
"react/globals": "off",
|
||||
"react/immutability": "off",
|
||||
"react/incompatible-library": "off",
|
||||
"react/preserve-manual-memoization": "off",
|
||||
"react/purity": "off",
|
||||
"react/refs": "off",
|
||||
"react/set-state-in-effect": "off",
|
||||
"react/set-state-in-render": "off",
|
||||
"react/static-components": "off",
|
||||
"react/unsupported-syntax": "off",
|
||||
"react/use-memo": "off",
|
||||
"react/void-use-memo": "off",
|
||||
"signoz/no-unsupported-asset-pattern": "error",
|
||||
// Prevents the wrong usage of assets to break custom base path installations
|
||||
"signoz/no-zustand-getstate-in-hooks": "error",
|
||||
@@ -582,6 +579,16 @@
|
||||
"rules": {
|
||||
"signoz/no-dashboard-fetch-outside-root": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
// TypeScript reports real redeclarations; this rule also flags `const X` + `type X` pairs.
|
||||
"files": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"rules": {
|
||||
"no-redeclare": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -204,9 +204,9 @@
|
||||
"lint-staged": "^17.0.4",
|
||||
"msw": "1.3.2",
|
||||
"orval": "8.22.0",
|
||||
"oxfmt": "0.54.0",
|
||||
"oxlint": "1.69.0",
|
||||
"oxlint-tsgolint": "0.23.0",
|
||||
"oxfmt": "0.73.0",
|
||||
"oxlint": "1.88.0",
|
||||
"oxlint-tsgolint": "7.0.2004",
|
||||
"postcss": "8.5.26",
|
||||
"postcss-scss": "4.0.9",
|
||||
"react-resizable": "3.0.4",
|
||||
|
||||
403
frontend/pnpm-lock.yaml
generated
403
frontend/pnpm-lock.yaml
generated
@@ -487,14 +487,14 @@ importers:
|
||||
specifier: 8.22.0
|
||||
version: 8.22.0(prettier@3.8.3)(typescript@5.9.3)
|
||||
oxfmt:
|
||||
specifier: 0.54.0
|
||||
version: 0.54.0
|
||||
specifier: 0.73.0
|
||||
version: 0.73.0
|
||||
oxlint:
|
||||
specifier: 1.69.0
|
||||
version: 1.69.0(oxlint-tsgolint@0.23.0)
|
||||
specifier: 1.88.0
|
||||
version: 1.88.0(oxlint-tsgolint@7.0.2004)
|
||||
oxlint-tsgolint:
|
||||
specifier: 0.23.0
|
||||
version: 0.23.0
|
||||
specifier: 7.0.2004
|
||||
version: 7.0.2004
|
||||
postcss:
|
||||
specifier: 8.5.26
|
||||
version: 8.5.26
|
||||
@@ -533,7 +533,7 @@ importers:
|
||||
version: 1.6.0(react@18.2.0)
|
||||
vite-plugin-checker:
|
||||
specifier: 0.12.0
|
||||
version: 0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.69.0(oxlint-tsgolint@0.23.0))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
version: 0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.88.0(oxlint-tsgolint@7.0.2004))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
vite-plugin-compression:
|
||||
specifier: 0.5.1
|
||||
version: 0.5.1(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))
|
||||
@@ -2555,276 +2555,276 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@oxfmt/binding-android-arm-eabi@0.54.0':
|
||||
resolution: {integrity: sha512-NAtpl/SiaeU103e7/OmZw0MvUnsUUopW7hEm/ecegJg7YM0skQaA0IXEZoyTV6NUdiNPupdIUreRqUZTShbn/g==}
|
||||
'@oxfmt/binding-android-arm-eabi@0.73.0':
|
||||
resolution: {integrity: sha512-dhunAdMbi/4o0Y66/+Wl6rpsNqpvE4i26Qw6bB77A97L2lJoktWg6m+X9QOon8iOEEqzD9rEZqFi5uZ4oIzhCA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@oxfmt/binding-android-arm64@0.54.0':
|
||||
resolution: {integrity: sha512-B4VZfBUlKK1rmMChsssNZbkZjE8+FzG3avMjGgMDwbGxXRoXkoeXiAZ+78Oa+eyDPHvDCiUb4zH/vmCOUSafLQ==}
|
||||
'@oxfmt/binding-android-arm64@0.73.0':
|
||||
resolution: {integrity: sha512-dBoypjC2/xeyKyIhN2waX0/XeoccH9k0lVVe+sGRc4X8HSJVRtqyZDoXdr7bGY9A75c51dX0z0dLDpGQKwCdCg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@oxfmt/binding-darwin-arm64@0.54.0':
|
||||
resolution: {integrity: sha512-i02vF75b+ePsQP3tHqSxVYI5S6b8X/xqdPu7/mDHXtpgXLTYXi3jJmfHU0j+dnZZDKaYTx/ioCK7QYJmtiJR2g==}
|
||||
'@oxfmt/binding-darwin-arm64@0.73.0':
|
||||
resolution: {integrity: sha512-Tn7Y+qWIpCQK+qdYwBcn6YvqeBuPiuG3tcNlYg16a5QL2o3rSmrgBB1nCyYqo0uvc1LNrfQW9/CK2tzFuID/Fg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxfmt/binding-darwin-x64@0.54.0':
|
||||
resolution: {integrity: sha512-8VMFvGvooXj7mswkbrhdVZ2/sgiDaBzWpkkbtO+qGDLV4EfJd67nQadHkQC0ZNbaWA9ajXfqI6i7PZLIeDzxEQ==}
|
||||
'@oxfmt/binding-darwin-x64@0.73.0':
|
||||
resolution: {integrity: sha512-yVb0E0tg/+9Xc9YJCBq66qF1DC/aN5mhb5nnHz0k4ikwkMANtSul2VPDSwjYdXR6/DFimc+pPUgLBqkIa5Ctuw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxfmt/binding-freebsd-x64@0.54.0':
|
||||
resolution: {integrity: sha512-0cRHnp43WN1Jrc5s0BdbdKgR1XirdvHy7TAFi3JEsoEVQVJxTXMbpVd76sxXlgRswNMDhVFSJw+y7Eb8mEavFQ==}
|
||||
'@oxfmt/binding-freebsd-x64@0.73.0':
|
||||
resolution: {integrity: sha512-IKR4lFnBkYNL9Uqdxa5GMTQLvnLj+3sDwTvh2atHCzah0xeo+1xR5bigRU8uyXlOsA4VpXUn0UAimq4E4RBGmQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@oxfmt/binding-linux-arm-gnueabihf@0.54.0':
|
||||
resolution: {integrity: sha512-JyQAk3hK/OEtup7Rw6kZwfdzbKqTVD5jXXb8Xpfay29suwZyfBDMVW/bj4RqEPySYWc6zCp198pOluf8n5uYzg==}
|
||||
'@oxfmt/binding-linux-arm-gnueabihf@0.73.0':
|
||||
resolution: {integrity: sha512-7qnZ4g0ZCO3aZevOOQpZIABYwwXJW1lLDo2elviGdPQ0i11255xKEMFsi97fC1/nauQFseCJfELUy/k2JoOP+g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxfmt/binding-linux-arm-musleabihf@0.54.0':
|
||||
resolution: {integrity: sha512-qnvLatTpM8vtvjOfcckBOzJjk+n6ce/wwpP8OFeUrD5aNLYcKyWAitwj+Rk3PK9jGanbZvKsJnv14JGQ6XqFdw==}
|
||||
'@oxfmt/binding-linux-arm-musleabihf@0.73.0':
|
||||
resolution: {integrity: sha512-36eBico2j/WPB84gb4FYd/jjqGCKOwU4M+TWLBUIqlnzTozQueLy0joA5BwsSp2OZFVkOlpCwvA3ndPamdiyLw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxfmt/binding-linux-arm64-gnu@0.54.0':
|
||||
resolution: {integrity: sha512-SMkhnCzIYZYDk9vw3W/80eeYKmrMpGF0Giuxt4HruFlCH7jEtnPeb3SdQKMfgYi/dgtaf+hZAb5XWPYnxqCQ3w==}
|
||||
'@oxfmt/binding-linux-arm64-gnu@0.73.0':
|
||||
resolution: {integrity: sha512-VZ3cSgCAT18knKo52DcACmAuIoqG6qVEyeZRFbJd0xmYQ8GyYjuBc6IAh6MZHijT6q7p4/HPI+6ZWsCuhdV6iw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-arm64-musl@0.54.0':
|
||||
resolution: {integrity: sha512-QrwJlBFFKnxOd95TAaszpMbZBLzMoYMpGaQTZF8oibacnF5rv8l12IhILhQRPmksWiBqg0YSe2Mnl7ayeJAHSA==}
|
||||
'@oxfmt/binding-linux-arm64-musl@0.73.0':
|
||||
resolution: {integrity: sha512-8YVzH59XSHcqwXNgc5qK851zCqwpmBgOifARXEPPe5/xTsIbHceYoPZAyqfpYV0CfKmzSaEDBgYHFza4rhx+Zw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.54.0':
|
||||
resolution: {integrity: sha512-WILatiol/TUHTlhod7R09+7Az/XlhKwmY1MHfLZNmewltPWNN/EwxP2rQSHahibZ/cB8gmckEBjBOByD+5bYsQ==}
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.73.0':
|
||||
resolution: {integrity: sha512-MqRJnmCys0rwOgpWyEidSA7v/bvOqfNDJhPkQoR9mPVYrvSmMaW2Xhdpl28cbTA+brOM1is62GjIvJplVlUVpw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.54.0':
|
||||
resolution: {integrity: sha512-f05YMG4BH4G8S4ME6UM6fi1MnJ9094mrnvO5Pa4SJlMfWlUM+1/ZWMEF4NnjM7shZAvbHsHRuVYpUo0PHC4P9Q==}
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.73.0':
|
||||
resolution: {integrity: sha512-4t2F3SWJSK/O/kRRFnL0elJzn75fJGGu71gs1HtiWcS80SmJOpkHiwwLnTp9suGdns8VHEPxYS0GTcyVwHKWBA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.54.0':
|
||||
resolution: {integrity: sha512-UfL+2hj1ClNqcCRT9s8vBU4axDpjxgVxX96G+9DYAYjoc5b0u15CJtn2jgsi9iM+EbGNc5CW1HVRgwVu76UsSA==}
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.73.0':
|
||||
resolution: {integrity: sha512-D8b4I8I1kLdJQFwdFXNV+cwh3xWSiNKofjdGUk0nDpc0AHOds8T+poGbP9RWQMvgsvMb9OMgp/aV7ahBR37TbQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.54.0':
|
||||
resolution: {integrity: sha512-3/XZe931Hka+J6NjnaqJzYpsWWxDTuRdUdwSQHnOuJEgbC+SehIMFJS8hsEjV7LBhVSL2OCnRLvbVW8O97XIyw==}
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.73.0':
|
||||
resolution: {integrity: sha512-QrC1S5VL1+N5rvorB15jH7CPvjVHA7GyUI5oVEd8Bd9m5605RDBQYTxDQ920DuylRoZOh2V7/xXpfCtFUrojUQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-x64-gnu@0.54.0':
|
||||
resolution: {integrity: sha512-Ik93RlObtu43GbxApafayFjwYE06L6Xr08cSwpBPYbDrLp2ReZx0Jm1DqwRyYRnukUJy+rK2WaEvUQOxdytU9Q==}
|
||||
'@oxfmt/binding-linux-x64-gnu@0.73.0':
|
||||
resolution: {integrity: sha512-dFcUKQPltCEPa+YlkF3zqufn6nJPZdG73LpnjBvZFFsU3laCMssDXSYB99mVaj43FqdEgjpz5TO6gbYXcczd9g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-x64-musl@0.54.0':
|
||||
resolution: {integrity: sha512-yZcakmPlD86CNymknd7KfW+FH+qfbqJH+i0h69CYfV1+KMoVeM9UED+8+TDVoU4haxI0NxY7RPCvRLy3Sqd2Qg==}
|
||||
'@oxfmt/binding-linux-x64-musl@0.73.0':
|
||||
resolution: {integrity: sha512-LmQe+nV8e+eUwfPNCxDF+owbsg88toBiR/8rZRgVOeKQP3MaGefXZRvBoc1UG37iWEQ/9JlCXG9Mc4nf4N3agw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-openharmony-arm64@0.54.0':
|
||||
resolution: {integrity: sha512-GiVBZNnEZnKu00f1jTg49nomv187d0GQX+O+ocykoLeiaALuEO+swoTehHn9TehTfi7V8H0i0e/yvUjCqnwk1w==}
|
||||
'@oxfmt/binding-openharmony-arm64@0.73.0':
|
||||
resolution: {integrity: sha512-8weOr1OKknE0G2DbR6ejJUT9AOsoBU6cXlsetmbihHv1K+bK7qP61T6lJxY0k/aAO7GyOJiRGNlL+04r4eH8Xg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@oxfmt/binding-win32-arm64-msvc@0.54.0':
|
||||
resolution: {integrity: sha512-J0SSB8Z1Fre2sxRolYcW6Rl1RQmKdQ2hnHyq4YJrfBRiXTObLw4DXnIVraM/UyqGqwOi7yTrQA4VT7DPxlHVKA==}
|
||||
'@oxfmt/binding-win32-arm64-msvc@0.73.0':
|
||||
resolution: {integrity: sha512-mZV+QxXYgb8E18gzAFItb937VIsE4dqWdStrD3tRRC52UubOYEuU+e7NaIqUsGdB5YsJqNeuck3Ue/af2r9wyA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@oxfmt/binding-win32-ia32-msvc@0.54.0':
|
||||
resolution: {integrity: sha512-O61UDVj8zz6yXJjkHPf05VaMLOXmEF8P5kf/N0W7AQMmd6bcQogl+KJc7rMutKTL524oE9iH32JXZClBFmEQIg==}
|
||||
'@oxfmt/binding-win32-ia32-msvc@0.73.0':
|
||||
resolution: {integrity: sha512-QJPUsbgUb97ZPPMkFUq6VQq0fLhC4Np19KL9aB/qKVwXnpKE6MIiILXqzhKnQ/UbnEDjKJ7Xv1d6zH4bYPCUdQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@oxfmt/binding-win32-x64-msvc@0.54.0':
|
||||
resolution: {integrity: sha512-1MDpqJPiFqxWtIHas8vkb1VZ7f7eKyTffAwmO8isxQYMaG1OFKsH666BWLeXQLO+IWNfiMssLD55hbR1lIPTqg==}
|
||||
'@oxfmt/binding-win32-x64-msvc@0.73.0':
|
||||
resolution: {integrity: sha512-cGcze2fu+ykIaFtkEvZlkZ3HvnCLk+WISthtq99zWTWn0x13t5L9TxvGdLZMUMvtfYR+ICkiCwKnguVubdcw0w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint-tsgolint/darwin-arm64@0.23.0':
|
||||
resolution: {integrity: sha512-gOs9PVr2wEg4ox9z0aJo+RKhhImW86YL5N6yav8BK/rgPsIrwN/igSZ+pbRr723NFvUNKde9fgMhRA6JrXAOZw==}
|
||||
'@oxlint-tsgolint/darwin-arm64@7.0.2004':
|
||||
resolution: {integrity: sha512-uJk3ttju7Lz+Nv8WG4CsLyewoL1qf/Ny8S8OksJ7Bm/F14Tc9gMfW6arM1PCc57IzH/X/I79YsCWTgxX1FKibQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint-tsgolint/darwin-x64@0.23.0':
|
||||
resolution: {integrity: sha512-kjJ8B+7n4tB9VJdxS5A9GdJt6/bYpzbu4lXp2uO1S3sRmCB5gDEABlGoiePNApRWaW+xqL4b4xgiE727jSLhuA==}
|
||||
'@oxlint-tsgolint/darwin-x64@7.0.2004':
|
||||
resolution: {integrity: sha512-0BFfHTzDxNDFPcu0UZAXT6CyCJHAXM0dZveyDb4KEHa2qF/DSnOKOrmvK/wFuFol9kvijbGM9JfuOsor+zWXRw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint-tsgolint/linux-arm64@0.23.0':
|
||||
resolution: {integrity: sha512-6dCZuKNu135seMXilkRk9SpCx6i1XgmiipYGalLij5WVRX6ZYS8c4xI7preN/zv9fCXhsQclTIMDu2Y/cytTjw==}
|
||||
'@oxlint-tsgolint/linux-arm64@7.0.2004':
|
||||
resolution: {integrity: sha512-TAehQc+qdRXyDN3nl/r/3/oPrBVrbtrDeGMkFyG6mf+XphpoJ2lAMVFLclyvrVZCw02RbKROjyw8h72XwQQHcw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint-tsgolint/linux-x64@0.23.0':
|
||||
resolution: {integrity: sha512-3bdilnyA7kmSTjK27rvjIjSxL5SIg3wt7vwNiRkouWB83ytssyKnuGvxSYJxgMEmFpSutzaBzcCUM2jDtPGcgA==}
|
||||
'@oxlint-tsgolint/linux-x64@7.0.2004':
|
||||
resolution: {integrity: sha512-LPunZvm6ZbWPfHMAyfZf9ci3HHvNEzLejO8zdqO3GoVGoSEHqqufHUD7+OgeatVSaJP7uXDJ2HaNOHIoH7go4Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint-tsgolint/win32-arm64@0.23.0':
|
||||
resolution: {integrity: sha512-j+OEp44SVYiQ+ZD+uttsX7u6L9SvmbbQ77SO1pSFCcJlsVMeCk8qZsjhKfGKuT/jIA+ipOJMVs/+pqUfObBWNw==}
|
||||
'@oxlint-tsgolint/win32-arm64@7.0.2004':
|
||||
resolution: {integrity: sha512-FVWplzDImWhiuawnIZVETaQecv4GWom6Jv4Fx2xj7hhXXDZ5okEgiw1gTCImHTIQsi2ycC5oSxVwrn69bMh7iw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint-tsgolint/win32-x64@0.23.0':
|
||||
resolution: {integrity: sha512-5MyjFuqf+g8OUPJBSGWHJtmoWnzFJYyOg4To9WMQshZYEWig/vtu7JtJ03VWnzHv9LJkAUeApY0gVCOywFR/iQ==}
|
||||
'@oxlint-tsgolint/win32-x64@7.0.2004':
|
||||
resolution: {integrity: sha512-7PsxuUx11Ry9DYTWcljTzRbmZY9B6O9fFFfZV+aodQEMy6pfRT3fhoQRv9PtGN/89z1pPz3nFKrsFm6WqNtX7w==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint/binding-android-arm-eabi@1.69.0':
|
||||
resolution: {integrity: sha512-DKQQbD5cZ/MYfDgDI7YGyGD9FSxABlsBsYFo5p26lloob543tP9+4N3guwdXIYJN+7HSZxLe8YJuwcOWw5qnHg==}
|
||||
'@oxlint/binding-android-arm-eabi@1.88.0':
|
||||
resolution: {integrity: sha512-k6Fgkf86ubfeH4GvqYsRolfuVAp0pfYVZvifSlXNHPdMaWUOfxUrmLEeDD7hF6U2oJuhsVxhSWq86/xCJ+9MmA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@oxlint/binding-android-arm64@1.69.0':
|
||||
resolution: {integrity: sha512-lEhb+I5pr4inux+JFwfCa1HRq3Os7NirEFQ0H1I35SVEHPm6byX0Ah47xmRha3qi6LAkxUcxViL8o/9PivjzBg==}
|
||||
'@oxlint/binding-android-arm64@1.88.0':
|
||||
resolution: {integrity: sha512-AsIyhl9o1ejwtac7NMbhKpMdSml6NWR0ljKjZERxC8OHYDAW8o/Om9n1QHVLi4VCEUo42Zte2/Op9kyoGpJZMg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@oxlint/binding-darwin-arm64@1.69.0':
|
||||
resolution: {integrity: sha512-GY2YE8lOZW59BW1Ia1y+1gR0XyjrZRvVWHAr8LGeGhYHE0OQJ/7cRKXTkx1P+E9/6awEc3SX8a68SFTjh/E//A==}
|
||||
'@oxlint/binding-darwin-arm64@1.88.0':
|
||||
resolution: {integrity: sha512-qgBjB7eEKcCB4RqjXGuoSZq74g0wD09fWFJ+HWcCJcKaga8TeUHtb+BY0SsxdleSLBgx4RZ7qmgo0EzqEq7JoQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint/binding-darwin-x64@1.69.0':
|
||||
resolution: {integrity: sha512-ax1oZnOjHX3LB7myQyHEaQkDwfLb6str3/nSP6O7EVUviQGNkEGzGV0EqcBJWK+Ufwx0l4xPgyYayurvhAdl2Q==}
|
||||
'@oxlint/binding-darwin-x64@1.88.0':
|
||||
resolution: {integrity: sha512-sCz3X5Mw/xdzEzn4fsCt1Iq8NKJnGNfhR+b9B5k+4DysYAHsmIhYoocnBPiQ9kvY3+pUun2A0Z8Vtun/Nv6YoQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint/binding-freebsd-x64@1.69.0':
|
||||
resolution: {integrity: sha512-kHWeHv4g2h8NY+mpCxzCtY4uerMJWTN/TSnNj1CPbakFpHEJ6cTya2wWV0pDSYWOJ2+0UiEbhn3AtXxHtsnKjg==}
|
||||
'@oxlint/binding-freebsd-x64@1.88.0':
|
||||
resolution: {integrity: sha512-xTOdYOIqCufgIj0knvxu86FBfovP6QBiD7ngNva01gb8xPQCD5gXkE5APGczH35zStim1O3zqvFbLQmY85xXRg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@oxlint/binding-linux-arm-gnueabihf@1.69.0':
|
||||
resolution: {integrity: sha512-gq84vM1a1oEehXo27YCDzGVcxPsZDI1yswZwz2Da1/cbnWtrL16XZZnz0G/+gIU8edtHpfjxq5c+vWEHqJfWoQ==}
|
||||
'@oxlint/binding-linux-arm-gnueabihf@1.88.0':
|
||||
resolution: {integrity: sha512-BLadtw88NdYJBrAtC1dYzrPfSP2d5zgx6iuZycdD0YmZBJ2Nqbit9Z4PSaIb6gxK25dweArGMPbOmq/0ugwUPg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint/binding-linux-arm-musleabihf@1.69.0':
|
||||
resolution: {integrity: sha512-kIqEa98JQ0VRyrcncxA417m2AzasqTlD+FyVT1AksjvjkqQcvm7pBWYvoW3/mpyOP2XYvi5nSCCTIe6De1yu5g==}
|
||||
'@oxlint/binding-linux-arm-musleabihf@1.88.0':
|
||||
resolution: {integrity: sha512-wILSczl/DaZsCcf7j7tvNbssp8hxEHIJxrv4j7+JjI3TAL1U/JTWbeUtoJJC/vo8+2hkTmHLYoKJjmiNbijdXw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint/binding-linux-arm64-gnu@1.69.0':
|
||||
resolution: {integrity: sha512-j+xYiXozxGWx2cpjCrwwGR4awTxPFsRv3JZrv23RCogEPMc4R7UqjHW47p/RG0aRlbWiROCJ8coUfCwy0dvzHA==}
|
||||
'@oxlint/binding-linux-arm64-gnu@1.88.0':
|
||||
resolution: {integrity: sha512-jutT2Hy8JCuZuMwIFHo2HtyxfOfBJHyQxlBHBK1SL9W2f8n/WhjAP0FnDMz9d/mjhv9+QA0mx5AdQ9fDjsrFFg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-arm64-musl@1.69.0':
|
||||
resolution: {integrity: sha512-xEPpNppTfN1l/nM7gYSf9iocscu/as+p/7vxkLeLEKnYU+09Dm+5V6IhDYDh+Uz6FajEupWwCLt5SOG0y1PCKg==}
|
||||
'@oxlint/binding-linux-arm64-musl@1.88.0':
|
||||
resolution: {integrity: sha512-8N8iGV9hh4Lkeuq7QsEkjoXNRRflqR1G2qyoGYXcA0k5qllrdqmfylKEXyHIVERxqNDyMO4StqDQ0uLxXcBQAg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.69.0':
|
||||
resolution: {integrity: sha512-Ug0+eU7HJBlek+SjklYH62IlOMirEJsdxpihH0kSqX0XdrDD4NdHpQc10fK1JC35yn6KrrcN+uYzlHD38XAf8Q==}
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.88.0':
|
||||
resolution: {integrity: sha512-5ydcQt3znNQ/rGEWYRvpS2cK9+T18P2VYgnyJ+eloEAwi9YUmsXy5VGrRqNWIK6qzWwHGnEE3ojVqqQNo+f0hA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.69.0':
|
||||
resolution: {integrity: sha512-iEyI3GIg0l/s3G4qy2TlaaWKdzj4PJJStwtlocpDTC00PY9hZueotf6OKUj9+yfQh0lrpBW/pLMgTztbAHKJEg==}
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.88.0':
|
||||
resolution: {integrity: sha512-iGx0uvFT54b/5DPJdEBCxYIrVB3v0uTy+JhRh3CyEp8/22BXDkG8rffkFwREdLsdj9mOrPbJYVOZw8ipcBuzjw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-riscv64-musl@1.69.0':
|
||||
resolution: {integrity: sha512-NjHjpiI4WIKSMwuoJSZi5VToPeoYOS1FR52HLIDG6lidMdqquusgtODb4iLk0+lb1q3Z0nv2/aPRcC/olmpQGg==}
|
||||
'@oxlint/binding-linux-riscv64-musl@1.88.0':
|
||||
resolution: {integrity: sha512-Wt47mKMWelVjRjuR7BCFxXkB0m79K1sTEbqMHRWciKUgo5iv7sWsF4RXSOyFYHD9zerNrkRZblnN273rhwOA1Q==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-linux-s390x-gnu@1.69.0':
|
||||
resolution: {integrity: sha512-Ai/prDewoItkDXbp38gwGZi41DycZbUTZJ3UidwoHgQC0/DaqC2TGdtBTQLJ6hSD+SAxASzh8+/eSBPmxfOacA==}
|
||||
'@oxlint/binding-linux-s390x-gnu@1.88.0':
|
||||
resolution: {integrity: sha512-g3L12KCS4+1gtUt3RbJ4F+Yxx4LNmmZLEPfhd8rAHEJjJk2lAgNfnjGoP/phJyhG5WS6VbNYbJtadJelNfvTMA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-x64-gnu@1.69.0':
|
||||
resolution: {integrity: sha512-Gt3KHgp46mRKz4sJeaASmKvD8ayXookRw07RMf+NowhEztGGDZ7VrXpoW96XuKJLjFukWizOFVNjmYb/u7caNQ==}
|
||||
'@oxlint/binding-linux-x64-gnu@1.88.0':
|
||||
resolution: {integrity: sha512-og/XHVDOD9ZJgRiZkT9DgzQd4sr+JZW7e4l17Q/jVMZiIbeEd4uIu9iAoUbTF1gIvVZoRc4QDOxyVUvQGp4WHg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-x64-musl@1.69.0':
|
||||
resolution: {integrity: sha512-7tQhJ2+p/oHv1zcfnjYI7YVzC/7iBaVOfIvFYtxdJ5F45mWgEdrCyXZXZGfiLey5t/5JhOhsaMnnv1kAzckd7g==}
|
||||
'@oxlint/binding-linux-x64-musl@1.88.0':
|
||||
resolution: {integrity: sha512-EmhBsp+xuKb6YbZNEMLBo80Xw71AZOIm18do8ni/GLX/Lw1XzNhVYpbxO1KasYtoAvrGtCOdCPULRlIE0bPbTQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-openharmony-arm64@1.69.0':
|
||||
resolution: {integrity: sha512-vmWz6TKp/3hfA4lksR0zHBv/6xuX1jhym6eqOjdH2DXsDDHZWcp2f0KG0VCAnlVbIrjk29G4wAWMXb/Hn1YobA==}
|
||||
'@oxlint/binding-openharmony-arm64@1.88.0':
|
||||
resolution: {integrity: sha512-DBUcx/KwYdg1DAz+G01P/GFv512J04EsZadbHptx2FTpWT/MWRg21ZToNHt9pnsfQRBQ3et/g+vqzbvrdAVbuA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@oxlint/binding-win32-arm64-msvc@1.69.0':
|
||||
resolution: {integrity: sha512-9RExaLgmaw6IoIkU9cTpT71mLfI0xZ86iZH8x518LVsOkjquJMYqb9P7KpC8lgd1t0Dxs41p2pxynq4XR3Ttzw==}
|
||||
'@oxlint/binding-win32-arm64-msvc@1.88.0':
|
||||
resolution: {integrity: sha512-xeVd0n4XG5HvynxfYvXi6uB+JvZbVZataowPS8cYpMbYg9mwE4sSyElFQd4LVJFifq8bRLDSR18XgEildmcXAg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint/binding-win32-ia32-msvc@1.69.0':
|
||||
resolution: {integrity: sha512-1907kRPF8/PrcIw1E7LMs9JbVrpgnt/MvFdss3an8oDkYNAACXzTntV3t3869ZZhMZxb2AzRGbz1pA/jdFatXA==}
|
||||
'@oxlint/binding-win32-ia32-msvc@1.88.0':
|
||||
resolution: {integrity: sha512-MA6iOw//amleaz2mVMsZrA7NHlEexjTrhIxSMFCXQi6IUuQO+3Od00GZhr2iCYw6RFzMW36jkKK/WHBXMsoQAw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint/binding-win32-x64-msvc@1.69.0':
|
||||
resolution: {integrity: sha512-w8SOXv3mT9Fi6jY8OXdXCfnvX/3KNLXGNr4HEz2TA7S4Mv/PYAOmpB8y/ge40mxvBMgGNaSaaDwZpAsQn7HtWA==}
|
||||
'@oxlint/binding-win32-x64-msvc@1.88.0':
|
||||
resolution: {integrity: sha512-3Sz6rVWe8Dy+ufsXurFN1GX2dX43F/lQMSLelK9gFjiba7nHvLlqlhR5cHlWVnCpSqUY1hN9pyCX9XToe6ve6g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -7785,29 +7785,32 @@ packages:
|
||||
oxc-resolver@11.24.2:
|
||||
resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==}
|
||||
|
||||
oxfmt@0.54.0:
|
||||
resolution: {integrity: sha512-DjnMwn7smSLF+Mc2+pRItnuPftm/dkUFpY/d4+33y9TfKrsHZo8GLhmUg9BrOIUEy94Rlom1Q11N6vuhE+e0oQ==}
|
||||
oxfmt@0.73.0:
|
||||
resolution: {integrity: sha512-Ke4xbpuzqIBP4bkd48q815RhqWNJqOBzxVA9199M4qcF0y0LtpUv7UTg1dJ5ea6jw0RBUDZvX3qOUMNmmxfxbw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@astrojs/compiler-rs': ^0.4.0 || ^0.5.0
|
||||
svelte: ^5.0.0
|
||||
vite-plus: '*'
|
||||
peerDependenciesMeta:
|
||||
'@astrojs/compiler-rs':
|
||||
optional: true
|
||||
svelte:
|
||||
optional: true
|
||||
vite-plus:
|
||||
optional: true
|
||||
|
||||
oxlint-tsgolint@0.23.0:
|
||||
resolution: {integrity: sha512-3mBv3CoPbh8dFbzfDGIWa2ytZjn2v+3EX4aKRXjIhsoGFzG8GCjfRirz3rwZf1wYbZzsNLTSgpw8VjQuWdp/jA==}
|
||||
oxlint-tsgolint@7.0.2004:
|
||||
resolution: {integrity: sha512-vQ9WDF3fiZJvRdSDosNOflerOCOpwol6vWzVDxW76RcU19cjTh7engvkHLvmv+tzedR8fAiJUVUGr6IVZdkzXQ==}
|
||||
hasBin: true
|
||||
|
||||
oxlint@1.69.0:
|
||||
resolution: {integrity: sha512-ypZkK/aDc5NQV8zIR6s2H2Tl3aNW8FmJ1m9+2qsaYuRenl8vgnHNCGwTHviWJdUQzglOlHFchgopdtGhSy17Rw==}
|
||||
oxlint@1.88.0:
|
||||
resolution: {integrity: sha512-uIcFgw8BmqvFRGzH5+r7d2zJMEexwNSrEmM6WQNnGh3VkO0XYTEFoyxyNE6tjKMUib9Cue4XJA2DoWruO5D6aw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
oxlint-tsgolint: '>=0.22.1'
|
||||
oxlint-tsgolint: '>=7.0.2003'
|
||||
vite-plus: '*'
|
||||
peerDependenciesMeta:
|
||||
oxlint-tsgolint:
|
||||
@@ -9399,8 +9402,8 @@ packages:
|
||||
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinypool@2.1.0:
|
||||
resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==}
|
||||
tinypool@2.2.0:
|
||||
resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==}
|
||||
engines: {node: ^20.0.0 || >=22.0.0}
|
||||
|
||||
tinyrainbow@2.0.0:
|
||||
@@ -12469,136 +12472,136 @@ snapshots:
|
||||
'@oxc-resolver/binding-win32-x64-msvc@11.24.2':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-android-arm-eabi@0.54.0':
|
||||
'@oxfmt/binding-android-arm-eabi@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-android-arm64@0.54.0':
|
||||
'@oxfmt/binding-android-arm64@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-darwin-arm64@0.54.0':
|
||||
'@oxfmt/binding-darwin-arm64@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-darwin-x64@0.54.0':
|
||||
'@oxfmt/binding-darwin-x64@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-freebsd-x64@0.54.0':
|
||||
'@oxfmt/binding-freebsd-x64@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-arm-gnueabihf@0.54.0':
|
||||
'@oxfmt/binding-linux-arm-gnueabihf@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-arm-musleabihf@0.54.0':
|
||||
'@oxfmt/binding-linux-arm-musleabihf@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-arm64-gnu@0.54.0':
|
||||
'@oxfmt/binding-linux-arm64-gnu@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-arm64-musl@0.54.0':
|
||||
'@oxfmt/binding-linux-arm64-musl@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.54.0':
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.54.0':
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.54.0':
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.54.0':
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-x64-gnu@0.54.0':
|
||||
'@oxfmt/binding-linux-x64-gnu@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-x64-musl@0.54.0':
|
||||
'@oxfmt/binding-linux-x64-musl@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-openharmony-arm64@0.54.0':
|
||||
'@oxfmt/binding-openharmony-arm64@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-win32-arm64-msvc@0.54.0':
|
||||
'@oxfmt/binding-win32-arm64-msvc@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-win32-ia32-msvc@0.54.0':
|
||||
'@oxfmt/binding-win32-ia32-msvc@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-win32-x64-msvc@0.54.0':
|
||||
'@oxfmt/binding-win32-x64-msvc@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/darwin-arm64@0.23.0':
|
||||
'@oxlint-tsgolint/darwin-arm64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/darwin-x64@0.23.0':
|
||||
'@oxlint-tsgolint/darwin-x64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/linux-arm64@0.23.0':
|
||||
'@oxlint-tsgolint/linux-arm64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/linux-x64@0.23.0':
|
||||
'@oxlint-tsgolint/linux-x64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/win32-arm64@0.23.0':
|
||||
'@oxlint-tsgolint/win32-arm64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/win32-x64@0.23.0':
|
||||
'@oxlint-tsgolint/win32-x64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-android-arm-eabi@1.69.0':
|
||||
'@oxlint/binding-android-arm-eabi@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-android-arm64@1.69.0':
|
||||
'@oxlint/binding-android-arm64@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-darwin-arm64@1.69.0':
|
||||
'@oxlint/binding-darwin-arm64@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-darwin-x64@1.69.0':
|
||||
'@oxlint/binding-darwin-x64@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-freebsd-x64@1.69.0':
|
||||
'@oxlint/binding-freebsd-x64@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm-gnueabihf@1.69.0':
|
||||
'@oxlint/binding-linux-arm-gnueabihf@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm-musleabihf@1.69.0':
|
||||
'@oxlint/binding-linux-arm-musleabihf@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm64-gnu@1.69.0':
|
||||
'@oxlint/binding-linux-arm64-gnu@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm64-musl@1.69.0':
|
||||
'@oxlint/binding-linux-arm64-musl@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.69.0':
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.69.0':
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-riscv64-musl@1.69.0':
|
||||
'@oxlint/binding-linux-riscv64-musl@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-s390x-gnu@1.69.0':
|
||||
'@oxlint/binding-linux-s390x-gnu@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-x64-gnu@1.69.0':
|
||||
'@oxlint/binding-linux-x64-gnu@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-x64-musl@1.69.0':
|
||||
'@oxlint/binding-linux-x64-musl@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-openharmony-arm64@1.69.0':
|
||||
'@oxlint/binding-openharmony-arm64@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-win32-arm64-msvc@1.69.0':
|
||||
'@oxlint/binding-win32-arm64-msvc@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-win32-ia32-msvc@1.69.0':
|
||||
'@oxlint/binding-win32-ia32-msvc@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-win32-x64-msvc@1.69.0':
|
||||
'@oxlint/binding-win32-x64-msvc@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.1':
|
||||
@@ -18323,61 +18326,61 @@ snapshots:
|
||||
'@oxc-resolver/binding-win32-arm64-msvc': 11.24.2
|
||||
'@oxc-resolver/binding-win32-x64-msvc': 11.24.2
|
||||
|
||||
oxfmt@0.54.0:
|
||||
oxfmt@0.73.0:
|
||||
dependencies:
|
||||
tinypool: 2.1.0
|
||||
tinypool: 2.2.0
|
||||
optionalDependencies:
|
||||
'@oxfmt/binding-android-arm-eabi': 0.54.0
|
||||
'@oxfmt/binding-android-arm64': 0.54.0
|
||||
'@oxfmt/binding-darwin-arm64': 0.54.0
|
||||
'@oxfmt/binding-darwin-x64': 0.54.0
|
||||
'@oxfmt/binding-freebsd-x64': 0.54.0
|
||||
'@oxfmt/binding-linux-arm-gnueabihf': 0.54.0
|
||||
'@oxfmt/binding-linux-arm-musleabihf': 0.54.0
|
||||
'@oxfmt/binding-linux-arm64-gnu': 0.54.0
|
||||
'@oxfmt/binding-linux-arm64-musl': 0.54.0
|
||||
'@oxfmt/binding-linux-ppc64-gnu': 0.54.0
|
||||
'@oxfmt/binding-linux-riscv64-gnu': 0.54.0
|
||||
'@oxfmt/binding-linux-riscv64-musl': 0.54.0
|
||||
'@oxfmt/binding-linux-s390x-gnu': 0.54.0
|
||||
'@oxfmt/binding-linux-x64-gnu': 0.54.0
|
||||
'@oxfmt/binding-linux-x64-musl': 0.54.0
|
||||
'@oxfmt/binding-openharmony-arm64': 0.54.0
|
||||
'@oxfmt/binding-win32-arm64-msvc': 0.54.0
|
||||
'@oxfmt/binding-win32-ia32-msvc': 0.54.0
|
||||
'@oxfmt/binding-win32-x64-msvc': 0.54.0
|
||||
'@oxfmt/binding-android-arm-eabi': 0.73.0
|
||||
'@oxfmt/binding-android-arm64': 0.73.0
|
||||
'@oxfmt/binding-darwin-arm64': 0.73.0
|
||||
'@oxfmt/binding-darwin-x64': 0.73.0
|
||||
'@oxfmt/binding-freebsd-x64': 0.73.0
|
||||
'@oxfmt/binding-linux-arm-gnueabihf': 0.73.0
|
||||
'@oxfmt/binding-linux-arm-musleabihf': 0.73.0
|
||||
'@oxfmt/binding-linux-arm64-gnu': 0.73.0
|
||||
'@oxfmt/binding-linux-arm64-musl': 0.73.0
|
||||
'@oxfmt/binding-linux-ppc64-gnu': 0.73.0
|
||||
'@oxfmt/binding-linux-riscv64-gnu': 0.73.0
|
||||
'@oxfmt/binding-linux-riscv64-musl': 0.73.0
|
||||
'@oxfmt/binding-linux-s390x-gnu': 0.73.0
|
||||
'@oxfmt/binding-linux-x64-gnu': 0.73.0
|
||||
'@oxfmt/binding-linux-x64-musl': 0.73.0
|
||||
'@oxfmt/binding-openharmony-arm64': 0.73.0
|
||||
'@oxfmt/binding-win32-arm64-msvc': 0.73.0
|
||||
'@oxfmt/binding-win32-ia32-msvc': 0.73.0
|
||||
'@oxfmt/binding-win32-x64-msvc': 0.73.0
|
||||
|
||||
oxlint-tsgolint@0.23.0:
|
||||
oxlint-tsgolint@7.0.2004:
|
||||
optionalDependencies:
|
||||
'@oxlint-tsgolint/darwin-arm64': 0.23.0
|
||||
'@oxlint-tsgolint/darwin-x64': 0.23.0
|
||||
'@oxlint-tsgolint/linux-arm64': 0.23.0
|
||||
'@oxlint-tsgolint/linux-x64': 0.23.0
|
||||
'@oxlint-tsgolint/win32-arm64': 0.23.0
|
||||
'@oxlint-tsgolint/win32-x64': 0.23.0
|
||||
'@oxlint-tsgolint/darwin-arm64': 7.0.2004
|
||||
'@oxlint-tsgolint/darwin-x64': 7.0.2004
|
||||
'@oxlint-tsgolint/linux-arm64': 7.0.2004
|
||||
'@oxlint-tsgolint/linux-x64': 7.0.2004
|
||||
'@oxlint-tsgolint/win32-arm64': 7.0.2004
|
||||
'@oxlint-tsgolint/win32-x64': 7.0.2004
|
||||
|
||||
oxlint@1.69.0(oxlint-tsgolint@0.23.0):
|
||||
oxlint@1.88.0(oxlint-tsgolint@7.0.2004):
|
||||
optionalDependencies:
|
||||
'@oxlint/binding-android-arm-eabi': 1.69.0
|
||||
'@oxlint/binding-android-arm64': 1.69.0
|
||||
'@oxlint/binding-darwin-arm64': 1.69.0
|
||||
'@oxlint/binding-darwin-x64': 1.69.0
|
||||
'@oxlint/binding-freebsd-x64': 1.69.0
|
||||
'@oxlint/binding-linux-arm-gnueabihf': 1.69.0
|
||||
'@oxlint/binding-linux-arm-musleabihf': 1.69.0
|
||||
'@oxlint/binding-linux-arm64-gnu': 1.69.0
|
||||
'@oxlint/binding-linux-arm64-musl': 1.69.0
|
||||
'@oxlint/binding-linux-ppc64-gnu': 1.69.0
|
||||
'@oxlint/binding-linux-riscv64-gnu': 1.69.0
|
||||
'@oxlint/binding-linux-riscv64-musl': 1.69.0
|
||||
'@oxlint/binding-linux-s390x-gnu': 1.69.0
|
||||
'@oxlint/binding-linux-x64-gnu': 1.69.0
|
||||
'@oxlint/binding-linux-x64-musl': 1.69.0
|
||||
'@oxlint/binding-openharmony-arm64': 1.69.0
|
||||
'@oxlint/binding-win32-arm64-msvc': 1.69.0
|
||||
'@oxlint/binding-win32-ia32-msvc': 1.69.0
|
||||
'@oxlint/binding-win32-x64-msvc': 1.69.0
|
||||
oxlint-tsgolint: 0.23.0
|
||||
'@oxlint/binding-android-arm-eabi': 1.88.0
|
||||
'@oxlint/binding-android-arm64': 1.88.0
|
||||
'@oxlint/binding-darwin-arm64': 1.88.0
|
||||
'@oxlint/binding-darwin-x64': 1.88.0
|
||||
'@oxlint/binding-freebsd-x64': 1.88.0
|
||||
'@oxlint/binding-linux-arm-gnueabihf': 1.88.0
|
||||
'@oxlint/binding-linux-arm-musleabihf': 1.88.0
|
||||
'@oxlint/binding-linux-arm64-gnu': 1.88.0
|
||||
'@oxlint/binding-linux-arm64-musl': 1.88.0
|
||||
'@oxlint/binding-linux-ppc64-gnu': 1.88.0
|
||||
'@oxlint/binding-linux-riscv64-gnu': 1.88.0
|
||||
'@oxlint/binding-linux-riscv64-musl': 1.88.0
|
||||
'@oxlint/binding-linux-s390x-gnu': 1.88.0
|
||||
'@oxlint/binding-linux-x64-gnu': 1.88.0
|
||||
'@oxlint/binding-linux-x64-musl': 1.88.0
|
||||
'@oxlint/binding-openharmony-arm64': 1.88.0
|
||||
'@oxlint/binding-win32-arm64-msvc': 1.88.0
|
||||
'@oxlint/binding-win32-ia32-msvc': 1.88.0
|
||||
'@oxlint/binding-win32-x64-msvc': 1.88.0
|
||||
oxlint-tsgolint: 7.0.2004
|
||||
|
||||
p-limit@2.3.0:
|
||||
dependencies:
|
||||
@@ -20206,7 +20209,7 @@ snapshots:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
|
||||
tinypool@2.1.0: {}
|
||||
tinypool@2.2.0: {}
|
||||
|
||||
tinyrainbow@2.0.0: {}
|
||||
|
||||
@@ -20588,7 +20591,7 @@ snapshots:
|
||||
unist-util-stringify-position: 4.0.0
|
||||
vfile-message: 4.0.2
|
||||
|
||||
vite-plugin-checker@0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.69.0(oxlint-tsgolint@0.23.0))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3):
|
||||
vite-plugin-checker@0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.88.0(oxlint-tsgolint@7.0.2004))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.0
|
||||
chokidar: 4.0.3
|
||||
@@ -20603,7 +20606,7 @@ snapshots:
|
||||
eslint: 10.2.1(jiti@2.6.1)
|
||||
meow: 13.2.0
|
||||
optionator: 0.9.4
|
||||
oxlint: 1.69.0(oxlint-tsgolint@0.23.0)
|
||||
oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)
|
||||
stylelint: 17.15.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
|
||||
|
||||
@@ -300,20 +300,17 @@ describe('PrivateRoute', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should redirect /logs-save-views to /logs/saved-views', () => {
|
||||
renderPrivateRoute({ initialRoute: '/logs-save-views' });
|
||||
it.each([
|
||||
['/logs-save-views', ROUTES.LOGS_EXPLORER],
|
||||
['/traces-save-views', ROUTES.TRACES_EXPLORER],
|
||||
[ROUTES.LOGS_SAVE_VIEWS, ROUTES.LOGS_EXPLORER],
|
||||
[ROUTES.TRACES_SAVE_VIEWS, ROUTES.TRACES_EXPLORER],
|
||||
[ROUTES.METRICS_EXPLORER_VIEWS, ROUTES.METRICS_EXPLORER_EXPLORER],
|
||||
[ROUTES.METER_EXPLORER_VIEWS, ROUTES.METER_EXPLORER],
|
||||
])('should redirect the old views route %s to %s', (from, to) => {
|
||||
renderPrivateRoute({ initialRoute: from });
|
||||
|
||||
expect(screen.getByTestId('location-display')).toHaveTextContent(
|
||||
'/logs/saved-views',
|
||||
);
|
||||
});
|
||||
|
||||
it('should redirect /traces-save-views to /traces/saved-views', () => {
|
||||
renderPrivateRoute({ initialRoute: '/traces-save-views' });
|
||||
|
||||
expect(screen.getByTestId('location-display')).toHaveTextContent(
|
||||
'/traces/saved-views',
|
||||
);
|
||||
expect(screen.getByTestId('location-display').textContent).toBe(to);
|
||||
});
|
||||
|
||||
it('should redirect /settings/access-tokens to /settings/service-accounts', () => {
|
||||
@@ -1635,11 +1632,6 @@ describe('PrivateRoute', () => {
|
||||
['LIST_LICENSES', ROUTES.LIST_LICENSES, USER_ROLES.EDITOR as ROLES],
|
||||
['ONBOARDING', ROUTES.ONBOARDING, USER_ROLES.VIEWER as ROLES],
|
||||
['APPLICATION', ROUTES.APPLICATION, USER_ROLES.ANONYMOUS as ROLES],
|
||||
[
|
||||
'METRICS_EXPLORER_VIEWS',
|
||||
ROUTES.METRICS_EXPLORER_VIEWS,
|
||||
USER_ROLES.ANONYMOUS as ROLES,
|
||||
],
|
||||
];
|
||||
|
||||
it.each(authzRouteRolePairs)(
|
||||
|
||||
@@ -513,8 +513,12 @@ export const LIST_LICENSES: AppRoutes = {
|
||||
export const oldNewRoutesMapping: Record<string, string> = {
|
||||
'/pipelines': '/logs/pipelines',
|
||||
'/logs-explorer': '/logs/logs-explorer',
|
||||
'/logs-save-views': '/logs/saved-views',
|
||||
'/traces-save-views': '/traces/saved-views',
|
||||
'/logs-save-views': '/logs/logs-explorer',
|
||||
'/traces-save-views': '/traces-explorer',
|
||||
'/logs/saved-views': '/logs/logs-explorer',
|
||||
'/traces/saved-views': '/traces-explorer',
|
||||
'/metrics-explorer/views': '/metrics-explorer/explorer',
|
||||
'/meter/explorer/views': '/meter/explorer',
|
||||
'/settings/access-tokens': '/settings/service-accounts',
|
||||
'/settings/api-keys': '/settings/service-accounts',
|
||||
'/messaging-queues': '/messaging-queues/overview',
|
||||
|
||||
@@ -220,8 +220,7 @@ export type ClarificationSummaryDTODiscoveredContextAnyOf = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type ClarificationSummaryDTODiscoveredContext =
|
||||
ClarificationSummaryDTODiscoveredContextAnyOf | null;
|
||||
export type ClarificationSummaryDTODiscoveredContext = ClarificationSummaryDTODiscoveredContextAnyOf | null;
|
||||
|
||||
export interface ClarificationSummaryDTO {
|
||||
/**
|
||||
@@ -784,8 +783,7 @@ export type ThreadDetailResponseDTOActiveExecutionId = string | null;
|
||||
|
||||
export type ThreadDetailResponseDTOPendingApproval = ApprovalSummaryDTO | null;
|
||||
|
||||
export type ThreadDetailResponseDTOPendingClarification =
|
||||
ClarificationSummaryDTO | null;
|
||||
export type ThreadDetailResponseDTOPendingClarification = ClarificationSummaryDTO | null;
|
||||
|
||||
/**
|
||||
* Full thread state for cold-start reconstruction (GET /threads/{id}).
|
||||
@@ -975,8 +973,7 @@ export type ClarificationEventDTODiscoveredContextAnyOf = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type ClarificationEventDTODiscoveredContext =
|
||||
ClarificationEventDTODiscoveredContextAnyOf | null;
|
||||
export type ClarificationEventDTODiscoveredContext = ClarificationEventDTODiscoveredContextAnyOf | null;
|
||||
|
||||
export interface ClarificationEventDTO {
|
||||
/**
|
||||
@@ -1299,8 +1296,7 @@ export type CreateThreadApiV1AssistantThreadsPostHeaders = {
|
||||
'X-SigNoz-URL'?: string | null;
|
||||
};
|
||||
|
||||
export type CreateThreadApiV1AssistantThreadsPostBody =
|
||||
CreateThreadRequestDTO | null;
|
||||
export type CreateThreadApiV1AssistantThreadsPostBody = CreateThreadRequestDTO | null;
|
||||
|
||||
export type ListThreadsApiV1AssistantThreadsGetParams = {
|
||||
/**
|
||||
|
||||
@@ -431,8 +431,7 @@ export type AlertmanagertypesChannelEmailConfigDTOHeadersAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AlertmanagertypesChannelEmailConfigDTOHeaders =
|
||||
AlertmanagertypesChannelEmailConfigDTOHeadersAnyOf | null;
|
||||
export type AlertmanagertypesChannelEmailConfigDTOHeaders = AlertmanagertypesChannelEmailConfigDTOHeadersAnyOf | null;
|
||||
|
||||
export interface AlertmanagertypesChannelEmailConfigDTO {
|
||||
/**
|
||||
@@ -510,8 +509,7 @@ export type AlertmanagertypesChannelPagerdutyConfigDTODetailsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AlertmanagertypesChannelPagerdutyConfigDTODetails =
|
||||
AlertmanagertypesChannelPagerdutyConfigDTODetailsAnyOf | null;
|
||||
export type AlertmanagertypesChannelPagerdutyConfigDTODetails = AlertmanagertypesChannelPagerdutyConfigDTODetailsAnyOf | null;
|
||||
|
||||
export interface AlertmanagertypesChannelPagerdutyConfigDTO {
|
||||
/**
|
||||
@@ -584,8 +582,7 @@ export type AlertmanagertypesChannelOpsgenieConfigDTODetailsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AlertmanagertypesChannelOpsgenieConfigDTODetails =
|
||||
AlertmanagertypesChannelOpsgenieConfigDTODetailsAnyOf | null;
|
||||
export type AlertmanagertypesChannelOpsgenieConfigDTODetails = AlertmanagertypesChannelOpsgenieConfigDTODetailsAnyOf | null;
|
||||
|
||||
export interface AlertmanagertypesChannelOpsgenieConfigDTO {
|
||||
/**
|
||||
@@ -706,8 +703,7 @@ export type AlertmanagertypesChannelJiraConfigDTOCustomFieldsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AlertmanagertypesChannelJiraConfigDTOCustomFields =
|
||||
AlertmanagertypesChannelJiraConfigDTOCustomFieldsAnyOf | null;
|
||||
export type AlertmanagertypesChannelJiraConfigDTOCustomFields = AlertmanagertypesChannelJiraConfigDTOCustomFieldsAnyOf | null;
|
||||
|
||||
export interface AlertmanagertypesChannelJiraConfigDTO {
|
||||
/**
|
||||
@@ -832,8 +828,7 @@ export type AlertmanagertypesChannelIncidentIOConfigDTOMetadataAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AlertmanagertypesChannelIncidentIOConfigDTOMetadata =
|
||||
AlertmanagertypesChannelIncidentIOConfigDTOMetadataAnyOf | null;
|
||||
export type AlertmanagertypesChannelIncidentIOConfigDTOMetadata = AlertmanagertypesChannelIncidentIOConfigDTOMetadataAnyOf | null;
|
||||
|
||||
export interface AlertmanagertypesChannelIncidentIOConfigDTO {
|
||||
/**
|
||||
@@ -872,6 +867,55 @@ export interface AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTy
|
||||
spec: AlertmanagertypesChannelIncidentIOConfigDTO;
|
||||
}
|
||||
|
||||
export enum AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTOKind {
|
||||
telegram = 'telegram',
|
||||
}
|
||||
export interface AlertmanagertypesChannelTelegramConfigDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
apiUrl?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format password
|
||||
*/
|
||||
botToken: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
chatId: number;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
disableNotifications?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
message?: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
messageThreadId?: number;
|
||||
/**
|
||||
* @type boolean,null
|
||||
*/
|
||||
sendResolved?: boolean | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTO {
|
||||
/**
|
||||
* @enum telegram
|
||||
* @type string
|
||||
*/
|
||||
kind: AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTOKind;
|
||||
spec: AlertmanagertypesChannelTelegramConfigDTO;
|
||||
}
|
||||
|
||||
export type AlertmanagertypesChannelConfigDTO =
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelSlackConfigDTO
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelEmailConfigDTO
|
||||
@@ -882,7 +926,8 @@ export type AlertmanagertypesChannelConfigDTO =
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelGoogleChatConfigDTO
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJiraConfigDTO
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJSMOpsConfigDTO
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelIncidentIOConfigDTO;
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelIncidentIOConfigDTO
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTO;
|
||||
|
||||
export enum AlertmanagertypesChannelDefectDTO {
|
||||
none = 'none',
|
||||
@@ -902,6 +947,7 @@ export enum AlertmanagertypesChannelKindDTO {
|
||||
jira = 'jira',
|
||||
jsmops = 'jsmops',
|
||||
incidentio = 'incidentio',
|
||||
telegram = 'telegram',
|
||||
}
|
||||
export enum AlertmanagertypesChannelListOrderDTO {
|
||||
asc = 'asc',
|
||||
@@ -2462,7 +2508,7 @@ export interface ConfigSNSConfigDTO {
|
||||
topic_arn?: string;
|
||||
}
|
||||
|
||||
export interface ConfigTelegramConfigDTO {
|
||||
export interface AlertmanagertypesTelegramReceiverConfigDTO {
|
||||
api_url?: ConfigURLType2DTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -2494,6 +2540,10 @@ export interface ConfigTelegramConfigDTO {
|
||||
* @type boolean
|
||||
*/
|
||||
send_resolved?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -2699,7 +2749,7 @@ export type AlertmanagertypesPostableChannelDTO = unknown & {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
telegram_configs?: ConfigTelegramConfigDTO[];
|
||||
telegram_configs?: AlertmanagertypesTelegramReceiverConfigDTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
@@ -2846,7 +2896,7 @@ export interface AlertmanagertypesReceiverDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
telegram_configs?: ConfigTelegramConfigDTO[];
|
||||
telegram_configs?: AlertmanagertypesTelegramReceiverConfigDTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
@@ -2899,6 +2949,9 @@ export interface AuthtypesAttributeMappingDTO {
|
||||
role?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of authn provider.
|
||||
*/
|
||||
export enum AuthtypesAuthDomainConfigSAMLDTOKind {
|
||||
saml = 'saml',
|
||||
}
|
||||
@@ -2926,11 +2979,18 @@ export interface AuthtypesAuthDomainConfigSAMLDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum saml
|
||||
* @description The kind of authn provider.
|
||||
*/
|
||||
kind: AuthtypesAuthDomainConfigSAMLDTOKind;
|
||||
/**
|
||||
* @description The saml configuration.
|
||||
*/
|
||||
spec: AuthtypesSamlConfigDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of authn provider.
|
||||
*/
|
||||
export enum AuthtypesAuthDomainConfigGoogleDTOKind {
|
||||
google = 'google',
|
||||
}
|
||||
@@ -2979,11 +3039,18 @@ export interface AuthtypesAuthDomainConfigGoogleDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum google
|
||||
* @description The kind of authn provider.
|
||||
*/
|
||||
kind: AuthtypesAuthDomainConfigGoogleDTOKind;
|
||||
/**
|
||||
* @description The google auth configuration.
|
||||
*/
|
||||
spec: AuthtypesGoogleConfigDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of authn provider.
|
||||
*/
|
||||
export enum AuthtypesAuthDomainConfigOIDCDTOKind {
|
||||
oidc = 'oidc',
|
||||
}
|
||||
@@ -3020,8 +3087,12 @@ export interface AuthtypesAuthDomainConfigOIDCDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum oidc
|
||||
* @description The kind of authn provider.
|
||||
*/
|
||||
kind: AuthtypesAuthDomainConfigOIDCDTOKind;
|
||||
/**
|
||||
* @description The oidc configuration.
|
||||
*/
|
||||
spec: AuthtypesOIDCConfigDTO;
|
||||
}
|
||||
|
||||
@@ -3080,8 +3151,7 @@ export type AuthtypesRoleMappingDTOGroupMappingsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AuthtypesRoleMappingDTOGroupMappings =
|
||||
AuthtypesRoleMappingDTOGroupMappingsAnyOf | null;
|
||||
export type AuthtypesRoleMappingDTOGroupMappings = AuthtypesRoleMappingDTOGroupMappingsAnyOf | null;
|
||||
|
||||
export interface AuthtypesRoleMappingDTO {
|
||||
/**
|
||||
@@ -3692,8 +3762,7 @@ export type CloudintegrationtypesAgentReportDTODataAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type CloudintegrationtypesAgentReportDTOData =
|
||||
CloudintegrationtypesAgentReportDTODataAnyOf | null;
|
||||
export type CloudintegrationtypesAgentReportDTOData = CloudintegrationtypesAgentReportDTODataAnyOf | null;
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
@@ -4179,8 +4248,7 @@ export type CloudintegrationtypesPostableAgentCheckInDTODataAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type CloudintegrationtypesPostableAgentCheckInDTOData =
|
||||
CloudintegrationtypesPostableAgentCheckInDTODataAnyOf | null;
|
||||
export type CloudintegrationtypesPostableAgentCheckInDTOData = CloudintegrationtypesPostableAgentCheckInDTODataAnyOf | null;
|
||||
|
||||
export interface CloudintegrationtypesPostableAgentCheckInDTO {
|
||||
/**
|
||||
@@ -4422,6 +4490,48 @@ export interface ConfigJiraConfigDTO {
|
||||
wont_fix_resolution?: string;
|
||||
}
|
||||
|
||||
export interface ConfigTelegramConfigDTO {
|
||||
api_url?: ConfigURLType2DTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
chat?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
chat_file?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
disable_notifications?: boolean;
|
||||
http_config?: ConfigHTTPClientConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
message?: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
message_thread_id?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
parse_mode?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
send_resolved?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
token?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
token_file?: string;
|
||||
}
|
||||
|
||||
export interface DashboardGridItemDTO {
|
||||
content?: CommonJSONRefDTO;
|
||||
/**
|
||||
@@ -4483,6 +4593,9 @@ export interface Querybuildertypesv5TraceAggregationDTO {
|
||||
expression?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* How the upper bounds are spaced.
|
||||
*/
|
||||
export enum Querybuildertypesv5BucketOptionsLinearDTOKind {
|
||||
linear = 'linear',
|
||||
}
|
||||
@@ -4502,11 +4615,18 @@ export interface Querybuildertypesv5BucketOptionsLinearDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum linear
|
||||
* @description How the upper bounds are spaced.
|
||||
*/
|
||||
kind: Querybuildertypesv5BucketOptionsLinearDTOKind;
|
||||
/**
|
||||
* @description The evenly spaced bucket specification.
|
||||
*/
|
||||
spec: Querybuildertypesv5LinearBucketsSpecDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* How the upper bounds are spaced.
|
||||
*/
|
||||
export enum Querybuildertypesv5BucketOptionsLogDTOKind {
|
||||
log = 'log',
|
||||
}
|
||||
@@ -4521,8 +4641,12 @@ export interface Querybuildertypesv5BucketOptionsLogDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum log
|
||||
* @description How the upper bounds are spaced.
|
||||
*/
|
||||
kind: Querybuildertypesv5BucketOptionsLogDTOKind;
|
||||
/**
|
||||
* @description The logarithmic bucket specification.
|
||||
*/
|
||||
spec: Querybuildertypesv5LogBucketsSpecDTO;
|
||||
}
|
||||
|
||||
@@ -4868,8 +4992,7 @@ export type DashboardtypesLegendDTOCustomColorsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type DashboardtypesLegendDTOCustomColors =
|
||||
DashboardtypesLegendDTOCustomColorsAnyOf | null;
|
||||
export type DashboardtypesLegendDTOCustomColors = DashboardtypesLegendDTOCustomColorsAnyOf | null;
|
||||
|
||||
export interface DashboardtypesLegendDTO {
|
||||
/**
|
||||
@@ -5461,8 +5584,7 @@ export type DashboardtypesTableFormattingDTOColumnUnitsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type DashboardtypesTableFormattingDTOColumnUnits =
|
||||
DashboardtypesTableFormattingDTOColumnUnitsAnyOf | null;
|
||||
export type DashboardtypesTableFormattingDTOColumnUnits = DashboardtypesTableFormattingDTOColumnUnitsAnyOf | null;
|
||||
|
||||
export interface DashboardtypesTableFormattingDTO {
|
||||
/**
|
||||
@@ -5747,26 +5869,40 @@ export type Querybuildertypesv5BuilderQuerySpecDTO =
|
||||
| Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregationDTO
|
||||
| Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregationDTO;
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopeBuilderDTOType {
|
||||
builder_query = 'builder_query',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopeBuilderDTO {
|
||||
/**
|
||||
* @description The builder query specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5BuilderQuerySpecDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum builder_query
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopeBuilderDTOType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopeBuilderAIDTOType {
|
||||
builder_ai_query = 'builder_ai_query',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopeBuilderAIDTO {
|
||||
/**
|
||||
* @description The AI builder query specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregationDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum builder_ai_query
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopeBuilderAIDTOType;
|
||||
}
|
||||
@@ -5804,14 +5940,21 @@ export interface Querybuildertypesv5QueryBuilderFormulaDTO {
|
||||
order?: Querybuildertypesv5OrderByDTO[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopeFormulaDTOType {
|
||||
builder_formula = 'builder_formula',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopeFormulaDTO {
|
||||
/**
|
||||
* @description The formula specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5QueryBuilderFormulaDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum builder_formula
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopeFormulaDTOType;
|
||||
}
|
||||
@@ -5874,14 +6017,21 @@ export interface Querybuildertypesv5QueryBuilderTraceOperatorDTO {
|
||||
stepInterval?: Querybuildertypesv5StepDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopeTraceOperatorDTOType {
|
||||
builder_trace_operator = 'builder_trace_operator',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopeTraceOperatorDTO {
|
||||
/**
|
||||
* @description The trace operator specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5QueryBuilderTraceOperatorDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum builder_trace_operator
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopeTraceOperatorDTOType;
|
||||
}
|
||||
@@ -5910,14 +6060,21 @@ export interface Querybuildertypesv5PromQueryDTO {
|
||||
step?: Querybuildertypesv5StepDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopePromQLDTOType {
|
||||
promql = 'promql',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopePromQLDTO {
|
||||
/**
|
||||
* @description The PromQL query specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5PromQueryDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum promql
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopePromQLDTOType;
|
||||
}
|
||||
@@ -5941,14 +6098,21 @@ export interface Querybuildertypesv5ClickHouseQueryDTO {
|
||||
query?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopeClickHouseSQLDTOType {
|
||||
clickhouse_sql = 'clickhouse_sql',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopeClickHouseSQLDTO {
|
||||
/**
|
||||
* @description The ClickHouse SQL query specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5ClickHouseQueryDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum clickhouse_sql
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopeClickHouseSQLDTOType;
|
||||
}
|
||||
@@ -6777,8 +6941,7 @@ export type FeaturetypesGettableFeatureDTOVariantsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type FeaturetypesGettableFeatureDTOVariants =
|
||||
FeaturetypesGettableFeatureDTOVariantsAnyOf | null;
|
||||
export type FeaturetypesGettableFeatureDTOVariants = FeaturetypesGettableFeatureDTOVariantsAnyOf | null;
|
||||
|
||||
export interface FeaturetypesGettableFeatureDTO {
|
||||
/**
|
||||
@@ -7256,8 +7419,7 @@ export type InframonitoringtypesClusterRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesClusterRecordDTOMeta =
|
||||
InframonitoringtypesClusterRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesClusterRecordDTOMeta = InframonitoringtypesClusterRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesNodeCountsByReadinessDTO {
|
||||
/**
|
||||
@@ -7530,8 +7692,7 @@ export type InframonitoringtypesContainerRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesContainerRecordDTOMeta =
|
||||
InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesContainerRecordDTOMeta = InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesContainerRecordDTO {
|
||||
containerCountsByReady: InframonitoringtypesContainerCountsByReadyDTO;
|
||||
@@ -7622,8 +7783,7 @@ export type InframonitoringtypesDaemonSetRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesDaemonSetRecordDTOMeta =
|
||||
InframonitoringtypesDaemonSetRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesDaemonSetRecordDTOMeta = InframonitoringtypesDaemonSetRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesDaemonSetRecordDTO {
|
||||
/**
|
||||
@@ -7718,8 +7878,7 @@ export type InframonitoringtypesDeploymentRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesDeploymentRecordDTOMeta =
|
||||
InframonitoringtypesDeploymentRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesDeploymentRecordDTOMeta = InframonitoringtypesDeploymentRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesDeploymentRecordDTO {
|
||||
/**
|
||||
@@ -7808,8 +7967,7 @@ export type InframonitoringtypesHostRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesHostRecordDTOMeta =
|
||||
InframonitoringtypesHostRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesHostRecordDTOMeta = InframonitoringtypesHostRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesHostRecordDTO {
|
||||
/**
|
||||
@@ -7891,8 +8049,7 @@ export type InframonitoringtypesJobRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesJobRecordDTOMeta =
|
||||
InframonitoringtypesJobRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesJobRecordDTOMeta = InframonitoringtypesJobRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesJobRecordDTO {
|
||||
/**
|
||||
@@ -8010,8 +8167,7 @@ export type InframonitoringtypesNamespaceRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesNamespaceRecordDTOMeta =
|
||||
InframonitoringtypesNamespaceRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesNamespaceRecordDTOMeta = InframonitoringtypesNamespaceRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesNamespaceRecordDTO {
|
||||
/**
|
||||
@@ -8078,8 +8234,7 @@ export type InframonitoringtypesNodeRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesNodeRecordDTOMeta =
|
||||
InframonitoringtypesNodeRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesNodeRecordDTOMeta = InframonitoringtypesNodeRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesNodeRecordDTO {
|
||||
condition: InframonitoringtypesNodeConditionDTO;
|
||||
@@ -8150,8 +8305,7 @@ export type InframonitoringtypesPodRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesPodRecordDTOMeta =
|
||||
InframonitoringtypesPodRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesPodRecordDTOMeta = InframonitoringtypesPodRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesPodRecordDTO {
|
||||
/**
|
||||
@@ -8538,8 +8692,7 @@ export type InframonitoringtypesStatefulSetRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesStatefulSetRecordDTOMeta =
|
||||
InframonitoringtypesStatefulSetRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesStatefulSetRecordDTOMeta = InframonitoringtypesStatefulSetRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesStatefulSetRecordDTO {
|
||||
/**
|
||||
@@ -8615,8 +8768,7 @@ export type InframonitoringtypesVolumeRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesVolumeRecordDTOMeta =
|
||||
InframonitoringtypesVolumeRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesVolumeRecordDTOMeta = InframonitoringtypesVolumeRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesVolumeRecordDTO {
|
||||
/**
|
||||
@@ -9734,8 +9886,7 @@ export type PrometheusMatrixSeriesSchemaDTOMetricAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type PrometheusMatrixSeriesSchemaDTOMetric =
|
||||
PrometheusMatrixSeriesSchemaDTOMetricAnyOf | null;
|
||||
export type PrometheusMatrixSeriesSchemaDTOMetric = PrometheusMatrixSeriesSchemaDTOMetricAnyOf | null;
|
||||
|
||||
export interface PrometheusMatrixSeriesSchemaDTO {
|
||||
/**
|
||||
@@ -9767,8 +9918,7 @@ export type PrometheusVectorSampleSchemaDTOMetricAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type PrometheusVectorSampleSchemaDTOMetric =
|
||||
PrometheusVectorSampleSchemaDTOMetricAnyOf | null;
|
||||
export type PrometheusVectorSampleSchemaDTOMetric = PrometheusVectorSampleSchemaDTOMetricAnyOf | null;
|
||||
|
||||
export interface PrometheusVectorSampleSchemaDTO {
|
||||
/**
|
||||
@@ -10178,8 +10328,7 @@ export type Querybuildertypesv5RawRowDTODataAnyOf = { [key: string]: unknown };
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type Querybuildertypesv5RawRowDTOData =
|
||||
Querybuildertypesv5RawRowDTODataAnyOf | null;
|
||||
export type Querybuildertypesv5RawRowDTOData = Querybuildertypesv5RawRowDTODataAnyOf | null;
|
||||
|
||||
export interface Querybuildertypesv5RawRowDTO {
|
||||
/**
|
||||
@@ -10250,8 +10399,7 @@ export type Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQueryAnyOf =
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQuery =
|
||||
Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQueryAnyOf | null;
|
||||
export type Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQuery = Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQueryAnyOf | null;
|
||||
|
||||
/**
|
||||
* Response from the v5 query range preview (dry-run) endpoint. For each query in the composite query, returns the underlying ClickHouse statement(s) it renders to without executing them (one per PromQL metric selector; exactly one for builder/ClickHouse/trace-operator queries), with the optional EXPLAIN ESTIMATE and granule analysis attached per statement when requested.
|
||||
@@ -10651,6 +10799,9 @@ export interface RuletypesCumulativeWindowDTO {
|
||||
timezone: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of evaluation.
|
||||
*/
|
||||
export enum RuletypesEvaluationCumulativeDTOKind {
|
||||
cumulative = 'cumulative',
|
||||
}
|
||||
@@ -10658,11 +10809,18 @@ export interface RuletypesEvaluationCumulativeDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum cumulative
|
||||
* @description The kind of evaluation.
|
||||
*/
|
||||
kind: RuletypesEvaluationCumulativeDTOKind;
|
||||
/**
|
||||
* @description The cumulative window evaluation specification.
|
||||
*/
|
||||
spec: RuletypesCumulativeWindowDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of evaluation.
|
||||
*/
|
||||
export enum RuletypesEvaluationRollingDTOKind {
|
||||
rolling = 'rolling',
|
||||
}
|
||||
@@ -10681,8 +10839,12 @@ export interface RuletypesEvaluationRollingDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum rolling
|
||||
* @description The kind of evaluation.
|
||||
*/
|
||||
kind: RuletypesEvaluationRollingDTOKind;
|
||||
/**
|
||||
* @description The rolling window evaluation specification.
|
||||
*/
|
||||
spec: RuletypesRollingWindowDTO;
|
||||
}
|
||||
|
||||
@@ -10888,16 +11050,28 @@ export enum RuletypesSeasonalityDTO {
|
||||
daily = 'daily',
|
||||
weekly = 'weekly',
|
||||
}
|
||||
/**
|
||||
* The kind of threshold.
|
||||
*/
|
||||
export enum RuletypesThresholdBasicDTOKind {
|
||||
basic = 'basic',
|
||||
}
|
||||
/**
|
||||
* The basic threshold specification (array of thresholds).
|
||||
*/
|
||||
export type RuletypesThresholdBasicDTOSpec = RuletypesBasicRuleThresholdsDTO | null;
|
||||
|
||||
export interface RuletypesThresholdBasicDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum basic
|
||||
* @description The kind of threshold.
|
||||
*/
|
||||
kind: RuletypesThresholdBasicDTOKind;
|
||||
spec: RuletypesBasicRuleThresholdsDTO | null;
|
||||
/**
|
||||
* @description The basic threshold specification (array of thresholds).
|
||||
*/
|
||||
spec: RuletypesThresholdBasicDTOSpec;
|
||||
}
|
||||
|
||||
export type RuletypesRuleThresholdDataDTO = RuletypesThresholdBasicDTO;
|
||||
@@ -11560,8 +11734,7 @@ export type SpantypesSpanMapperTestSpanDTOAttributesAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type SpantypesSpanMapperTestSpanDTOAttributes =
|
||||
SpantypesSpanMapperTestSpanDTOAttributesAnyOf | null;
|
||||
export type SpantypesSpanMapperTestSpanDTOAttributes = SpantypesSpanMapperTestSpanDTOAttributesAnyOf | null;
|
||||
|
||||
export type SpantypesSpanMapperTestSpanDTOResourceAnyOf = {
|
||||
[key: string]: unknown;
|
||||
@@ -11570,8 +11743,7 @@ export type SpantypesSpanMapperTestSpanDTOResourceAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type SpantypesSpanMapperTestSpanDTOResource =
|
||||
SpantypesSpanMapperTestSpanDTOResourceAnyOf | null;
|
||||
export type SpantypesSpanMapperTestSpanDTOResource = SpantypesSpanMapperTestSpanDTOResourceAnyOf | null;
|
||||
|
||||
export interface SpantypesSpanMapperTestSpanDTO {
|
||||
/**
|
||||
@@ -11683,8 +11855,7 @@ export type SpantypesSpanAggregationResultDTOValueAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type SpantypesSpanAggregationResultDTOValue =
|
||||
SpantypesSpanAggregationResultDTOValueAnyOf | null;
|
||||
export type SpantypesSpanAggregationResultDTOValue = SpantypesSpanAggregationResultDTOValueAnyOf | null;
|
||||
|
||||
export interface SpantypesSpanAggregationResultDTO {
|
||||
aggregation: SpantypesSpanAggregationTypeDTO;
|
||||
@@ -11886,16 +12057,14 @@ export type SpantypesWaterfallSpanDTOAttributesAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type SpantypesWaterfallSpanDTOAttributes =
|
||||
SpantypesWaterfallSpanDTOAttributesAnyOf | null;
|
||||
export type SpantypesWaterfallSpanDTOAttributes = SpantypesWaterfallSpanDTOAttributesAnyOf | null;
|
||||
|
||||
export type SpantypesWaterfallSpanDTOResourceAnyOf = { [key: string]: string };
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type SpantypesWaterfallSpanDTOResource =
|
||||
SpantypesWaterfallSpanDTOResourceAnyOf | null;
|
||||
export type SpantypesWaterfallSpanDTOResource = SpantypesWaterfallSpanDTOResourceAnyOf | null;
|
||||
|
||||
export interface SpantypesWaterfallSpanDTO {
|
||||
/**
|
||||
@@ -12335,8 +12504,7 @@ export type TelemetrytypesGettableFieldKeysDTOKeysAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type TelemetrytypesGettableFieldKeysDTOKeys =
|
||||
TelemetrytypesGettableFieldKeysDTOKeysAnyOf | null;
|
||||
export type TelemetrytypesGettableFieldKeysDTOKeys = TelemetrytypesGettableFieldKeysDTOKeysAnyOf | null;
|
||||
|
||||
export interface TelemetrytypesGettableFieldKeysDTO {
|
||||
/**
|
||||
|
||||
@@ -165,17 +165,15 @@ function createBaseSpec(
|
||||
filter: getFilter(queryData),
|
||||
groupBy:
|
||||
queryData.groupBy?.length > 0
|
||||
? queryData.groupBy.map(
|
||||
(item: any): GroupByKey => ({
|
||||
name: item.key,
|
||||
fieldDataType: item?.dataType || '',
|
||||
fieldContext: item?.type || '',
|
||||
description: item?.description,
|
||||
unit: item?.unit,
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}),
|
||||
)
|
||||
? queryData.groupBy.map((item: any): GroupByKey => ({
|
||||
name: item.key,
|
||||
fieldDataType: item?.dataType || '',
|
||||
fieldContext: item?.type || '',
|
||||
description: item?.description,
|
||||
unit: item?.unit,
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}))
|
||||
: undefined,
|
||||
limit:
|
||||
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
|
||||
@@ -187,14 +185,12 @@ function createBaseSpec(
|
||||
: undefined,
|
||||
order:
|
||||
queryData.orderBy?.length > 0
|
||||
? queryData.orderBy.map(
|
||||
(order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}),
|
||||
)
|
||||
? queryData.orderBy.map((order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}))
|
||||
: undefined,
|
||||
legend: isEmpty(queryData.legend) ? undefined : queryData.legend,
|
||||
having: normalizeHaving(queryData.having),
|
||||
@@ -397,17 +393,15 @@ function createTraceOperatorBaseSpec(
|
||||
stepInterval: stepInterval || undefined,
|
||||
groupBy:
|
||||
groupBy?.length > 0
|
||||
? groupBy.map(
|
||||
(item: any): GroupByKey => ({
|
||||
name: item.key,
|
||||
fieldDataType: item?.dataType,
|
||||
fieldContext: item?.type,
|
||||
description: item?.description,
|
||||
unit: item?.unit,
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}),
|
||||
)
|
||||
? groupBy.map((item: any): GroupByKey => ({
|
||||
name: item.key,
|
||||
fieldDataType: item?.dataType,
|
||||
fieldContext: item?.type,
|
||||
description: item?.description,
|
||||
unit: item?.unit,
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}))
|
||||
: undefined,
|
||||
limit:
|
||||
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
|
||||
@@ -416,28 +410,24 @@ function createTraceOperatorBaseSpec(
|
||||
offset: requestType === 'raw' || requestType === 'trace' ? offset : undefined,
|
||||
order:
|
||||
orderBy?.length > 0
|
||||
? orderBy.map(
|
||||
(order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}),
|
||||
)
|
||||
? orderBy.map((order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}))
|
||||
: undefined,
|
||||
legend: isEmpty(legend) ? undefined : legend,
|
||||
having: normalizeHaving(having),
|
||||
selectFields: isEmpty(nonEmptySelectColumns)
|
||||
? undefined
|
||||
: nonEmptySelectColumns?.map(
|
||||
(column: any): TelemetryFieldKey => ({
|
||||
name: column.name ?? column.key,
|
||||
fieldDataType:
|
||||
column?.fieldDataType ?? (column?.dataType as FieldDataType),
|
||||
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
|
||||
signal: column?.signal ?? undefined,
|
||||
}),
|
||||
),
|
||||
: nonEmptySelectColumns?.map((column: any): TelemetryFieldKey => ({
|
||||
name: column.name ?? column.key,
|
||||
fieldDataType:
|
||||
column?.fieldDataType ?? (column?.dataType as FieldDataType),
|
||||
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
|
||||
signal: column?.signal ?? undefined,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -610,14 +600,12 @@ export const prepareQueryRangePayloadV5 = (
|
||||
disabled: formulaData.disabled,
|
||||
limit: formulaData.limit ?? undefined,
|
||||
legend: isEmpty(formulaData.legend) ? undefined : formulaData.legend,
|
||||
order: formulaData.orderBy?.map(
|
||||
(order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}),
|
||||
),
|
||||
order: formulaData.orderBy?.map((order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
})),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -9,3 +9,8 @@
|
||||
margin-inline-end: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: var(--danger-background);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
@@ -111,14 +111,11 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||
|
||||
const lineChartRef = useRef<Chart>();
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
(): ToggleGraphProps => ({
|
||||
toggleGraph(graphIndex: number, isVisible: boolean): void {
|
||||
toggleGraph(graphIndex, isVisible, lineChartRef);
|
||||
},
|
||||
}),
|
||||
);
|
||||
useImperativeHandle(ref, (): ToggleGraphProps => ({
|
||||
toggleGraph(graphIndex: number, isVisible: boolean): void {
|
||||
toggleGraph(graphIndex, isVisible, lineChartRef);
|
||||
},
|
||||
}));
|
||||
|
||||
const getGridColor = useCallback(() => {
|
||||
if (currentTheme === undefined) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { DrawerProps } from 'antd';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||
import { ActionItemProps } from 'container/LogDetailedView/LogDetailedView.types';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
@@ -20,9 +18,7 @@ export type LogDetailProps = {
|
||||
handleOpenInExplorer?: MouseEventHandler;
|
||||
getContainer?: DrawerProps['getContainer'];
|
||||
onApplyLogFilter?: (expression: string) => void;
|
||||
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
||||
Partial<Pick<ActionItemProps, 'onClickActionItem'>> &
|
||||
Pick<DrawerProps, 'onClose'>;
|
||||
} & Pick<DrawerProps, 'onClose'>;
|
||||
|
||||
export type LogDetailInnerProps = LogDetailProps & {
|
||||
log: NonNullable<LogDetailProps['log']>;
|
||||
|
||||
@@ -46,8 +46,6 @@ function renderDrawer(props: Partial<LogDetailProps> = {}): void {
|
||||
<LogDetail
|
||||
log={mockLog}
|
||||
selectedTab={VIEW_TYPES.OVERVIEW}
|
||||
onAddToQuery={jest.fn()}
|
||||
onClickActionItem={jest.fn()}
|
||||
onClose={jest.fn()}
|
||||
{...props}
|
||||
/>,
|
||||
|
||||
@@ -175,21 +175,19 @@ function OptionsMenu({
|
||||
<div className="title">FORMAT</div>
|
||||
|
||||
<div className="menu-items">
|
||||
{items.map(
|
||||
(item: any): JSX.Element => (
|
||||
<div
|
||||
className="item"
|
||||
key={item.label}
|
||||
onClick={(): void => handleMenuItemClick(item.key)}
|
||||
>
|
||||
<div className={cx('item-label')}>
|
||||
{item.label}
|
||||
{items.map((item: any): JSX.Element => (
|
||||
<div
|
||||
className="item"
|
||||
key={item.label}
|
||||
onClick={(): void => handleMenuItemClick(item.key)}
|
||||
>
|
||||
<div className={cx('item-label')}>
|
||||
{item.label}
|
||||
|
||||
{selectedItem === item.key && <Check size={12} />}
|
||||
</div>
|
||||
{selectedItem === item.key && <Check size={12} />}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,13 +11,8 @@
|
||||
border-top: 1px solid var(--l1-border);
|
||||
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', sans-serif;
|
||||
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
|
||||
@@ -12,13 +12,8 @@ $max-recents-shown: 5;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', sans-serif;
|
||||
|
||||
.query-where-clause-editor-container {
|
||||
position: relative;
|
||||
@@ -138,8 +133,7 @@ $max-recents-shown: 5;
|
||||
// least N recents visible.
|
||||
@for $i from 1 through $max-recents-shown {
|
||||
&:has(> li:nth-of-type(#{$i}) .cm-completionIcon-recent) {
|
||||
max-height: $dropdown-base-height +
|
||||
$recents-header-height +
|
||||
max-height: $dropdown-base-height + $recents-header-height +
|
||||
($i * $recent-row-height) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,26 +13,22 @@ function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className="label-column">
|
||||
{newLabels.map(
|
||||
(label: string): JSX.Element => (
|
||||
<BadgeWithTooltip key={label} label={label} value={value} />
|
||||
),
|
||||
)}
|
||||
{newLabels.map((label: string): JSX.Element => (
|
||||
<BadgeWithTooltip key={label} label={label} value={value} />
|
||||
))}
|
||||
{remainingLabels.length > 0 && (
|
||||
<Popover
|
||||
placement="bottomRight"
|
||||
showArrow={false}
|
||||
content={
|
||||
<div>
|
||||
{labels.map(
|
||||
(label: string): JSX.Element => (
|
||||
<div key={label}>
|
||||
<Badge textTransform="none" variant="solid" color="secondary">
|
||||
{getLabelAndValueContent(label, value && value[label])}
|
||||
</Badge>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
{labels.map((label: string): JSX.Element => (
|
||||
<div key={label}>
|
||||
<Badge textTransform="none" variant="solid" color="secondary">
|
||||
{getLabelAndValueContent(label, value && value[label])}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
trigger="hover"
|
||||
|
||||
@@ -50,14 +50,11 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
|
||||
const onCreateRef = useRef(onCreate);
|
||||
const onDeleteRef = useRef(onDelete);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
(): ToggleGraphProps => ({
|
||||
toggleGraph(graphIndex: number, isVisible: boolean): void {
|
||||
chartRef.current?.setSeries(graphIndex, { show: isVisible });
|
||||
},
|
||||
}),
|
||||
);
|
||||
useImperativeHandle(ref, (): ToggleGraphProps => ({
|
||||
toggleGraph(graphIndex: number, isVisible: boolean): void {
|
||||
chartRef.current?.setSeries(graphIndex, { show: isVisible });
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
onCreateRef.current = onCreate;
|
||||
|
||||
@@ -511,7 +511,7 @@ export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
|
||||
};
|
||||
|
||||
export enum OperatorConfigKeys {
|
||||
'EXCEPTIONS' = 'EXCEPTIONS',
|
||||
EXCEPTIONS = 'EXCEPTIONS',
|
||||
}
|
||||
|
||||
export const OPERATORS_CONFIG = {
|
||||
|
||||
@@ -13,20 +13,19 @@ jest.mock(
|
||||
'../Explorer/Domains/DomainDetails/components/EndPointsDropDown',
|
||||
() => ({
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation(
|
||||
({ setSelectedEndPointName }): JSX.Element => (
|
||||
default: jest
|
||||
.fn()
|
||||
.mockImplementation(({ setSelectedEndPointName }): JSX.Element => (
|
||||
<div data-testid="endpoints-dropdown-mock">
|
||||
<select
|
||||
data-testid="endpoints-select"
|
||||
onChange={(e): void => setSelectedEndPointName(e.target.value)}
|
||||
role="combobox"
|
||||
>
|
||||
<option value="/api/test">/api/test</option>
|
||||
<option value="/api/new-endpoint">/api/new-endpoint</option>
|
||||
</select>
|
||||
</div>
|
||||
),
|
||||
),
|
||||
)),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { useQuery } from 'react-query';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import getAllChannels from 'api/channels/getAll';
|
||||
import classNames from 'classnames';
|
||||
import { ChartLine } from '@signozhq/icons';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { useChannelOptions } from 'hooks/notificationChannels/useChannelOptions';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import { useCreateAlertState } from '../context';
|
||||
import AdvancedOptions from '../EvaluationSettings/AdvancedOptions';
|
||||
@@ -21,14 +17,11 @@ function AlertCondition(): JSX.Element {
|
||||
const { alertType, setAlertType } = useCreateAlertState();
|
||||
|
||||
const {
|
||||
data,
|
||||
data: channels,
|
||||
isLoading: isLoadingChannels,
|
||||
isError: isErrorChannels,
|
||||
refetch: refreshChannels,
|
||||
} = useQuery<SuccessResponseV2<Channels[]>, APIError>(['getChannels'], {
|
||||
queryFn: () => getAllChannels(),
|
||||
});
|
||||
const channels = data?.data || [];
|
||||
} = useChannelOptions();
|
||||
|
||||
const showMultipleTabs =
|
||||
alertType === AlertTypes.ANOMALY_BASED_ALERT ||
|
||||
|
||||
@@ -2,12 +2,12 @@ import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
|
||||
|
||||
import { CreateAlertProvider } from '../../context';
|
||||
import AlertThreshold from '../AlertThreshold';
|
||||
|
||||
const mockChannels: Channels[] = [];
|
||||
const mockChannels: ChannelOption[] = [];
|
||||
const mockRefreshChannels = jest.fn();
|
||||
const mockIsLoadingChannels = false;
|
||||
const mockIsErrorChannels = false;
|
||||
@@ -85,16 +85,17 @@ jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
}));
|
||||
|
||||
// Mock getAllChannels API
|
||||
jest.mock('api/channels/getAll', () => ({
|
||||
jest.mock('hooks/notificationChannels/useChannelOptions', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: [
|
||||
{ id: '1', name: 'Email Channel' },
|
||||
{ id: '2', name: 'Slack Channel' },
|
||||
] as Channels[],
|
||||
}),
|
||||
),
|
||||
useChannelOptions: jest.fn(() => ({
|
||||
data: [
|
||||
{ id: '1', name: 'Email Channel' },
|
||||
{ id: '2', name: 'Slack Channel' },
|
||||
] as ChannelOption[],
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
refetch: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock alert format categories
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { DefaultOptionType } from 'antd/es/select';
|
||||
import { createMockAlertContextState } from 'container/CreateAlertV2/EvaluationSettings/__tests__/testUtils';
|
||||
import { getAppContextMockState } from 'container/RoutingPolicies/__tests__/testUtils';
|
||||
import * as appHooks from 'providers/App/App';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
|
||||
|
||||
import * as context from '../../context';
|
||||
import ThresholdItem from '../ThresholdItem';
|
||||
@@ -57,7 +57,7 @@ const mockThreshold = {
|
||||
color: '#ff0000',
|
||||
};
|
||||
|
||||
const mockChannels: Channels[] = [
|
||||
const mockChannels: ChannelOption[] = [
|
||||
{
|
||||
id: TEST_CONSTANTS.CHANNEL_1,
|
||||
name: TEST_CONSTANTS.EMAIL_CHANNEL_NAME,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DefaultOptionType } from 'antd/es/select';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
|
||||
|
||||
import {
|
||||
NotificationSettingsAction,
|
||||
@@ -21,7 +21,7 @@ export interface ThresholdItemProps {
|
||||
updateThreshold: UpdateThreshold;
|
||||
removeThreshold: (thresholdId: string) => void;
|
||||
showRemoveButton: boolean;
|
||||
channels: Channels[];
|
||||
channels: ChannelOption[];
|
||||
isLoadingChannels: boolean;
|
||||
units: DefaultOptionType[];
|
||||
isErrorChannels: boolean;
|
||||
@@ -29,7 +29,7 @@ export interface ThresholdItemProps {
|
||||
}
|
||||
|
||||
export interface AnomalyAndThresholdProps {
|
||||
channels: Channels[];
|
||||
channels: ChannelOption[];
|
||||
isLoadingChannels: boolean;
|
||||
isErrorChannels: boolean;
|
||||
refreshChannels: () => void;
|
||||
|
||||
@@ -16,19 +16,19 @@ export interface IAdvancedOptionItemProps {
|
||||
}
|
||||
|
||||
export enum RollingWindowTimeframes {
|
||||
'LAST_5_MINUTES' = '5m0s',
|
||||
'LAST_10_MINUTES' = '10m0s',
|
||||
'LAST_15_MINUTES' = '15m0s',
|
||||
'LAST_30_MINUTES' = '30m0s',
|
||||
'LAST_1_HOUR' = '1h0m0s',
|
||||
'LAST_2_HOURS' = '2h0m0s',
|
||||
'LAST_4_HOURS' = '4h0m0s',
|
||||
LAST_5_MINUTES = '5m0s',
|
||||
LAST_10_MINUTES = '10m0s',
|
||||
LAST_15_MINUTES = '15m0s',
|
||||
LAST_30_MINUTES = '30m0s',
|
||||
LAST_1_HOUR = '1h0m0s',
|
||||
LAST_2_HOURS = '2h0m0s',
|
||||
LAST_4_HOURS = '4h0m0s',
|
||||
}
|
||||
|
||||
export enum CumulativeWindowTimeframes {
|
||||
'CURRENT_HOUR' = 'currentHour',
|
||||
'CURRENT_DAY' = 'currentDay',
|
||||
'CURRENT_MONTH' = 'currentMonth',
|
||||
CURRENT_HOUR = 'currentHour',
|
||||
CURRENT_DAY = 'currentDay',
|
||||
CURRENT_MONTH = 'currentMonth',
|
||||
}
|
||||
|
||||
export interface IEvaluationWindowPopoverProps {
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Plus } from '@signozhq/icons';
|
||||
import { Button, Flex, Form, Select, Tooltip } from 'antd';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import getAll from 'api/channels/getAll';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useChannelOptions } from 'hooks/notificationChannels/useChannelOptions';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { AlertDef, Labels } from 'types/api/alerts/def';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
@@ -47,12 +44,13 @@ function BasicInfo({
|
||||
}: BasicInfoProps): JSX.Element {
|
||||
const { t } = useTranslation('alerts');
|
||||
|
||||
const { isLoading, data, error, isError, refetch } = useQuery<
|
||||
SuccessResponseV2<Channels[]>,
|
||||
APIError
|
||||
>(['getChannels'], {
|
||||
queryFn: () => getAll(),
|
||||
});
|
||||
const {
|
||||
isLoading,
|
||||
data: channels,
|
||||
error,
|
||||
isError,
|
||||
refetch,
|
||||
} = useChannelOptions();
|
||||
|
||||
const { user } = useAppContext();
|
||||
const [addNewChannelPermission] = useComponentPermission(
|
||||
@@ -81,7 +79,7 @@ function BasicInfo({
|
||||
});
|
||||
};
|
||||
|
||||
const noChannels = data?.data?.length === 0;
|
||||
const noChannels = channels.length === 0;
|
||||
const handleCreateNewChannels = useCallback(() => {
|
||||
logEvent('Alert: Create notification channel button clicked', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
@@ -96,7 +94,7 @@ function BasicInfo({
|
||||
if (!isLoading && isNewRule && !hasLoggedEvent.current) {
|
||||
logEvent('Alert: New alert creation page visited', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
numberOfChannels: data?.data?.length,
|
||||
numberOfChannels: channels.length,
|
||||
});
|
||||
hasLoggedEvent.current = true;
|
||||
}
|
||||
@@ -230,7 +228,7 @@ function BasicInfo({
|
||||
disabled={shouldBroadCastToAllChannels}
|
||||
currentValue={alertDef.preferredChannels}
|
||||
handleCreateNewChannels={handleCreateNewChannels}
|
||||
channels={data?.data || []}
|
||||
channels={channels}
|
||||
isLoading={isLoading}
|
||||
hasError={isError}
|
||||
error={error as APIError}
|
||||
|
||||
@@ -2,10 +2,10 @@ import { ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Plus } from '@signozhq/icons';
|
||||
import { Select, Spin } from 'antd';
|
||||
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import { StyledCreateChannelOption, StyledSelect } from './styles';
|
||||
@@ -16,7 +16,7 @@ export interface ChannelSelectProps {
|
||||
onSelectChannels: (s: string[]) => void;
|
||||
onDropdownOpen: () => void;
|
||||
isLoading: boolean;
|
||||
channels: Channels[];
|
||||
channels: ChannelOption[];
|
||||
hasError: boolean;
|
||||
error: APIError;
|
||||
handleCreateNewChannels: () => void;
|
||||
|
||||
@@ -25,10 +25,8 @@ import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants'
|
||||
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import RunQueryBtn from 'container/QueryBuilder/components/RunQueryBtn/RunQueryBtn';
|
||||
import { getOldLogsOperatorFromNew } from 'hooks/logs/useActiveLog';
|
||||
import useLogDetailHandlers from 'hooks/logs/useLogDetailHandlers';
|
||||
import useScrollToLog from 'hooks/logs/useScrollToLog';
|
||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
||||
import { saveRecentQueryByExpression } from 'lib/recentQueries/saveRecentQuery';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@@ -82,29 +80,6 @@ function EntityLogsContent({
|
||||
const { activeLog, selectedTab, handleSetActiveLog, handleCloseLogDetail } =
|
||||
useLogDetailHandlers();
|
||||
|
||||
// TODO: Move away from using onAddToQuery after old drawer cleanup
|
||||
const onAddToQuery = useCallback(
|
||||
(fieldKey: string, fieldValue: string, operator: string): void => {
|
||||
handleCloseLogDetail();
|
||||
|
||||
const partExpression = generateFilterQuery({
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
type: getOldLogsOperatorFromNew(operator),
|
||||
});
|
||||
|
||||
const currentUser = userExpression;
|
||||
const newUser = currentUser.trim()
|
||||
? `${currentUser} AND ${partExpression}`
|
||||
: partExpression;
|
||||
|
||||
querySearchOnRun(newUser);
|
||||
|
||||
logInfraDrawerFilterCustomizedEvent(category, 'logs', newUser, 'logs');
|
||||
},
|
||||
[userExpression, querySearchOnRun, handleCloseLogDetail, category],
|
||||
);
|
||||
|
||||
const onApplyLogFilter = useCallback(
|
||||
(expression: string): void => {
|
||||
handleCloseLogDetail();
|
||||
@@ -342,8 +317,6 @@ function EntityLogsContent({
|
||||
logs={logs}
|
||||
onNavigateLog={handleSetActiveLog}
|
||||
selectedTab={selectedTab}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
onApplyLogFilter={onApplyLogFilter}
|
||||
onScrollToLog={handleScrollToLog}
|
||||
handleOpenInExplorer={(e) => handleOpenInExplorer(e, activeLog)}
|
||||
|
||||
@@ -66,9 +66,9 @@ jest.mock('../MetricsTable', () => ({
|
||||
__esModule: true,
|
||||
MetricsTable: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(): JSX.Element => <div data-testid="metrics-table">Metrics Table</div>,
|
||||
),
|
||||
.mockImplementation((): JSX.Element => (
|
||||
<div data-testid="metrics-table">Metrics Table</div>
|
||||
)),
|
||||
}));
|
||||
|
||||
const mockUseQuery = jest.fn();
|
||||
|
||||
@@ -111,9 +111,9 @@ const POD_STATUS_CATEGORY_MAP: Record<
|
||||
};
|
||||
|
||||
type ErrorStatusKey = {
|
||||
[K in keyof InframonitoringtypesPodCountsByStatusDTO]: (typeof POD_STATUS_CATEGORY_MAP)[K] extends 'error'
|
||||
? K
|
||||
: never;
|
||||
[
|
||||
K in keyof InframonitoringtypesPodCountsByStatusDTO
|
||||
]: (typeof POD_STATUS_CATEGORY_MAP)[K] extends 'error' ? K : never;
|
||||
}[keyof InframonitoringtypesPodCountsByStatusDTO];
|
||||
|
||||
const ERROR_STATUS_LABELS: Record<ErrorStatusKey, string> = {
|
||||
|
||||
@@ -259,8 +259,6 @@ function LiveLogsList({
|
||||
selectedTab={selectedTab}
|
||||
log={activeLog}
|
||||
onClose={handleCloseLogDetail}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
handleChangeSelectedView={handleChangeSelectedView}
|
||||
logs={formattedLogs}
|
||||
onNavigateLog={handleSetActiveLog}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { MetricsType } from 'container/MetricsApplication/constant';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
|
||||
export type AnyObject = { [key: string]: any };
|
||||
|
||||
@@ -19,15 +18,3 @@ export interface DataType {
|
||||
field: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ActionItemProps {
|
||||
fieldKey: string;
|
||||
fieldValue: string;
|
||||
onClickActionItem: (
|
||||
fieldKey: string,
|
||||
fieldValue: string,
|
||||
operator: string,
|
||||
dataType?: DataTypes,
|
||||
fieldType?: string,
|
||||
) => void;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ const normalizeDataType = (
|
||||
// key is fabricated locally (empty source list), so no click-time getAggregateKeys fetch.
|
||||
export const getFilterQueryData = (
|
||||
item: IBuilderQuery,
|
||||
target: LogFilterTarget,
|
||||
target: Pick<LogFilterTarget, 'fieldKey' | 'dataType' | 'metricsType'>,
|
||||
value: unknown,
|
||||
operator: string,
|
||||
): IBuilderQuery => {
|
||||
|
||||
@@ -74,12 +74,7 @@
|
||||
}
|
||||
|
||||
tr:has(.active-log) {
|
||||
background-color: rgba(
|
||||
78,
|
||||
116,
|
||||
248,
|
||||
0.5
|
||||
) !important; // same as bg-robin-500
|
||||
background-color: rgba(78, 116, 248, 0.5) !important; // same as bg-robin-500
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,8 +381,6 @@ function LogsExplorerList({
|
||||
selectedTab={selectedTab}
|
||||
log={activeLog}
|
||||
onClose={handleCloseLogDetail}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
handleChangeSelectedView={handleChangeSelectedView}
|
||||
logs={logs}
|
||||
onNavigateLog={handleSetActiveLog}
|
||||
|
||||
@@ -91,13 +91,8 @@ function LogsPanelComponent({
|
||||
() => logs.map((log) => FlatLogData(log) as RowData),
|
||||
[logs],
|
||||
);
|
||||
const {
|
||||
activeLog,
|
||||
onAddToQuery,
|
||||
selectedTab,
|
||||
handleSetActiveLog,
|
||||
handleCloseLogDetail,
|
||||
} = useLogDetailHandlers();
|
||||
const { activeLog, selectedTab, handleSetActiveLog, handleCloseLogDetail } =
|
||||
useLogDetailHandlers();
|
||||
|
||||
const handleRow = useCallback(
|
||||
(record: RowData): HTMLAttributes<RowData> => ({
|
||||
@@ -179,8 +174,6 @@ function LogsPanelComponent({
|
||||
selectedTab={selectedTab}
|
||||
log={activeLog}
|
||||
onClose={handleCloseLogDetail}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
isListViewPanel
|
||||
listViewPanelSelectedFields={widget?.selectedLogFields}
|
||||
logs={logs}
|
||||
|
||||
@@ -14,6 +14,7 @@ import * as useQueryBuilderHooks from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import * as useHandleExplorerTabChangeHooks from 'hooks/useHandleExplorerTabChange';
|
||||
import * as appContextHooks from 'providers/App/App';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
||||
import * as timezoneHooks from 'providers/Timezone';
|
||||
import store from 'store';
|
||||
import { LicenseEvent } from 'types/api/licensesV3/getActive';
|
||||
@@ -147,7 +148,9 @@ function renderExplorer(): void {
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<ErrorModalProvider>
|
||||
<Explorer />
|
||||
<PreferenceContextProvider>
|
||||
<Explorer />
|
||||
</PreferenceContextProvider>
|
||||
</ErrorModalProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>
|
||||
|
||||
@@ -10,8 +10,7 @@ import { ILog } from 'types/api/logs/log';
|
||||
import './styles.scss';
|
||||
|
||||
function LogsList({ logs }: LogsListProps): JSX.Element {
|
||||
const { activeLog, onSetActiveLog, onClearActiveLog, onAddToQuery } =
|
||||
useActiveLog();
|
||||
const { activeLog, onSetActiveLog, onClearActiveLog } = useActiveLog();
|
||||
|
||||
const makeLogDetailsHandler = (log: ILog) => (): void => onSetActiveLog(log);
|
||||
|
||||
@@ -45,8 +44,6 @@ function LogsList({ logs }: LogsListProps): JSX.Element {
|
||||
selectedTab={VIEW_TYPES.OVERVIEW}
|
||||
log={activeLog}
|
||||
onClose={onClearActiveLog}
|
||||
onAddToQuery={onAddToQuery}
|
||||
onClickActionItem={onAddToQuery}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,16 +15,14 @@ function PromQLQueryContainer(): JSX.Element | null {
|
||||
|
||||
return (
|
||||
<>
|
||||
{currentQuery.promql.map(
|
||||
(q: IPromQLQuery, idx: number): JSX.Element => (
|
||||
<PromQLQueryBuilder
|
||||
key={q.name}
|
||||
deletable={currentQuery.promql.length > 1}
|
||||
queryIndex={idx}
|
||||
queryData={q}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{currentQuery.promql.map((q: IPromQLQuery, idx: number): JSX.Element => (
|
||||
<PromQLQueryBuilder
|
||||
key={q.name}
|
||||
deletable={currentQuery.promql.length > 1}
|
||||
queryIndex={idx}
|
||||
queryData={q}
|
||||
/>
|
||||
))}
|
||||
<QueryButton
|
||||
onClick={addQueryHandler}
|
||||
icon={<Plus size="md" />}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiRoutingPolicy } from 'api/routingPolicies/getRoutingPolicies';
|
||||
import { IAppContext, IUser } from 'providers/App/types';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
|
||||
|
||||
import { RoutingPolicy, UseRoutingPoliciesReturn } from '../types';
|
||||
|
||||
@@ -28,21 +28,13 @@ export const MOCK_ROUTING_POLICY_2: RoutingPolicy = {
|
||||
updatedBy: 'user2@signoz.io',
|
||||
};
|
||||
|
||||
export const MOCK_CHANNEL_1: Channels = {
|
||||
export const MOCK_CHANNEL_1: ChannelOption = {
|
||||
name: 'Channel 1',
|
||||
created_at: '2021-01-01',
|
||||
data: 'data 1',
|
||||
id: '1',
|
||||
type: 'type 1',
|
||||
updated_at: '2021-01-01',
|
||||
};
|
||||
export const MOCK_CHANNEL_2: Channels = {
|
||||
export const MOCK_CHANNEL_2: ChannelOption = {
|
||||
name: 'Channel 2',
|
||||
created_at: '2021-01-02',
|
||||
data: 'data 2',
|
||||
id: '2',
|
||||
type: 'type 2',
|
||||
updated_at: '2021-01-02',
|
||||
};
|
||||
|
||||
export function getUseRoutingPoliciesMockData(
|
||||
|
||||
@@ -77,12 +77,14 @@ jest.mock('hooks/routingPolicies/useDeleteRoutingPolicy', () => ({
|
||||
isLoading: false,
|
||||
}),
|
||||
}));
|
||||
jest.mock('api/channels/getAll', () => ({
|
||||
jest.mock('hooks/notificationChannels/useChannelOptions', () => ({
|
||||
__esModule: true,
|
||||
default: (): any =>
|
||||
Promise.resolve({
|
||||
data: [MOCK_CHANNEL_1, MOCK_CHANNEL_2],
|
||||
}),
|
||||
useChannelOptions: (): any => ({
|
||||
data: [MOCK_CHANNEL_1, MOCK_CHANNEL_2],
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
refetch: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const ROUTING_POLICY_1_NAME = 'Routing Policy 1';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import { ChannelOption } from 'hooks/notificationChannels/useChannelOptions';
|
||||
|
||||
export interface RoutingPolicy {
|
||||
id: string;
|
||||
@@ -62,7 +62,7 @@ export interface RoutingPolicyDetailsProps {
|
||||
routingPolicy: RoutingPolicy | null;
|
||||
closeModal: () => void;
|
||||
mode: PolicyDetailsModalMode;
|
||||
channels: Channels[];
|
||||
channels: ChannelOption[];
|
||||
isErrorChannels: boolean;
|
||||
isLoadingChannels: boolean;
|
||||
handlePolicyDetailsModalAction: HandlePolicyDetailsModalAction;
|
||||
@@ -86,7 +86,7 @@ export interface UseRoutingPoliciesReturn {
|
||||
isErrorRoutingPolicies: boolean;
|
||||
refetchRoutingPolicies: () => void;
|
||||
// Channels
|
||||
channels: Channels[];
|
||||
channels: ChannelOption[];
|
||||
isLoadingChannels: boolean;
|
||||
isErrorChannels: boolean;
|
||||
refreshChannels: () => void;
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useQuery, useQueryClient } from 'react-query';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import getAllChannels from 'api/channels/getAll';
|
||||
import { GetRoutingPoliciesResponse } from 'api/routingPolicies/getRoutingPolicies';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useCreateRoutingPolicy } from 'hooks/routingPolicies/useCreateRoutingPolicy';
|
||||
import { useDeleteRoutingPolicy } from 'hooks/routingPolicies/useDeleteRoutingPolicy';
|
||||
import { useChannelOptions } from 'hooks/notificationChannels/useChannelOptions';
|
||||
import { useGetRoutingPolicies } from 'hooks/routingPolicies/useGetRoutingPolicies';
|
||||
import { useUpdateRoutingPolicy } from 'hooks/routingPolicies/useUpdateRoutingPolicy';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import {
|
||||
PolicyDetailsModalMode,
|
||||
@@ -83,14 +81,11 @@ function useRoutingPolicies(): UseRoutingPoliciesReturn {
|
||||
|
||||
// Channels list
|
||||
const {
|
||||
data,
|
||||
data: channels,
|
||||
isLoading: isLoadingChannels,
|
||||
isError: isErrorChannels,
|
||||
refetch: refetchChannels,
|
||||
} = useQuery<SuccessResponseV2<Channels[]>, APIError>(['getChannels'], {
|
||||
queryFn: () => getAllChannels(),
|
||||
});
|
||||
const channels = data?.data || [];
|
||||
} = useChannelOptions();
|
||||
|
||||
const refreshChannels = (): void => {
|
||||
refetchChannels();
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
gap: var(--spacing-1);
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.collapseIcon {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { rest, type RequestHandler } from 'msw';
|
||||
import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
import remove from 'api/browser/localstorage/remove';
|
||||
import {
|
||||
type AuthtypesTransactionDTO,
|
||||
@@ -11,11 +12,13 @@ import {
|
||||
SavedviewtypesSourceDTO,
|
||||
type SavedviewtypesUpdatableSavedViewDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { SIDEBAR_TOOLTIP_DELAY_MS } from 'components/QuickFilters/QuickFiltersLayout/constants';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import {
|
||||
AUTHZ_CHECK_URL,
|
||||
authzMockResponse,
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
import { expect, screen, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { accessFor } from '@/storybook/access/access';
|
||||
import {
|
||||
@@ -25,6 +28,7 @@ import {
|
||||
} from '@/storybook/controls/controls';
|
||||
import { defineStoryMocks } from '@/storybook/controls/defineStoryMocks';
|
||||
import type { MockControlValues, StoryMocks } from '@/storybook/controls/types';
|
||||
import { withCanvas } from '@/storybook/decorators/withCanvas';
|
||||
import { authzMocks, type AuthzArgs } from '@/storybook/globals/authzMocks';
|
||||
import { respondWith } from '@/storybook/runtime/responseState';
|
||||
|
||||
@@ -43,6 +47,10 @@ import {
|
||||
unreadableViewId,
|
||||
} from './__story_mockdata__/savedViews';
|
||||
|
||||
import SavedViewsHeader from '../SavedViewsHeader';
|
||||
import SavedViewsPanel from '../SavedViewsPanel';
|
||||
import type { SavedViewRowActionKey } from '../types';
|
||||
|
||||
const API = 'http://localhost/api/v2/saved_views';
|
||||
|
||||
const LIST = 'Saved Views · list';
|
||||
@@ -216,3 +224,106 @@ export const savedViewsMocks: StoryMocks<typeof controls> = {
|
||||
...definition,
|
||||
read: (args) => ({ ...authzMocks.read(args), ...definition.read(args) }),
|
||||
};
|
||||
|
||||
/** The sidebar slot of an explorer without quick filters. */
|
||||
export const sidebarCanvas = withCanvas({
|
||||
width: 280,
|
||||
height: 640,
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderRight: '1px solid var(--l1-border)',
|
||||
});
|
||||
|
||||
/**
|
||||
* The header above the pinned list. `onOpenViews` adds All views, which the
|
||||
* header only shows in an explorer whose quick filters hide the list.
|
||||
*/
|
||||
export function SavedViewsSidebar({
|
||||
source,
|
||||
onOpenViews,
|
||||
}: {
|
||||
source: SavedViewSource;
|
||||
onOpenViews?: () => void;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<TooltipProvider delay={SIDEBAR_TOOLTIP_DELAY_MS}>
|
||||
<SavedViewsHeader source={source} onOpenViews={onOpenViews} />
|
||||
<SavedViewsPanel source={source} />
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/** The panel fetches the list and checks every row's permissions before it settles. */
|
||||
export const UNTIL_LOADED = { timeout: 15_000 };
|
||||
|
||||
/**
|
||||
* Re-queried on every attempt: a button whose check settles is a new node, so a
|
||||
* reference taken while it was pending never changes.
|
||||
*/
|
||||
type FindElement = () => HTMLElement;
|
||||
|
||||
export const byTestId =
|
||||
(testId: string): FindElement =>
|
||||
(): HTMLElement =>
|
||||
screen.getByTestId(testId);
|
||||
|
||||
/** A check still in flight: `aria-busy` on a button, `data-loading` on a menu row. */
|
||||
const isPending = (element: HTMLElement): boolean =>
|
||||
element.getAttribute('aria-busy') === 'true' ||
|
||||
element.hasAttribute('data-loading');
|
||||
|
||||
const isDisabled = (element: HTMLElement): boolean =>
|
||||
element.hasAttribute('disabled') || element.hasAttribute('data-disabled');
|
||||
|
||||
export const expectDisabled = (
|
||||
find: FindElement,
|
||||
disabled = true,
|
||||
): Promise<void> =>
|
||||
waitFor(async () => {
|
||||
const element = find();
|
||||
|
||||
await expect(isPending(element)).toBe(false);
|
||||
await expect(isDisabled(element)).toBe(disabled);
|
||||
}, UNTIL_LOADED);
|
||||
|
||||
export const expectPending = (find: FindElement): Promise<void> =>
|
||||
waitFor(() => expect(isPending(find())).toBe(true), UNTIL_LOADED);
|
||||
|
||||
export const findRows = (canvasElement: HTMLElement): Promise<HTMLElement[]> =>
|
||||
within(canvasElement).findAllByTestId(
|
||||
'saved-views-row',
|
||||
undefined,
|
||||
UNTIL_LOADED,
|
||||
);
|
||||
|
||||
export const rowName =
|
||||
(row: HTMLElement): FindElement =>
|
||||
(): HTMLElement =>
|
||||
within(row).getByTestId('saved-views-row-name');
|
||||
|
||||
export const openRowMenu = async (row: HTMLElement): Promise<void> => {
|
||||
await userEvent.click(within(row).getByTestId('saved-views-row-menu'));
|
||||
await screen.findByRole('menu', undefined, UNTIL_LOADED);
|
||||
};
|
||||
|
||||
export const rowMenuItem = (key: SavedViewRowActionKey): FindElement =>
|
||||
byTestId(`saved-views-row-menu-item-${key}`);
|
||||
|
||||
export const openSaveChangesMenu = async (
|
||||
canvasElement: HTMLElement,
|
||||
): Promise<void> => {
|
||||
const trigger = await waitFor(async () => {
|
||||
const button = within(canvasElement).getByTestId('saved-views-save-changes');
|
||||
|
||||
await expect(button).not.toHaveAttribute('aria-busy');
|
||||
|
||||
return button;
|
||||
}, UNTIL_LOADED);
|
||||
|
||||
await userEvent.click(trigger);
|
||||
await screen.findByRole('menu', undefined, UNTIL_LOADED);
|
||||
};
|
||||
|
||||
export const saveChangesItem = (key: 'save-as-new' | 'update'): FindElement =>
|
||||
byTestId(`saved-views-save-changes-item-${key}`);
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
import { SIDEBAR_TOOLTIP_DELAY_MS } from 'components/QuickFilters/QuickFiltersLayout/constants';
|
||||
import { expect, screen, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { storyMocks } from '@/storybook/controls/defineStoryMocks';
|
||||
import { withCanvas } from '@/storybook/decorators/withCanvas';
|
||||
import type { PageStoryArgs } from '@/storybook/runtime/resolveStory';
|
||||
|
||||
import SavedViewsHeader from '../SavedViewsHeader';
|
||||
import SavedViewsPanel from '../SavedViewsPanel';
|
||||
import { savedViewsMocks } from './SavedViews.stories.mocks';
|
||||
import {
|
||||
findRows,
|
||||
openRowMenu,
|
||||
openSaveChangesMenu,
|
||||
SavedViewsSidebar,
|
||||
savedViewsMocks,
|
||||
sidebarCanvas,
|
||||
} from './SavedViews.stories.mocks';
|
||||
|
||||
type SavedViewsArgs = PageStoryArgs<typeof savedViewsMocks>;
|
||||
|
||||
const componentStory = storyMocks(savedViewsMocks);
|
||||
|
||||
/** The panel fetches the list and checks every row's permissions before it settles. */
|
||||
const untilLoaded = { timeout: 15_000 };
|
||||
|
||||
/**
|
||||
* The saved views sidebar of an explorer without quick filters: the header with
|
||||
* the open view and its actions, above the list of views created by the user
|
||||
@@ -28,25 +27,11 @@ const untilLoaded = { timeout: 15_000 };
|
||||
* `viewKey`.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'Components/Saved Views',
|
||||
title: 'Components/Saved Views/Overview',
|
||||
tags: ['authz', 'play'],
|
||||
component: SavedViewsPanel,
|
||||
decorators: [
|
||||
withCanvas({
|
||||
width: 280,
|
||||
height: 640,
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderRight: '1px solid var(--l1-border)',
|
||||
}),
|
||||
],
|
||||
render: ({ source }): JSX.Element => (
|
||||
<TooltipProvider delay={SIDEBAR_TOOLTIP_DELAY_MS}>
|
||||
<SavedViewsHeader source={source} />
|
||||
<SavedViewsPanel source={source} />
|
||||
</TooltipProvider>
|
||||
),
|
||||
decorators: [sidebarCanvas],
|
||||
render: ({ source }): JSX.Element => <SavedViewsSidebar source={source} />,
|
||||
...componentStory,
|
||||
parameters: { ...componentStory.parameters },
|
||||
} satisfies Meta<SavedViewsArgs>;
|
||||
@@ -71,25 +56,15 @@ export const UnsavedChanges: Story = {
|
||||
args: { openView: 'unsaved-changes' },
|
||||
};
|
||||
|
||||
/** Without `list` on saved views, the panel shows the permission callout. */
|
||||
export const NoAccess: Story = {
|
||||
args: { revoked: ['list:saved-view'] },
|
||||
};
|
||||
|
||||
/**
|
||||
* The first row's menu. Edit details and Delete follow `update` and `delete` on
|
||||
* that view, so revoking either disables its item.
|
||||
*/
|
||||
export const RowMenu: Story = {
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
const [row] = await within(canvasElement).findAllByTestId(
|
||||
'saved-views-row',
|
||||
undefined,
|
||||
untilLoaded,
|
||||
);
|
||||
const [row] = await findRows(canvasElement);
|
||||
|
||||
await userEvent.click(within(row).getByTestId('saved-views-row-menu'));
|
||||
await screen.findByRole('menu', undefined, untilLoaded);
|
||||
await openRowMenu(row);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -100,15 +75,6 @@ export const RowMenu: Story = {
|
||||
export const SaveChangesMenu: Story = {
|
||||
args: { openView: 'unsaved-changes' },
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
const trigger = await waitFor(async () => {
|
||||
const button = within(canvasElement).getByTestId('saved-views-save-changes');
|
||||
|
||||
await expect(button).not.toHaveAttribute('aria-busy');
|
||||
|
||||
return button;
|
||||
}, untilLoaded);
|
||||
|
||||
await userEvent.click(trigger);
|
||||
await screen.findByRole('menu', undefined, untilLoaded);
|
||||
await openSaveChangesMenu(canvasElement);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { expect, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { storyMocks } from '@/storybook/controls/defineStoryMocks';
|
||||
import type { PageStoryArgs } from '@/storybook/runtime/resolveStory';
|
||||
|
||||
import { unreadableViewId } from '../__story_mockdata__/savedViews';
|
||||
import {
|
||||
byTestId,
|
||||
expectDisabled,
|
||||
expectPending,
|
||||
findRows,
|
||||
openRowMenu,
|
||||
openSaveChangesMenu,
|
||||
rowMenuItem,
|
||||
rowName,
|
||||
saveChangesItem,
|
||||
SavedViewsSidebar,
|
||||
savedViewsMocks,
|
||||
sidebarCanvas,
|
||||
UNTIL_LOADED,
|
||||
} from '../SavedViews.stories.mocks';
|
||||
|
||||
import SavedViewsPanel from '../../SavedViewsPanel';
|
||||
|
||||
type SavedViewsArgs = PageStoryArgs<typeof savedViewsMocks>;
|
||||
|
||||
const componentStory = storyMocks(savedViewsMocks);
|
||||
|
||||
/**
|
||||
* The saved views sidebar with one permission missing at a time. Every story
|
||||
* here is an admin denied exactly what its name says. `list` and `create` are
|
||||
* workspace-wide; `read`, `update` and `delete` are checked per view, so a row
|
||||
* menu only checks its view once it is opened.
|
||||
*
|
||||
* Route: the explorer of the Source control, with the Open view control's
|
||||
* `viewKey`.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'Components/Saved Views/Authz',
|
||||
tags: ['authz', 'play'],
|
||||
component: SavedViewsPanel,
|
||||
decorators: [sidebarCanvas],
|
||||
render: ({ source }): JSX.Element => <SavedViewsSidebar source={source} />,
|
||||
...componentStory,
|
||||
parameters: { ...componentStory.parameters },
|
||||
} satisfies Meta<SavedViewsArgs>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<SavedViewsArgs>;
|
||||
|
||||
const LIST = 'list:saved-view';
|
||||
const CREATE = 'create:saved-view';
|
||||
const UPDATE = 'update:saved-view';
|
||||
const DELETE = 'delete:saved-view';
|
||||
|
||||
/**
|
||||
* The panel shows the permission callout in place of the list, and All views,
|
||||
* in an explorer whose quick filters hide the list, is disabled.
|
||||
*/
|
||||
export const NoList: Story = {
|
||||
args: { revoked: [LIST] },
|
||||
render: ({ source }): JSX.Element => (
|
||||
<SavedViewsSidebar source={source} onOpenViews={(): void => {}} />
|
||||
),
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await expectDisabled(byTestId('saved-views-open'));
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(canvas.getByTestId('saved-views-panel')).toHaveTextContent(
|
||||
/is not authorized to perform list/,
|
||||
),
|
||||
UNTIL_LOADED,
|
||||
);
|
||||
await expect(canvas.queryByTestId('saved-views-search')).toBeNull();
|
||||
},
|
||||
};
|
||||
|
||||
/** An empty workspace with both ways to create a view disabled: + and Create view. */
|
||||
export const NoCreate: Story = {
|
||||
args: { revoked: [CREATE], createdByMe: 0, createdByOthers: 0 },
|
||||
play: async () => {
|
||||
await expectDisabled(byTestId('saved-views-create'));
|
||||
await expectDisabled(byTestId('saved-views-list-create'));
|
||||
},
|
||||
};
|
||||
|
||||
/** The ✓ menu on unsaved changes: Save as new view disabled, Update selected view open. */
|
||||
export const NoCreateSaveMenu: Story = {
|
||||
args: { revoked: [CREATE], openView: 'unsaved-changes' },
|
||||
play: async ({ canvasElement }) => {
|
||||
await openSaveChangesMenu(canvasElement);
|
||||
await expectDisabled(saveChangesItem('save-as-new'));
|
||||
await expectDisabled(saveChangesItem('update'), false);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* `read` denied on one view made by someone else: its name is disabled and its
|
||||
* hover card never opens, while every other view still opens.
|
||||
*/
|
||||
export const NoRead: Story = {
|
||||
args: { unreadableView: true },
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const rows = await findRows(canvasElement);
|
||||
const deniedId = unreadableViewId(args.source);
|
||||
|
||||
await Promise.all(
|
||||
rows.map((row) =>
|
||||
expectDisabled(rowName(row), row.getAttribute('data-view-id') === deniedId),
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
/** The unreadable view open from its link: the header names it No view access. */
|
||||
export const NoReadOpenView: Story = {
|
||||
args: { unreadableView: true, openView: 'created-by-others' },
|
||||
play: async ({ canvasElement }) => {
|
||||
await expect(
|
||||
await within(canvasElement).findByText(
|
||||
'No view access',
|
||||
undefined,
|
||||
UNTIL_LOADED,
|
||||
),
|
||||
).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
/** A row's menu: Edit details disabled; Copy link, Copy ID and Delete open. */
|
||||
export const NoUpdate: Story = {
|
||||
args: { revoked: [UPDATE] },
|
||||
play: async ({ canvasElement }) => {
|
||||
const [row] = await findRows(canvasElement);
|
||||
|
||||
await openRowMenu(row);
|
||||
await expectDisabled(rowMenuItem('editDetails'));
|
||||
await expectDisabled(rowMenuItem('copyLink'), false);
|
||||
await expectDisabled(rowMenuItem('copyId'), false);
|
||||
await expectDisabled(rowMenuItem('delete'), false);
|
||||
},
|
||||
};
|
||||
|
||||
/** The ✓ menu on unsaved changes: Update selected view disabled, Save as new view open. */
|
||||
export const NoUpdateSaveMenu: Story = {
|
||||
args: { revoked: [UPDATE], openView: 'unsaved-changes' },
|
||||
play: async ({ canvasElement }) => {
|
||||
await openSaveChangesMenu(canvasElement);
|
||||
await expectDisabled(saveChangesItem('update'));
|
||||
await expectDisabled(saveChangesItem('save-as-new'), false);
|
||||
},
|
||||
};
|
||||
|
||||
/** A row's menu: Delete disabled, Edit details still open. */
|
||||
export const NoDelete: Story = {
|
||||
args: { revoked: [DELETE] },
|
||||
play: async ({ canvasElement }) => {
|
||||
const [row] = await findRows(canvasElement);
|
||||
|
||||
await openRowMenu(row);
|
||||
await expectDisabled(rowMenuItem('delete'));
|
||||
await expectDisabled(rowMenuItem('editDetails'), false);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Everything but listing and reading: + is disabled, and a row's menu has only
|
||||
* Copy link and Copy ID left.
|
||||
*/
|
||||
export const ReadOnly: Story = {
|
||||
args: { revoked: [CREATE, UPDATE, DELETE] },
|
||||
play: async ({ canvasElement }) => {
|
||||
await expectDisabled(byTestId('saved-views-create'));
|
||||
|
||||
const [row] = await findRows(canvasElement);
|
||||
|
||||
await expectDisabled(rowName(row), false);
|
||||
await openRowMenu(row);
|
||||
await expectDisabled(rowMenuItem('editDetails'));
|
||||
await expectDisabled(rowMenuItem('delete'));
|
||||
await expectDisabled(rowMenuItem('copyLink'), false);
|
||||
await expectDisabled(rowMenuItem('copyId'), false);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The permission check never answering: + spins, and the list stays blank
|
||||
* behind its `list` check.
|
||||
*/
|
||||
export const CheckLoading: Story = {
|
||||
args: { authzState: 'loading' },
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await expectPending(byTestId('saved-views-create'));
|
||||
await expect(canvas.queryByTestId('saved-views-search')).toBeNull();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The permission check failing rather than denying: the sidebar fails open,
|
||||
* so the list renders and every action stays enabled for the API to refuse.
|
||||
*/
|
||||
export const CheckFailed: Story = {
|
||||
args: { authzState: 'error' },
|
||||
parameters: { allowConsoleErrors: true },
|
||||
play: async ({ canvasElement }) => {
|
||||
await expectDisabled(byTestId('saved-views-create'), false);
|
||||
|
||||
const [row] = await findRows(canvasElement);
|
||||
|
||||
await expectDisabled(rowName(row), false);
|
||||
await openRowMenu(row);
|
||||
await expectDisabled(rowMenuItem('editDetails'), false);
|
||||
await expectDisabled(rowMenuItem('delete'), false);
|
||||
},
|
||||
};
|
||||
@@ -62,25 +62,21 @@ export const getAbbreviatedLabel = (label: string): string => {
|
||||
export const showAllDataSetFromApiResponse = (
|
||||
apiResponse: QueryData[],
|
||||
): LegendEntryProps[] =>
|
||||
apiResponse.map(
|
||||
(item): LegendEntryProps => ({
|
||||
label: getLabelName(
|
||||
item.metric || {},
|
||||
item.queryName || '',
|
||||
item.legend || '',
|
||||
),
|
||||
show: true,
|
||||
}),
|
||||
);
|
||||
apiResponse.map((item): LegendEntryProps => ({
|
||||
label: getLabelName(
|
||||
item.metric || {},
|
||||
item.queryName || '',
|
||||
item.legend || '',
|
||||
),
|
||||
show: true,
|
||||
}));
|
||||
|
||||
export const showAllDataSet = (options: uPlot.Options): LegendEntryProps[] =>
|
||||
options.series
|
||||
.map(
|
||||
(item): LegendEntryProps => ({
|
||||
label: item.label || '',
|
||||
show: true,
|
||||
}),
|
||||
)
|
||||
.map((item): LegendEntryProps => ({
|
||||
label: item.label || '',
|
||||
show: true,
|
||||
}))
|
||||
.filter((_, index) => index !== 0);
|
||||
|
||||
export const saveLegendEntriesToLocalStorage = ({
|
||||
@@ -90,12 +86,10 @@ export const saveLegendEntriesToLocalStorage = ({
|
||||
}: SaveLegendEntriesToLocalStoreProps): void => {
|
||||
const newLegendEntry = {
|
||||
name,
|
||||
dataIndex: options.series.map(
|
||||
(item, index): LegendEntryProps => ({
|
||||
label: item.label || '',
|
||||
show: graphVisibilityState[index],
|
||||
}),
|
||||
),
|
||||
dataIndex: options.series.map((item, index): LegendEntryProps => ({
|
||||
label: item.label || '',
|
||||
show: graphVisibilityState[index],
|
||||
})),
|
||||
};
|
||||
|
||||
let existingEntries: { name: string; dataIndex: LegendEntryProps[] }[] = [];
|
||||
|
||||
61
frontend/src/hooks/logs/__tests__/useActiveLog.test.ts
Normal file
61
frontend/src/hooks/logs/__tests__/useActiveLog.test.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { useActiveLog } from '../useActiveLog';
|
||||
|
||||
const mockRedirect = jest.fn();
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: (): unknown => ({
|
||||
currentQuery: initialQueriesMap.logs,
|
||||
redirectWithQueryBuilderData: mockRedirect,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useUrlQuery', () => ({
|
||||
__esModule: true,
|
||||
default: (): URLSearchParams => new URLSearchParams(),
|
||||
}));
|
||||
|
||||
describe('useActiveLog', () => {
|
||||
beforeEach(() => {
|
||||
mockRedirect.mockClear();
|
||||
});
|
||||
|
||||
it('adds the chip field as a filter with its data type and redirects', () => {
|
||||
const { result } = renderHook(() => useActiveLog());
|
||||
|
||||
result.current.onAddToQuery(
|
||||
'service.name',
|
||||
'frontend',
|
||||
'=',
|
||||
DataTypes.String,
|
||||
);
|
||||
|
||||
expect(mockRedirect).toHaveBeenCalledTimes(1);
|
||||
const query = mockRedirect.mock.calls[0][0] as Query;
|
||||
const item = query.builder.queryData[0].filters?.items.at(-1);
|
||||
expect(item?.key).toMatchObject({
|
||||
key: 'service.name',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
});
|
||||
expect(item?.op).toBe('=');
|
||||
expect(item?.value).toBe('frontend');
|
||||
});
|
||||
|
||||
it('keeps the existing filter items ahead of the new one', () => {
|
||||
const { result } = renderHook(() => useActiveLog());
|
||||
|
||||
result.current.onAddToQuery('k8s.pod.name', 'pod-1', '=');
|
||||
|
||||
const query = mockRedirect.mock.calls[0][0] as Query;
|
||||
const items = query.builder.queryData[0].filters?.items ?? [];
|
||||
expect(items).toHaveLength(
|
||||
(initialQueriesMap.logs.builder.queryData[0].filters?.items.length ?? 0) + 1,
|
||||
);
|
||||
expect(items.at(-1)?.key?.key).toBe('k8s.pod.name');
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,6 @@ jest.mock('hooks/useIsTextSelected');
|
||||
const mockOnSetActiveLog = jest.fn();
|
||||
const mockOnClearActiveLog = jest.fn();
|
||||
const mockOnAddToQuery = jest.fn();
|
||||
const mockOnGroupByAttribute = jest.fn();
|
||||
const mockIsTextSelected = jest.fn();
|
||||
|
||||
const mockLog: ILog = {
|
||||
@@ -44,7 +43,6 @@ beforeEach(() => {
|
||||
onSetActiveLog: mockOnSetActiveLog,
|
||||
onClearActiveLog: mockOnClearActiveLog,
|
||||
onAddToQuery: mockOnAddToQuery,
|
||||
onGroupByAttribute: mockOnGroupByAttribute,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,7 +78,6 @@ it('should toggle off when clicking the same active log', () => {
|
||||
onSetActiveLog: mockOnSetActiveLog,
|
||||
onClearActiveLog: mockOnClearActiveLog,
|
||||
onAddToQuery: mockOnAddToQuery,
|
||||
onGroupByAttribute: mockOnGroupByAttribute,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLogDetailHandlers());
|
||||
|
||||
@@ -25,5 +25,4 @@ export type UseActiveLog = {
|
||||
operator: string,
|
||||
dataType?: DataTypes,
|
||||
) => void;
|
||||
onGroupByAttribute: (fieldKey: string, dataType?: DataTypes) => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -1,40 +1,16 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { OPERATORS, QueryBuilderKeys } from 'constants/queryBuilder';
|
||||
import { MetricsType } from 'container/MetricsApplication/constant';
|
||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearchV2/utils';
|
||||
import { getFilterQueryData } from 'container/LogDetailedView/logAttributeActions.utils';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import {
|
||||
BaseAutocompleteData,
|
||||
DataTypes,
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { UseActiveLog } from './types';
|
||||
|
||||
export function getOldLogsOperatorFromNew(operator: string): string {
|
||||
switch (operator) {
|
||||
case OPERATORS['=']:
|
||||
return OPERATORS.IN;
|
||||
case OPERATORS['!=']:
|
||||
return OPERATORS.NIN;
|
||||
default:
|
||||
return operator;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export const useActiveLog = (): UseActiveLog => {
|
||||
const queryClient = useQueryClient();
|
||||
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const [activeLog, setActiveLog] = useState<ILog | null>(null);
|
||||
|
||||
@@ -58,121 +34,32 @@ export const useActiveLog = (): UseActiveLog => {
|
||||
|
||||
const onClearActiveLog = useCallback((): void => setActiveLog(null), []);
|
||||
|
||||
const onAddToQueryExplorer = useCallback(
|
||||
async (
|
||||
const onAddToQuery = useCallback(
|
||||
(
|
||||
fieldKey: string,
|
||||
fieldValue: string,
|
||||
operator: string,
|
||||
dataType?: DataTypes,
|
||||
fieldType?: MetricsType | undefined,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const keysAutocompleteResponse = await queryClient.fetchQuery(
|
||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS, fieldKey],
|
||||
async () =>
|
||||
getAggregateKeys({
|
||||
searchText: fieldKey,
|
||||
aggregateOperator:
|
||||
currentQuery.builder.queryData[0].aggregateOperator || '',
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute:
|
||||
currentQuery.builder.queryData[0].aggregateAttribute?.key || '',
|
||||
}),
|
||||
);
|
||||
): void => {
|
||||
const nextQuery: Query = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item) =>
|
||||
getFilterQueryData(item, { fieldKey, dataType }, fieldValue, operator),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
const keysAutocomplete: BaseAutocompleteData[] =
|
||||
keysAutocompleteResponse.payload?.attributeKeys || [];
|
||||
|
||||
const existAutocompleteKey = chooseAutocompleteFromCustomValue(
|
||||
keysAutocomplete,
|
||||
fieldKey,
|
||||
dataType,
|
||||
fieldType,
|
||||
);
|
||||
|
||||
const currentOperator = getOperatorValue(operator);
|
||||
|
||||
const nextQuery: Query = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
filters: {
|
||||
...item.filters,
|
||||
items: [
|
||||
...(item.filters?.items || []),
|
||||
{
|
||||
id: uuid(),
|
||||
key: existAutocompleteKey,
|
||||
op: currentOperator,
|
||||
value: fieldValue,
|
||||
},
|
||||
],
|
||||
op: item.filters?.op || 'AND',
|
||||
},
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
redirectWithQueryBuilderData(nextQuery);
|
||||
} catch {
|
||||
notifications.error({ message: SOMETHING_WENT_WRONG });
|
||||
}
|
||||
redirectWithQueryBuilderData(nextQuery);
|
||||
},
|
||||
[currentQuery, notifications, queryClient, redirectWithQueryBuilderData],
|
||||
);
|
||||
|
||||
const onGroupByAttribute = useCallback(
|
||||
async (fieldKey: string, dataType?: DataTypes): Promise<void> => {
|
||||
try {
|
||||
const keysAutocompleteResponse = await queryClient.fetchQuery(
|
||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS, fieldKey],
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
async () =>
|
||||
getAggregateKeys({
|
||||
searchText: fieldKey,
|
||||
aggregateOperator:
|
||||
currentQuery.builder.queryData[0].aggregateOperator || '',
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute:
|
||||
currentQuery.builder.queryData[0].aggregateAttribute?.key || '',
|
||||
}),
|
||||
);
|
||||
|
||||
const keysAutocomplete: BaseAutocompleteData[] =
|
||||
keysAutocompleteResponse.payload?.attributeKeys || [];
|
||||
|
||||
const existAutocompleteKey = chooseAutocompleteFromCustomValue(
|
||||
keysAutocomplete,
|
||||
fieldKey,
|
||||
dataType,
|
||||
);
|
||||
|
||||
const nextQuery: Query = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
groupBy: [...item.groupBy, existAutocompleteKey],
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
redirectWithQueryBuilderData(nextQuery);
|
||||
} catch {
|
||||
notifications.error({ message: SOMETHING_WENT_WRONG });
|
||||
}
|
||||
},
|
||||
[currentQuery, notifications, queryClient, redirectWithQueryBuilderData],
|
||||
[currentQuery, redirectWithQueryBuilderData],
|
||||
);
|
||||
|
||||
return {
|
||||
activeLog,
|
||||
onSetActiveLog,
|
||||
onClearActiveLog,
|
||||
onAddToQuery: onAddToQueryExplorer,
|
||||
onGroupByAttribute,
|
||||
onAddToQuery,
|
||||
};
|
||||
};
|
||||
|
||||
68
frontend/src/hooks/notificationChannels/useChannelOptions.ts
Normal file
68
frontend/src/hooks/notificationChannels/useChannelOptions.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import { listNotificationChannels } from 'api/generated/services/channels';
|
||||
import {
|
||||
AlertmanagertypesChannelListOrderDTO,
|
||||
AlertmanagertypesChannelListSortDTO,
|
||||
AlertmanagertypesListedNotificationChannelDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { range } from 'lodash-es';
|
||||
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
export const CHANNEL_OPTIONS_QUERY_KEY = ['notificationChannelOptions'];
|
||||
|
||||
export interface ChannelOption {
|
||||
id: string;
|
||||
/** The display name, which is what rules and routing policies reference. */
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type UseChannelOptionsResult = Omit<
|
||||
UseQueryResult<ChannelOption[], Error>,
|
||||
'data'
|
||||
> & {
|
||||
data: ChannelOption[];
|
||||
};
|
||||
|
||||
function fetchPage(
|
||||
offset: number,
|
||||
): ReturnType<typeof listNotificationChannels> {
|
||||
return listNotificationChannels({
|
||||
limit: PAGE_SIZE,
|
||||
offset,
|
||||
sort: AlertmanagertypesChannelListSortDTO.name,
|
||||
order: AlertmanagertypesChannelListOrderDTO.asc,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Every channel, for the pickers that let a rule or a policy name one. The list
|
||||
* API is paginated and silently clamps a larger limit, so the first page reports
|
||||
* the total and any remainder is fetched alongside rather than truncating. One
|
||||
* request covers an org with a page's worth of channels or fewer.
|
||||
*/
|
||||
async function fetchAllChannels(): Promise<ChannelOption[]> {
|
||||
const first = await fetchPage(0);
|
||||
const rest = await Promise.all(
|
||||
range(PAGE_SIZE, first.data.total, PAGE_SIZE).map(fetchPage),
|
||||
);
|
||||
|
||||
const channels: AlertmanagertypesListedNotificationChannelDTO[] = [
|
||||
...first.data.channels,
|
||||
...rest.flatMap((page) => page.data.channels),
|
||||
];
|
||||
|
||||
return channels.map((channel) => ({
|
||||
id: channel.id,
|
||||
name: channel.displayName,
|
||||
}));
|
||||
}
|
||||
|
||||
export function useChannelOptions(): UseChannelOptionsResult {
|
||||
const query = useQuery<ChannelOption[], Error>(
|
||||
CHANNEL_OPTIONS_QUERY_KEY,
|
||||
fetchAllChannels,
|
||||
);
|
||||
|
||||
return { ...query, data: query.data ?? [] };
|
||||
}
|
||||
@@ -1,11 +1,3 @@
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { useState } from 'react';
|
||||
|
||||
export function useSavedViewEnabled(): boolean {
|
||||
const [isEnabled] = useState(
|
||||
() => getLocalStorageKey(LOCALSTORAGE.SAVED_VIEW_ENABLED) === 'true',
|
||||
);
|
||||
|
||||
return isEnabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -82,10 +82,9 @@ export function applyAuthZ(
|
||||
items: AuthZDropdownItemType[],
|
||||
state: GateState,
|
||||
): DropdownItemType[] {
|
||||
return items.map(
|
||||
(item): DropdownItemType =>
|
||||
item.type === 'group'
|
||||
? { ...item, items: item.items.map((child) => gateRow(child, state)) }
|
||||
: gateRow(item, state),
|
||||
return items.map((item): DropdownItemType =>
|
||||
item.type === 'group'
|
||||
? { ...item, items: item.items.map((child) => gateRow(child, state)) }
|
||||
: gateRow(item, state),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { QueryOperatorsMultiVal } from 'lib/logql/tokens';
|
||||
|
||||
type Keys = keyof typeof QueryOperatorsMultiVal;
|
||||
type Values = (typeof QueryOperatorsMultiVal)[Keys];
|
||||
|
||||
interface GenerateFilterQueryParams {
|
||||
fieldKey: string;
|
||||
fieldValue: string;
|
||||
type: Values;
|
||||
}
|
||||
export const generateFilterQuery = ({
|
||||
fieldKey,
|
||||
fieldValue,
|
||||
type,
|
||||
}: GenerateFilterQueryParams): string => {
|
||||
let generatedQueryString = `${fieldKey} ${type} `;
|
||||
if (typeof fieldValue === 'number') {
|
||||
generatedQueryString += `(${fieldValue})`;
|
||||
} else {
|
||||
generatedQueryString += `('${fieldValue}')`;
|
||||
}
|
||||
|
||||
return generatedQueryString;
|
||||
};
|
||||
@@ -241,9 +241,9 @@ describe('Tooltip renderTooltipFooter', () => {
|
||||
});
|
||||
|
||||
it('renders content returned by renderTooltipFooter', () => {
|
||||
const renderTooltipFooter = jest.fn(
|
||||
(): JSX.Element => <div data-testid="custom-tooltip-footer">Footer</div>,
|
||||
);
|
||||
const renderTooltipFooter = jest.fn((): JSX.Element => (
|
||||
<div data-testid="custom-tooltip-footer">Footer</div>
|
||||
));
|
||||
|
||||
renderTooltip({ renderTooltipFooter });
|
||||
|
||||
|
||||
@@ -51,3 +51,38 @@ export const opsGeniePriorityDefaultValue =
|
||||
|
||||
export const pagerDutySeverityTextDefaultValue =
|
||||
'{{ (index .Alerts 0).Labels.severity }}';
|
||||
|
||||
export const notificationChannelsV2 = [
|
||||
{
|
||||
id: '3',
|
||||
name: 'dummy-channel',
|
||||
displayName: 'Dummy-Channel',
|
||||
kind: 'slack',
|
||||
createdAt: '2023-08-09T04:45:19.239344617Z',
|
||||
updatedAt: '2024-06-27T11:37:14.841184399Z',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'oncall-pagerduty',
|
||||
displayName: 'Oncall PagerDuty',
|
||||
kind: 'pagerduty',
|
||||
createdAt: '2024-02-03T04:45:19.239344617Z',
|
||||
updatedAt: '2024-06-28T11:37:14.841184399Z',
|
||||
},
|
||||
];
|
||||
|
||||
export const notificationChannelV2 = {
|
||||
id: '3',
|
||||
name: 'dummy-channel',
|
||||
displayName: 'Dummy-Channel',
|
||||
createdAt: '2023-08-09T04:45:19.239344617Z',
|
||||
updatedAt: '2024-06-27T11:37:14.841184399Z',
|
||||
config: {
|
||||
kind: 'slack',
|
||||
spec: {
|
||||
apiUrl: 'https://hooks.slack.com/services/dummy',
|
||||
channel: '#dummy_channel',
|
||||
sendResolved: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,7 +2,11 @@ import { rest } from 'msw';
|
||||
|
||||
import commonEnTranslation from '../../public/locales/en/common.json';
|
||||
import enTranslation from '../../public/locales/en/translation.json';
|
||||
import { allAlertChannels } from './__mockdata__/alerts';
|
||||
import {
|
||||
allAlertChannels,
|
||||
notificationChannelsV2,
|
||||
notificationChannelV2,
|
||||
} from './__mockdata__/alerts';
|
||||
import { alertRulesFixture } from './__mockdata__/alert_rules';
|
||||
import { triggeredAlertsFixture } from './__mockdata__/triggered_alerts';
|
||||
import { billingSuccessResponse } from './__mockdata__/billing';
|
||||
@@ -205,6 +209,32 @@ export const handlers = [
|
||||
rest.get('http://localhost/api/v1/channels', (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ data: allAlertChannels, status: 'success' })),
|
||||
),
|
||||
rest.get('http://localhost/api/v2/notification_channels', (req, res, ctx) => {
|
||||
const query = req.url.searchParams.get('query')?.toLowerCase() ?? '';
|
||||
const kind = req.url.searchParams.get('kind');
|
||||
const channels = notificationChannelsV2.filter(
|
||||
(channel) =>
|
||||
(!query || channel.displayName.toLowerCase().includes(query)) &&
|
||||
(!kind || channel.kind === kind),
|
||||
);
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: { channels, total: channels.length },
|
||||
status: 'success',
|
||||
}),
|
||||
);
|
||||
}),
|
||||
rest.get('http://localhost/api/v2/notification_channels/:id', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({ data: notificationChannelV2, status: 'success' }),
|
||||
),
|
||||
),
|
||||
rest.delete(
|
||||
'http://localhost/api/v2/notification_channels/:id',
|
||||
(_, res, ctx) => res(ctx.status(204)),
|
||||
),
|
||||
rest.get('http://localhost/api/v1/alerts', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
import {
|
||||
alertRuleByIdResponse,
|
||||
ALERT_SCHEMAS,
|
||||
channelsResponse,
|
||||
notificationChannelsResponse,
|
||||
CHANNEL_MAX,
|
||||
FIRST_RULE_NAME,
|
||||
type AlertSchema,
|
||||
@@ -148,8 +148,8 @@ export const alertHistoryMocks = defineStoryMocks({
|
||||
),
|
||||
|
||||
rest.get(
|
||||
'http://localhost/api/v1/channels',
|
||||
response.json(() => channelsResponse(CHANNEL_MAX)),
|
||||
'http://localhost/api/v2/notification_channels',
|
||||
response.json(() => notificationChannelsResponse(CHANNEL_MAX)),
|
||||
),
|
||||
],
|
||||
config: () => ({
|
||||
|
||||
@@ -12,7 +12,7 @@ import { defineStoryMocks } from '@/storybook/controls/defineStoryMocks';
|
||||
import {
|
||||
alertRuleByIdResponse,
|
||||
ALERT_SCHEMAS,
|
||||
channelsResponse,
|
||||
notificationChannelsResponse,
|
||||
CHANNEL_MAX,
|
||||
RULE_STATE_CHOICES,
|
||||
SEVERITY_CHOICES,
|
||||
@@ -116,8 +116,8 @@ export const alertOverviewMocks = defineStoryMocks({
|
||||
),
|
||||
|
||||
rest.get(
|
||||
'http://localhost/api/v1/channels',
|
||||
response.json(() => channelsResponse(values.channels)),
|
||||
'http://localhost/api/v2/notification_channels',
|
||||
response.json(() => notificationChannelsResponse(values.channels)),
|
||||
),
|
||||
|
||||
rest.post(
|
||||
|
||||
@@ -10,7 +10,8 @@ import { defineStoryMocks } from '@/storybook/controls/defineStoryMocks';
|
||||
|
||||
import {
|
||||
CHANNEL_MAX,
|
||||
channelsResponse,
|
||||
notificationChannelByRowId,
|
||||
notificationChannelsPage,
|
||||
} from '../../stories/__story_mockdata__/alerts';
|
||||
import { AlertListTabs } from '../../types';
|
||||
|
||||
@@ -27,8 +28,15 @@ export const channelsMocks = defineStoryMocks({
|
||||
},
|
||||
handlers: (values, response) => [
|
||||
rest.get(
|
||||
'http://localhost/api/v1/channels',
|
||||
response.json(() => channelsResponse(values.channels)),
|
||||
'http://localhost/api/v2/notification_channels/:id',
|
||||
response.json((req) => notificationChannelByRowId(String(req.params.id))),
|
||||
),
|
||||
|
||||
rest.get(
|
||||
'http://localhost/api/v2/notification_channels',
|
||||
response.json((req) =>
|
||||
notificationChannelsPage(values.channels, req.url.searchParams),
|
||||
),
|
||||
),
|
||||
],
|
||||
config: () => ({ route: `/alerts?tab=${AlertListTabs.CHANNELS}` }),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { expect, screen, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { storyMocks } from '@/storybook/controls/defineStoryMocks';
|
||||
import type { PageStoryArgs } from '@/storybook/runtime/resolveStory';
|
||||
@@ -13,12 +14,14 @@ const pageStory = storyMocks(channelsMocks, { layout: 'app' });
|
||||
|
||||
/**
|
||||
* Notification channels tab: what a rule can notify, one row per channel.
|
||||
* Search, the type filter and paging all run against the API, so each one is
|
||||
* a request rather than a filter over rows already on screen.
|
||||
*
|
||||
* Route: `/alerts?tab=Channels`.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'Pages/Alerts/Channels/List',
|
||||
tags: ['role-gated'],
|
||||
tags: ['play'],
|
||||
component: AlertList,
|
||||
...pageStory,
|
||||
parameters: { ...pageStory.parameters },
|
||||
@@ -28,9 +31,27 @@ export default meta;
|
||||
|
||||
type Story = StoryObj<ChannelsArgs>;
|
||||
|
||||
/** The list settles after its own request, so the first row outlives the default wait. */
|
||||
const UNTIL_LOADED = { timeout: 15_000 };
|
||||
|
||||
/**
|
||||
* A row's permission check only starts once its menu is open, so the items are
|
||||
* disabled for a moment and a click into that gap lands on nothing.
|
||||
*/
|
||||
const clickRowAction = async (name: string): Promise<void> => {
|
||||
const item = await screen.findByRole('menuitem', { name }, UNTIL_LOADED);
|
||||
|
||||
await waitFor(() => {
|
||||
void expect(item).not.toHaveAttribute('aria-disabled', 'true');
|
||||
void expect(item.closest('[data-disabled]')).toBeNull();
|
||||
}, UNTIL_LOADED);
|
||||
|
||||
await userEvent.click(item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Where notifications go: every configured channel with the integration it
|
||||
* sends through.
|
||||
* sends through, when it was made and when it last changed.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
@@ -52,10 +73,122 @@ export const LoadError: Story = {
|
||||
parameters: { allowConsoleErrors: true },
|
||||
};
|
||||
|
||||
/**
|
||||
* A viewer: the Action column and the New Alert Channel button are gone, and
|
||||
* the button explains who to ask.
|
||||
*/
|
||||
export const Viewer: Story = {
|
||||
args: { access: 'viewer' },
|
||||
/** Interaction: typing a name narrows the list to the channel that matches. */
|
||||
export const Search: Story = {
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('sre-email', undefined, UNTIL_LOADED);
|
||||
await userEvent.type(canvas.getByTestId('channels-search'), 'slack');
|
||||
|
||||
// the table holds the previous page while the debounced request is in
|
||||
// flight, so the row going away is what says the search landed
|
||||
await waitFor(
|
||||
() => expect(canvas.queryByText('sre-email')).not.toBeInTheDocument(),
|
||||
UNTIL_LOADED,
|
||||
);
|
||||
await canvas.findByText('ops-slack');
|
||||
},
|
||||
};
|
||||
|
||||
/** Interaction: the type filter keeps only the channels of the kind it names. */
|
||||
export const KindFilter: Story = {
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('ops-slack', undefined, UNTIL_LOADED);
|
||||
await userEvent.click(canvas.getByTestId('channels-kind-filter'));
|
||||
await userEvent.click(await screen.findByRole('option', { name: 'Email' }));
|
||||
|
||||
await waitFor(
|
||||
() => expect(canvas.queryByText('ops-slack')).not.toBeInTheDocument(),
|
||||
UNTIL_LOADED,
|
||||
);
|
||||
await canvas.findByText('sre-email');
|
||||
},
|
||||
};
|
||||
|
||||
/** Interaction: a search matching nothing offers to clear itself. */
|
||||
export const NoResults: Story = {
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('ops-slack', undefined, UNTIL_LOADED);
|
||||
await userEvent.type(
|
||||
canvas.getByTestId('channels-search'),
|
||||
'no-such-channel',
|
||||
);
|
||||
|
||||
await canvas.findByText('No matching channels', undefined, UNTIL_LOADED);
|
||||
await userEvent.click(canvas.getByRole('button', { name: 'Clear Search' }));
|
||||
await canvas.findByText('ops-slack', undefined, UNTIL_LOADED);
|
||||
},
|
||||
};
|
||||
|
||||
/** Interaction: a row's actions menu, with open, JSON and delete on it. */
|
||||
export const RowActions: Story = {
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('ops-slack', undefined, UNTIL_LOADED);
|
||||
await userEvent.click(
|
||||
canvas.getByRole('button', { name: 'Actions for ops-slack' }),
|
||||
);
|
||||
|
||||
await screen.findByRole('menu');
|
||||
await screen.findByRole('menuitem', { name: 'View JSON' }, UNTIL_LOADED);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Interaction: the JSON view of a channel, with its secrets masked until the
|
||||
* toolbar is asked to reveal them.
|
||||
*/
|
||||
export const ChannelJson: Story = {
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('ops-slack', undefined, UNTIL_LOADED);
|
||||
await userEvent.click(
|
||||
canvas.getByRole('button', { name: 'Actions for ops-slack' }),
|
||||
);
|
||||
await clickRowAction('View JSON');
|
||||
|
||||
await screen.findByTestId('channel-json-dialog', undefined, UNTIL_LOADED);
|
||||
await screen.findByTestId('channel-json-toggle-secrets');
|
||||
},
|
||||
};
|
||||
|
||||
/** Interaction: deleting a channel asks first, naming what stops reaching it. */
|
||||
export const DeleteConfirm: Story = {
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('ops-slack', undefined, UNTIL_LOADED);
|
||||
await userEvent.click(
|
||||
canvas.getByRole('button', { name: 'Actions for ops-slack' }),
|
||||
);
|
||||
await clickRowAction('Delete');
|
||||
|
||||
// the name sits in its own element, so the sentence spans several nodes
|
||||
const confirmation = await screen.findByRole('dialog');
|
||||
await expect(confirmation.textContent).toContain(
|
||||
'Are you sure you want to delete the ops-slack channel?',
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
/** Interaction: Copy Link confirms it put the channel's URL on the clipboard. */
|
||||
export const CopyLink: Story = {
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('ops-slack', undefined, UNTIL_LOADED);
|
||||
await userEvent.click(
|
||||
canvas.getByRole('button', { name: 'Actions for ops-slack' }),
|
||||
);
|
||||
await clickRowAction('Copy Link');
|
||||
|
||||
await screen.findByText('Link copied to clipboard', undefined, UNTIL_LOADED);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 62px);
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.headerBand {
|
||||
--control-height: 2rem;
|
||||
--combobox-trigger-height: var(--control-height);
|
||||
--select-trigger-height: var(--control-height);
|
||||
--input-height: var(--control-height);
|
||||
--input-wrapper-height: var(--control-height);
|
||||
--button-height: var(--control-height);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-6);
|
||||
flex-shrink: 0;
|
||||
padding: var(--spacing-6) var(--spacing-8);
|
||||
}
|
||||
|
||||
/* Title and count on the left, the create action at the far end, the way the
|
||||
dashboards list opens. */
|
||||
.commandHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.headingBlock {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
color: var(--l1-foreground);
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 28px;
|
||||
letter-spacing: -0.09px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.countPill {
|
||||
padding: 2px 9px;
|
||||
border-radius: 999px;
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
color: var(--l2-foreground);
|
||||
font-size: var(--font-size-xs);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Search and the type filter, the band's second row. */
|
||||
.filtersRow {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Spans the row, the way the dashboards filter bar does. The input's own
|
||||
wrapper is `width: 100%`, so the basis has to be 0 or `auto` resolves to the
|
||||
full row and wraps the type filter onto the next line. */
|
||||
.searchInput {
|
||||
flex: 1 1 0;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.kindSelect {
|
||||
/* the trigger sizes itself to 100%, so the flex item has to be bounded */
|
||||
flex: 0 0 180px;
|
||||
width: 180px;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
/* The options panel is rendered inline (withPortal={false}) so it can be told to
|
||||
match the trigger; Radix publishes that width at the popper layer. */
|
||||
.filtersRow :global([data-slot='select-content']) {
|
||||
width: var(--radix-select-trigger-width);
|
||||
min-width: var(--radix-select-trigger-width);
|
||||
}
|
||||
|
||||
.commandHeader > button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.searchIcon {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.actionsHeader {
|
||||
display: block;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Left on the table's own defaults, the way Infrastructure Monitoring uses it:
|
||||
transparent cells over the page background rather than a raised slab. */
|
||||
.tableContainer {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
--tanstack-table-row-hover-bg: var(--l2-background-hover);
|
||||
--tanstack-table-row-active-bg: var(--l2-background-active);
|
||||
--tanstack-table-row-height: 42px;
|
||||
|
||||
--tanstack-cell-padding-top-override: 5px;
|
||||
--tanstack-cell-padding-bottom-override: 5px;
|
||||
--tanstack-cell-padding-left-override: 16px;
|
||||
--tanstack-cell-padding-right-override: 16px;
|
||||
|
||||
--tanstack-table-row-odd-bg: color-mix(
|
||||
in srgb,
|
||||
var(--l1-foreground) 2%,
|
||||
transparent
|
||||
);
|
||||
--tanstack-table-row-even-bg: color-mix(
|
||||
in srgb,
|
||||
var(--l1-foreground) 1%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.paginationContainer {
|
||||
padding-right: var(--spacing-12);
|
||||
height: 62px;
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Plus, Search } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { SelectSimple } from '@signozhq/ui/select';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ErrorEmptyState from 'components/Alerts/ErrorEmptyState';
|
||||
import NoResultsEmptyState from 'components/Alerts/NoResultsEmptyState';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { useCalculatedPageSize } from 'components/TanStackTableView/useCalculatedPageSize';
|
||||
import { useTableParams } from 'components/TanStackTableView/useTableParams';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { useUrlSearchState } from 'hooks/useUrlSearchState';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
|
||||
import ChannelActionsMenu from './components/ChannelActionsMenu/ChannelActionsMenu';
|
||||
import ChannelJsonDialog from './components/ChannelJsonDialog/ChannelJsonDialog';
|
||||
import {
|
||||
KIND_FILTER_ALL,
|
||||
KIND_FILTER_OPTIONS,
|
||||
KIND_KEY,
|
||||
SEARCH_KEY,
|
||||
} from './constants';
|
||||
import { useChannelDelete } from './hooks/useChannelDelete';
|
||||
import { useChannelFormView } from './hooks/useChannelFormView';
|
||||
import { useChannelsData } from './hooks/useChannelsData';
|
||||
import styles from './NotificationChannels.module.scss';
|
||||
import { getChannelColumns } from './table.config';
|
||||
import type { Channel } from './types';
|
||||
|
||||
const QUERY_PARAMS_CONFIG = {
|
||||
orderBy: 'orderBy',
|
||||
page: 'page',
|
||||
limit: 'limit',
|
||||
} as const;
|
||||
|
||||
const DEFAULT_PAGE = 1;
|
||||
const DEFAULT_LIMIT = 10;
|
||||
|
||||
function NotificationChannels(): JSX.Element {
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
const formView = useChannelFormView();
|
||||
|
||||
const { containerRef, calculatedPageSize } = useCalculatedPageSize({
|
||||
rowHeight: 46,
|
||||
});
|
||||
|
||||
const { orderBy, page, limit, setLimit, setPage } = useTableParams(
|
||||
QUERY_PARAMS_CONFIG,
|
||||
{
|
||||
page: DEFAULT_PAGE,
|
||||
limit: DEFAULT_LIMIT,
|
||||
storageKey: 'notification-channels',
|
||||
calculatedPageSize,
|
||||
cleanupOnUnmount: true,
|
||||
},
|
||||
);
|
||||
|
||||
const resetPageOnSearch = useCallback((): void => {
|
||||
setPage(1);
|
||||
}, [setPage]);
|
||||
|
||||
const { searchText, debouncedSearch, handleSearchChange, clearSearch } =
|
||||
useUrlSearchState(SEARCH_KEY, { onDebouncedChange: resetPageOnSearch });
|
||||
|
||||
const [kind, setKind] = useQueryState(
|
||||
KIND_KEY,
|
||||
parseAsString.withDefault(KIND_FILTER_ALL),
|
||||
);
|
||||
|
||||
const handleKindChange = useCallback(
|
||||
(value: string | string[]): void => {
|
||||
const next = value as string;
|
||||
void setKind(next === KIND_FILTER_ALL ? null : next);
|
||||
setPage(1);
|
||||
},
|
||||
[setKind, setPage],
|
||||
);
|
||||
|
||||
const { channels, total, isFetching, isError, refetch } = useChannelsData({
|
||||
search: debouncedSearch,
|
||||
kind,
|
||||
orderBy,
|
||||
page,
|
||||
limit,
|
||||
});
|
||||
|
||||
const deletion = useChannelDelete();
|
||||
const [jsonChannel, setJsonChannel] = useState<Channel | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
void logEvent('Alert Channel: Channel list page visited', {});
|
||||
}, []);
|
||||
|
||||
const handleEdit = useCallback(
|
||||
(channel: Channel, options?: { newTab?: boolean }): void => {
|
||||
formView.openEdit(channel.id, options);
|
||||
},
|
||||
[formView],
|
||||
);
|
||||
|
||||
const handleRowClick = useCallback(
|
||||
(channel: Channel): void => formView.openEdit(channel.id),
|
||||
[formView],
|
||||
);
|
||||
|
||||
const handleRowClickNewTab = useCallback(
|
||||
(channel: Channel): void => formView.openEdit(channel.id, { newTab: true }),
|
||||
[formView],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => getChannelColumns(formatTimezoneAdjustedTimestamp),
|
||||
[formatTimezoneAdjustedTimestamp],
|
||||
);
|
||||
|
||||
const columnsWithActions = useMemo(
|
||||
() => [
|
||||
...columns,
|
||||
{
|
||||
id: 'actions',
|
||||
header: (): JSX.Element => (
|
||||
<span className={styles.actionsHeader}>Actions</span>
|
||||
),
|
||||
accessorKey: 'id',
|
||||
width: { fixed: '96px', ignoreLastColumnFill: true },
|
||||
enableSort: false,
|
||||
enableResize: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
pin: 'right' as const,
|
||||
cell: ({ row }: { row: Channel }): JSX.Element => (
|
||||
<ChannelActionsMenu
|
||||
channel={row}
|
||||
onOpen={handleEdit}
|
||||
onDelete={deletion.request}
|
||||
onViewJson={setJsonChannel}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
[columns, handleEdit, deletion.request],
|
||||
);
|
||||
|
||||
const hasActiveFilters = searchText.length > 0 || kind !== KIND_FILTER_ALL;
|
||||
const isEmpty = !isFetching && channels.length === 0;
|
||||
|
||||
const handleClearFilters = useCallback((): void => {
|
||||
clearSearch();
|
||||
void setKind(null);
|
||||
}, [clearSearch, setKind]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<header className={styles.headerBand}>
|
||||
<div className={styles.commandHeader}>
|
||||
<div className={styles.headingBlock}>
|
||||
<Typography.Title className={styles.title}>
|
||||
All notification channels
|
||||
</Typography.Title>
|
||||
<span className={styles.countPill}>{total}</span>
|
||||
</div>
|
||||
<div className={styles.grow} />
|
||||
<TextToolTip
|
||||
text="More details on how to configure notification channels"
|
||||
url="https://signoz.io/docs/setup-alerts-notification/"
|
||||
urlText="Learn More"
|
||||
/>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="md"
|
||||
prefix={<Plus size={14} />}
|
||||
onClick={formView.openCreate}
|
||||
testId="channels-create"
|
||||
>
|
||||
New channel
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={styles.filtersRow}>
|
||||
<Input
|
||||
containerClassName={styles.searchInput}
|
||||
placeholder="Search by channel name"
|
||||
value={searchText}
|
||||
onChange={handleSearchChange}
|
||||
suffix={<Search size={14} className={styles.searchIcon} />}
|
||||
testId="channels-search"
|
||||
/>
|
||||
<SelectSimple
|
||||
className={styles.kindSelect}
|
||||
items={KIND_FILTER_OPTIONS}
|
||||
value={kind}
|
||||
onChange={handleKindChange}
|
||||
withPortal={false}
|
||||
testId="channels-kind-filter"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div ref={containerRef} className={styles.tableContainer}>
|
||||
{isError ? (
|
||||
<ErrorEmptyState
|
||||
title="Failed to load notification channels"
|
||||
onRefresh={refetch}
|
||||
/>
|
||||
) : isEmpty && hasActiveFilters ? (
|
||||
<NoResultsEmptyState
|
||||
title="No matching channels"
|
||||
subtitle="No notification channels match your search. Try adjusting your search criteria."
|
||||
onClear={handleClearFilters}
|
||||
clearButtonText="Clear Search"
|
||||
/>
|
||||
) : (
|
||||
<TanStackTable<Channel>
|
||||
data={channels}
|
||||
columns={columnsWithActions}
|
||||
isLoading={isFetching}
|
||||
getRowKey={(row): string => row.id}
|
||||
getItemKey={(row): string => row.id}
|
||||
columnStorageKey="notification-channels-columns"
|
||||
enableQueryParams={QUERY_PARAMS_CONFIG}
|
||||
onRowClick={handleRowClick}
|
||||
onRowClickNewTab={handleRowClickNewTab}
|
||||
pagination={{
|
||||
total,
|
||||
calculatedPageSize,
|
||||
onLimitChange: setLimit,
|
||||
showTotalCount: true,
|
||||
}}
|
||||
paginationClassname={styles.paginationContainer}
|
||||
enableAlternatingRowColors
|
||||
plainTextCellLineClamp={1}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{jsonChannel && (
|
||||
<ChannelJsonDialog
|
||||
channelId={jsonChannel.id}
|
||||
channelName={jsonChannel.displayName}
|
||||
onClose={(): void => setJsonChannel(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{deletion.contextHolder}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotificationChannels;
|
||||
@@ -0,0 +1,98 @@
|
||||
import { server } from 'mocks-server/server';
|
||||
import { screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import { renderChannels } from './renderChannels';
|
||||
|
||||
const FIND = { timeout: 5000 };
|
||||
const TEST_TIMEOUT = 20000;
|
||||
|
||||
describe('Notification channels list', () => {
|
||||
beforeEach(() => {
|
||||
// search, filter and page live in the url, so one test's filter would
|
||||
// otherwise still be applied in the next
|
||||
window.history.replaceState({}, '', '/');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
it(
|
||||
'heads the page with the channel count',
|
||||
async () => {
|
||||
renderChannels();
|
||||
|
||||
await expect(
|
||||
screen.findByText('All notification channels', {}, FIND),
|
||||
).resolves.toBeInTheDocument();
|
||||
// the count comes from the API's total, not the loaded page
|
||||
await expect(screen.findByText('2', {}, FIND)).resolves.toBeInTheDocument();
|
||||
},
|
||||
TEST_TIMEOUT,
|
||||
);
|
||||
|
||||
it(
|
||||
'lists every channel with its type',
|
||||
async () => {
|
||||
renderChannels();
|
||||
|
||||
await expect(
|
||||
screen.findByText('Dummy-Channel', {}, FIND),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(screen.getByText('Oncall PagerDuty')).toBeInTheDocument();
|
||||
// the type reads as plain text, no colour coding
|
||||
expect(screen.getByText('Slack')).toBeInTheDocument();
|
||||
expect(screen.getByText('PagerDuty')).toBeInTheDocument();
|
||||
},
|
||||
TEST_TIMEOUT,
|
||||
);
|
||||
|
||||
it(
|
||||
'filters server-side as the user types',
|
||||
async () => {
|
||||
renderChannels();
|
||||
|
||||
await screen.findByText('Dummy-Channel', {}, FIND);
|
||||
|
||||
await userEvent.type(screen.getByTestId('channels-search'), 'oncall');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Dummy-Channel')).not.toBeInTheDocument();
|
||||
}, FIND);
|
||||
await expect(
|
||||
screen.findByText('Oncall PagerDuty', {}, FIND),
|
||||
).resolves.toBeInTheDocument();
|
||||
},
|
||||
TEST_TIMEOUT,
|
||||
);
|
||||
|
||||
it(
|
||||
'asks for confirmation before deleting, from the row menu',
|
||||
async () => {
|
||||
renderChannels();
|
||||
|
||||
await screen.findByText('Dummy-Channel', {}, FIND);
|
||||
// the table paints a loading layer over the rows until the fetch settles
|
||||
const user = userEvent.setup({ delay: null, pointerEventsCheck: 0 });
|
||||
await user.click(screen.getByTestId('channel-actions-3'));
|
||||
// Radix renders the menu into a portal once the trigger is activated
|
||||
await screen.findByRole('menu', {}, FIND);
|
||||
// the menu lives inside the row's clickable cell, so opening it must not
|
||||
// also fire the row click and route to the edit screen
|
||||
expect(window.location.pathname).not.toContain('/channels/edit/');
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('menuitem', { name: 'Delete' }, FIND),
|
||||
);
|
||||
|
||||
// the shared destructive confirmation, the one dashboards also use. The
|
||||
// name sits in its own element, so the sentence spans several nodes.
|
||||
const confirmation = await screen.findByRole('dialog', {}, FIND);
|
||||
expect(confirmation.textContent).toContain(
|
||||
'Are you sure you want to delete the Dummy-Channel channel?',
|
||||
);
|
||||
},
|
||||
TEST_TIMEOUT,
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { AlertmanagertypesGettableNotificationChannelDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import {
|
||||
channelToJson,
|
||||
hasSecrets,
|
||||
SECRET_PLACEHOLDER,
|
||||
} from '../utils/channelJson';
|
||||
|
||||
const slackChannel = {
|
||||
id: '1',
|
||||
name: 'prod-alerts',
|
||||
displayName: 'prod alerts',
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
config: {
|
||||
kind: 'slack',
|
||||
spec: {
|
||||
apiUrl: 'https://hooks.slack.com/services/T/B/SECRET',
|
||||
channel: '#alerts',
|
||||
sendResolved: true,
|
||||
},
|
||||
},
|
||||
} as AlertmanagertypesGettableNotificationChannelDTO;
|
||||
|
||||
describe('channelToJson', () => {
|
||||
it('hides the credential the API returns in the clear', () => {
|
||||
const json = JSON.parse(channelToJson(slackChannel, false));
|
||||
|
||||
expect(json.config.spec.apiUrl).toBe(SECRET_PLACEHOLDER);
|
||||
expect(json.config.spec.channel).toBe('#alerts');
|
||||
expect(channelToJson(slackChannel, false)).not.toContain('SECRET');
|
||||
});
|
||||
|
||||
it('reveals it only when asked', () => {
|
||||
const json = JSON.parse(channelToJson(slackChannel, true));
|
||||
|
||||
expect(json.config.spec.apiUrl).toBe(
|
||||
'https://hooks.slack.com/services/T/B/SECRET',
|
||||
);
|
||||
});
|
||||
|
||||
it('leaves the timestamps and id out, so the JSON can be reused', () => {
|
||||
const json = JSON.parse(channelToJson(slackChannel, true));
|
||||
|
||||
expect(json).not.toHaveProperty('id');
|
||||
expect(json).not.toHaveProperty('createdAt');
|
||||
expect(json.name).toBe('prod-alerts');
|
||||
});
|
||||
|
||||
it('knows which kinds carry a credential', () => {
|
||||
expect(hasSecrets('slack')).toBe(true);
|
||||
expect(hasSecrets('email')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||
import { render } from 'tests/test-utils';
|
||||
|
||||
import NotificationChannels from '../NotificationChannels';
|
||||
|
||||
/** The list is virtualized, so jsdom needs a viewport for rows to mount. */
|
||||
export function renderChannels(): ReturnType<typeof render> {
|
||||
return render(
|
||||
<VirtuosoMockContext.Provider value={{ viewportHeight: 800, itemHeight: 46 }}>
|
||||
<NotificationChannels />
|
||||
</VirtuosoMockContext.Provider>,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { useMemo } from 'react';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import {
|
||||
Braces,
|
||||
Ellipsis,
|
||||
Expand,
|
||||
Link2,
|
||||
SquareArrowOutUpRight,
|
||||
Trash2,
|
||||
} from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Dropdown, type DropdownItemType } from '@signozhq/ui/dropdown';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
import type { Channel } from '../../types';
|
||||
import styles from './ChannelActionsMenu.module.scss';
|
||||
|
||||
interface ChannelActionsMenuProps {
|
||||
channel: Channel;
|
||||
onOpen: (channel: Channel, options?: { newTab?: boolean }) => void;
|
||||
onDelete: (channel: Channel) => void;
|
||||
onViewJson: (channel: Channel) => void;
|
||||
}
|
||||
|
||||
function ChannelActionsMenu({
|
||||
channel,
|
||||
onOpen,
|
||||
onDelete,
|
||||
onViewJson,
|
||||
}: ChannelActionsMenuProps): JSX.Element {
|
||||
const [, setCopy] = useCopyToClipboard();
|
||||
|
||||
const menuItems = useMemo<DropdownItemType[]>(
|
||||
() => [
|
||||
{
|
||||
type: 'item',
|
||||
value: 'open',
|
||||
label: 'View',
|
||||
prefix: <Expand size={14} />,
|
||||
onClick: (): void => onOpen(channel),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
value: 'open-new-tab',
|
||||
label: 'Open in New Tab',
|
||||
prefix: <SquareArrowOutUpRight size={14} />,
|
||||
onClick: (): void => onOpen(channel, { newTab: true }),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
value: 'copy-link',
|
||||
label: 'Copy Link',
|
||||
prefix: <Link2 size={14} />,
|
||||
onClick: (): void => {
|
||||
setCopy(
|
||||
getAbsoluteUrl(
|
||||
generatePath(ROUTES.CHANNELS_EDIT, { channelId: channel.id }),
|
||||
),
|
||||
);
|
||||
toast.success('Link copied to clipboard');
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
value: 'view-json',
|
||||
label: 'View JSON',
|
||||
prefix: <Braces size={14} />,
|
||||
onClick: (): void => onViewJson(channel),
|
||||
},
|
||||
{ type: 'separator', value: 'before-delete' },
|
||||
{
|
||||
type: 'item',
|
||||
value: 'delete',
|
||||
label: 'Delete',
|
||||
prefix: <Trash2 size={14} />,
|
||||
danger: true,
|
||||
onClick: (): void => onDelete(channel),
|
||||
},
|
||||
],
|
||||
[channel, onOpen, onDelete, onViewJson, setCopy],
|
||||
);
|
||||
|
||||
return (
|
||||
// the row itself opens the channel, so the actions keep their clicks
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className={styles.actions}
|
||||
onClick={(event): void => event.stopPropagation()}
|
||||
>
|
||||
<Dropdown items={menuItems} nativeButton align="end" side="bottom">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
icon
|
||||
testId={`channel-actions-${channel.id}`}
|
||||
aria-label={`Actions for ${channel.displayName}`}
|
||||
>
|
||||
<Ellipsis size={16} />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelActionsMenu;
|
||||
@@ -0,0 +1,26 @@
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-3);
|
||||
height: 100%;
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.editor {
|
||||
flex: 1 1 auto;
|
||||
min-height: 360px;
|
||||
border: 1px solid var(--l2-border);
|
||||
border-radius: var(--radius-2);
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import MEditor from '@monaco-editor/react';
|
||||
import { DrawerWrapper } from '@signozhq/ui/drawer';
|
||||
import { useGetNotificationChannel } from 'api/generated/services/channels';
|
||||
import ErrorEmptyState from 'components/Alerts/ErrorEmptyState';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { READONLY_EDITOR_OPTIONS } from 'container/RolesSettings/monaco.config';
|
||||
import {
|
||||
defineJsonEditorTheme,
|
||||
JSON_EDITOR_THEME,
|
||||
} from 'lib/monaco/jsonEditorTheme';
|
||||
|
||||
import { channelToJson, hasSecrets } from '../../utils/channelJson';
|
||||
import styles from './ChannelJsonDialog.module.scss';
|
||||
import ChannelJsonToolbar from './ChannelJsonToolbar';
|
||||
|
||||
interface ChannelJsonDialogProps {
|
||||
channelId: string;
|
||||
channelName: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function ChannelJsonDialog({
|
||||
channelId,
|
||||
channelName,
|
||||
onClose,
|
||||
}: ChannelJsonDialogProps): JSX.Element {
|
||||
const [copyState, copyToClipboard] = useCopyToClipboard();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showSecrets, setShowSecrets] = useState(false);
|
||||
|
||||
const { data, isLoading, refetch } = useGetNotificationChannel({
|
||||
id: channelId,
|
||||
});
|
||||
const channel = data?.data;
|
||||
|
||||
const json = useMemo(
|
||||
() => (channel ? channelToJson(channel, showSecrets) : ''),
|
||||
[channel, showSecrets],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (copyState.value) {
|
||||
setCopied(true);
|
||||
const timer = setTimeout(() => setCopied(false), 1500);
|
||||
return (): void => clearTimeout(timer);
|
||||
}
|
||||
return undefined;
|
||||
}, [copyState]);
|
||||
|
||||
const handleCopy = useCallback(
|
||||
(): void => copyToClipboard(json),
|
||||
[copyToClipboard, json],
|
||||
);
|
||||
|
||||
const handleRefetch = useCallback((): void => {
|
||||
void refetch();
|
||||
}, [refetch]);
|
||||
|
||||
const handleToggleSecrets = useCallback(
|
||||
(): void => setShowSecrets((value) => !value),
|
||||
[],
|
||||
);
|
||||
|
||||
const kindHasSecrets = channel ? hasSecrets(channel.config.kind) : false;
|
||||
|
||||
return (
|
||||
<DrawerWrapper
|
||||
open
|
||||
onOpenChange={(isOpen): void => {
|
||||
if (!isOpen) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
title={`${channelName} / JSON`}
|
||||
width="wide"
|
||||
testId="channel-json-dialog"
|
||||
>
|
||||
{isLoading ? (
|
||||
<Spinner tip="Loading channel..." />
|
||||
) : !channel ? (
|
||||
<ErrorEmptyState title="Failed to load channel" onRefresh={handleRefetch} />
|
||||
) : (
|
||||
<div className={styles.body}>
|
||||
<ChannelJsonToolbar
|
||||
kindHasSecrets={kindHasSecrets}
|
||||
showSecrets={showSecrets}
|
||||
onToggleSecrets={handleToggleSecrets}
|
||||
copied={copied}
|
||||
onCopy={handleCopy}
|
||||
/>
|
||||
|
||||
<div className={styles.editor}>
|
||||
<MEditor
|
||||
value={json}
|
||||
language="json"
|
||||
options={READONLY_EDITOR_OPTIONS}
|
||||
height="100%"
|
||||
theme="vs-dark"
|
||||
// the same theme the dashboard JSON drawer uses: it reads the
|
||||
// page's own tokens, so it follows light and dark
|
||||
onMount={(editor, monaco): void => {
|
||||
defineJsonEditorTheme(monaco, editor.getContainerDomNode());
|
||||
monaco.editor.setTheme(JSON_EDITOR_THEME);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DrawerWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelJsonDialog;
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Check, Copy, Eye, EyeOff } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import styles from './ChannelJsonDialog.module.scss';
|
||||
|
||||
interface ChannelJsonToolbarProps {
|
||||
kindHasSecrets: boolean;
|
||||
showSecrets: boolean;
|
||||
onToggleSecrets: () => void;
|
||||
copied: boolean;
|
||||
onCopy: () => void;
|
||||
}
|
||||
|
||||
function ChannelJsonToolbar({
|
||||
kindHasSecrets,
|
||||
showSecrets,
|
||||
onToggleSecrets,
|
||||
copied,
|
||||
onCopy,
|
||||
}: ChannelJsonToolbarProps): JSX.Element {
|
||||
return (
|
||||
<div className={styles.toolbar}>
|
||||
{kindHasSecrets && (
|
||||
<Typography.Text color="muted" size="sm">
|
||||
{showSecrets ? 'Credentials are visible.' : 'Credentials are hidden.'}
|
||||
</Typography.Text>
|
||||
)}
|
||||
<span className={styles.spacer} />
|
||||
{kindHasSecrets && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={showSecrets ? <EyeOff size={14} /> : <Eye size={14} />}
|
||||
onClick={onToggleSecrets}
|
||||
testId="channel-json-toggle-secrets"
|
||||
>
|
||||
{showSecrets ? 'Hide credentials' : 'Show credentials'}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={
|
||||
copied ? (
|
||||
<Check size={14} color={Color.BG_FOREST_400} />
|
||||
) : (
|
||||
<Copy size={14} />
|
||||
)
|
||||
}
|
||||
onClick={onCopy}
|
||||
testId="channel-json-copy"
|
||||
>
|
||||
{copied ? 'Copied' : 'Copy'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelJsonToolbar;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { AlertmanagertypesChannelKindDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
export const SEARCH_KEY = 'search';
|
||||
export const KIND_KEY = 'kind';
|
||||
export const PAGE_KEY = 'page';
|
||||
|
||||
export const PAGE_SIZE = 20;
|
||||
export const SEARCH_DEBOUNCE_MS = 300;
|
||||
|
||||
export const CHANNEL_KIND_LABEL: Record<
|
||||
AlertmanagertypesChannelKindDTO,
|
||||
string
|
||||
> = {
|
||||
[AlertmanagertypesChannelKindDTO.slack]: 'Slack',
|
||||
[AlertmanagertypesChannelKindDTO.email]: 'Email',
|
||||
[AlertmanagertypesChannelKindDTO.webhook]: 'Webhook',
|
||||
[AlertmanagertypesChannelKindDTO.pagerduty]: 'PagerDuty',
|
||||
[AlertmanagertypesChannelKindDTO.opsgenie]: 'Opsgenie',
|
||||
[AlertmanagertypesChannelKindDTO.msteams]: 'MS Teams',
|
||||
[AlertmanagertypesChannelKindDTO.googlechat]: 'Google Chat',
|
||||
[AlertmanagertypesChannelKindDTO.jira]: 'Jira',
|
||||
[AlertmanagertypesChannelKindDTO.jsmops]: 'JSM Ops',
|
||||
[AlertmanagertypesChannelKindDTO.incidentio]: 'incident.io',
|
||||
[AlertmanagertypesChannelKindDTO.telegram]: 'Telegram',
|
||||
};
|
||||
|
||||
export const KIND_FILTER_ALL = 'all';
|
||||
|
||||
export const KIND_FILTER_OPTIONS = [
|
||||
{ value: KIND_FILTER_ALL, label: 'All types' },
|
||||
...Object.entries(CHANNEL_KIND_LABEL).map(([value, label]) => ({
|
||||
value,
|
||||
label,
|
||||
})),
|
||||
];
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import {
|
||||
invalidateListNotificationChannels,
|
||||
useDeleteNotificationChannel,
|
||||
} from 'api/generated/services/channels';
|
||||
import { useDeleteConfirm } from 'components/DeleteConfirmModal/useDeleteConfirm';
|
||||
import styles from 'components/DeleteConfirmModal/DeleteConfirmModal.module.scss';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { ReactNode } from 'react';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import type { Channel } from '../types';
|
||||
|
||||
export interface ChannelDeleteState {
|
||||
/** Must be rendered by the caller for the confirmation to appear. */
|
||||
contextHolder: ReactNode;
|
||||
request: (channel: Channel) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deleting a channel goes through the shared destructive confirmation, the same
|
||||
* one dashboards and saved views use, so the wording and the danger button match.
|
||||
*/
|
||||
export function useChannelDelete(onDeleted?: () => void): ChannelDeleteState {
|
||||
const queryClient = useQueryClient();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
const { contextHolder, confirmDelete } = useDeleteConfirm();
|
||||
const { mutateAsync } = useDeleteNotificationChannel();
|
||||
|
||||
const request = (channel: Channel): void => {
|
||||
confirmDelete({
|
||||
title: (
|
||||
<Typography.Title level={5}>
|
||||
Are you sure you want to delete the
|
||||
<Typography.Text className={styles.name}>
|
||||
{' '}
|
||||
{channel.displayName}{' '}
|
||||
</Typography.Text>
|
||||
channel?
|
||||
</Typography.Title>
|
||||
),
|
||||
// Returning the promise keeps Delete loading until the request settles.
|
||||
onConfirm: async (): Promise<void> => {
|
||||
try {
|
||||
await mutateAsync({ pathParams: { id: channel.id } });
|
||||
await invalidateListNotificationChannels(queryClient);
|
||||
onDeleted?.();
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { contextHolder, request };
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
export interface ChannelFormView {
|
||||
openCreate: () => void;
|
||||
openEdit: (channelId: string, options?: { newTab?: boolean }) => void;
|
||||
}
|
||||
|
||||
/** Create and edit both render on their own route. */
|
||||
export function useChannelFormView(): ChannelFormView {
|
||||
const openEdit = (channelId: string, options?: { newTab?: boolean }): void => {
|
||||
const path = generatePath(ROUTES.CHANNELS_EDIT, { channelId });
|
||||
|
||||
if (options?.newTab) {
|
||||
openInNewTab(path);
|
||||
return;
|
||||
}
|
||||
|
||||
history.push(path);
|
||||
};
|
||||
|
||||
return {
|
||||
openCreate: (): void => history.push(ROUTES.CHANNELS_NEW),
|
||||
openEdit,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useListNotificationChannels } from 'api/generated/services/channels';
|
||||
import {
|
||||
AlertmanagertypesChannelKindDTO,
|
||||
AlertmanagertypesChannelListOrderDTO,
|
||||
AlertmanagertypesChannelListSortDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import type { SortState } from 'components/TanStackTableView';
|
||||
|
||||
import { KIND_FILTER_ALL } from '../constants';
|
||||
import type { Channel } from '../types';
|
||||
|
||||
/** Table column ids the list API can order by. */
|
||||
const SORT_BY_COLUMN: Record<string, AlertmanagertypesChannelListSortDTO> = {
|
||||
name: AlertmanagertypesChannelListSortDTO.name,
|
||||
createdAt: AlertmanagertypesChannelListSortDTO.created_at,
|
||||
updatedAt: AlertmanagertypesChannelListSortDTO.updated_at,
|
||||
};
|
||||
|
||||
interface UseChannelsDataArgs {
|
||||
search: string;
|
||||
kind: string;
|
||||
orderBy: SortState | null;
|
||||
page: number;
|
||||
limit: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ChannelsData {
|
||||
channels: Channel[];
|
||||
total: number;
|
||||
isFetching: boolean;
|
||||
isError: boolean;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search, kind filter, sort and paging all run on the API, so the table shows
|
||||
* one page at a time rather than filtering a full download in the browser.
|
||||
*/
|
||||
export function useChannelsData({
|
||||
search,
|
||||
kind,
|
||||
orderBy,
|
||||
page,
|
||||
limit,
|
||||
enabled = true,
|
||||
}: UseChannelsDataArgs): ChannelsData {
|
||||
const sort = orderBy ? SORT_BY_COLUMN[orderBy.columnName] : undefined;
|
||||
|
||||
const { data, isFetching, isError, refetch } = useListNotificationChannels(
|
||||
{
|
||||
limit,
|
||||
offset: (page - 1) * limit,
|
||||
...(sort
|
||||
? {
|
||||
sort,
|
||||
order: orderBy?.order as AlertmanagertypesChannelListOrderDTO,
|
||||
}
|
||||
: {}),
|
||||
...(search ? { query: search } : {}),
|
||||
...(kind !== KIND_FILTER_ALL
|
||||
? { kind: kind as AlertmanagertypesChannelKindDTO }
|
||||
: {}),
|
||||
},
|
||||
{ query: { enabled, keepPreviousData: true } },
|
||||
);
|
||||
|
||||
return {
|
||||
channels: data?.data?.channels ?? [],
|
||||
total: data?.data?.total ?? 0,
|
||||
isFetching,
|
||||
isError,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import type { TableColumnDef } from 'components/TanStackTableView';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
|
||||
import { CHANNEL_KIND_LABEL } from './constants';
|
||||
import styles from './table.module.scss';
|
||||
import type { Channel } from './types';
|
||||
import { toRelativeTime } from './utils/relativeTime';
|
||||
|
||||
export function getChannelColumns(
|
||||
formatTimezoneAdjustedTimestamp: (date: string, format: string) => string,
|
||||
): TableColumnDef<Channel>[] {
|
||||
const timestampCell = (row: Channel, value: string): JSX.Element => {
|
||||
if (!value) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
<TanStackTable.HoverTooltip
|
||||
rowId={row.id}
|
||||
title={formatTimezoneAdjustedTimestamp(
|
||||
value,
|
||||
DATE_TIME_FORMATS.DASH_DATETIME,
|
||||
)}
|
||||
>
|
||||
{/* A plain inline-block hugs the text. TanStackTable.Text is a
|
||||
`-webkit-box` for line clamping, which fills the cell and leaves
|
||||
the arrow pointing at the cell's middle instead of the words. */}
|
||||
<span className={styles.timeText}>{toRelativeTime(value)}</span>
|
||||
</TanStackTable.HoverTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'name',
|
||||
header: 'Channel',
|
||||
accessorKey: 'displayName',
|
||||
width: { default: '100%', min: '220px' },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
cell: ({ row }): JSX.Element => (
|
||||
<TanStackTable.Text
|
||||
title={row.displayName}
|
||||
data-testid={`channel-row-${row.id}-name`}
|
||||
>
|
||||
{row.displayName}
|
||||
</TanStackTable.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'kind',
|
||||
header: 'Type',
|
||||
accessorKey: 'kind',
|
||||
width: { default: '150px', min: '110px' },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
enableMove: false,
|
||||
cell: ({ row }): JSX.Element => (
|
||||
<TanStackTable.Text data-testid={`channel-row-${row.id}-kind`}>
|
||||
{CHANNEL_KIND_LABEL[row.kind]}
|
||||
</TanStackTable.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'createdAt',
|
||||
header: 'Created At',
|
||||
accessorKey: 'createdAt',
|
||||
width: { default: '150px', min: '120px' },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
enableMove: false,
|
||||
cell: ({ row }): JSX.Element => timestampCell(row, row.createdAt),
|
||||
},
|
||||
{
|
||||
id: 'updatedAt',
|
||||
header: 'Updated At',
|
||||
accessorKey: 'updatedAt',
|
||||
width: { default: '150px', min: '120px' },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
enableMove: false,
|
||||
cell: ({ row }): JSX.Element => timestampCell(row, row.updatedAt),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.timeText {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
font-size: var(--tanstack-plain-cell-font-size, 14px);
|
||||
line-height: var(--tanstack-plain-cell-line-height, 18px);
|
||||
color: inherit;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { AlertmanagertypesListedNotificationChannelDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
/** A channel as the list API returns it: identity and kind, but no config. */
|
||||
export type Channel = AlertmanagertypesListedNotificationChannelDTO;
|
||||
@@ -0,0 +1,50 @@
|
||||
import { AlertmanagertypesGettableNotificationChannelDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
export const SECRET_PLACEHOLDER = '••••••••';
|
||||
|
||||
/**
|
||||
* The spec fields the API marks `format: password`. A fetch by ID returns them
|
||||
* in the clear, so the JSON view hides them until asked.
|
||||
*/
|
||||
const SECRET_FIELDS_BY_KIND: Record<string, readonly string[]> = {
|
||||
slack: ['apiUrl'],
|
||||
webhook: ['url', 'password', 'bearerToken'],
|
||||
pagerduty: ['routingKey'],
|
||||
opsgenie: ['apiKey'],
|
||||
msteams: ['webhookUrl'],
|
||||
googlechat: ['webhookUrl'],
|
||||
jira: ['apiToken'],
|
||||
jsmops: ['apiKey'],
|
||||
incidentio: ['token'],
|
||||
};
|
||||
|
||||
export function channelToJson(
|
||||
channel: AlertmanagertypesGettableNotificationChannelDTO,
|
||||
showSecrets: boolean,
|
||||
): string {
|
||||
const { kind, spec } = channel.config;
|
||||
const secrets = SECRET_FIELDS_BY_KIND[kind] ?? [];
|
||||
|
||||
const safeSpec = showSecrets
|
||||
? spec
|
||||
: Object.fromEntries(
|
||||
Object.entries(spec).map(([key, value]) => [
|
||||
key,
|
||||
secrets.includes(key) && value ? SECRET_PLACEHOLDER : value,
|
||||
]),
|
||||
);
|
||||
|
||||
return JSON.stringify(
|
||||
{
|
||||
name: channel.name,
|
||||
displayName: channel.displayName,
|
||||
config: { kind, spec: safeSpec },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
export function hasSecrets(kind: string): boolean {
|
||||
return (SECRET_FIELDS_BY_KIND[kind] ?? []).length > 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTimePlugin from 'dayjs/plugin/relativeTime';
|
||||
|
||||
dayjs.extend(relativeTimePlugin);
|
||||
|
||||
/** "2 months ago". The exact timestamp stays available on hover. */
|
||||
export function toRelativeTime(value: string | undefined): string {
|
||||
const parsed = value ? dayjs(value) : null;
|
||||
return parsed?.isValid() ? parsed.fromNow() : '-';
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import {
|
||||
CHANNEL_MAX,
|
||||
channelNames,
|
||||
channelsResponse,
|
||||
notificationChannelsResponse,
|
||||
} from '../../stories/__story_mockdata__/alerts';
|
||||
import { AlertListSubTabs, AlertListTabs } from '../../types';
|
||||
|
||||
@@ -118,10 +118,10 @@ export const routingPoliciesMocks = defineStoryMocks({
|
||||
: res(ctx.status(200), ctx.json({ status: 'success', data: null })),
|
||||
),
|
||||
|
||||
rest.get('http://localhost/api/v1/channels', (_req, res, ctx) =>
|
||||
rest.get('http://localhost/api/v2/notification_channels', (_req, res, ctx) =>
|
||||
values.channelsState === 'error'
|
||||
? res(ctx.status(500), ctx.json({ status: 'error' }))
|
||||
: res(ctx.json(channelsResponse(values.channels))),
|
||||
: res(ctx.json(notificationChannelsResponse(values.channels))),
|
||||
),
|
||||
],
|
||||
config: () => ({
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Tabs, TabsProps } from 'antd';
|
||||
import ConfigureIcon from 'assets/AlertHistory/ConfigureIcon';
|
||||
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
|
||||
import ROUTES from 'constants/routes';
|
||||
import AllAlertChannels from 'container/AllAlertChannels';
|
||||
import AllAlertRules from 'container/ListAlertRules';
|
||||
import { PlannedDowntime } from 'container/PlannedDowntime/PlannedDowntime';
|
||||
import RoutingPolicies from 'container/RoutingPolicies';
|
||||
@@ -16,6 +15,8 @@ import AlertDetails from 'pages/AlertDetails';
|
||||
import ChannelsEdit from 'pages/ChannelsEdit';
|
||||
import ChannelsNew from 'pages/ChannelsNew';
|
||||
|
||||
import NotificationChannels from './NotificationChannels/NotificationChannels';
|
||||
|
||||
import { AlertListSubTabs, AlertListTabs } from './types';
|
||||
|
||||
import './AlertList.styles.scss';
|
||||
@@ -104,7 +105,7 @@ function AllAlertList(): JSX.Element {
|
||||
<div className="alert-rules-container">
|
||||
{isChannelsNew && <ChannelsNew />}
|
||||
{isChannelsEdit && <ChannelsEdit />}
|
||||
{!isChannelDetails && <AllAlertChannels />}
|
||||
{!isChannelDetails && <NotificationChannels />}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -26,6 +26,9 @@ import {
|
||||
type RuletypesAlertCompositeQueryDTO,
|
||||
type RuletypesRuleConditionDTO,
|
||||
type RuletypesRuleDTO,
|
||||
type AlertmanagertypesChannelConfigDTO,
|
||||
type AlertmanagertypesGettableNotificationChannelDTO,
|
||||
type AlertmanagertypesListedNotificationChannelDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { NEW_ALERT_SCHEMA_VERSION } from 'types/api/alerts/alertTypesV2';
|
||||
import type { Channels } from 'types/api/channels/getAll';
|
||||
@@ -704,6 +707,22 @@ const buildChannel = (index: number): Channels => {
|
||||
};
|
||||
};
|
||||
|
||||
const buildListedChannel = (
|
||||
index: number,
|
||||
): AlertmanagertypesListedNotificationChannelDTO => {
|
||||
const seed = CHANNEL_SEEDS[index % CHANNEL_SEEDS.length];
|
||||
|
||||
return {
|
||||
id: String(index + 1),
|
||||
name: seed.name,
|
||||
displayName: seed.name,
|
||||
kind: seed.type as AlertmanagertypesListedNotificationChannelDTO['kind'],
|
||||
createdAt: ago((index + 10) * DAY),
|
||||
updatedAt: ago((index + 1) * DAY),
|
||||
};
|
||||
};
|
||||
|
||||
/** The v2 list, which is what the channel pickers read. */
|
||||
export const channelsResponse = (
|
||||
count: number,
|
||||
): { status: string; data: Channels[] } => ({
|
||||
@@ -711,10 +730,145 @@ export const channelsResponse = (
|
||||
data: Array.from({ length: count }, (_unused, index) => buildChannel(index)),
|
||||
});
|
||||
|
||||
/** The v2 list, which is what the redesigned channels list reads. */
|
||||
export const notificationChannelsResponse = (
|
||||
count: number,
|
||||
): {
|
||||
status: string;
|
||||
data: {
|
||||
channels: AlertmanagertypesListedNotificationChannelDTO[];
|
||||
total: number;
|
||||
};
|
||||
} => {
|
||||
const channels = Array.from({ length: count }, (_unused, index) =>
|
||||
buildListedChannel(index),
|
||||
);
|
||||
|
||||
return { status: 'success', data: { channels, total: channels.length } };
|
||||
};
|
||||
|
||||
/**
|
||||
* The v2 list as the API answers it: search, kind and paging are applied on the
|
||||
* server, so the story has to narrow the rows rather than hand back all of them.
|
||||
*/
|
||||
export const notificationChannelsPage = (
|
||||
count: number,
|
||||
params: URLSearchParams,
|
||||
): {
|
||||
status: string;
|
||||
data: {
|
||||
channels: AlertmanagertypesListedNotificationChannelDTO[];
|
||||
total: number;
|
||||
};
|
||||
} => {
|
||||
const query = params.get('query')?.trim().toLowerCase() ?? '';
|
||||
const kind = params.get('kind') ?? '';
|
||||
const limit = Number(params.get('limit')) || CHANNEL_MAX;
|
||||
const offset = Number(params.get('offset')) || 0;
|
||||
|
||||
const matched = Array.from({ length: count }, (_unused, index) =>
|
||||
buildListedChannel(index),
|
||||
).filter(
|
||||
(channel) =>
|
||||
(!query || channel.displayName.toLowerCase().includes(query)) &&
|
||||
(!kind || channel.kind === kind),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
data: {
|
||||
channels: matched.slice(offset, offset + limit),
|
||||
total: matched.length,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* One listed row fetched on its own, as the JSON dialog does. Indexed the same
|
||||
* way the list builds its rows, so a row's id resolves to the kind it displays.
|
||||
*/
|
||||
export const notificationChannelByRowId = (
|
||||
id: string,
|
||||
): ReturnType<typeof notificationChannelResponse> => {
|
||||
const index = (Number(id) - 1 + CHANNEL_SEEDS.length) % CHANNEL_SEEDS.length;
|
||||
|
||||
return notificationChannelResponse(id, CHANNEL_SEEDS[index].type);
|
||||
};
|
||||
|
||||
/** Channel names the alert form and the routing policies pick from. */
|
||||
export const channelNames = (count: number): string[] =>
|
||||
channelsResponse(count).data.map((channel) => channel.name);
|
||||
|
||||
/**
|
||||
* The v2 spec each kind is fetched with, so the edit form renders populated
|
||||
* fields. Keys mirror the generated channel spec schemas.
|
||||
*/
|
||||
const CHANNEL_SPECS: Record<ChannelType, Record<string, unknown>> = {
|
||||
slack: {
|
||||
apiUrl: 'https://hooks.slack.com/services/T000/B000/story-token',
|
||||
channel: '#ops-alerts',
|
||||
title: '[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}',
|
||||
text: '{{ range .Alerts -}}*Alert:* {{ .Labels.alertname }}\n{{ end }}',
|
||||
sendResolved: true,
|
||||
},
|
||||
webhook: {
|
||||
url: 'https://example.com/story-hook',
|
||||
username: 'story-user',
|
||||
sendResolved: true,
|
||||
},
|
||||
pagerduty: {
|
||||
routingKey: 'story-routing-key',
|
||||
description: '{{ .CommonLabels.alertname }}',
|
||||
severity: 'critical',
|
||||
sendResolved: true,
|
||||
},
|
||||
opsgenie: {
|
||||
apiKey: 'story-opsgenie-key',
|
||||
message: '{{ .CommonLabels.alertname }}',
|
||||
description: 'Story channel',
|
||||
sendResolved: true,
|
||||
},
|
||||
email: {
|
||||
to: 'oncall@story.signoz.io',
|
||||
html: '<p>{{ .CommonLabels.alertname }}</p>',
|
||||
sendResolved: true,
|
||||
},
|
||||
msteams: {
|
||||
webhookUrl: 'https://story.webhook.office.com/webhookb2/story',
|
||||
title: '[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}',
|
||||
text: 'Story channel',
|
||||
sendResolved: true,
|
||||
},
|
||||
googlechat: {
|
||||
webhookUrl: 'https://chat.googleapis.com/v1/spaces/STORY/messages',
|
||||
title: '[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}',
|
||||
text: 'Story channel',
|
||||
sendResolved: true,
|
||||
},
|
||||
jira: {
|
||||
site: 'https://story.atlassian.net',
|
||||
project: 'OPS',
|
||||
issueType: 'Task',
|
||||
email: 'story@signoz.io',
|
||||
apiToken: 'story-jira-token',
|
||||
summary: '{{ .CommonLabels.alertname }}',
|
||||
sendResolved: true,
|
||||
},
|
||||
jsmops: {
|
||||
apiKey: 'story-jsm-key',
|
||||
message: '{{ .CommonLabels.alertname }}',
|
||||
description: 'Story channel',
|
||||
sendResolved: true,
|
||||
},
|
||||
incidentio: {
|
||||
url: 'https://api.incident.io/v2/alert_events/http/story',
|
||||
token: 'story-incidentio-token',
|
||||
title: '{{ .CommonLabels.alertname }}',
|
||||
sendResolved: true,
|
||||
},
|
||||
};
|
||||
|
||||
/** A single channel as the v2 API returns it, which the edit screen reads. */
|
||||
export const channelResponse = (
|
||||
id: string,
|
||||
type: ChannelType,
|
||||
@@ -727,6 +881,36 @@ export const channelResponse = (
|
||||
};
|
||||
};
|
||||
|
||||
/** The v2 fetch by id, which is what the redesigned screens read. */
|
||||
export const notificationChannelResponse = (
|
||||
id: string,
|
||||
type: ChannelType,
|
||||
): {
|
||||
status: string;
|
||||
data: AlertmanagertypesGettableNotificationChannelDTO;
|
||||
} => {
|
||||
const index = Math.max(
|
||||
CHANNEL_SEEDS.findIndex((seed) => seed.type === type),
|
||||
0,
|
||||
);
|
||||
const seed = CHANNEL_SEEDS[index];
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
data: {
|
||||
id,
|
||||
name: seed.name,
|
||||
displayName: seed.name,
|
||||
createdAt: ago((index + 10) * DAY),
|
||||
updatedAt: ago((index + 1) * DAY),
|
||||
config: {
|
||||
kind: type,
|
||||
spec: CHANNEL_SPECS[type],
|
||||
} as unknown as AlertmanagertypesChannelConfigDTO,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const CHANNEL_ACTION_OUTCOMES = ['succeeds', 'fails'] as const;
|
||||
|
||||
export type ChannelActionOutcome = (typeof CHANNEL_ACTION_OUTCOMES)[number];
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
alertPreviewSeries,
|
||||
} from '../../AlertList/stories/__story_mockdata__/alertQuery';
|
||||
import {
|
||||
channelsResponse,
|
||||
notificationChannelsResponse,
|
||||
CHANNEL_MAX,
|
||||
} from '../../AlertList/stories/__story_mockdata__/alerts';
|
||||
|
||||
@@ -127,8 +127,8 @@ export const createAlertMocks = defineStoryMocks({
|
||||
),
|
||||
|
||||
rest.get(
|
||||
'http://localhost/api/v1/channels',
|
||||
response.json(() => channelsResponse(values.channels)),
|
||||
'http://localhost/api/v2/notification_channels',
|
||||
response.json(() => notificationChannelsResponse(values.channels)),
|
||||
),
|
||||
|
||||
rest.post(
|
||||
|
||||
@@ -12,7 +12,10 @@ import { DashboardDetailEvents } from 'pages/DashboardPage/constants/events';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
|
||||
import { defineJsonEditorTheme, JSON_EDITOR_THEME } from './editorTheme';
|
||||
import {
|
||||
defineJsonEditorTheme,
|
||||
JSON_EDITOR_THEME,
|
||||
} from 'lib/monaco/jsonEditorTheme';
|
||||
import styles from './JsonEditorDrawer.module.scss';
|
||||
import JsonEditorToolbar from './JsonEditorToolbar';
|
||||
import { useJsonEditor } from './useJsonEditor';
|
||||
|
||||
@@ -39,12 +39,14 @@ export function useContextLinkVariables(): VariableItem[] {
|
||||
return useMemo(
|
||||
() => [
|
||||
...GLOBAL_TIMESTAMP_VARIABLES,
|
||||
...fieldVariableNames.map(
|
||||
(name): VariableItem => ({ name, source: 'Query variable' }),
|
||||
),
|
||||
...dashboardVariableNames.map(
|
||||
(name): VariableItem => ({ name, source: 'Dashboard variable' }),
|
||||
),
|
||||
...fieldVariableNames.map((name): VariableItem => ({
|
||||
name,
|
||||
source: 'Query variable',
|
||||
})),
|
||||
...dashboardVariableNames.map((name): VariableItem => ({
|
||||
name,
|
||||
source: 'Dashboard variable',
|
||||
})),
|
||||
],
|
||||
[fieldVariableNames, dashboardVariableNames],
|
||||
);
|
||||
|
||||
@@ -217,8 +217,6 @@ function ListPanelRenderer({
|
||||
log={logDetail.activeLog}
|
||||
selectedTab={logDetail.selectedTab}
|
||||
onClose={logDetail.onClose}
|
||||
onAddToQuery={logDetail.onAddToQuery}
|
||||
onClickActionItem={logDetail.onAddToQuery}
|
||||
onNavigateLog={logDetail.onNavigateLog}
|
||||
logs={logDetail.logs}
|
||||
isListViewPanel
|
||||
|
||||
@@ -18,7 +18,6 @@ export interface ListLogDetail {
|
||||
selectedTab: VIEWS;
|
||||
logs: ILog[];
|
||||
onClose: () => void;
|
||||
onAddToQuery: ReturnType<typeof useLogDetailHandlers>['onAddToQuery'];
|
||||
onNavigateLog: (log: ILog) => void;
|
||||
}
|
||||
|
||||
@@ -49,13 +48,8 @@ export function useListRowInteraction({
|
||||
const isLogs = signal === TelemetrytypesSignalDTO.logs;
|
||||
const isTraces = signal === TelemetrytypesSignalDTO.traces;
|
||||
|
||||
const {
|
||||
activeLog,
|
||||
onAddToQuery,
|
||||
selectedTab,
|
||||
handleSetActiveLog,
|
||||
handleCloseLogDetail,
|
||||
} = useLogDetailHandlers();
|
||||
const { activeLog, selectedTab, handleSetActiveLog, handleCloseLogDetail } =
|
||||
useLogDetailHandlers();
|
||||
|
||||
const logs = useMemo(
|
||||
() => (isLogs ? rows.map(rawRowToILog) : []),
|
||||
@@ -90,7 +84,6 @@ export function useListRowInteraction({
|
||||
selectedTab: selectedTab ?? VIEW_TYPES.OVERVIEW,
|
||||
logs,
|
||||
onClose: handleCloseLogDetail,
|
||||
onAddToQuery,
|
||||
onNavigateLog: handleSetActiveLog,
|
||||
}
|
||||
: null,
|
||||
@@ -100,7 +93,6 @@ export function useListRowInteraction({
|
||||
selectedTab,
|
||||
logs,
|
||||
handleCloseLogDetail,
|
||||
onAddToQuery,
|
||||
handleSetActiveLog,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -54,15 +54,13 @@ export function buildDownloadMenuItem({
|
||||
|
||||
const items = DOWNLOAD_FORMAT_OPTIONS.filter(
|
||||
({ format }) => supported[format],
|
||||
).map(
|
||||
({ format, label, icon }): DropdownActionItemType => ({
|
||||
type: 'item',
|
||||
value: `download-${format}`,
|
||||
label,
|
||||
prefix: icon,
|
||||
onClick: (): void => onDownload(format),
|
||||
}),
|
||||
);
|
||||
).map(({ format, label, icon }): DropdownActionItemType => ({
|
||||
type: 'item',
|
||||
value: `download-${format}`,
|
||||
label,
|
||||
prefix: icon,
|
||||
onClick: (): void => onDownload(format),
|
||||
}));
|
||||
|
||||
if (items.length === 0) {
|
||||
return null;
|
||||
|
||||
@@ -338,10 +338,12 @@ export const dashboardMocks = defineStoryMocks({
|
||||
|
||||
// The header reads the public link on every load, so it answers even while
|
||||
// the panels are held in the loading or failed state.
|
||||
rest.get('http://localhost/api/v1/dashboards/:id/public', (_req, res, ctx) =>
|
||||
values.published
|
||||
? res(ctx.status(200), ctx.json(publicMeta()))
|
||||
: res(ctx.status(404), ctx.json(NOT_PUBLIC)),
|
||||
rest.get(
|
||||
'http://localhost/api/v1/dashboards/:id/public',
|
||||
(_req, res, ctx) =>
|
||||
values.published
|
||||
? res(ctx.status(200), ctx.json(publicMeta()))
|
||||
: res(ctx.status(404), ctx.json(NOT_PUBLIC)),
|
||||
),
|
||||
|
||||
rest.post('http://localhost/api/v1/dashboards/:id/public', (req, res, ctx) =>
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import {
|
||||
alertRuleByIdResponse,
|
||||
ALERT_SCHEMAS,
|
||||
channelsResponse,
|
||||
notificationChannelsResponse,
|
||||
CHANNEL_MAX,
|
||||
type AlertSchema,
|
||||
} from '../../AlertList/stories/__story_mockdata__/alerts';
|
||||
@@ -77,8 +77,8 @@ export const editRulesMocks = defineStoryMocks({
|
||||
),
|
||||
|
||||
rest.get(
|
||||
'http://localhost/api/v1/channels',
|
||||
response.json(() => channelsResponse(values.channels)),
|
||||
'http://localhost/api/v2/notification_channels',
|
||||
response.json(() => notificationChannelsResponse(values.channels)),
|
||||
),
|
||||
|
||||
rest.post(
|
||||
|
||||
@@ -41,12 +41,10 @@ export const liveLogsQuery = (): Query => {
|
||||
...seed,
|
||||
builder: {
|
||||
...seed.builder,
|
||||
queryData: seed.builder.queryData.map(
|
||||
(data): IBuilderQuery => ({
|
||||
...data,
|
||||
filter: { expression: LIVE_TAIL_FILTER },
|
||||
}),
|
||||
),
|
||||
queryData: seed.builder.queryData.map((data): IBuilderQuery => ({
|
||||
...data,
|
||||
filter: { expression: LIVE_TAIL_FILTER },
|
||||
})),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* AI-owned. Generated and maintained by the `signoz-page-story` skill.
|
||||
* Do not hand-edit: regenerate instead.
|
||||
*/
|
||||
|
||||
import ROUTES from 'constants/routes';
|
||||
import { rest } from 'msw';
|
||||
|
||||
import { countControl } from '@/storybook/controls/controls';
|
||||
import { defineStoryMocks } from '@/storybook/controls/defineStoryMocks';
|
||||
|
||||
import {
|
||||
logsSavedViewsResponse,
|
||||
SAVED_VIEW_MAX,
|
||||
SAVED_VIEWS_PAGE_SIZE,
|
||||
savedViewWriteResponse,
|
||||
} from './__story_mockdata__/savedViews';
|
||||
|
||||
const LIST = 'Views · list';
|
||||
|
||||
export const savedViewsMocks = defineStoryMocks({
|
||||
controls: {
|
||||
views: countControl('Saved views', {
|
||||
group: LIST,
|
||||
description: `The views the org saved for logs. Past ${SAVED_VIEWS_PAGE_SIZE} the table paginates.`,
|
||||
value: 4,
|
||||
max: SAVED_VIEW_MAX,
|
||||
}),
|
||||
},
|
||||
handlers: (values, response) => [
|
||||
rest.get(
|
||||
'http://localhost/api/v1/explorer/views',
|
||||
response.json(() => logsSavedViewsResponse(values.views)),
|
||||
),
|
||||
|
||||
rest.put(
|
||||
'http://localhost/api/v1/explorer/views/:viewId',
|
||||
response.json(savedViewWriteResponse),
|
||||
),
|
||||
|
||||
rest.delete(
|
||||
'http://localhost/api/v1/explorer/views/:viewId',
|
||||
response.json(savedViewWriteResponse),
|
||||
),
|
||||
],
|
||||
config: () => ({ route: ROUTES.LOGS_SAVE_VIEWS }),
|
||||
});
|
||||
@@ -1,88 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { screen, userEvent, within } from 'storybook/test';
|
||||
|
||||
import { storyMocks } from '@/storybook/controls/defineStoryMocks';
|
||||
import type { PageStoryArgs } from '@/storybook/runtime/resolveStory';
|
||||
|
||||
import { savedViewsMocks } from './SavedViews.stories.mocks';
|
||||
|
||||
import LogsModulePage from '../../LogsModulePage';
|
||||
|
||||
type SavedViewsArgs = PageStoryArgs<typeof savedViewsMocks>;
|
||||
|
||||
const pageStory = storyMocks(savedViewsMocks, { layout: 'app' });
|
||||
|
||||
/**
|
||||
* Saved views on the logs explorer, and the actions the legacy editor role gates.
|
||||
*
|
||||
* Route: `/logs/saved-views`.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'Pages/Logs/Saved Views',
|
||||
tags: ['role-gated', 'play'],
|
||||
component: LogsModulePage,
|
||||
...pageStory,
|
||||
parameters: { ...pageStory.parameters },
|
||||
} satisfies Meta<SavedViewsArgs>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<SavedViewsArgs>;
|
||||
|
||||
/** The page fetches before it renders a row, which outlasts the 1s default. */
|
||||
const untilLoaded = { timeout: 15_000 };
|
||||
|
||||
/** Row actions are per-view icons, so a story that opens one picks the first. */
|
||||
const openFirstAction = async (
|
||||
canvasElement: HTMLElement,
|
||||
testId: string,
|
||||
): Promise<void> => {
|
||||
const [action] = await within(canvasElement).findAllByTestId(
|
||||
testId,
|
||||
undefined,
|
||||
untilLoaded,
|
||||
);
|
||||
|
||||
await userEvent.click(action);
|
||||
};
|
||||
|
||||
/**
|
||||
* The views tab: every view the org saved for logs, who saved it and when, with
|
||||
* the colour the view picker shows it under.
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
|
||||
/** A workspace where nobody has saved a view yet. */
|
||||
export const NoViews: Story = {
|
||||
args: { views: 0 },
|
||||
};
|
||||
|
||||
/** Enough views to paginate, which is the only place the second page shows up. */
|
||||
export const Paginated: Story = {
|
||||
args: { views: 8 },
|
||||
};
|
||||
|
||||
/** Renaming a view: its label and the colour the picker shows it under. */
|
||||
export const EditView: Story = {
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
await openFirstAction(canvasElement, 'edit-view');
|
||||
await screen.findByText('Edit view details', undefined, untilLoaded);
|
||||
},
|
||||
};
|
||||
|
||||
/** The confirmation a view has to pass before it is deleted. */
|
||||
export const DeleteView: Story = {
|
||||
play: async ({ canvasElement }): Promise<void> => {
|
||||
await openFirstAction(canvasElement, 'delete-view');
|
||||
await screen.findByText(
|
||||
/are you sure you want to delete/i,
|
||||
undefined,
|
||||
untilLoaded,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
/** A viewer: the views are listed, none of them editable. */
|
||||
export const ViewerAccess: Story = {
|
||||
args: { access: 'viewer' },
|
||||
};
|
||||
@@ -38,12 +38,10 @@ export function getTableData(data: DropRateResponse[]): RowData[] {
|
||||
}
|
||||
|
||||
const tableData: RowData[] =
|
||||
data?.map(
|
||||
(row: DropRateResponse, index: number): RowData => ({
|
||||
...(row.data as any), // todo-sagar
|
||||
key: index,
|
||||
}),
|
||||
) || [];
|
||||
data?.map((row: DropRateResponse, index: number): RowData => ({
|
||||
...(row.data as any), // todo-sagar
|
||||
key: index,
|
||||
})) || [];
|
||||
|
||||
return tableData;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user