Compare commits
54 Commits
kubecon-de
...
v0.78.2-js
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5adcd7b98a | ||
|
|
cc58bba9ce | ||
|
|
2a4e38cf39 | ||
|
|
0b1faec092 | ||
|
|
ba6f31b1c3 | ||
|
|
eed92978a4 | ||
|
|
41cbd316b5 | ||
|
|
8d7d33393d | ||
|
|
8d143b44b1 | ||
|
|
423aebd6eb | ||
|
|
8d630707af | ||
|
|
a5b52431b7 | ||
|
|
0138d757c8 | ||
|
|
844195b84f | ||
|
|
8ff05b2e8f | ||
|
|
c8c56c544e | ||
|
|
1c43655336 | ||
|
|
c269c8c6b8 | ||
|
|
3142b6cc6d | ||
|
|
58e141685a | ||
|
|
e17f63a50c | ||
|
|
838ef5dcc5 | ||
|
|
e53d3d1269 | ||
|
|
2330420c0d | ||
|
|
65ac277074 | ||
|
|
b7982ca348 | ||
|
|
2748b49a44 | ||
|
|
7345027762 | ||
|
|
68f874e433 | ||
|
|
54a82b1664 | ||
|
|
93dc585145 | ||
|
|
6a143efd2c | ||
|
|
0116eb20ab | ||
|
|
79e9d1b357 | ||
|
|
b89ce82e25 | ||
|
|
b43a198fd8 | ||
|
|
b40ca4baf3 | ||
|
|
8df77c9221 | ||
|
|
f67555576f | ||
|
|
f0a4c37073 | ||
|
|
7972261237 | ||
|
|
3b4a8e5e0f | ||
|
|
5ef3b8ee3f | ||
|
|
597752a4bc | ||
|
|
07a244f569 | ||
|
|
eb9385840f | ||
|
|
30b689037a | ||
|
|
ba33c885d5 | ||
|
|
a4ed9e4d47 | ||
|
|
df5767198c | ||
|
|
81c7f3221a | ||
|
|
2cbd8733a1 | ||
|
|
71d1dfe9bd | ||
|
|
459712d25c |
81
.github/workflows/build-community.yaml
vendored
Normal file
81
.github/workflows/build-community.yaml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: build-community
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
PRIMUS_HOME: .primus
|
||||
MAKE: make --no-print-directory --makefile=.primus/src/make/main.mk
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.build-info.outputs.version }}
|
||||
hash: ${{ steps.build-info.outputs.hash }}
|
||||
time: ${{ steps.build-info.outputs.time }}
|
||||
branch: ${{ steps.build-info.outputs.branch }}
|
||||
steps:
|
||||
- name: self-checkout
|
||||
uses: actions/checkout@v4
|
||||
- id: token
|
||||
name: github-token-gen
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.PRIMUS_APP_ID }}
|
||||
private-key: ${{ secrets.PRIMUS_PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: primus-checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: signoz/primus
|
||||
ref: main
|
||||
path: .primus
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: build-info
|
||||
run: |
|
||||
echo "version=$($MAKE info-version)" >> $GITHUB_OUTPUT
|
||||
echo "hash=$($MAKE info-commit-short)" >> $GITHUB_OUTPUT
|
||||
echo "time=$($MAKE info-timestamp)" >> $GITHUB_OUTPUT
|
||||
echo "branch=$($MAKE info-branch)" >> $GITHUB_OUTPUT
|
||||
js-build:
|
||||
uses: signoz/primus.workflows/.github/workflows/js-build.yaml@main
|
||||
needs: prepare
|
||||
secrets: inherit
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_OUTPUT_ARTIFACT_CACHE_KEY: community-jsbuild-${{ github.sha }}
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
DOCKER_BUILD: false
|
||||
DOCKER_MANIFEST: false
|
||||
go-build:
|
||||
uses: signoz/primus.workflows/.github/workflows/go-build.yaml@main
|
||||
needs: [prepare, js-build]
|
||||
secrets: inherit
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
GO_NAME: signoz-community
|
||||
GO_INPUT_ARTIFACT_CACHE_KEY: community-jsbuild-${{ github.sha }}
|
||||
GO_INPUT_ARTIFACT_PATH: frontend/build
|
||||
GO_BUILD_CONTEXT: ./pkg/query-service
|
||||
GO_BUILD_FLAGS: >-
|
||||
-tags timetzdata
|
||||
-ldflags='-linkmode external -extldflags \"-static\" -s -w
|
||||
-X github.com/SigNoz/signoz/pkg/version.version=${{ needs.prepare.outputs.version }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.variant=community
|
||||
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.time=${{ needs.prepare.outputs.time }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}'
|
||||
GO_CGO_ENABLED: 1
|
||||
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
|
||||
DOCKER_DOCKERFILE_PATH: ./pkg/query-service/Dockerfile.multi-arch
|
||||
DOCKER_MANIFEST: true
|
||||
DOCKER_PROVIDERS: dockerhub
|
||||
113
.github/workflows/build-enterprise.yaml
vendored
Normal file
113
.github/workflows/build-enterprise.yaml
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
name: build-enterprise
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
PRIMUS_HOME: .primus
|
||||
MAKE: make --no-print-directory --makefile=.primus/src/make/main.mk
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
docker_providers: ${{ steps.set-docker-providers.outputs.providers }}
|
||||
version: ${{ steps.build-info.outputs.version }}
|
||||
hash: ${{ steps.build-info.outputs.hash }}
|
||||
time: ${{ steps.build-info.outputs.time }}
|
||||
branch: ${{ steps.build-info.outputs.branch }}
|
||||
steps:
|
||||
- name: self-checkout
|
||||
uses: actions/checkout@v4
|
||||
- id: token
|
||||
name: github-token-gen
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.PRIMUS_APP_ID }}
|
||||
private-key: ${{ secrets.PRIMUS_PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: primus-checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: signoz/primus
|
||||
ref: main
|
||||
path: .primus
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: build-info
|
||||
id: build-info
|
||||
run: |
|
||||
echo "version=$($MAKE info-version)" >> $GITHUB_OUTPUT
|
||||
echo "hash=$($MAKE info-commit-short)" >> $GITHUB_OUTPUT
|
||||
echo "time=$($MAKE info-timestamp)" >> $GITHUB_OUTPUT
|
||||
echo "branch=$($MAKE info-branch)" >> $GITHUB_OUTPUT
|
||||
- name: set-docker-providers
|
||||
id: set-docker-providers
|
||||
run: |
|
||||
if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ || ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
|
||||
echo "providers=dockerhub gcp" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "providers=gcp" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: create-dotenv
|
||||
run: |
|
||||
mkdir -p frontend
|
||||
echo 'CI=1' > frontend/.env
|
||||
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' >> frontend/.env
|
||||
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
|
||||
echo 'SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
|
||||
echo 'SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
|
||||
echo 'SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
|
||||
echo 'SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
|
||||
echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env
|
||||
echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env
|
||||
echo 'POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env
|
||||
echo 'CUSTOMERIO_ID="${{ secrets.CUSTOMERIO_ID }}"' >> frontend/.env
|
||||
echo 'CUSTOMERIO_SITE_ID="${{ secrets.CUSTOMERIO_SITE_ID }}"' >> frontend/.env
|
||||
- name: cache-dotenv
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: frontend/.env
|
||||
key: dotenv-${{ github.sha }}
|
||||
js-build:
|
||||
uses: signoz/primus.workflows/.github/workflows/js-build.yaml@main
|
||||
needs: prepare
|
||||
secrets: inherit
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_INPUT_ARTIFACT_CACHE_KEY: dotenv-${{ github.sha }}
|
||||
JS_INPUT_ARTIFACT_PATH: frontend/.env
|
||||
JS_OUTPUT_ARTIFACT_CACHE_KEY: jsbuild-${{ github.sha }}
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
DOCKER_BUILD: false
|
||||
DOCKER_MANIFEST: false
|
||||
go-build:
|
||||
uses: signoz/primus.workflows/.github/workflows/go-build.yaml@main
|
||||
needs: [prepare, js-build]
|
||||
secrets: inherit
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
GO_INPUT_ARTIFACT_CACHE_KEY: jsbuild-${{ github.sha }}
|
||||
GO_INPUT_ARTIFACT_PATH: frontend/build
|
||||
GO_BUILD_CONTEXT: ./ee/query-service
|
||||
GO_BUILD_FLAGS: >-
|
||||
-tags timetzdata
|
||||
-ldflags='-linkmode external -extldflags \"-static\" -s -w
|
||||
-X github.com/SigNoz/signoz/pkg/version.version=${{ needs.prepare.outputs.version }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.variant=enterprise
|
||||
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.time=${{ needs.prepare.outputs.time }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}
|
||||
-X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.signoz.cloud
|
||||
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1'
|
||||
GO_CGO_ENABLED: 1
|
||||
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
|
||||
DOCKER_DOCKERFILE_PATH: ./ee/query-service/Dockerfile.multi-arch
|
||||
DOCKER_MANIFEST: true
|
||||
DOCKER_PROVIDERS: ${{ needs.prepare.outputs.docker_providers }}
|
||||
131
.github/workflows/build-staging.yaml
vendored
Normal file
131
.github/workflows/build-staging.yaml
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
name: build-staging
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
PRIMUS_HOME: .primus
|
||||
MAKE: make --no-print-directory --makefile=.primus/src/make/main.mk
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ contains(github.event.label.name, 'staging:') || github.event.ref == 'refs/heads/main' }}
|
||||
outputs:
|
||||
version: ${{ steps.build-info.outputs.version }}
|
||||
hash: ${{ steps.build-info.outputs.hash }}
|
||||
time: ${{ steps.build-info.outputs.time }}
|
||||
branch: ${{ steps.build-info.outputs.branch }}
|
||||
deployment: ${{ steps.build-info.outputs.deployment }}
|
||||
steps:
|
||||
- name: self-checkout
|
||||
uses: actions/checkout@v4
|
||||
- id: token
|
||||
name: github-token-gen
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.PRIMUS_APP_ID }}
|
||||
private-key: ${{ secrets.PRIMUS_PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: primus-checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: signoz/primus
|
||||
ref: main
|
||||
path: .primus
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
- name: build-info
|
||||
id: build-info
|
||||
run: |
|
||||
echo "version=$($MAKE info-version)" >> $GITHUB_OUTPUT
|
||||
echo "hash=$($MAKE info-commit-short)" >> $GITHUB_OUTPUT
|
||||
echo "time=$($MAKE info-timestamp)" >> $GITHUB_OUTPUT
|
||||
echo "branch=$($MAKE info-branch)" >> $GITHUB_OUTPUT
|
||||
|
||||
staging_label="${{ github.event.label.name }}"
|
||||
if [[ "${staging_label}" == "staging:"* ]]; then
|
||||
deployment=${staging_label#"staging:"}
|
||||
elif [[ "${{ github.event.ref }}" == "refs/heads/main" ]]; then
|
||||
deployment="staging"
|
||||
else
|
||||
echo "error: not able to determine deployment - please verify the PR label or the branch"
|
||||
exit 1
|
||||
fi
|
||||
echo "deployment=${deployment}" >> $GITHUB_OUTPUT
|
||||
- name: create-dotenv
|
||||
run: |
|
||||
mkdir -p frontend
|
||||
echo 'CI=1' > frontend/.env
|
||||
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' >> frontend/.env
|
||||
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
|
||||
echo 'SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
|
||||
echo 'SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
|
||||
echo 'SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
|
||||
echo 'SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
|
||||
echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env
|
||||
echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env
|
||||
echo 'POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env
|
||||
echo 'CUSTOMERIO_ID="${{ secrets.CUSTOMERIO_ID }}"' >> frontend/.env
|
||||
echo 'CUSTOMERIO_SITE_ID="${{ secrets.CUSTOMERIO_SITE_ID }}"' >> frontend/.env
|
||||
- name: cache-dotenv
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: frontend/.env
|
||||
key: dotenv-${{ github.sha }}
|
||||
js-build:
|
||||
uses: signoz/primus.workflows/.github/workflows/js-build.yaml@main
|
||||
needs: prepare
|
||||
secrets: inherit
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_INPUT_ARTIFACT_CACHE_KEY: dotenv-${{ github.sha }}
|
||||
JS_INPUT_ARTIFACT_PATH: frontend/.env
|
||||
JS_OUTPUT_ARTIFACT_CACHE_KEY: jsbuild-${{ github.sha }}
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
DOCKER_BUILD: false
|
||||
DOCKER_MANIFEST: false
|
||||
go-build:
|
||||
uses: signoz/primus.workflows/.github/workflows/go-build.yaml@main
|
||||
needs: [prepare, js-build]
|
||||
secrets: inherit
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
GO_INPUT_ARTIFACT_CACHE_KEY: jsbuild-${{ github.sha }}
|
||||
GO_INPUT_ARTIFACT_PATH: frontend/build
|
||||
GO_BUILD_CONTEXT: ./ee/query-service
|
||||
GO_BUILD_FLAGS: >-
|
||||
-tags timetzdata
|
||||
-ldflags='-linkmode external -extldflags \"-static\" -s -w
|
||||
-X github.com/SigNoz/signoz/pkg/version.version=${{ needs.prepare.outputs.version }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.variant=enterprise
|
||||
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.time=${{ needs.prepare.outputs.time }}
|
||||
-X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}
|
||||
-X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.staging.signoz.cloud
|
||||
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.staging.signoz.cloud/api/v1'
|
||||
GO_CGO_ENABLED: 1
|
||||
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
|
||||
DOCKER_DOCKERFILE_PATH: ./ee/query-service/Dockerfile.multi-arch
|
||||
DOCKER_MANIFEST: true
|
||||
DOCKER_PROVIDERS: gcp
|
||||
staging:
|
||||
if: ${{ contains(github.event.label.name, 'staging:') || github.event.ref == 'refs/heads/main' }}
|
||||
uses: signoz/primus.workflows/.github/workflows/github-trigger.yaml@main
|
||||
secrets: inherit
|
||||
needs: [prepare, go-build]
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
GITHUB_ENVIRONMENT: staging
|
||||
GITHUB_SILENT: true
|
||||
GITHUB_REPOSITORY_NAME: charts-saas-v3-staging
|
||||
GITHUB_EVENT_NAME: releaser
|
||||
GITHUB_EVENT_PAYLOAD: "{\"deployment\": \"${{ needs.prepare.outputs.deployment }}\", \"signoz_version\": \"${{ needs.prepare.outputs.version }}\"}"
|
||||
122
.github/workflows/build.yaml
vendored
122
.github/workflows/build.yaml
vendored
@@ -1,122 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
enterprise:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: setup
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22"
|
||||
- name: setup-qemu
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: setup-buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
- name: docker-login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: create-env-file
|
||||
run: |
|
||||
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
|
||||
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
|
||||
echo 'SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
|
||||
echo 'SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
|
||||
echo 'SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
|
||||
echo 'SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
|
||||
echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env
|
||||
echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env
|
||||
echo 'POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env
|
||||
echo 'CUSTOMERIO_ID="${{ secrets.CUSTOMERIO_ID }}"' >> frontend/.env
|
||||
echo 'CUSTOMERIO_SITE_ID="${{ secrets.CUSTOMERIO_SITE_ID }}"' >> frontend/.env
|
||||
- name: github-ref-info
|
||||
shell: bash
|
||||
run: |
|
||||
GH_REF=${{ github.ref }}
|
||||
if [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||
PREFIX="refs/tags/"
|
||||
echo "GH_IS_TAG=true" >> $GITHUB_ENV
|
||||
echo "GH_TAG=${GH_REF#$PREFIX}" >> $GITHUB_ENV
|
||||
else
|
||||
PREFIX="refs/heads/"
|
||||
echo "GH_IS_TAG=false" >> $GITHUB_ENV
|
||||
echo "GH_BRANCH_NAME=${GH_REF#$PREFIX}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: set-version
|
||||
run: |
|
||||
if [ '${{ env.GH_IS_TAG }}' == 'true' ]; then
|
||||
echo "VERSION=${{ env.GH_TAG }}" >> $GITHUB_ENV
|
||||
elif [ '${{ env.GH_BRANCH_NAME }}' == 'main' ]; then
|
||||
echo "VERSION=latest" >> $GITHUB_ENV
|
||||
else
|
||||
echo "VERSION=${{ env.GH_BRANCH_NAME }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: cross-compilation-tools
|
||||
run: |
|
||||
set -ex
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools
|
||||
- name: publish
|
||||
run: make docker-buildx-enterprise
|
||||
|
||||
community:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: setup-go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22"
|
||||
- name: setup-qemu
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: setup-buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
- name: docker-login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: github-ref-info
|
||||
shell: bash
|
||||
run: |
|
||||
GH_REF=${{ github.ref }}
|
||||
if [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||
PREFIX="refs/tags/"
|
||||
echo "GH_IS_TAG=true" >> $GITHUB_ENV
|
||||
echo "GH_TAG=${GH_REF#$PREFIX}" >> $GITHUB_ENV
|
||||
else
|
||||
PREFIX="refs/heads/"
|
||||
echo "GH_IS_TAG=false" >> $GITHUB_ENV
|
||||
echo "GH_BRANCH_NAME=${GH_REF#$PREFIX}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: set-version
|
||||
run: |
|
||||
if [ '${{ env.GH_IS_TAG }}' == 'true' ]; then
|
||||
echo "VERSION=${{ env.GH_TAG }}" >> $GITHUB_ENV
|
||||
elif [ '${{ env.GH_BRANCH_NAME }}' == 'main' ]; then
|
||||
echo "VERSION=latest" >> $GITHUB_ENV
|
||||
else
|
||||
echo "VERSION=${{ env.GH_BRANCH_NAME }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: cross-compilation-tools
|
||||
run: |
|
||||
set -ex
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools
|
||||
- name: publish
|
||||
run: make docker-buildx-community
|
||||
13
.github/workflows/staging-deployment.yaml
vendored
13
.github/workflows/staging-deployment.yaml
vendored
@@ -36,12 +36,17 @@ jobs:
|
||||
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
|
||||
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
||||
export VERSION="${GITHUB_SHA:0:7}" # needed for child process to access it
|
||||
export OTELCOL_TAG="main"
|
||||
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
|
||||
export KAFKA_SPAN_EVAL="true"
|
||||
docker system prune --force
|
||||
docker pull signoz/signoz-otel-collector:main
|
||||
docker pull signoz/signoz-schema-migrator:main
|
||||
docker system prune --force --all
|
||||
OTELCOL_TAG=$(curl -s https://api.github.com/repos/SigNoz/signoz-otel-collector/releases/latest | jq -r '.tag_name // "not-found"')
|
||||
if [[ "${OTELCOL_TAG}" == "not-found" ]]; then
|
||||
echo "warning: unable to determine latest SigNoz OtelCollector release tag, skipping latest otelcol deployment"
|
||||
else
|
||||
export OTELCOL_TAG=${OTELCOL_TAG}
|
||||
docker pull signoz/signoz-otel-collector:${OTELCOL_TAG}
|
||||
docker pull signoz/signoz-schema-migrator:${OTELCOL_TAG}
|
||||
fi
|
||||
cd ~/signoz
|
||||
git status
|
||||
git add .
|
||||
|
||||
2
.github/workflows/testing-deployment.yaml
vendored
2
.github/workflows/testing-deployment.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
export VERSION="${GITHUB_SHA:0:7}" # needed for child process to access it
|
||||
export DEV_BUILD="1"
|
||||
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
|
||||
docker system prune --force
|
||||
docker system prune --force --all
|
||||
cd ~/signoz
|
||||
git status
|
||||
git add .
|
||||
|
||||
17
.versions/alpine
Normal file
17
.versions/alpine
Normal file
@@ -0,0 +1,17 @@
|
||||
#### Auto generated by make docker-version-alpine. DO NOT EDIT! ####
|
||||
amd64=029a752048e32e843bd6defe3841186fb8d19a28dae8ec287f433bb9d6d1ad85
|
||||
unknown=5fea95373b9ec85974843f31446fa6a9df4492dddae4e1cb056193c34a20a5be
|
||||
arm=b4aef1a899e0271f06d948c9a8fa626ecdb2202d3a178bc14775dd559e23df8e
|
||||
unknown=a4d1e27e63a9d6353046eb25a2f0ec02945012b217f4364cd83a73fe6dfb0b15
|
||||
arm=4fdafe217d0922f3c3e2b4f64cf043f8403a4636685cd9c51fea2cbd1f419740
|
||||
unknown=7f21ac2018d95b2c51a5779c1d5ca6c327504adc3b0fdc747a6725d30b3f13c2
|
||||
arm64=ea3c5a9671f7b3f7eb47eab06f73bc6591df978b0d5955689a9e6f943aa368c0
|
||||
unknown=a8ba68c1a9e6eea8041b4b8f996c235163440808b9654a865976fdcbede0f433
|
||||
386=dea9f02e103e837849f984d5679305c758aba7fea1b95b7766218597f61a05ab
|
||||
unknown=3c6629bec05c8273a927d46b77428bf4a378dad911a0ae284887becdc149b734
|
||||
ppc64le=0880443bffa028dfbbc4094a32dd6b7ac25684e4c0a3d50da9e0acae355c5eaf
|
||||
unknown=bb48308f976b266e3ab39bbf9af84521959bd9c295d3c763690cf41f8df2a626
|
||||
riscv64=d76e6fbe348ff20c2931bb7f101e49379648e026de95dd37f96e00ce1909dcf7
|
||||
unknown=dd807544365f6dc187cbe6de0806adce2ea9de3e7124717d1d8e8b7a18b77b64
|
||||
s390x=b815fadf80495594eb6296a6af0bc647ae5f193e0044e07acec7e5b378c9ce2d
|
||||
unknown=74681be74a280a88abb53ff1e048eb1fb624b30d0066730df6d8afd02ba82e01
|
||||
4
Makefile
4
Makefile
@@ -74,6 +74,10 @@ go-run-enterprise: ## Runs the enterprise go backend server
|
||||
--use-logs-new-schema true \
|
||||
--use-trace-new-schema true
|
||||
|
||||
.PHONY: go-test
|
||||
go-test: ## Runs go unit tests
|
||||
@go test -race ./...
|
||||
|
||||
.PHONY: go-run-community
|
||||
go-run-community: ## Runs the community go backend server
|
||||
@SIGNOZ_INSTRUMENTATION_LOGS_LEVEL=debug \
|
||||
|
||||
@@ -72,7 +72,6 @@ sqlstore:
|
||||
# The path to the SQLite database file.
|
||||
path: /var/lib/signoz/signoz.db
|
||||
|
||||
|
||||
##################### APIServer #####################
|
||||
apiserver:
|
||||
timeout:
|
||||
@@ -91,20 +90,29 @@ apiserver:
|
||||
- /api/v1/version
|
||||
- /
|
||||
|
||||
|
||||
##################### TelemetryStore #####################
|
||||
telemetrystore:
|
||||
# Specifies the telemetrystore provider to use.
|
||||
provider: clickhouse
|
||||
# Maximum number of idle connections in the connection pool.
|
||||
max_idle_conns: 50
|
||||
# Maximum number of open connections to the database.
|
||||
max_open_conns: 100
|
||||
# Maximum time to wait for a connection to be established.
|
||||
dial_timeout: 5s
|
||||
# Specifies the telemetrystore provider to use.
|
||||
provider: clickhouse
|
||||
clickhouse:
|
||||
# The DSN to use for ClickHouse.
|
||||
dsn: http://localhost:9000
|
||||
# The DSN to use for clickhouse.
|
||||
dsn: tcp://localhost:9000
|
||||
|
||||
##################### Prometheus #####################
|
||||
prometheus:
|
||||
active_query_tracker:
|
||||
# Whether to enable the active query tracker.
|
||||
enabled: true
|
||||
# The path to use for the active query tracker.
|
||||
path: ""
|
||||
# The maximum number of concurrent queries.
|
||||
max_concurrent: 20
|
||||
|
||||
##################### Alertmanager #####################
|
||||
alertmanager:
|
||||
@@ -117,7 +125,7 @@ alertmanager:
|
||||
# The poll interval for periodically syncing the alertmanager with the config in the store.
|
||||
poll_interval: 1m
|
||||
# The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy). Used for generating relative and absolute links back to Alertmanager itself.
|
||||
external_url: http://localhost:9093
|
||||
external_url: http://localhost:8080
|
||||
# The global configuration for the alertmanager. All the exahustive fields can be found in the upstream: https://github.com/prometheus/alertmanager/blob/efa05feffd644ba4accb526e98a8c6545d26a783/config/config.go#L833
|
||||
global:
|
||||
# ResolveTimeout is the time after which an alert is declared resolved if it has not been updated.
|
||||
|
||||
@@ -174,7 +174,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.76.2
|
||||
image: signoz/signoz:v0.78.1
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
- --use-logs-new-schema=true
|
||||
@@ -208,7 +208,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.111.34
|
||||
image: signoz/signoz-otel-collector:v0.111.38
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
@@ -232,7 +232,7 @@ services:
|
||||
- signoz
|
||||
schema-migrator:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:v0.111.34
|
||||
image: signoz/signoz-schema-migrator:v0.111.38
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -110,7 +110,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.76.2
|
||||
image: signoz/signoz:v0.78.1
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
- --use-logs-new-schema=true
|
||||
@@ -143,7 +143,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.111.34
|
||||
image: signoz/signoz-otel-collector:v0.111.38
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
@@ -167,7 +167,7 @@ services:
|
||||
- signoz
|
||||
schema-migrator:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:v0.111.34
|
||||
image: signoz/signoz-schema-migrator:v0.111.38
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -177,7 +177,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.76.2}
|
||||
image: signoz/signoz:${VERSION:-v0.78.1}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -212,7 +212,7 @@ services:
|
||||
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.34}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.38}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
@@ -238,7 +238,7 @@ services:
|
||||
condition: service_healthy
|
||||
schema-migrator-sync:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
|
||||
container_name: schema-migrator-sync
|
||||
command:
|
||||
- sync
|
||||
@@ -249,7 +249,7 @@ services:
|
||||
condition: service_healthy
|
||||
schema-migrator-async:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
@@ -110,7 +110,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.76.2}
|
||||
image: signoz/signoz:${VERSION:-v0.78.1}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -146,7 +146,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.34}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.38}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
@@ -168,7 +168,7 @@ services:
|
||||
condition: service_healthy
|
||||
schema-migrator-sync:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
|
||||
container_name: schema-migrator-sync
|
||||
command:
|
||||
- sync
|
||||
@@ -180,7 +180,7 @@ services:
|
||||
restart: on-failure
|
||||
schema-migrator-async:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
@@ -110,7 +110,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.76.2}
|
||||
image: signoz/signoz:${VERSION:-v0.78.1}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -144,7 +144,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.34}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.38}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
@@ -166,7 +166,7 @@ services:
|
||||
condition: service_healthy
|
||||
schema-migrator-sync:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
|
||||
container_name: schema-migrator-sync
|
||||
command:
|
||||
- sync
|
||||
@@ -178,7 +178,7 @@ services:
|
||||
restart: on-failure
|
||||
schema-migrator-async:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
eeTypes "github.com/SigNoz/signoz/ee/types"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
@@ -24,7 +25,7 @@ func (p *Pat) Wrap(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var values []string
|
||||
var patToken string
|
||||
var pat types.StorablePersonalAccessToken
|
||||
var pat eeTypes.StorablePersonalAccessToken
|
||||
|
||||
for _, header := range p.headers {
|
||||
values = append(values, r.Header.Get(header))
|
||||
|
||||
@@ -18,4 +18,4 @@ COPY frontend/build/ /etc/signoz/web/
|
||||
RUN chmod 755 /root /root/signoz
|
||||
|
||||
ENTRYPOINT ["./signoz"]
|
||||
CMD ["-config", "/root/config/prometheus.yml"]
|
||||
CMD ["-config", "/root/config/prometheus.yml"]
|
||||
22
ee/query-service/Dockerfile.multi-arch
Normal file
22
ee/query-service/Dockerfile.multi-arch
Normal file
@@ -0,0 +1,22 @@
|
||||
ARG ALPINE_SHA="pass-a-valid-docker-sha-otherwise-this-will-fail"
|
||||
|
||||
FROM alpine@sha256:${ALPINE_SHA}
|
||||
LABEL maintainer="signoz"
|
||||
WORKDIR /root
|
||||
|
||||
ARG OS="linux"
|
||||
ARG ARCH
|
||||
|
||||
RUN apk update && \
|
||||
apk add ca-certificates && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
COPY ./target/${OS}-${ARCH}/signoz /root/signoz
|
||||
COPY ./conf/prometheus.yml /root/config/prometheus.yml
|
||||
COPY ./templates/email /root/templates
|
||||
COPY frontend/build/ /etc/signoz/web/
|
||||
|
||||
RUN chmod 755 /root /root/signoz
|
||||
|
||||
ENTRYPOINT ["./signoz"]
|
||||
CMD ["-config", "/root/config/prometheus.yml"]
|
||||
@@ -28,11 +28,10 @@ func NewDailyProvider(opts ...GenericProviderOption[*DailyProvider]) *DailyProvi
|
||||
}
|
||||
|
||||
dp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
|
||||
Reader: dp.reader,
|
||||
Cache: dp.cache,
|
||||
KeyGenerator: queryBuilder.NewKeyGenerator(),
|
||||
FluxInterval: dp.fluxInterval,
|
||||
FeatureLookup: dp.ff,
|
||||
Reader: dp.reader,
|
||||
Cache: dp.cache,
|
||||
KeyGenerator: queryBuilder.NewKeyGenerator(),
|
||||
FluxInterval: dp.fluxInterval,
|
||||
})
|
||||
|
||||
return dp
|
||||
|
||||
@@ -28,11 +28,10 @@ func NewHourlyProvider(opts ...GenericProviderOption[*HourlyProvider]) *HourlyPr
|
||||
}
|
||||
|
||||
hp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
|
||||
Reader: hp.reader,
|
||||
Cache: hp.cache,
|
||||
KeyGenerator: queryBuilder.NewKeyGenerator(),
|
||||
FluxInterval: hp.fluxInterval,
|
||||
FeatureLookup: hp.ff,
|
||||
Reader: hp.reader,
|
||||
Cache: hp.cache,
|
||||
KeyGenerator: queryBuilder.NewKeyGenerator(),
|
||||
FluxInterval: hp.fluxInterval,
|
||||
})
|
||||
|
||||
return hp
|
||||
|
||||
@@ -38,12 +38,6 @@ func WithKeyGenerator[T BaseProvider](keyGenerator cache.KeyGenerator) GenericPr
|
||||
}
|
||||
}
|
||||
|
||||
func WithFeatureLookup[T BaseProvider](ff interfaces.FeatureLookup) GenericProviderOption[T] {
|
||||
return func(p T) {
|
||||
p.GetBaseSeasonalProvider().ff = ff
|
||||
}
|
||||
}
|
||||
|
||||
func WithReader[T BaseProvider](reader interfaces.Reader) GenericProviderOption[T] {
|
||||
return func(p T) {
|
||||
p.GetBaseSeasonalProvider().reader = reader
|
||||
@@ -56,7 +50,6 @@ type BaseSeasonalProvider struct {
|
||||
fluxInterval time.Duration
|
||||
cache cache.Cache
|
||||
keyGenerator cache.KeyGenerator
|
||||
ff interfaces.FeatureLookup
|
||||
}
|
||||
|
||||
func (p *BaseSeasonalProvider) getQueryParams(req *GetAnomaliesRequest) *anomalyQueryParams {
|
||||
|
||||
@@ -27,11 +27,10 @@ func NewWeeklyProvider(opts ...GenericProviderOption[*WeeklyProvider]) *WeeklyPr
|
||||
}
|
||||
|
||||
wp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
|
||||
Reader: wp.reader,
|
||||
Cache: wp.cache,
|
||||
KeyGenerator: queryBuilder.NewKeyGenerator(),
|
||||
FluxInterval: wp.fluxInterval,
|
||||
FeatureLookup: wp.ff,
|
||||
Reader: wp.reader,
|
||||
Cache: wp.cache,
|
||||
KeyGenerator: queryBuilder.NewKeyGenerator(),
|
||||
FluxInterval: wp.fluxInterval,
|
||||
})
|
||||
|
||||
return wp
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"github.com/SigNoz/signoz/ee/query-service/license"
|
||||
"github.com/SigNoz/signoz/ee/query-service/usage"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/modules/preference"
|
||||
preferencecore "github.com/SigNoz/signoz/pkg/modules/preference/core"
|
||||
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||
@@ -21,6 +23,7 @@ import (
|
||||
rules "github.com/SigNoz/signoz/pkg/query-service/rules"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -54,6 +57,7 @@ type APIHandler struct {
|
||||
|
||||
// NewAPIHandler returns an APIHandler
|
||||
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) {
|
||||
preference := preference.NewAPI(preferencecore.NewPreference(preferencecore.NewStore(signoz.SQLStore), preferencetypes.NewDefaultPreferenceMap()))
|
||||
|
||||
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
|
||||
Reader: opts.DataConnector,
|
||||
@@ -71,6 +75,7 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
|
||||
UseTraceNewSchema: opts.UseTraceNewSchema,
|
||||
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
|
||||
Signoz: signoz,
|
||||
Preference: preference,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -157,7 +162,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
|
||||
router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(ah.getInvite)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/register", am.OpenAccess(ah.registerUser)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/login", am.OpenAccess(ah.loginUser)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/traces/{traceId}", am.ViewAccess(ah.searchTraces)).Methods(http.MethodGet)
|
||||
|
||||
// PAT APIs
|
||||
router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
eeTypes "github.com/SigNoz/signoz/ee/types"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/auth"
|
||||
baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
@@ -135,19 +135,12 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
|
||||
zap.String("cloudProvider", cloudProvider),
|
||||
)
|
||||
|
||||
newPAT := model.PAT{
|
||||
StorablePersonalAccessToken: types.StorablePersonalAccessToken{
|
||||
Token: generatePATToken(),
|
||||
UserID: integrationUser.ID,
|
||||
Name: integrationPATName,
|
||||
Role: baseconstants.ViewerGroup,
|
||||
ExpiresAt: 0,
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
},
|
||||
}
|
||||
newPAT := eeTypes.NewGettablePAT(
|
||||
integrationPATName,
|
||||
baseconstants.ViewerGroup,
|
||||
integrationUser.ID,
|
||||
0,
|
||||
)
|
||||
integrationPAT, err := ah.AppDao().CreatePAT(ctx, orgId, newPAT)
|
||||
if err != nil {
|
||||
return "", basemodel.InternalError(fmt.Errorf(
|
||||
|
||||
@@ -2,31 +2,24 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
"github.com/SigNoz/signoz/ee/types"
|
||||
eeTypes "github.com/SigNoz/signoz/ee/types"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/auth"
|
||||
baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func generatePATToken() string {
|
||||
// Generate a 32-byte random token.
|
||||
token := make([]byte, 32)
|
||||
rand.Read(token)
|
||||
// Encode the token in base64.
|
||||
encodedToken := base64.StdEncoding.EncodeToString(token)
|
||||
return encodedToken
|
||||
}
|
||||
|
||||
func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -43,31 +36,18 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
|
||||
}, nil)
|
||||
return
|
||||
}
|
||||
pat := model.PAT{
|
||||
StorablePersonalAccessToken: types.StorablePersonalAccessToken{
|
||||
Name: req.Name,
|
||||
Role: req.Role,
|
||||
ExpiresAt: req.ExpiresInDays,
|
||||
},
|
||||
}
|
||||
pat := eeTypes.NewGettablePAT(
|
||||
req.Name,
|
||||
req.Role,
|
||||
user.ID,
|
||||
req.ExpiresInDays,
|
||||
)
|
||||
err = validatePATRequest(pat)
|
||||
if err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// All the PATs are associated with the user creating the PAT.
|
||||
pat.UserID = user.ID
|
||||
pat.CreatedAt = time.Now()
|
||||
pat.UpdatedAt = time.Now()
|
||||
pat.LastUsed = 0
|
||||
pat.Token = generatePATToken()
|
||||
|
||||
if pat.ExpiresAt != 0 {
|
||||
// convert expiresAt to unix timestamp from days
|
||||
pat.ExpiresAt = time.Now().Unix() + (pat.ExpiresAt * 24 * 60 * 60)
|
||||
}
|
||||
|
||||
zap.L().Info("Got Create PAT request", zap.Any("pat", pat))
|
||||
var apierr basemodel.BaseApiError
|
||||
if pat, apierr = ah.AppDao().CreatePAT(ctx, user.OrgID, pat); apierr != nil {
|
||||
@@ -78,7 +58,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
|
||||
ah.Respond(w, &pat)
|
||||
}
|
||||
|
||||
func validatePATRequest(req model.PAT) error {
|
||||
func validatePATRequest(req types.GettablePAT) error {
|
||||
if req.Role == "" || (req.Role != baseconstants.ViewerGroup && req.Role != baseconstants.EditorGroup && req.Role != baseconstants.AdminGroup) {
|
||||
return fmt.Errorf("valid role is required")
|
||||
}
|
||||
@@ -94,7 +74,7 @@ func validatePATRequest(req model.PAT) error {
|
||||
func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
|
||||
req := model.PAT{}
|
||||
req := types.GettablePAT{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
@@ -116,7 +96,12 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
req.UpdatedByUserID = user.ID
|
||||
id := mux.Vars(r)["id"]
|
||||
idStr := mux.Vars(r)["id"]
|
||||
id, err := valuer.NewUUID(idStr)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
req.UpdatedAt = time.Now()
|
||||
zap.L().Info("Got Update PAT request", zap.Any("pat", req))
|
||||
var apierr basemodel.BaseApiError
|
||||
@@ -149,7 +134,12 @@ func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
id := mux.Vars(r)["id"]
|
||||
idStr := mux.Vars(r)["id"]
|
||||
id, err := valuer.NewUUID(idStr)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
user, err := auth.GetUserFromReqContext(r.Context())
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{
|
||||
@@ -159,7 +149,7 @@ func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Info("Revoke PAT with id", zap.String("id", id))
|
||||
zap.L().Info("Revoke PAT with id", zap.String("id", id.StringValue()))
|
||||
if apierr := ah.AppDao().RevokePAT(ctx, user.OrgID, id, user.ID); apierr != nil {
|
||||
RespondError(w, apierr, nil)
|
||||
return
|
||||
|
||||
@@ -88,28 +88,24 @@ func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
anomaly.WithCache[*anomaly.WeeklyProvider](aH.opts.Cache),
|
||||
anomaly.WithKeyGenerator[*anomaly.WeeklyProvider](queryBuilder.NewKeyGenerator()),
|
||||
anomaly.WithReader[*anomaly.WeeklyProvider](aH.opts.DataConnector),
|
||||
anomaly.WithFeatureLookup[*anomaly.WeeklyProvider](aH.opts.FeatureFlags),
|
||||
)
|
||||
case anomaly.SeasonalityDaily:
|
||||
provider = anomaly.NewDailyProvider(
|
||||
anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache),
|
||||
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
|
||||
anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector),
|
||||
anomaly.WithFeatureLookup[*anomaly.DailyProvider](aH.opts.FeatureFlags),
|
||||
)
|
||||
case anomaly.SeasonalityHourly:
|
||||
provider = anomaly.NewHourlyProvider(
|
||||
anomaly.WithCache[*anomaly.HourlyProvider](aH.opts.Cache),
|
||||
anomaly.WithKeyGenerator[*anomaly.HourlyProvider](queryBuilder.NewKeyGenerator()),
|
||||
anomaly.WithReader[*anomaly.HourlyProvider](aH.opts.DataConnector),
|
||||
anomaly.WithFeatureLookup[*anomaly.HourlyProvider](aH.opts.FeatureFlags),
|
||||
)
|
||||
default:
|
||||
provider = anomaly.NewDailyProvider(
|
||||
anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache),
|
||||
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
|
||||
anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector),
|
||||
anomaly.WithFeatureLookup[*anomaly.DailyProvider](aH.opts.FeatureFlags),
|
||||
)
|
||||
}
|
||||
anomalies, err := provider.GetAnomalies(r.Context(), &anomaly.GetAnomaliesRequest{Params: queryRangeParams})
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/app/db"
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (ah *APIHandler) searchTraces(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if !ah.CheckFeature(basemodel.SmartTraceDetail) {
|
||||
zap.L().Info("SmartTraceDetail feature is not enabled in this plan")
|
||||
ah.APIHandler.SearchTraces(w, r)
|
||||
return
|
||||
}
|
||||
searchTracesParams, err := baseapp.ParseSearchTracesParams(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading params")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := ah.opts.DataConnector.SearchTraces(r.Context(), searchTracesParams, db.SmartTraceAlgorithm)
|
||||
if ah.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
ah.WriteJSON(w, r, result)
|
||||
|
||||
}
|
||||
@@ -5,38 +5,33 @@ import (
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
basechr "github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
)
|
||||
|
||||
type ClickhouseReader struct {
|
||||
conn clickhouse.Conn
|
||||
appdb *sqlx.DB
|
||||
appdb sqlstore.SQLStore
|
||||
*basechr.ClickHouseReader
|
||||
}
|
||||
|
||||
func NewDataConnector(
|
||||
localDB *sqlx.DB,
|
||||
ch clickhouse.Conn,
|
||||
promConfigPath string,
|
||||
lm interfaces.FeatureLookup,
|
||||
sqlDB sqlstore.SQLStore,
|
||||
telemetryStore telemetrystore.TelemetryStore,
|
||||
prometheus prometheus.Prometheus,
|
||||
cluster string,
|
||||
useLogsNewSchema bool,
|
||||
useTraceNewSchema bool,
|
||||
fluxIntervalForTraceDetail time.Duration,
|
||||
cache cache.Cache,
|
||||
) *ClickhouseReader {
|
||||
chReader := basechr.NewReader(localDB, ch, promConfigPath, lm, cluster, useLogsNewSchema, useTraceNewSchema, fluxIntervalForTraceDetail, cache)
|
||||
chReader := basechr.NewReader(sqlDB, telemetryStore, prometheus, cluster, useLogsNewSchema, useTraceNewSchema, fluxIntervalForTraceDetail, cache)
|
||||
return &ClickhouseReader{
|
||||
conn: ch,
|
||||
appdb: localDB,
|
||||
conn: telemetryStore.ClickhouseDB(),
|
||||
appdb: sqlDB,
|
||||
ClickHouseReader: chReader,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ClickhouseReader) Start(readerReady chan bool) {
|
||||
r.ClickHouseReader.Start(readerReady)
|
||||
}
|
||||
|
||||
@@ -18,13 +18,14 @@ import (
|
||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||
"github.com/SigNoz/signoz/ee/query-service/dao"
|
||||
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
||||
"github.com/SigNoz/signoz/ee/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/ee/query-service/rules"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/auth"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/web"
|
||||
@@ -43,13 +44,11 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
|
||||
opAmpModel "github.com/SigNoz/signoz/pkg/query-service/app/opamp/model"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/preferences"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/cache"
|
||||
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/healthcheck"
|
||||
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
pqle "github.com/SigNoz/signoz/pkg/query-service/pqlEngine"
|
||||
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
@@ -116,10 +115,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := preferences.InitDB(serverOptions.SigNoz.SQLStore.SQLxDB()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -137,27 +132,22 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
|
||||
// set license manager as feature flag provider in dao
|
||||
modelDao.SetFlagProvider(lm)
|
||||
readerReady := make(chan bool)
|
||||
|
||||
fluxIntervalForTraceDetail, err := time.ParseDuration(serverOptions.FluxIntervalForTraceDetail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reader interfaces.DataConnector
|
||||
qb := db.NewDataConnector(
|
||||
serverOptions.SigNoz.SQLStore.SQLxDB(),
|
||||
serverOptions.SigNoz.TelemetryStore.ClickHouseDB(),
|
||||
serverOptions.PromConfigPath,
|
||||
lm,
|
||||
reader := db.NewDataConnector(
|
||||
serverOptions.SigNoz.SQLStore,
|
||||
serverOptions.SigNoz.TelemetryStore,
|
||||
serverOptions.SigNoz.Prometheus,
|
||||
serverOptions.Cluster,
|
||||
serverOptions.UseLogsNewSchema,
|
||||
serverOptions.UseTraceNewSchema,
|
||||
fluxIntervalForTraceDetail,
|
||||
serverOptions.SigNoz.Cache,
|
||||
)
|
||||
go qb.Start(readerReady)
|
||||
reader = qb
|
||||
|
||||
skipConfig := &basemodel.SkipConfig{}
|
||||
if serverOptions.SkipTopLvlOpsPath != "" {
|
||||
@@ -176,19 +166,18 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
c = cache.NewCache(cacheOpts)
|
||||
}
|
||||
|
||||
<-readerReady
|
||||
rm, err := makeRulesManager(
|
||||
serverOptions.PromConfigPath,
|
||||
serverOptions.RuleRepoURL,
|
||||
serverOptions.SigNoz.SQLStore.SQLxDB(),
|
||||
reader,
|
||||
c,
|
||||
serverOptions.DisableRules,
|
||||
lm,
|
||||
serverOptions.UseLogsNewSchema,
|
||||
serverOptions.UseTraceNewSchema,
|
||||
serverOptions.SigNoz.Alertmanager,
|
||||
serverOptions.SigNoz.SQLStore,
|
||||
serverOptions.SigNoz.TelemetryStore,
|
||||
serverOptions.SigNoz.Prometheus,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -233,7 +222,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
// start the usagemanager
|
||||
usageManager, err := usage.New(modelDao, lm.GetRepo(), serverOptions.SigNoz.TelemetryStore.ClickHouseDB(), serverOptions.Config.TelemetryStore.ClickHouse.DSN)
|
||||
usageManager, err := usage.New(modelDao, lm.GetRepo(), serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.Config.TelemetryStore.Clickhouse.DSN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -304,7 +293,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
&opAmpModel.AllAgents, agentConfMgr,
|
||||
)
|
||||
|
||||
errorList := qb.PreloadMetricsMetadata(context.Background())
|
||||
errorList := reader.PreloadMetricsMetadata(context.Background())
|
||||
for _, er := range errorList {
|
||||
zap.L().Error("failed to preload metrics metadata", zap.Error(er))
|
||||
}
|
||||
@@ -385,7 +374,6 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
apiHandler.RegisterMessagingQueuesRoutes(r, am)
|
||||
apiHandler.RegisterThirdPartyApiRoutes(r, am)
|
||||
apiHandler.MetricExplorerRoutes(r, am)
|
||||
apiHandler.RegisterTraceFunnelsRoutes(r, am)
|
||||
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
@@ -538,33 +526,27 @@ func (s *Server) Stop() error {
|
||||
}
|
||||
|
||||
func makeRulesManager(
|
||||
promConfigPath,
|
||||
ruleRepoURL string,
|
||||
db *sqlx.DB,
|
||||
ch baseint.Reader,
|
||||
cache cache.Cache,
|
||||
disableRules bool,
|
||||
fm baseint.FeatureLookup,
|
||||
useLogsNewSchema bool,
|
||||
useTraceNewSchema bool,
|
||||
alertmanager alertmanager.Alertmanager,
|
||||
sqlstore sqlstore.SQLStore,
|
||||
telemetryStore telemetrystore.TelemetryStore,
|
||||
prometheus prometheus.Prometheus,
|
||||
) (*baserules.Manager, error) {
|
||||
// create engine
|
||||
pqle, err := pqle.FromConfigPath(promConfigPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create pql engine : %v", err)
|
||||
}
|
||||
|
||||
// create manager opts
|
||||
managerOpts := &baserules.ManagerOptions{
|
||||
PqlEngine: pqle,
|
||||
TelemetryStore: telemetryStore,
|
||||
Prometheus: prometheus,
|
||||
RepoURL: ruleRepoURL,
|
||||
DBConn: db,
|
||||
Context: context.Background(),
|
||||
Logger: zap.L(),
|
||||
DisableRules: disableRules,
|
||||
FeatureFlags: fm,
|
||||
Reader: ch,
|
||||
Cache: cache,
|
||||
EvalDelay: baseconst.GetEvalDelay(),
|
||||
|
||||
@@ -4,13 +4,13 @@ import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
"github.com/SigNoz/signoz/ee/types"
|
||||
basedao "github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
ossTypes "github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/google/uuid"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
@@ -36,11 +36,11 @@ type ModelDao interface {
|
||||
DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError
|
||||
GetDomainByEmail(ctx context.Context, email string) (*types.GettableOrgDomain, basemodel.BaseApiError)
|
||||
|
||||
CreatePAT(ctx context.Context, orgID string, p model.PAT) (model.PAT, basemodel.BaseApiError)
|
||||
UpdatePAT(ctx context.Context, orgID string, p model.PAT, id string) basemodel.BaseApiError
|
||||
GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError)
|
||||
GetPATByID(ctx context.Context, orgID string, id string) (*model.PAT, basemodel.BaseApiError)
|
||||
CreatePAT(ctx context.Context, orgID string, p types.GettablePAT) (types.GettablePAT, basemodel.BaseApiError)
|
||||
UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id valuer.UUID) basemodel.BaseApiError
|
||||
GetPAT(ctx context.Context, pat string) (*types.GettablePAT, basemodel.BaseApiError)
|
||||
GetPATByID(ctx context.Context, orgID string, id valuer.UUID) (*types.GettablePAT, basemodel.BaseApiError)
|
||||
GetUserByPAT(ctx context.Context, orgID string, token string) (*ossTypes.GettableUser, basemodel.BaseApiError)
|
||||
ListPATs(ctx context.Context, orgID string) ([]model.PAT, basemodel.BaseApiError)
|
||||
RevokePAT(ctx context.Context, orgID string, id string, userID string) basemodel.BaseApiError
|
||||
ListPATs(ctx context.Context, orgID string) ([]types.GettablePAT, basemodel.BaseApiError)
|
||||
RevokePAT(ctx context.Context, orgID string, id valuer.UUID, userID string) basemodel.BaseApiError
|
||||
}
|
||||
|
||||
@@ -6,45 +6,53 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
"github.com/SigNoz/signoz/ee/types"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
ossTypes "github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (m *modelDao) CreatePAT(ctx context.Context, orgID string, p model.PAT) (model.PAT, basemodel.BaseApiError) {
|
||||
func (m *modelDao) CreatePAT(ctx context.Context, orgID string, p types.GettablePAT) (types.GettablePAT, basemodel.BaseApiError) {
|
||||
p.StorablePersonalAccessToken.OrgID = orgID
|
||||
p.StorablePersonalAccessToken.ID = valuer.GenerateUUID()
|
||||
_, err := m.DB().NewInsert().
|
||||
Model(&p.StorablePersonalAccessToken).
|
||||
Returning("id").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("Failed to insert PAT in db, err: %v", zap.Error(err))
|
||||
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
|
||||
return types.GettablePAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
|
||||
}
|
||||
|
||||
createdByUser, _ := m.GetUser(ctx, p.UserID)
|
||||
if createdByUser == nil {
|
||||
p.CreatedByUser = model.User{
|
||||
p.CreatedByUser = types.PatUser{
|
||||
NotFound: true,
|
||||
}
|
||||
} else {
|
||||
p.CreatedByUser = model.User{
|
||||
Id: createdByUser.ID,
|
||||
Name: createdByUser.Name,
|
||||
Email: createdByUser.Email,
|
||||
CreatedAt: createdByUser.CreatedAt.Unix(),
|
||||
ProfilePictureURL: createdByUser.ProfilePictureURL,
|
||||
NotFound: false,
|
||||
p.CreatedByUser = types.PatUser{
|
||||
User: ossTypes.User{
|
||||
ID: createdByUser.ID,
|
||||
Name: createdByUser.Name,
|
||||
Email: createdByUser.Email,
|
||||
TimeAuditable: ossTypes.TimeAuditable{
|
||||
CreatedAt: createdByUser.CreatedAt,
|
||||
UpdatedAt: createdByUser.UpdatedAt,
|
||||
},
|
||||
ProfilePictureURL: createdByUser.ProfilePictureURL,
|
||||
},
|
||||
NotFound: false,
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p model.PAT, id string) basemodel.BaseApiError {
|
||||
func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id valuer.UUID) basemodel.BaseApiError {
|
||||
_, err := m.DB().NewUpdate().
|
||||
Model(&p.StorablePersonalAccessToken).
|
||||
Column("role", "name", "updated_at", "updated_by_user_id").
|
||||
Where("id = ?", id).
|
||||
Where("id = ?", id.StringValue()).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("revoked = false").
|
||||
Exec(ctx)
|
||||
@@ -55,7 +63,7 @@ func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p model.PAT, id
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]model.PAT, basemodel.BaseApiError) {
|
||||
func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]types.GettablePAT, basemodel.BaseApiError) {
|
||||
pats := []types.StorablePersonalAccessToken{}
|
||||
|
||||
if err := m.DB().NewSelect().
|
||||
@@ -68,41 +76,51 @@ func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]model.PAT, bas
|
||||
return nil, model.InternalError(fmt.Errorf("failed to fetch PATs"))
|
||||
}
|
||||
|
||||
patsWithUsers := []model.PAT{}
|
||||
patsWithUsers := []types.GettablePAT{}
|
||||
for i := range pats {
|
||||
patWithUser := model.PAT{
|
||||
patWithUser := types.GettablePAT{
|
||||
StorablePersonalAccessToken: pats[i],
|
||||
}
|
||||
|
||||
createdByUser, _ := m.GetUser(ctx, pats[i].UserID)
|
||||
if createdByUser == nil {
|
||||
patWithUser.CreatedByUser = model.User{
|
||||
patWithUser.CreatedByUser = types.PatUser{
|
||||
NotFound: true,
|
||||
}
|
||||
} else {
|
||||
patWithUser.CreatedByUser = model.User{
|
||||
Id: createdByUser.ID,
|
||||
Name: createdByUser.Name,
|
||||
Email: createdByUser.Email,
|
||||
CreatedAt: createdByUser.CreatedAt.Unix(),
|
||||
ProfilePictureURL: createdByUser.ProfilePictureURL,
|
||||
NotFound: false,
|
||||
patWithUser.CreatedByUser = types.PatUser{
|
||||
User: ossTypes.User{
|
||||
ID: createdByUser.ID,
|
||||
Name: createdByUser.Name,
|
||||
Email: createdByUser.Email,
|
||||
TimeAuditable: ossTypes.TimeAuditable{
|
||||
CreatedAt: createdByUser.CreatedAt,
|
||||
UpdatedAt: createdByUser.UpdatedAt,
|
||||
},
|
||||
ProfilePictureURL: createdByUser.ProfilePictureURL,
|
||||
},
|
||||
NotFound: false,
|
||||
}
|
||||
}
|
||||
|
||||
updatedByUser, _ := m.GetUser(ctx, pats[i].UpdatedByUserID)
|
||||
if updatedByUser == nil {
|
||||
patWithUser.UpdatedByUser = model.User{
|
||||
patWithUser.UpdatedByUser = types.PatUser{
|
||||
NotFound: true,
|
||||
}
|
||||
} else {
|
||||
patWithUser.UpdatedByUser = model.User{
|
||||
Id: updatedByUser.ID,
|
||||
Name: updatedByUser.Name,
|
||||
Email: updatedByUser.Email,
|
||||
CreatedAt: updatedByUser.CreatedAt.Unix(),
|
||||
ProfilePictureURL: updatedByUser.ProfilePictureURL,
|
||||
NotFound: false,
|
||||
patWithUser.UpdatedByUser = types.PatUser{
|
||||
User: ossTypes.User{
|
||||
ID: updatedByUser.ID,
|
||||
Name: updatedByUser.Name,
|
||||
Email: updatedByUser.Email,
|
||||
TimeAuditable: ossTypes.TimeAuditable{
|
||||
CreatedAt: updatedByUser.CreatedAt,
|
||||
UpdatedAt: updatedByUser.UpdatedAt,
|
||||
},
|
||||
ProfilePictureURL: updatedByUser.ProfilePictureURL,
|
||||
},
|
||||
NotFound: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,14 +129,14 @@ func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]model.PAT, bas
|
||||
return patsWithUsers, nil
|
||||
}
|
||||
|
||||
func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id string, userID string) basemodel.BaseApiError {
|
||||
func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id valuer.UUID, userID string) basemodel.BaseApiError {
|
||||
updatedAt := time.Now().Unix()
|
||||
_, err := m.DB().NewUpdate().
|
||||
Model(&types.StorablePersonalAccessToken{}).
|
||||
Set("revoked = ?", true).
|
||||
Set("updated_by_user_id = ?", userID).
|
||||
Set("updated_at = ?", updatedAt).
|
||||
Where("id = ?", id).
|
||||
Where("id = ?", id.StringValue()).
|
||||
Where("org_id = ?", orgID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
@@ -128,7 +146,7 @@ func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id string, userI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemodel.BaseApiError) {
|
||||
func (m *modelDao) GetPAT(ctx context.Context, token string) (*types.GettablePAT, basemodel.BaseApiError) {
|
||||
pats := []types.StorablePersonalAccessToken{}
|
||||
|
||||
if err := m.DB().NewSelect().
|
||||
@@ -146,19 +164,19 @@ func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemo
|
||||
}
|
||||
}
|
||||
|
||||
patWithUser := model.PAT{
|
||||
patWithUser := types.GettablePAT{
|
||||
StorablePersonalAccessToken: pats[0],
|
||||
}
|
||||
|
||||
return &patWithUser, nil
|
||||
}
|
||||
|
||||
func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*model.PAT, basemodel.BaseApiError) {
|
||||
func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id valuer.UUID) (*types.GettablePAT, basemodel.BaseApiError) {
|
||||
pats := []types.StorablePersonalAccessToken{}
|
||||
|
||||
if err := m.DB().NewSelect().
|
||||
Model(&pats).
|
||||
Where("id = ?", id).
|
||||
Where("id = ?", id.StringValue()).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("revoked = false").
|
||||
Scan(ctx); err != nil {
|
||||
@@ -172,7 +190,7 @@ func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*mo
|
||||
}
|
||||
}
|
||||
|
||||
patWithUser := model.PAT{
|
||||
patWithUser := types.GettablePAT{
|
||||
StorablePersonalAccessToken: pats[0],
|
||||
}
|
||||
|
||||
@@ -180,8 +198,8 @@ func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*mo
|
||||
}
|
||||
|
||||
// deprecated
|
||||
func (m *modelDao) GetUserByPAT(ctx context.Context, orgID string, token string) (*types.GettableUser, basemodel.BaseApiError) {
|
||||
users := []types.GettableUser{}
|
||||
func (m *modelDao) GetUserByPAT(ctx context.Context, orgID string, token string) (*ossTypes.GettableUser, basemodel.BaseApiError) {
|
||||
users := []ossTypes.GettableUser{}
|
||||
|
||||
if err := m.DB().NewSelect().
|
||||
Model(&users).
|
||||
|
||||
@@ -7,6 +7,5 @@ import (
|
||||
// Connector defines methods for interaction
|
||||
// with o11y data. for example - clickhouse
|
||||
type DataConnector interface {
|
||||
Start(readerReady chan bool)
|
||||
baseint.Reader
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
@@ -32,10 +30,6 @@ func initZapLog() *zap.Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
func init() {
|
||||
prommodel.NameValidationScheme = prommodel.UTF8Validation
|
||||
}
|
||||
|
||||
func main() {
|
||||
var promConfigPath, skipTopLvlOpsPath string
|
||||
|
||||
@@ -89,6 +83,7 @@ func main() {
|
||||
MaxIdleConns: maxIdleConns,
|
||||
MaxOpenConns: maxOpenConns,
|
||||
DialTimeout: dialTimeout,
|
||||
Config: promConfigPath,
|
||||
})
|
||||
if err != nil {
|
||||
zap.L().Fatal("Failed to create config", zap.Error(err))
|
||||
@@ -110,7 +105,7 @@ func main() {
|
||||
signoz.NewTelemetryStoreProviderFactories(),
|
||||
)
|
||||
if err != nil {
|
||||
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
|
||||
zap.L().Fatal("Failed to create signoz", zap.Error(err))
|
||||
}
|
||||
|
||||
jwtSecret := os.Getenv("SIGNOZ_JWT_SECRET")
|
||||
|
||||
@@ -157,8 +157,6 @@ func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
|
||||
}
|
||||
|
||||
switch planName {
|
||||
case PlanNameTeams:
|
||||
features = append(features, ProPlan...)
|
||||
case PlanNameEnterprise:
|
||||
features = append(features, EnterprisePlan...)
|
||||
case PlanNameBasic:
|
||||
|
||||
@@ -74,21 +74,21 @@ func TestNewLicenseV3(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Parse the entire license properly",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"ENTERPRISE"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||
pass: true,
|
||||
expected: &LicenseV3{
|
||||
ID: "does-not-matter",
|
||||
Key: "does-not-matter-key",
|
||||
Data: map[string]interface{}{
|
||||
"plan": map[string]interface{}{
|
||||
"name": "TEAMS",
|
||||
"name": "ENTERPRISE",
|
||||
},
|
||||
"category": "FREE",
|
||||
"status": "ACTIVE",
|
||||
"valid_from": float64(1730899309),
|
||||
"valid_until": float64(-1),
|
||||
},
|
||||
PlanName: PlanNameTeams,
|
||||
PlanName: PlanNameEnterprise,
|
||||
ValidFrom: 1730899309,
|
||||
ValidUntil: -1,
|
||||
Status: "ACTIVE",
|
||||
@@ -98,14 +98,14 @@ func TestNewLicenseV3(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Fallback to basic plan if license status is invalid",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INVALID","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INVALID","plan":{"name":"ENTERPRISE"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||
pass: true,
|
||||
expected: &LicenseV3{
|
||||
ID: "does-not-matter",
|
||||
Key: "does-not-matter-key",
|
||||
Data: map[string]interface{}{
|
||||
"plan": map[string]interface{}{
|
||||
"name": "TEAMS",
|
||||
"name": "ENTERPRISE",
|
||||
},
|
||||
"category": "FREE",
|
||||
"status": "INVALID",
|
||||
@@ -122,21 +122,21 @@ func TestNewLicenseV3(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "fallback states for validFrom and validUntil",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from":1234.456,"valid_until":5678.567}`),
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"ENTERPRISE"},"valid_from":1234.456,"valid_until":5678.567}`),
|
||||
pass: true,
|
||||
expected: &LicenseV3{
|
||||
ID: "does-not-matter",
|
||||
Key: "does-not-matter-key",
|
||||
Data: map[string]interface{}{
|
||||
"plan": map[string]interface{}{
|
||||
"name": "TEAMS",
|
||||
"name": "ENTERPRISE",
|
||||
},
|
||||
"valid_from": 1234.456,
|
||||
"valid_until": 5678.567,
|
||||
"category": "FREE",
|
||||
"status": "ACTIVE",
|
||||
},
|
||||
PlanName: PlanNameTeams,
|
||||
PlanName: PlanNameEnterprise,
|
||||
ValidFrom: 1234,
|
||||
ValidUntil: 5678,
|
||||
Status: "ACTIVE",
|
||||
|
||||
@@ -1,25 +1,7 @@
|
||||
package model
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/types"
|
||||
|
||||
type User struct {
|
||||
Id string `json:"id" db:"id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Email string `json:"email" db:"email"`
|
||||
CreatedAt int64 `json:"createdAt" db:"created_at"`
|
||||
ProfilePictureURL string `json:"profilePictureURL" db:"profile_picture_url"`
|
||||
NotFound bool `json:"notFound"`
|
||||
}
|
||||
|
||||
type CreatePATRequestBody struct {
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role"`
|
||||
ExpiresInDays int64 `json:"expiresInDays"`
|
||||
}
|
||||
|
||||
type PAT struct {
|
||||
CreatedByUser User `json:"createdByUser"`
|
||||
UpdatedByUser User `json:"updatedByUser"`
|
||||
|
||||
types.StorablePersonalAccessToken
|
||||
}
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
const SSO = "SSO"
|
||||
const Basic = "BASIC_PLAN"
|
||||
const Pro = "PRO_PLAN"
|
||||
const Enterprise = "ENTERPRISE_PLAN"
|
||||
|
||||
var (
|
||||
PlanNameEnterprise = "ENTERPRISE"
|
||||
PlanNameTeams = "TEAMS"
|
||||
PlanNameBasic = "BASIC"
|
||||
)
|
||||
|
||||
var (
|
||||
MapOldPlanKeyToNewPlanName map[string]string = map[string]string{PlanNameBasic: Basic, PlanNameTeams: Pro, PlanNameEnterprise: Enterprise}
|
||||
MapOldPlanKeyToNewPlanName map[string]string = map[string]string{PlanNameBasic: Basic, PlanNameEnterprise: Enterprise}
|
||||
)
|
||||
|
||||
var (
|
||||
LicenseStatusInvalid = "INVALID"
|
||||
)
|
||||
|
||||
const DisableUpsell = "DISABLE_UPSELL"
|
||||
const Onboarding = "ONBOARDING"
|
||||
const ChatSupport = "CHAT_SUPPORT"
|
||||
const Gateway = "GATEWAY"
|
||||
@@ -38,90 +34,6 @@ var BasicPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.OSS,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: DisableUpsell,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.SmartTraceDetail,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.CustomMetricsFunction,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderPanels,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderAlerts,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelSlack,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelWebhook,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelPagerduty,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelEmail,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.UseSpanMetrics,
|
||||
Active: false,
|
||||
@@ -150,142 +62,6 @@ var BasicPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.HostsInfraMonitoring,
|
||||
Active: constants.EnableHostsInfraMonitoring(),
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.TraceFunnels,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
}
|
||||
|
||||
var ProPlan = basemodel.FeatureSet{
|
||||
basemodel.Feature{
|
||||
Name: SSO,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.OSS,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.SmartTraceDetail,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.CustomMetricsFunction,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderPanels,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderAlerts,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelSlack,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelWebhook,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelPagerduty,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelEmail,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.UseSpanMetrics,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: Gateway,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: PremiumSupport,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AnomalyDetection,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.HostsInfraMonitoring,
|
||||
Active: constants.EnableHostsInfraMonitoring(),
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.TraceFunnels,
|
||||
Active: false,
|
||||
@@ -303,83 +79,6 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.OSS,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.SmartTraceDetail,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.CustomMetricsFunction,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderPanels,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderAlerts,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelSlack,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelWebhook,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelPagerduty,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelEmail,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.UseSpanMetrics,
|
||||
Active: false,
|
||||
@@ -422,13 +121,6 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.HostsInfraMonitoring,
|
||||
Active: constants.EnableHostsInfraMonitoring(),
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.TraceFunnels,
|
||||
Active: false,
|
||||
|
||||
@@ -53,7 +53,6 @@ type AnomalyRule struct {
|
||||
func NewAnomalyRule(
|
||||
id string,
|
||||
p *baserules.PostableRule,
|
||||
featureFlags interfaces.FeatureLookup,
|
||||
reader interfaces.Reader,
|
||||
cache cache.Cache,
|
||||
opts ...baserules.RuleOption,
|
||||
@@ -89,10 +88,9 @@ func NewAnomalyRule(
|
||||
zap.L().Info("using seasonality", zap.String("seasonality", t.seasonality.String()))
|
||||
|
||||
querierOptsV2 := querierV2.QuerierOptions{
|
||||
Reader: reader,
|
||||
Cache: cache,
|
||||
KeyGenerator: queryBuilder.NewKeyGenerator(),
|
||||
FeatureLookup: featureFlags,
|
||||
Reader: reader,
|
||||
Cache: cache,
|
||||
KeyGenerator: queryBuilder.NewKeyGenerator(),
|
||||
}
|
||||
|
||||
t.querierV2 = querierV2.NewQuerier(querierOptsV2)
|
||||
@@ -102,21 +100,18 @@ func NewAnomalyRule(
|
||||
anomaly.WithCache[*anomaly.HourlyProvider](cache),
|
||||
anomaly.WithKeyGenerator[*anomaly.HourlyProvider](queryBuilder.NewKeyGenerator()),
|
||||
anomaly.WithReader[*anomaly.HourlyProvider](reader),
|
||||
anomaly.WithFeatureLookup[*anomaly.HourlyProvider](featureFlags),
|
||||
)
|
||||
} else if t.seasonality == anomaly.SeasonalityDaily {
|
||||
t.provider = anomaly.NewDailyProvider(
|
||||
anomaly.WithCache[*anomaly.DailyProvider](cache),
|
||||
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
|
||||
anomaly.WithReader[*anomaly.DailyProvider](reader),
|
||||
anomaly.WithFeatureLookup[*anomaly.DailyProvider](featureFlags),
|
||||
)
|
||||
} else if t.seasonality == anomaly.SeasonalityWeekly {
|
||||
t.provider = anomaly.NewWeeklyProvider(
|
||||
anomaly.WithCache[*anomaly.WeeklyProvider](cache),
|
||||
anomaly.WithKeyGenerator[*anomaly.WeeklyProvider](queryBuilder.NewKeyGenerator()),
|
||||
anomaly.WithReader[*anomaly.WeeklyProvider](reader),
|
||||
anomaly.WithFeatureLookup[*anomaly.WeeklyProvider](featureFlags),
|
||||
)
|
||||
}
|
||||
return &t, nil
|
||||
|
||||
@@ -23,7 +23,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
tr, err := baserules.NewThresholdRule(
|
||||
ruleId,
|
||||
opts.Rule,
|
||||
opts.FF,
|
||||
opts.Reader,
|
||||
opts.UseLogsNewSchema,
|
||||
opts.UseTraceNewSchema,
|
||||
@@ -48,7 +47,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.Rule,
|
||||
opts.Logger,
|
||||
opts.Reader,
|
||||
opts.ManagerOpts.PqlEngine,
|
||||
opts.ManagerOpts.Prometheus,
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
)
|
||||
|
||||
@@ -66,7 +65,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
ar, err := NewAnomalyRule(
|
||||
ruleId,
|
||||
opts.Rule,
|
||||
opts.FF,
|
||||
opts.Reader,
|
||||
opts.Cache,
|
||||
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||
@@ -123,7 +121,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
rule, err = baserules.NewThresholdRule(
|
||||
alertname,
|
||||
parsedRule,
|
||||
opts.FF,
|
||||
opts.Reader,
|
||||
opts.UseLogsNewSchema,
|
||||
opts.UseTraceNewSchema,
|
||||
@@ -145,7 +142,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
parsedRule,
|
||||
opts.Logger,
|
||||
opts.Reader,
|
||||
opts.ManagerOpts.PqlEngine,
|
||||
opts.ManagerOpts.Prometheus,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
@@ -160,7 +157,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
rule, err = NewAnomalyRule(
|
||||
alertname,
|
||||
parsedRule,
|
||||
opts.FF,
|
||||
opts.Reader,
|
||||
opts.Cache,
|
||||
baserules.WithSendAlways(),
|
||||
|
||||
@@ -2,11 +2,30 @@ package postgressqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
var (
|
||||
Identity = "id"
|
||||
Integer = "bigint"
|
||||
Text = "text"
|
||||
)
|
||||
|
||||
var (
|
||||
Org = "org"
|
||||
User = "user"
|
||||
)
|
||||
|
||||
var (
|
||||
OrgReference = `("org_id") REFERENCES "organizations" ("id")`
|
||||
UserReference = `("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE`
|
||||
)
|
||||
|
||||
type dialect struct {
|
||||
}
|
||||
|
||||
@@ -174,7 +193,10 @@ func (dialect *dialect) TableExists(ctx context.Context, bun bun.IDB, table inte
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, cb func(context.Context) error) error {
|
||||
func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, references []string, cb func(context.Context) error) error {
|
||||
if len(references) == 0 {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot run migration without reference")
|
||||
}
|
||||
exists, err := dialect.TableExists(ctx, bun, newModel)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -183,12 +205,25 @@ func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.I
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = bun.
|
||||
var fkReferences []string
|
||||
for _, reference := range references {
|
||||
if reference == Org && !slices.Contains(fkReferences, OrgReference) {
|
||||
fkReferences = append(fkReferences, OrgReference)
|
||||
} else if reference == User && !slices.Contains(fkReferences, UserReference) {
|
||||
fkReferences = append(fkReferences, UserReference)
|
||||
}
|
||||
}
|
||||
|
||||
createTable := bun.
|
||||
NewCreateTable().
|
||||
IfNotExists().
|
||||
Model(newModel).
|
||||
Exec(ctx)
|
||||
Model(newModel)
|
||||
|
||||
for _, fk := range fkReferences {
|
||||
createTable = createTable.ForeignKey(fk)
|
||||
}
|
||||
|
||||
_, err = createTable.Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -209,3 +244,123 @@ func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.I
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dialect *dialect) AddNotNullDefaultToColumn(ctx context.Context, bun bun.IDB, table string, column, columnType, defaultValue string) error {
|
||||
query := fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s, ALTER COLUMN %s SET NOT NULL", table, column, defaultValue, column)
|
||||
if _, err := bun.ExecContext(ctx, query); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dialect *dialect) UpdatePrimaryKey(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, reference string, cb func(context.Context) error) error {
|
||||
if reference == "" {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot run migration without reference")
|
||||
}
|
||||
oldTableName := bun.Dialect().Tables().Get(reflect.TypeOf(oldModel)).Name
|
||||
newTableName := bun.Dialect().Tables().Get(reflect.TypeOf(newModel)).Name
|
||||
|
||||
columnType, err := dialect.GetColumnType(ctx, bun, oldTableName, Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if columnType == Text {
|
||||
return nil
|
||||
}
|
||||
|
||||
fkReference := ""
|
||||
if reference == Org {
|
||||
fkReference = OrgReference
|
||||
} else if reference == User {
|
||||
fkReference = UserReference
|
||||
}
|
||||
|
||||
_, err = bun.
|
||||
NewCreateTable().
|
||||
IfNotExists().
|
||||
Model(newModel).
|
||||
ForeignKey(fkReference).
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cb(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = bun.
|
||||
NewDropTable().
|
||||
IfExists().
|
||||
Model(oldModel).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = bun.
|
||||
ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s RENAME TO %s", newTableName, oldTableName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dialect *dialect) AddPrimaryKey(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, reference string, cb func(context.Context) error) error {
|
||||
if reference == "" {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot run migration without reference")
|
||||
}
|
||||
oldTableName := bun.Dialect().Tables().Get(reflect.TypeOf(oldModel)).Name
|
||||
newTableName := bun.Dialect().Tables().Get(reflect.TypeOf(newModel)).Name
|
||||
|
||||
identityExists, err := dialect.ColumnExists(ctx, bun, oldTableName, Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if identityExists {
|
||||
return nil
|
||||
}
|
||||
|
||||
fkReference := ""
|
||||
if reference == Org {
|
||||
fkReference = OrgReference
|
||||
} else if reference == User {
|
||||
fkReference = UserReference
|
||||
}
|
||||
|
||||
_, err = bun.
|
||||
NewCreateTable().
|
||||
IfNotExists().
|
||||
Model(newModel).
|
||||
ForeignKey(fkReference).
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cb(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = bun.
|
||||
NewDropTable().
|
||||
IfExists().
|
||||
Model(oldModel).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = bun.
|
||||
ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s RENAME TO %s", newTableName, oldTableName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
76
ee/types/personal_access_token.go
Normal file
76
ee/types/personal_access_token.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type GettablePAT struct {
|
||||
CreatedByUser PatUser `json:"createdByUser"`
|
||||
UpdatedByUser PatUser `json:"updatedByUser"`
|
||||
|
||||
StorablePersonalAccessToken
|
||||
}
|
||||
|
||||
type PatUser struct {
|
||||
types.User
|
||||
NotFound bool `json:"notFound"`
|
||||
}
|
||||
|
||||
func NewGettablePAT(name, role, userID string, expiresAt int64) GettablePAT {
|
||||
return GettablePAT{
|
||||
StorablePersonalAccessToken: NewStorablePersonalAccessToken(name, role, userID, expiresAt),
|
||||
}
|
||||
}
|
||||
|
||||
type StorablePersonalAccessToken struct {
|
||||
bun.BaseModel `bun:"table:personal_access_token"`
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
OrgID string `json:"orgId" bun:"org_id,type:text,notnull"`
|
||||
Role string `json:"role" bun:"role,type:text,notnull,default:'ADMIN'"`
|
||||
UserID string `json:"userId" bun:"user_id,type:text,notnull"`
|
||||
Token string `json:"token" bun:"token,type:text,notnull,unique"`
|
||||
Name string `json:"name" bun:"name,type:text,notnull"`
|
||||
ExpiresAt int64 `json:"expiresAt" bun:"expires_at,notnull,default:0"`
|
||||
LastUsed int64 `json:"lastUsed" bun:"last_used,notnull,default:0"`
|
||||
Revoked bool `json:"revoked" bun:"revoked,notnull,default:false"`
|
||||
UpdatedByUserID string `json:"updatedByUserId" bun:"updated_by_user_id,type:text,notnull,default:''"`
|
||||
}
|
||||
|
||||
func NewStorablePersonalAccessToken(name, role, userID string, expiresAt int64) StorablePersonalAccessToken {
|
||||
now := time.Now()
|
||||
if expiresAt != 0 {
|
||||
// convert expiresAt to unix timestamp from days
|
||||
expiresAt = now.Unix() + (expiresAt * 24 * 60 * 60)
|
||||
}
|
||||
|
||||
// Generate a 32-byte random token.
|
||||
token := make([]byte, 32)
|
||||
rand.Read(token)
|
||||
// Encode the token in base64.
|
||||
encodedToken := base64.StdEncoding.EncodeToString(token)
|
||||
|
||||
return StorablePersonalAccessToken{
|
||||
Token: encodedToken,
|
||||
Name: name,
|
||||
Role: role,
|
||||
UserID: userID,
|
||||
ExpiresAt: expiresAt,
|
||||
LastUsed: 0,
|
||||
Revoked: false,
|
||||
UpdatedByUserID: "",
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -1 +0,0 @@
|
||||
<svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><g stroke="#C0C1C3" stroke-width="1.167" stroke-linecap="round" stroke-linejoin="round"><path d="m12.192 3.18-1.167 2.33-.583 1.165M7.31 12.74a.583.583 0 0 1-.835-.24L1.808 3.179"/><path d="M7 1.167c2.9 0 5.25.783 5.25 1.75 0 .966-2.35 1.75-5.25 1.75s-5.25-.784-5.25-1.75c0-.967 2.35-1.75 5.25-1.75ZM8.75 10.5h3.5M10.5 12.25v-3.5"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 418 B |
@@ -1 +0,0 @@
|
||||
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)" stroke-linecap="round" stroke-linejoin="round"><path d="M8 14.666A6.667 6.667 0 1 0 8 1.333a6.667 6.667 0 0 0 0 13.333Z" fill="#C0C1C3" stroke="#C0C1C3" stroke-width="2"/><path d="M8 11.333v-4H6.333M8 4.667h.007" stroke="#121317" stroke-width="1.333"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
Before Width: | Height: | Size: 439 B |
@@ -18,6 +18,13 @@
|
||||
"field_send_resolved": "Send resolved alerts",
|
||||
"field_channel_type": "Type",
|
||||
"field_webhook_url": "Webhook URL",
|
||||
"tooltip_webhook_url": "The URL of the webhook to send alerts to. Learn more about webhook integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/webhook/). Integrates with [Incident.io](https://signoz.io/docs/alerts-management/notification-channel/incident-io/), [Rootly](https://signoz.io/docs/alerts-management/notification-channel/rootly/), [Zenduty](https://signoz.io/docs/alerts-management/notification-channel/zenduty/) and [more](https://signoz.io/docs/alerts-management/notification-channel/webhook/#my-incident-management-tool-is-not-listed-can-i-still-integrate).",
|
||||
"tooltip_slack_url": "The URL of the slack [incoming webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) to send alerts to. Learn more about slack integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/slack/).",
|
||||
"tooltip_pager_routing_key": "Learn how to obtain the routing key from your PagerDuty account [here](https://signoz.io/docs/alerts-management/notification-channel/pagerduty/#obtaining-integration-or-routing-key).",
|
||||
"tooltip_opsgenie_api_key": "Learn how to obtain the API key from your OpsGenie account [here](https://support.atlassian.com/opsgenie/docs/integrate-opsgenie-with-prometheus/).",
|
||||
"tooltip_email_to": "Enter email addresses separated by commas.",
|
||||
"tooltip_ms_teams_url": "The URL of the Microsoft Teams [webhook](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498) to send alerts to. Learn more about Microsoft Teams integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/ms-teams/).",
|
||||
|
||||
"field_slack_recipient": "Recipient",
|
||||
"field_slack_title": "Title",
|
||||
"field_slack_description": "Description",
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
"field_send_resolved": "Send resolved alerts",
|
||||
"field_channel_type": "Type",
|
||||
"field_webhook_url": "Webhook URL",
|
||||
"tooltip_webhook_url": "The URL of the webhook to send alerts to. Learn more about webhook integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/webhook/). Integrates with [Incident.io](https://signoz.io/docs/alerts-management/notification-channel/incident-io/), [Rootly](https://signoz.io/docs/alerts-management/notification-channel/rootly/), [Zenduty](https://signoz.io/docs/alerts-management/notification-channel/zenduty/) and [more](https://signoz.io/docs/alerts-management/notification-channel/webhook/#my-incident-management-tool-is-not-listed-can-i-still-integrate).",
|
||||
"tooltip_slack_url": "The URL of the slack [incoming webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) to send alerts to. Learn more about slack integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/slack/).",
|
||||
"tooltip_pager_routing_key": "Learn how to obtain the routing key from your PagerDuty account [here](https://signoz.io/docs/alerts-management/notification-channel/pagerduty/#obtaining-integration-or-routing-key).",
|
||||
"tooltip_opsgenie_api_key": "Learn how to obtain the API key from your OpsGenie account [here](https://support.atlassian.com/opsgenie/docs/integrate-opsgenie-with-prometheus/).",
|
||||
"tooltip_email_to": "Enter email addresses separated by commas.",
|
||||
"tooltip_ms_teams_url": "The URL of the Microsoft Teams [webhook](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498) to send alerts to. Learn more about Microsoft Teams integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/ms-teams/).",
|
||||
"field_slack_recipient": "Recipient",
|
||||
"field_slack_title": "Title",
|
||||
"field_slack_description": "Description",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
@@ -15,6 +16,7 @@ import { LICENSE_PLAN_KEY } from 'hooks/useLicense';
|
||||
import { NotificationProvider } from 'hooks/useNotifications';
|
||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||
import history from 'lib/history';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import posthog from 'posthog-js';
|
||||
import AlertRuleProvider from 'providers/Alert';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
@@ -46,7 +48,6 @@ function App(): JSX.Element {
|
||||
activeLicenseV3,
|
||||
isFetchingActiveLicenseV3,
|
||||
userFetchError,
|
||||
licensesFetchError,
|
||||
featureFlagsFetchError,
|
||||
isLoggedIn: isLoggedInState,
|
||||
featureFlags,
|
||||
@@ -56,10 +57,7 @@ function App(): JSX.Element {
|
||||
|
||||
const { hostname, pathname } = window.location;
|
||||
|
||||
const {
|
||||
isCloudUser: isCloudUserVal,
|
||||
isEECloudUser: isEECloudUserVal,
|
||||
} = useGetTenantLicense();
|
||||
const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
|
||||
|
||||
const enableAnalytics = useCallback(
|
||||
(user: IUser): void => {
|
||||
@@ -169,7 +167,7 @@ function App(): JSX.Element {
|
||||
|
||||
let updatedRoutes = defaultRoutes;
|
||||
// if the user is a cloud user
|
||||
if (isCloudUserVal || isEECloudUserVal) {
|
||||
if (isCloudUser || isEnterpriseSelfHostedUser) {
|
||||
// if the user is on basic plan then remove billing
|
||||
if (isOnBasicPlan) {
|
||||
updatedRoutes = updatedRoutes.filter(
|
||||
@@ -191,10 +189,10 @@ function App(): JSX.Element {
|
||||
isLoggedInState,
|
||||
user,
|
||||
licenses,
|
||||
isCloudUserVal,
|
||||
isCloudUser,
|
||||
isEnterpriseSelfHostedUser,
|
||||
isFetchingLicenses,
|
||||
isFetchingUser,
|
||||
isEECloudUserVal,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -209,6 +207,7 @@ function App(): JSX.Element {
|
||||
}
|
||||
}, [pathname]);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
useEffect(() => {
|
||||
// feature flag shouldn't be loading and featureFlags or fetchError any one of this should be true indicating that req is complete
|
||||
// licenses should also be present. there is no check for licenses for loading and error as that is mandatory if not present then routing
|
||||
@@ -234,7 +233,12 @@ function App(): JSX.Element {
|
||||
const showAddCreditCardModal =
|
||||
!isPremiumSupportEnabled && !trialInfo?.trialConvertedToSubscription;
|
||||
|
||||
if (isLoggedInState && isChatSupportEnabled && !showAddCreditCardModal) {
|
||||
if (
|
||||
isLoggedInState &&
|
||||
isChatSupportEnabled &&
|
||||
!showAddCreditCardModal &&
|
||||
(isCloudUser || isEnterpriseSelfHostedUser)
|
||||
) {
|
||||
window.Intercom('boot', {
|
||||
app_id: process.env.INTERCOM_APP_ID,
|
||||
email: user?.email || '',
|
||||
@@ -253,13 +257,53 @@ function App(): JSX.Element {
|
||||
licenses,
|
||||
activeLicenseV3,
|
||||
trialInfo,
|
||||
isCloudUser,
|
||||
isEnterpriseSelfHostedUser,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetchingUser && isCloudUserVal && user && user.email) {
|
||||
if (!isFetchingUser && isCloudUser && user && user.email) {
|
||||
enableAnalytics(user);
|
||||
}
|
||||
}, [user, isFetchingUser, isCloudUserVal, enableAnalytics]);
|
||||
}, [user, isFetchingUser, isCloudUser, enableAnalytics]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isCloudUser || isEnterpriseSelfHostedUser) {
|
||||
if (process.env.POSTHOG_KEY) {
|
||||
posthog.init(process.env.POSTHOG_KEY, {
|
||||
api_host: 'https://us.i.posthog.com',
|
||||
person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well
|
||||
});
|
||||
}
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
tunnel: process.env.TUNNEL_URL,
|
||||
environment: 'production',
|
||||
integrations: [
|
||||
Sentry.browserTracingIntegration(),
|
||||
Sentry.replayIntegration({
|
||||
maskAllText: false,
|
||||
blockAllMedia: false,
|
||||
}),
|
||||
],
|
||||
// Performance Monitoring
|
||||
tracesSampleRate: 1.0, // Capture 100% of the transactions
|
||||
// Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled
|
||||
tracePropagationTargets: [],
|
||||
// Session Replay
|
||||
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
|
||||
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
|
||||
});
|
||||
} else {
|
||||
posthog.reset();
|
||||
Sentry.close();
|
||||
|
||||
if (window.cioanalytics && typeof window.cioanalytics.reset === 'function') {
|
||||
window.cioanalytics.reset();
|
||||
}
|
||||
}
|
||||
}, [isCloudUser, isEnterpriseSelfHostedUser]);
|
||||
|
||||
// if the user is in logged in state
|
||||
if (isLoggedInState) {
|
||||
@@ -271,61 +315,55 @@ function App(): JSX.Element {
|
||||
// if the required calls fails then return a something went wrong error
|
||||
// this needs to be on top of data missing error because if there is an error, data will never be loaded and it will
|
||||
// move to indefinitive loading
|
||||
if (
|
||||
(userFetchError || licensesFetchError) &&
|
||||
pathname !== ROUTES.SOMETHING_WENT_WRONG
|
||||
) {
|
||||
if (userFetchError && pathname !== ROUTES.SOMETHING_WENT_WRONG) {
|
||||
history.replace(ROUTES.SOMETHING_WENT_WRONG);
|
||||
}
|
||||
|
||||
// if all of the data is not set then return a spinner, this is required because there is some gap between loading states and data setting
|
||||
if (
|
||||
(!licenses || !user.email || !featureFlags) &&
|
||||
!userFetchError &&
|
||||
!licensesFetchError
|
||||
) {
|
||||
if ((!licenses || !user.email || !featureFlags) && !userFetchError) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
<Router history={history}>
|
||||
<CompatRouter>
|
||||
<NotificationProvider>
|
||||
<PrivateRoute>
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
<DashboardProvider>
|
||||
<KeyboardHotkeysProvider>
|
||||
<AlertRuleProvider>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</AlertRuleProvider>
|
||||
</KeyboardHotkeysProvider>
|
||||
</DashboardProvider>
|
||||
</QueryBuilderProvider>
|
||||
</ResourceProvider>
|
||||
</PrivateRoute>
|
||||
</NotificationProvider>
|
||||
</CompatRouter>
|
||||
</Router>
|
||||
</ConfigProvider>
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
<Router history={history}>
|
||||
<CompatRouter>
|
||||
<NotificationProvider>
|
||||
<PrivateRoute>
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
<DashboardProvider>
|
||||
<KeyboardHotkeysProvider>
|
||||
<AlertRuleProvider>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</AlertRuleProvider>
|
||||
</KeyboardHotkeysProvider>
|
||||
</DashboardProvider>
|
||||
</QueryBuilderProvider>
|
||||
</ResourceProvider>
|
||||
</PrivateRoute>
|
||||
</NotificationProvider>
|
||||
</CompatRouter>
|
||||
</Router>
|
||||
</ConfigProvider>
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,10 +47,9 @@ export const TracesFunnels = Loadable(
|
||||
import(/* webpackChunkName: "Traces Funnels" */ 'pages/TracesModulePage'),
|
||||
);
|
||||
export const TracesFunnelDetails = Loadable(
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "Traces Funnel Details" */ 'pages/TracesModulePage'
|
||||
/* webpackChunkName: "Traces Funnel Details" */ 'pages/TracesFunnelDetails'
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -11,9 +11,12 @@ const logEvent = async (
|
||||
rateLimited?: boolean,
|
||||
): Promise<SuccessResponse<EventSuccessPayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
// add tenant_url to attributes
|
||||
const { hostname } = window.location;
|
||||
const updatedAttributes = { ...attributes, tenant_url: hostname };
|
||||
const response = await axios.post('/event', {
|
||||
eventName,
|
||||
attributes,
|
||||
attributes: updatedAttributes,
|
||||
eventType: eventType || 'track',
|
||||
rateLimited: rateLimited || false, // TODO: Update this once we have a proper way to handle rate limiting
|
||||
});
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
CreateFunnelPayload,
|
||||
CreateFunnelResponse,
|
||||
FunnelData,
|
||||
FunnelStepData,
|
||||
} from 'types/api/traceFunnels';
|
||||
|
||||
const FUNNELS_BASE_PATH = '/trace-funnels';
|
||||
@@ -55,7 +54,7 @@ export const getFunnelsList = async ({
|
||||
};
|
||||
|
||||
export const getFunnelById = async (
|
||||
funnelId?: string,
|
||||
funnelId: string,
|
||||
): Promise<SuccessResponse<FunnelData> | ErrorResponse> => {
|
||||
const response: AxiosResponse = await axios.get(
|
||||
`${FUNNELS_BASE_PATH}/get/${funnelId}`,
|
||||
@@ -108,267 +107,3 @@ export const deleteFunnel = async (
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export interface UpdateFunnelStepsPayload {
|
||||
funnel_id: string;
|
||||
steps: FunnelStepData[];
|
||||
updated_timestamp: number;
|
||||
}
|
||||
|
||||
export const updateFunnelSteps = async (
|
||||
payload: UpdateFunnelStepsPayload,
|
||||
): Promise<SuccessResponse<FunnelData> | ErrorResponse> => {
|
||||
const response: AxiosResponse = await axios.put(
|
||||
`${FUNNELS_BASE_PATH}/steps/update`,
|
||||
payload,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Funnel steps updated successfully',
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export interface ValidateFunnelPayload {
|
||||
start_time: number;
|
||||
end_time: number;
|
||||
}
|
||||
|
||||
export interface ValidateFunnelResponse {
|
||||
status: string;
|
||||
data: Array<{
|
||||
timestamp: string;
|
||||
data: {
|
||||
trace_id: string;
|
||||
};
|
||||
}> | null;
|
||||
}
|
||||
|
||||
export const validateFunnelSteps = async (
|
||||
funnelId: string,
|
||||
payload: ValidateFunnelPayload,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SuccessResponse<ValidateFunnelResponse> | ErrorResponse> => {
|
||||
const response = await axios.post(
|
||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/validate`,
|
||||
payload,
|
||||
{ signal },
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export interface UpdateFunnelStepDetailsPayload {
|
||||
funnel_id: string;
|
||||
steps: Array<{
|
||||
step_name: string;
|
||||
description: string;
|
||||
}>;
|
||||
updated_timestamp: number;
|
||||
}
|
||||
|
||||
export const updateFunnelStepDetails = async ({
|
||||
stepOrder,
|
||||
payload,
|
||||
}: {
|
||||
stepOrder: number;
|
||||
payload: UpdateFunnelStepDetailsPayload;
|
||||
}): Promise<SuccessResponse<FunnelData> | ErrorResponse> => {
|
||||
const response: AxiosResponse = await axios.put(
|
||||
`${FUNNELS_BASE_PATH}/steps/${stepOrder}/update`,
|
||||
payload,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Funnel step details updated successfully',
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
interface UpdateFunnelDescriptionPayload {
|
||||
funnel_id: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const saveFunnelDescription = async (
|
||||
payload: UpdateFunnelDescriptionPayload,
|
||||
): Promise<SuccessResponse<FunnelData> | ErrorResponse> => {
|
||||
const response: AxiosResponse = await axios.post(
|
||||
`${FUNNELS_BASE_PATH}/save`,
|
||||
payload,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Funnel description updated successfully',
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export interface FunnelOverviewPayload {
|
||||
start_time: number;
|
||||
end_time: number;
|
||||
step_start?: number;
|
||||
step_end?: number;
|
||||
}
|
||||
|
||||
export interface FunnelOverviewResponse {
|
||||
status: string;
|
||||
data: Array<{
|
||||
timestamp: string;
|
||||
data: {
|
||||
avg_duration: number;
|
||||
avg_rate: number;
|
||||
conversion_rate: number | null;
|
||||
errors: number;
|
||||
p99_latency: number;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export const getFunnelOverview = async (
|
||||
funnelId: string,
|
||||
payload: FunnelOverviewPayload,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SuccessResponse<FunnelOverviewResponse> | ErrorResponse> => {
|
||||
const response = await axios.post(
|
||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/overview`,
|
||||
payload,
|
||||
{
|
||||
signal,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export interface SlowTracesPayload {
|
||||
start_time: number;
|
||||
end_time: number;
|
||||
step_a_order: number;
|
||||
step_b_order: number;
|
||||
}
|
||||
|
||||
export interface SlowTraceData {
|
||||
status: string;
|
||||
data: Array<{
|
||||
timestamp: string;
|
||||
data: {
|
||||
duration_ms: string;
|
||||
span_count: number;
|
||||
trace_id: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export const getFunnelSlowTraces = async (
|
||||
funnelId: string,
|
||||
payload: SlowTracesPayload,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SuccessResponse<SlowTraceData> | ErrorResponse> => {
|
||||
const response = await axios.post(
|
||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/slow-traces`,
|
||||
payload,
|
||||
{
|
||||
signal,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
export interface ErrorTracesPayload {
|
||||
start_time: number;
|
||||
end_time: number;
|
||||
step_a_order: number;
|
||||
step_b_order: number;
|
||||
}
|
||||
|
||||
export interface ErrorTraceData {
|
||||
status: string;
|
||||
data: Array<{
|
||||
timestamp: string;
|
||||
data: {
|
||||
duration_ms: string;
|
||||
span_count: number;
|
||||
trace_id: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export const getFunnelErrorTraces = async (
|
||||
funnelId: string,
|
||||
payload: ErrorTracesPayload,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SuccessResponse<ErrorTraceData> | ErrorResponse> => {
|
||||
const response: AxiosResponse = await axios.post(
|
||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/error-traces`,
|
||||
payload,
|
||||
{
|
||||
signal,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
export interface FunnelStepsPayload {
|
||||
start_time: number;
|
||||
end_time: number;
|
||||
}
|
||||
|
||||
export interface FunnelStepGraphMetrics {
|
||||
[key: `total_s${number}_spans`]: number;
|
||||
[key: `total_s${number}_errored_spans`]: number;
|
||||
}
|
||||
|
||||
export interface FunnelStepsResponse {
|
||||
status: string;
|
||||
data: Array<{
|
||||
timestamp: string;
|
||||
data: FunnelStepGraphMetrics;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const getFunnelSteps = async (
|
||||
funnelId: string,
|
||||
payload: FunnelStepsPayload,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SuccessResponse<FunnelStepsResponse> | ErrorResponse> => {
|
||||
const response = await axios.post(
|
||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/steps`,
|
||||
payload,
|
||||
{ signal },
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: '',
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,24 +11,16 @@ import { QueryParams } from 'constants/query';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
|
||||
export interface SelectOptionConfig {
|
||||
interface SelectOptionConfig {
|
||||
placeholder: string;
|
||||
queryParam: QueryParams;
|
||||
filterType: string | string[];
|
||||
shouldSetQueryParams?: boolean;
|
||||
onChange?: (value: string | string[]) => void;
|
||||
values?: string | string[];
|
||||
isMultiple?: boolean;
|
||||
}
|
||||
|
||||
export function FilterSelect({
|
||||
function FilterSelect({
|
||||
placeholder,
|
||||
queryParam,
|
||||
filterType,
|
||||
values,
|
||||
shouldSetQueryParams,
|
||||
onChange,
|
||||
isMultiple,
|
||||
}: SelectOptionConfig): JSX.Element {
|
||||
const { handleSearch, isFetching, options } = useCeleryFilterOptions(
|
||||
filterType,
|
||||
@@ -43,8 +35,7 @@ export function FilterSelect({
|
||||
key={filterType.toString()}
|
||||
placeholder={placeholder}
|
||||
showSearch
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...(isMultiple ? { mode: 'multiple' } : {})}
|
||||
mode="multiple"
|
||||
options={options}
|
||||
loading={isFetching}
|
||||
className="config-select-option"
|
||||
@@ -52,11 +43,7 @@ export function FilterSelect({
|
||||
maxTagCount={4}
|
||||
allowClear
|
||||
maxTagPlaceholder={SelectMaxTagPlaceholder}
|
||||
value={
|
||||
!shouldSetQueryParams && !!values?.length
|
||||
? values
|
||||
: getValuesFromQueryParams(queryParam, urlQuery) || []
|
||||
}
|
||||
value={getValuesFromQueryParams(queryParam, urlQuery) || []}
|
||||
notFoundContent={
|
||||
isFetching ? (
|
||||
<span>
|
||||
@@ -68,28 +55,12 @@ export function FilterSelect({
|
||||
}
|
||||
onChange={(value): void => {
|
||||
handleSearch('');
|
||||
if (shouldSetQueryParams) {
|
||||
setQueryParamsFromOptions(
|
||||
value as string[],
|
||||
urlQuery,
|
||||
history,
|
||||
location,
|
||||
queryParam,
|
||||
);
|
||||
}
|
||||
onChange?.(value);
|
||||
setQueryParamsFromOptions(value, urlQuery, history, location, queryParam);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
FilterSelect.defaultProps = {
|
||||
shouldSetQueryParams: true,
|
||||
onChange: (): void => {},
|
||||
values: [],
|
||||
isMultiple: true,
|
||||
};
|
||||
|
||||
function CeleryOverviewConfigOptions(): JSX.Element {
|
||||
const selectConfigs: SelectOptionConfig[] = [
|
||||
{
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
.change-percentage-pill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 1px 4px;
|
||||
border-radius: 50px;
|
||||
|
||||
&__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
&__label {
|
||||
font-family: 'Geist Mono';
|
||||
font-size: 12px;
|
||||
line-height: normal;
|
||||
}
|
||||
&--positive {
|
||||
.change-percentage-pill {
|
||||
&__icon {
|
||||
color: var(--bg-forest-500);
|
||||
}
|
||||
&__label {
|
||||
color: var(--bg-forest-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
&--negative {
|
||||
background: rgba(229, 72, 77, 0.1);
|
||||
|
||||
.change-percentage-pill {
|
||||
&__icon {
|
||||
color: var(--bg-cherry-500);
|
||||
}
|
||||
&__label {
|
||||
color: var(--bg-cherry-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import './ChangePercentagePill.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import cx from 'classnames';
|
||||
import { ArrowDown, ArrowUp } from 'lucide-react';
|
||||
|
||||
interface ChangePercentagePillProps {
|
||||
percentage: number;
|
||||
direction: number;
|
||||
}
|
||||
function ChangePercentagePill({
|
||||
percentage,
|
||||
direction,
|
||||
}: ChangePercentagePillProps): JSX.Element | null {
|
||||
if (direction === 0 || percentage === 0) {
|
||||
return null;
|
||||
}
|
||||
const isPositive = direction > 0;
|
||||
return (
|
||||
<div
|
||||
className={cx('change-percentage-pill', {
|
||||
'change-percentage-pill--positive': isPositive,
|
||||
'change-percentage-pill--negative': !isPositive,
|
||||
})}
|
||||
>
|
||||
<div className="change-percentage-pill__icon">
|
||||
{isPositive ? (
|
||||
<ArrowUp size={12} color={Color.BG_FOREST_500} />
|
||||
) : (
|
||||
<ArrowDown size={12} color={Color.BG_CHERRY_500} />
|
||||
)}
|
||||
</div>
|
||||
<div className="change-percentage-pill__label">{percentage}%</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChangePercentagePill;
|
||||
@@ -1,55 +0,0 @@
|
||||
.signoz-radio-group.ant-radio-group {
|
||||
color: var(--text-vanilla-400);
|
||||
|
||||
.view-title {
|
||||
display: flex;
|
||||
gap: var(--margin-2);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-xs);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
&:hover {
|
||||
color: var(--text-vanilla-100);
|
||||
}
|
||||
&::before {
|
||||
background: var(--bg-slate-400);
|
||||
}
|
||||
}
|
||||
|
||||
.selected_view {
|
||||
&,
|
||||
&:hover {
|
||||
background: var(--bg-slate-300);
|
||||
color: var(--text-vanilla-100);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
}
|
||||
&::before {
|
||||
background: var(--bg-slate-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Light mode styles
|
||||
.lightMode {
|
||||
.signoz-radio-group {
|
||||
.tab {
|
||||
background: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
.selected_view {
|
||||
background: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-slate-300);
|
||||
color: var(--text-ink-400);
|
||||
}
|
||||
|
||||
.selected_view::before {
|
||||
background: var(--bg-vanilla-300);
|
||||
border-left: 1px solid var(--bg-slate-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import './SignozRadioGroup.styles.scss';
|
||||
|
||||
import { Radio } from 'antd';
|
||||
import { RadioChangeEvent } from 'antd/es/radio';
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface SignozRadioGroupProps {
|
||||
value: string;
|
||||
options: Option[];
|
||||
onChange: (e: RadioChangeEvent) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function SignozRadioGroup({
|
||||
value,
|
||||
options,
|
||||
onChange,
|
||||
className = '',
|
||||
}: SignozRadioGroupProps): JSX.Element {
|
||||
return (
|
||||
<Radio.Group
|
||||
value={value}
|
||||
buttonStyle="solid"
|
||||
className={`signoz-radio-group ${className}`}
|
||||
onChange={onChange}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<Radio.Button
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className={value === option.value ? 'selected_view tab' : 'tab'}
|
||||
>
|
||||
{option.label}
|
||||
</Radio.Button>
|
||||
))}
|
||||
</Radio.Group>
|
||||
);
|
||||
}
|
||||
|
||||
SignozRadioGroup.defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
export default SignozRadioGroup;
|
||||
@@ -1,30 +1,12 @@
|
||||
// keep this consistent with backend constants.go
|
||||
export enum FeatureKeys {
|
||||
SSO = 'SSO',
|
||||
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
|
||||
BASIC_PLAN = 'BASIC_PLAN',
|
||||
ALERT_CHANNEL_SLACK = 'ALERT_CHANNEL_SLACK',
|
||||
ALERT_CHANNEL_WEBHOOK = 'ALERT_CHANNEL_WEBHOOK',
|
||||
ALERT_CHANNEL_PAGERDUTY = 'ALERT_CHANNEL_PAGERDUTY',
|
||||
ALERT_CHANNEL_OPSGENIE = 'ALERT_CHANNEL_OPSGENIE',
|
||||
ALERT_CHANNEL_MSTEAMS = 'ALERT_CHANNEL_MSTEAMS',
|
||||
DurationSort = 'DurationSort',
|
||||
TimestampSort = 'TimestampSort',
|
||||
SMART_TRACE_DETAIL = 'SMART_TRACE_DETAIL',
|
||||
CUSTOM_METRICS_FUNCTION = 'CUSTOM_METRICS_FUNCTION',
|
||||
QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS',
|
||||
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
|
||||
DISABLE_UPSELL = 'DISABLE_UPSELL',
|
||||
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
|
||||
OSS = 'OSS',
|
||||
ONBOARDING = 'ONBOARDING',
|
||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||
GATEWAY = 'GATEWAY',
|
||||
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
|
||||
QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2',
|
||||
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
|
||||
AWS_INTEGRATION = 'AWS_INTEGRATION',
|
||||
ONBOARDING_V3 = 'ONBOARDING_V3',
|
||||
THIRD_PARTY_API = 'THIRD_PARTY_API',
|
||||
TRACE_FUNNELS = 'TRACE_FUNNELS',
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export const REACT_QUERY_KEY = {
|
||||
GET_METRIC_DETAILS: 'GET_METRIC_DETAILS',
|
||||
GET_RELATED_METRICS: 'GET_RELATED_METRICS',
|
||||
|
||||
// Traces Funnels Query Keys
|
||||
// API Monitoring Query Keys
|
||||
GET_DOMAINS_LIST: 'GET_DOMAINS_LIST',
|
||||
GET_ENDPOINTS_LIST_BY_DOMAIN: 'GET_ENDPOINTS_LIST_BY_DOMAIN',
|
||||
GET_NESTED_ENDPOINTS_LIST: 'GET_NESTED_ENDPOINTS_LIST',
|
||||
@@ -68,11 +68,4 @@ export const REACT_QUERY_KEY = {
|
||||
'GET_ENDPOINT_STATUS_CODE_LATENCY_BAR_CHARTS_DATA',
|
||||
GET_FUNNELS_LIST: 'GET_FUNNELS_LIST',
|
||||
GET_FUNNEL_DETAILS: 'GET_FUNNEL_DETAILS',
|
||||
UPDATE_FUNNEL_STEPS: 'UPDATE_FUNNEL_STEPS',
|
||||
VALIDATE_FUNNEL_STEPS: 'VALIDATE_FUNNEL_STEPS',
|
||||
UPDATE_FUNNEL_STEP_DETAILS: 'UPDATE_FUNNEL_STEP_DETAILS',
|
||||
GET_FUNNEL_OVERVIEW: 'GET_FUNNEL_OVERVIEW',
|
||||
GET_FUNNEL_SLOW_TRACES: 'GET_FUNNEL_SLOW_TRACES',
|
||||
GET_FUNNEL_ERROR_TRACES: 'GET_FUNNEL_ERROR_TRACES',
|
||||
GET_FUNNEL_STEPS_GRAPH_DATA: 'GET_FUNNEL_STEPS_GRAPH_DATA',
|
||||
} as const;
|
||||
|
||||
@@ -31,6 +31,10 @@ jest.mock('hooks/useNotifications', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
|
||||
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
|
||||
}));
|
||||
|
||||
describe('Create Alert Channel', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
@@ -18,6 +18,10 @@ import { render, screen } from 'tests/test-utils';
|
||||
|
||||
import { testLabelInputAndHelpValue } from './testUtils';
|
||||
|
||||
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
|
||||
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
|
||||
}));
|
||||
|
||||
describe('Create Alert Channel (Normal User)', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
@@ -20,6 +20,10 @@ jest.mock('hooks/useNotifications', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
|
||||
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
|
||||
}));
|
||||
|
||||
describe('Should check if the edit alert channel is properly displayed ', () => {
|
||||
beforeEach(() => {
|
||||
render(<EditAlertChannels initialValue={editAlertChannelInitialValue} />);
|
||||
|
||||
@@ -42,7 +42,7 @@ import { Helmet } from 'react-helmet-async';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMutation, useQueries } from 'react-query';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
@@ -360,9 +360,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
routeKey === 'INFRASTRUCTURE_MONITORING_HOSTS' ||
|
||||
routeKey === 'INFRASTRUCTURE_MONITORING_KUBERNETES';
|
||||
const isTracesFunnels = (): boolean => routeKey === 'TRACES_FUNNELS';
|
||||
const isTracesFunnelDetails = (): boolean =>
|
||||
!!matchPath(pathname, ROUTES.TRACES_FUNNELS_DETAIL);
|
||||
|
||||
const isPathMatch = (regex: RegExp): boolean => regex.test(pathname);
|
||||
|
||||
const isDashboardView = (): boolean =>
|
||||
@@ -668,11 +665,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
? 0
|
||||
: '0 1rem',
|
||||
|
||||
...(isTraceDetailsView() ||
|
||||
isTracesFunnels() ||
|
||||
isTracesFunnelDetails()
|
||||
? { margin: 0 }
|
||||
: {}),
|
||||
...(isTraceDetailsView() || isTracesFunnels() ? { margin: 0 } : {}),
|
||||
}}
|
||||
>
|
||||
{isToDisplayLayout && !renderFullScreen && <TopNav />}
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function CustomDomainSettings(): JSX.Element {
|
||||
isLoading: isLoadingDeploymentsData,
|
||||
isFetching: isFetchingDeploymentsData,
|
||||
refetch: refetchDeploymentsData,
|
||||
} = useGetDeploymentsData();
|
||||
} = useGetDeploymentsData(true);
|
||||
|
||||
const {
|
||||
mutate: updateSubDomain,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -9,7 +10,20 @@ function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="webhook_url" label={t('field_webhook_url')}>
|
||||
<Form.Item
|
||||
name="webhook_url"
|
||||
label={t('field_webhook_url')}
|
||||
tooltip={{
|
||||
title: (
|
||||
<MarkdownRenderer
|
||||
markdownContent={t('tooltip_ms_teams_url')}
|
||||
variables={{}}
|
||||
/>
|
||||
),
|
||||
overlayInnerStyle: { maxWidth: 400 },
|
||||
placement: 'right',
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
onChange={(event): void => {
|
||||
setSelectedConfig((value) => ({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { OpsgenieChannel } from '../../CreateAlertChannels/config';
|
||||
@@ -19,7 +20,21 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="api_key" label={t('field_opsgenie_api_key')} required>
|
||||
<Form.Item
|
||||
name="api_key"
|
||||
label={t('field_opsgenie_api_key')}
|
||||
tooltip={{
|
||||
title: (
|
||||
<MarkdownRenderer
|
||||
markdownContent={t('tooltip_opsgenie_api_key')}
|
||||
variables={{}}
|
||||
/>
|
||||
),
|
||||
overlayInnerStyle: { maxWidth: 400 },
|
||||
placement: 'right',
|
||||
}}
|
||||
required
|
||||
>
|
||||
<Input
|
||||
onChange={handleInputChange('api_key')}
|
||||
data-testid="opsgenie-api-key-textbox"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -10,7 +11,20 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
||||
const { t } = useTranslation('channels');
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="routing_key" label={t('field_pager_routing_key')} required>
|
||||
<Form.Item
|
||||
name="routing_key"
|
||||
label={t('field_pager_routing_key')}
|
||||
tooltip={{
|
||||
title: (
|
||||
<MarkdownRenderer
|
||||
markdownContent={t('tooltip_pager_routing_key')}
|
||||
variables={{}}
|
||||
/>
|
||||
),
|
||||
overlayInnerStyle: { maxWidth: 400 },
|
||||
placement: 'right',
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
onChange={(event): void => {
|
||||
setSelectedConfig((value) => ({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -11,7 +12,20 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="api_url" label={t('field_webhook_url')}>
|
||||
<Form.Item
|
||||
name="api_url"
|
||||
label={t('field_webhook_url')}
|
||||
tooltip={{
|
||||
title: (
|
||||
<MarkdownRenderer
|
||||
markdownContent={t('tooltip_slack_url')}
|
||||
variables={{}}
|
||||
/>
|
||||
),
|
||||
overlayInnerStyle: { maxWidth: 400 },
|
||||
placement: 'right',
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
onChange={(event): void => {
|
||||
setSelectedConfig((value) => ({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -9,7 +10,20 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="api_url" label={t('field_webhook_url')}>
|
||||
<Form.Item
|
||||
name="api_url"
|
||||
label={t('field_webhook_url')}
|
||||
tooltip={{
|
||||
title: (
|
||||
<MarkdownRenderer
|
||||
markdownContent={t('tooltip_webhook_url')}
|
||||
variables={{}}
|
||||
/>
|
||||
),
|
||||
overlayInnerStyle: { maxWidth: 400 },
|
||||
placement: 'right',
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
onChange={(event): void => {
|
||||
setSelectedConfig((value) => ({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Form, FormInstance, Input, Select, Switch, Typography } from 'antd';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
ChannelType,
|
||||
@@ -11,11 +10,8 @@ import {
|
||||
WebhookChannel,
|
||||
} from 'container/CreateAlertChannels/config';
|
||||
import history from 'lib/history';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Dispatch, ReactElement, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
|
||||
import { isFeatureKeys } from 'utils/app';
|
||||
|
||||
import EmailSettings from './Settings/Email';
|
||||
import MsTeamsSettings from './Settings/MsTeams';
|
||||
@@ -39,17 +35,6 @@ function FormAlertChannels({
|
||||
editing = false,
|
||||
}: FormAlertChannelsProps): JSX.Element {
|
||||
const { t } = useTranslation('channels');
|
||||
const { featureFlags } = useAppContext();
|
||||
|
||||
const feature = `ALERT_CHANNEL_${type.toUpperCase()}`;
|
||||
|
||||
const featureKey = isFeatureKeys(feature)
|
||||
? feature
|
||||
: FeatureKeys.ALERT_CHANNEL_SLACK;
|
||||
|
||||
const hasFeature = featureFlags?.find(
|
||||
(flag: FeatureFlagProps) => flag.name === featureKey,
|
||||
);
|
||||
|
||||
const renderSettings = (): ReactElement | null => {
|
||||
switch (type) {
|
||||
@@ -146,7 +131,7 @@ function FormAlertChannels({
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
disabled={savingState || !hasFeature}
|
||||
disabled={savingState}
|
||||
loading={savingState}
|
||||
type="primary"
|
||||
onClick={(): void => onSaveHandler(type)}
|
||||
@@ -154,7 +139,7 @@ function FormAlertChannels({
|
||||
{t('button_save_channel')}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={testingState || !hasFeature}
|
||||
disabled={testingState}
|
||||
loading={testingState}
|
||||
onClick={(): void => onTestHandler(type)}
|
||||
>
|
||||
|
||||
@@ -467,10 +467,6 @@ function FormAlertRules({
|
||||
panelType,
|
||||
]);
|
||||
|
||||
const isAlertAvailable =
|
||||
!featureFlags?.find((flag) => flag.name === FeatureKeys.QUERY_BUILDER_ALERTS)
|
||||
?.active || false;
|
||||
|
||||
const saveRule = useCallback(async () => {
|
||||
if (!isFormValid()) {
|
||||
return;
|
||||
@@ -688,11 +684,6 @@ function FormAlertRules({
|
||||
|
||||
const isAlertNameMissing = !formInstance.getFieldValue('alert');
|
||||
|
||||
const isAlertAvailableToSave =
|
||||
isAlertAvailable &&
|
||||
currentQuery.queryType === EQueryType.QUERY_BUILDER &&
|
||||
alertType !== AlertTypes.METRICS_BASED_ALERT;
|
||||
|
||||
const onUnitChangeHandler = (value: string): void => {
|
||||
setYAxisUnit(value);
|
||||
// reset target unit
|
||||
@@ -865,7 +856,6 @@ function FormAlertRules({
|
||||
icon={<SaveOutlined />}
|
||||
disabled={
|
||||
isAlertNameMissing ||
|
||||
isAlertAvailableToSave ||
|
||||
!isChannelConfigurationValid ||
|
||||
queryStatus === 'error'
|
||||
}
|
||||
|
||||
@@ -23,10 +23,13 @@ function DataSourceInfo({
|
||||
|
||||
const notSendingData = !dataSentToSigNoz;
|
||||
|
||||
const isEnabled =
|
||||
activeLicenseV3 && activeLicenseV3.platform === LicensePlatform.CLOUD;
|
||||
|
||||
const {
|
||||
data: deploymentsData,
|
||||
isError: isErrorDeploymentsData,
|
||||
} = useGetDeploymentsData();
|
||||
} = useGetDeploymentsData(isEnabled || false);
|
||||
|
||||
const [region, setRegion] = useState<string>('');
|
||||
const [url, setUrl] = useState<string>('');
|
||||
|
||||
@@ -293,7 +293,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
isLoading: isLoadingDeploymentsData,
|
||||
isFetching: isFetchingDeploymentsData,
|
||||
isError: isErrorDeploymentsData,
|
||||
} = useGetDeploymentsData();
|
||||
} = useGetDeploymentsData(true);
|
||||
|
||||
const {
|
||||
mutate: createIngestionKey,
|
||||
|
||||
@@ -10,7 +10,6 @@ import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQue
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { useEventSourceEvent } from 'hooks/useEventSourceEvent';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { prepareQueryRangePayload } from 'lib/dashboard/prepareQueryRangePayload';
|
||||
import { useEventSource } from 'providers/EventSource';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
@@ -38,8 +37,6 @@ function LiveLogsContainer(): JSX.Element {
|
||||
|
||||
const batchedEventsRef = useRef<ILog[]>([]);
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { selectedTime: globalSelectedTime } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
@@ -50,6 +47,8 @@ function LiveLogsContainer(): JSX.Element {
|
||||
handleCloseConnection,
|
||||
initialLoading,
|
||||
isConnectionLoading,
|
||||
isConnectionError,
|
||||
reconnectDueToError,
|
||||
} = useEventSource();
|
||||
|
||||
const compositeQuery = useGetCompositeQueryParam();
|
||||
@@ -86,8 +85,8 @@ function LiveLogsContainer(): JSX.Element {
|
||||
);
|
||||
|
||||
const handleError = useCallback(() => {
|
||||
notifications.error({ message: 'Sorry, something went wrong' });
|
||||
}, [notifications]);
|
||||
console.error('Sorry, something went wrong');
|
||||
}, []);
|
||||
|
||||
useEventSourceEvent('message', handleGetLiveLogs);
|
||||
useEventSourceEvent('error', handleError);
|
||||
@@ -153,6 +152,23 @@ function LiveLogsContainer(): JSX.Element {
|
||||
handleStartNewConnection,
|
||||
]);
|
||||
|
||||
useEffect((): (() => void) | undefined => {
|
||||
if (isConnectionError && reconnectDueToError && compositeQuery) {
|
||||
// Small delay to prevent immediate reconnection attempts
|
||||
const reconnectTimer = setTimeout(() => {
|
||||
handleStartNewConnection(compositeQuery);
|
||||
}, 1000);
|
||||
|
||||
return (): void => clearTimeout(reconnectTimer);
|
||||
}
|
||||
return undefined;
|
||||
}, [
|
||||
isConnectionError,
|
||||
reconnectDueToError,
|
||||
compositeQuery,
|
||||
handleStartNewConnection,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const prefetchedList = queryLocationState?.listQueryPayload[0]?.list;
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
.inspect-metrics-modal {
|
||||
.inspect-metrics-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
|
||||
.inspect-metrics-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: default;
|
||||
color: var(--text-vanilla-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
frontend/src/container/MetricsExplorer/Inspect/Inspect.tsx
Normal file
48
frontend/src/container/MetricsExplorer/Inspect/Inspect.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import './Inspect.styles.scss';
|
||||
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Drawer, Typography } from 'antd';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { Compass } from 'lucide-react';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
|
||||
import { InspectProps } from './types';
|
||||
|
||||
function Inspect({ metricName, isOpen, onClose }: InspectProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<Drawer
|
||||
width="100%"
|
||||
title={
|
||||
<div className="inspect-metrics-title">
|
||||
<Typography.Text>Metrics Explorer —</Typography.Text>
|
||||
<Button
|
||||
className="inspect-metrics-button"
|
||||
size="small"
|
||||
icon={<Compass size={14} />}
|
||||
disabled
|
||||
>
|
||||
Inspect Metric
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
open={isOpen}
|
||||
onClose={onClose}
|
||||
style={{
|
||||
overscrollBehavior: 'contain',
|
||||
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
||||
}}
|
||||
className="inspect-metrics-modal"
|
||||
destroyOnClose
|
||||
>
|
||||
<div>Inspect</div>
|
||||
<div>{metricName}</div>
|
||||
</Drawer>
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export default Inspect;
|
||||
@@ -0,0 +1 @@
|
||||
export const INSPECT_FEATURE_FLAG_KEY = 'metrics-explorer-inspect-feature-flag';
|
||||
3
frontend/src/container/MetricsExplorer/Inspect/index.ts
Normal file
3
frontend/src/container/MetricsExplorer/Inspect/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Inspect from './Inspect';
|
||||
|
||||
export default Inspect;
|
||||
5
frontend/src/container/MetricsExplorer/Inspect/types.ts
Normal file
5
frontend/src/container/MetricsExplorer/Inspect/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type InspectProps = {
|
||||
metricName: string | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
11
frontend/src/container/MetricsExplorer/Inspect/utils.tsx
Normal file
11
frontend/src/container/MetricsExplorer/Inspect/utils.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { INSPECT_FEATURE_FLAG_KEY } from './constants';
|
||||
|
||||
/**
|
||||
* Check if the inspect feature flag is enabled
|
||||
* returns true if the feature flag is enabled, false otherwise
|
||||
* Show the inspect button in metrics explorer if the feature flag is enabled
|
||||
*/
|
||||
export function isInspectEnabled(): boolean {
|
||||
const featureFlag = localStorage.getItem(INSPECT_FEATURE_FLAG_KEY);
|
||||
return featureFlag === 'true';
|
||||
}
|
||||
@@ -20,6 +20,12 @@
|
||||
gap: 4px;
|
||||
background-color: var(--bg-slate-300);
|
||||
}
|
||||
|
||||
.inspect-metrics-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.metric-details-content {
|
||||
|
||||
@@ -2,12 +2,21 @@ import './MetricDetails.styles.scss';
|
||||
import '../Summary/Summary.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Divider, Drawer, Empty, Skeleton, Tooltip, Typography } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Drawer,
|
||||
Empty,
|
||||
Skeleton,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { useGetMetricDetails } from 'hooks/metricsExplorer/useGetMetricDetails';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { X } from 'lucide-react';
|
||||
import { Compass, X } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { isInspectEnabled } from '../Inspect/utils';
|
||||
import { formatNumberIntoHumanReadableFormat } from '../Summary/utils';
|
||||
import AllAttributes from './AllAttributes';
|
||||
import DashboardsAndAlertsPopover from './DashboardsAndAlertsPopover';
|
||||
@@ -22,6 +31,7 @@ function MetricDetails({
|
||||
onClose,
|
||||
isOpen,
|
||||
metricName,
|
||||
openInspectModal,
|
||||
}: MetricDetailsProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
// const { safeNavigate } = useSafeNavigate();
|
||||
@@ -43,6 +53,8 @@ function MetricDetails({
|
||||
return formatTimestampToReadableDate(metric.lastReceived);
|
||||
}, [metric]);
|
||||
|
||||
const showInspectFeature = useMemo(() => isInspectEnabled(), []);
|
||||
|
||||
const isMetricDetailsLoading = isLoading || isFetching;
|
||||
|
||||
const timeSeries = useMemo(() => {
|
||||
@@ -92,6 +104,19 @@ function MetricDetails({
|
||||
>
|
||||
Open in Explorer
|
||||
</Button> */}
|
||||
{/* Show the based on the feature flag. Will remove before releasing the feature */}
|
||||
{showInspectFeature && (
|
||||
<Button
|
||||
className="inspect-metrics-button"
|
||||
aria-label="Inspect Metric"
|
||||
icon={<Compass size={18} />}
|
||||
onClick={(): void => {
|
||||
if (metric?.name) {
|
||||
openInspectModal(metric.name);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
placement="right"
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface MetricDetailsProps {
|
||||
isOpen: boolean;
|
||||
metricName: string | null;
|
||||
isModalTimeSelection: boolean;
|
||||
openInspectModal: (metricName: string) => void;
|
||||
}
|
||||
|
||||
export interface DashboardsAndAlertsPopoverProps {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { AppState } from 'store/reducers';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import InspectModal from '../Inspect';
|
||||
import MetricDetails from '../MetricDetails';
|
||||
import MetricsSearch from './MetricsSearch';
|
||||
import MetricsTable from './MetricsTable';
|
||||
@@ -35,6 +36,7 @@ function Summary(): JSX.Element {
|
||||
TreemapViewType.TIMESERIES,
|
||||
);
|
||||
const [isMetricDetailsOpen, setIsMetricDetailsOpen] = useState(false);
|
||||
const [isInspectModalOpen, setIsInspectModalOpen] = useState(false);
|
||||
const [selectedMetricName, setSelectedMetricName] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
@@ -150,6 +152,17 @@ function Summary(): JSX.Element {
|
||||
setIsMetricDetailsOpen(false);
|
||||
};
|
||||
|
||||
const openInspectModal = (metricName: string): void => {
|
||||
setSelectedMetricName(metricName);
|
||||
setIsInspectModalOpen(true);
|
||||
setIsMetricDetailsOpen(false);
|
||||
};
|
||||
|
||||
const closeInspectModal = (): void => {
|
||||
setIsInspectModalOpen(false);
|
||||
setSelectedMetricName(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<div className="metrics-explorer-summary-tab">
|
||||
@@ -184,6 +197,14 @@ function Summary(): JSX.Element {
|
||||
onClose={closeMetricDetails}
|
||||
metricName={selectedMetricName}
|
||||
isModalTimeSelection={false}
|
||||
openInspectModal={openInspectModal}
|
||||
/>
|
||||
)}
|
||||
{isInspectModalOpen && (
|
||||
<InspectModal
|
||||
isOpen={isInspectModalOpen}
|
||||
onClose={closeInspectModal}
|
||||
metricName={selectedMetricName}
|
||||
/>
|
||||
)}
|
||||
</Sentry.ErrorBoundary>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { WarningOutlined } from '@ant-design/icons';
|
||||
import { Button, Flex, Modal, Space, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import {
|
||||
initialQueriesMap,
|
||||
@@ -27,7 +26,6 @@ import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { cloneDeep, defaultTo, isEmpty, isUndefined } from 'lodash-es';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import {
|
||||
getNextWidgets,
|
||||
@@ -79,8 +77,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
|
||||
const { t } = useTranslation(['dashboard']);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
|
||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||
|
||||
const {
|
||||
@@ -566,12 +562,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const isQueryBuilderActive =
|
||||
!featureFlags?.find((flag) => flag.name === FeatureKeys.QUERY_BUILDER_PANELS)
|
||||
?.active || false;
|
||||
|
||||
const isNewTraceLogsAvailable =
|
||||
isQueryBuilderActive &&
|
||||
currentQuery.queryType === EQueryType.QUERY_BUILDER &&
|
||||
currentQuery.builder.queryData.find(
|
||||
(query) => query.dataSource !== DataSource.METRICS,
|
||||
|
||||
@@ -15,11 +15,12 @@ import {
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import history from 'lib/history';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { ArrowRight, X } from 'lucide-react';
|
||||
import { CheckIcon, Goal, UserPlus, X } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import OnboardingIngestionDetails from '../IngestionDetails/IngestionDetails';
|
||||
import InviteTeamMembers from '../InviteTeamMembers/InviteTeamMembers';
|
||||
@@ -68,6 +69,7 @@ interface Entity {
|
||||
};
|
||||
};
|
||||
tags: string[];
|
||||
relatedSearchKeywords?: string[];
|
||||
link?: string;
|
||||
}
|
||||
|
||||
@@ -99,8 +101,11 @@ const ONBOARDING_V3_ANALYTICS_EVENTS_MAP = {
|
||||
GET_EXPERT_ASSISTANCE_BUTTON_CLICKED: 'Get expert assistance clicked',
|
||||
INVITE_TEAM_MEMBER_BUTTON_CLICKED: 'Invite team member clicked',
|
||||
CLOSE_ONBOARDING_CLICKED: 'Close onboarding clicked',
|
||||
DATA_SOURCE_REQUESTED: 'Datasource requested',
|
||||
DATA_SOURCE_SEARCHED: 'Searched',
|
||||
};
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function OnboardingAddDataSource(): JSX.Element {
|
||||
const [groupedDataSources, setGroupedDataSources] = useState<{
|
||||
[tag: string]: Entity[];
|
||||
@@ -110,6 +115,8 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
|
||||
const [setupStepItems, setSetupStepItems] = useState(setupStepItemsBase);
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
|
||||
const question2Ref = useRef<HTMLDivElement | null>(null);
|
||||
const question3Ref = useRef<HTMLDivElement | null>(null);
|
||||
const configureProdRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -120,8 +127,15 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
|
||||
const [dataSourceRequest, setDataSourceRequest] = useState<string>('');
|
||||
|
||||
const [hasMoreQuestions, setHasMoreQuestions] = useState<boolean>(true);
|
||||
|
||||
const [
|
||||
showRequestDataSourceModal,
|
||||
setShowRequestDataSourceModal,
|
||||
] = useState<boolean>(false);
|
||||
|
||||
const [
|
||||
showInviteTeamMembersModal,
|
||||
setShowInviteTeamMembersModal,
|
||||
@@ -145,6 +159,11 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('All');
|
||||
|
||||
const [
|
||||
dataSourceRequestSubmitted,
|
||||
setDataSourceRequestSubmitted,
|
||||
] = useState<boolean>(false);
|
||||
|
||||
const handleScrollToStep = (ref: React.RefObject<HTMLDivElement>): void => {
|
||||
setTimeout(() => {
|
||||
ref.current?.scrollIntoView({
|
||||
@@ -286,8 +305,10 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
setGroupedDataSources(groupedDataSources);
|
||||
}, []);
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const query = e.target.value.toLowerCase();
|
||||
const debouncedUpdate = useDebouncedFn((query) => {
|
||||
setSearchQuery(query as string);
|
||||
|
||||
setDataSourceRequestSubmitted(false);
|
||||
|
||||
if (query === '') {
|
||||
setGroupedDataSources(
|
||||
@@ -298,15 +319,35 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
|
||||
const filteredDataSources = onboardingConfigWithLinks.filter(
|
||||
(dataSource) =>
|
||||
dataSource.label.toLowerCase().includes(query) ||
|
||||
dataSource.tags.some((tag) => tag.toLowerCase().includes(query)),
|
||||
dataSource.label.toLowerCase().includes(query as string) ||
|
||||
dataSource.tags.some((tag) =>
|
||||
tag.toLowerCase().includes(query as string),
|
||||
) ||
|
||||
dataSource.relatedSearchKeywords?.some((keyword) =>
|
||||
keyword?.toLowerCase().includes(query as string),
|
||||
),
|
||||
);
|
||||
|
||||
setGroupedDataSources(
|
||||
groupDataSourcesByTags(filteredDataSources as Entity[]),
|
||||
);
|
||||
};
|
||||
|
||||
logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_SEARCHED}`,
|
||||
{
|
||||
searchedDataSource: query,
|
||||
},
|
||||
);
|
||||
}, 300);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const query = e.target.value.trim().toLowerCase();
|
||||
|
||||
debouncedUpdate(query || '');
|
||||
},
|
||||
[debouncedUpdate],
|
||||
);
|
||||
const handleFilterByCategory = (category: string): void => {
|
||||
setSelectedDataSource(null);
|
||||
setSelectedFramework(null);
|
||||
@@ -409,6 +450,129 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
setShowInviteTeamMembersModal(true);
|
||||
};
|
||||
|
||||
const handleSubmitDataSourceRequest = (): void => {
|
||||
logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_REQUESTED}`,
|
||||
{
|
||||
requestedDataSource: dataSourceRequest,
|
||||
},
|
||||
);
|
||||
setShowRequestDataSourceModal(false);
|
||||
setDataSourceRequestSubmitted(true);
|
||||
};
|
||||
|
||||
const handleRequestDataSource = (): void => {
|
||||
setShowRequestDataSourceModal(true);
|
||||
};
|
||||
|
||||
const handleRaiseRequest = (): void => {
|
||||
logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_REQUESTED}`,
|
||||
{
|
||||
requestedDataSource: searchQuery,
|
||||
},
|
||||
);
|
||||
|
||||
setDataSourceRequestSubmitted(true);
|
||||
};
|
||||
|
||||
const renderRequestDataSource = (): JSX.Element => {
|
||||
const isSearchQueryEmpty = searchQuery.length === 0;
|
||||
const isNoResultsFound = Object.keys(groupedDataSources).length === 0;
|
||||
|
||||
return (
|
||||
<div className="request-data-source-container">
|
||||
{!isNoResultsFound && (
|
||||
<>
|
||||
<Typography.Text>Can’t find what you’re looking for?</Typography.Text>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="279"
|
||||
height="2"
|
||||
viewBox="0 0 279 2"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M0 1L279 1"
|
||||
stroke="#7190F9"
|
||||
strokeOpacity="0.2"
|
||||
strokeDasharray="4 4"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{!dataSourceRequestSubmitted && (
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn request-data-source-btn secondary"
|
||||
icon={<Goal size={16} />}
|
||||
onClick={handleRequestDataSource}
|
||||
>
|
||||
Request Data Source
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{dataSourceRequestSubmitted && (
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn request-data-source-btn success"
|
||||
icon={<CheckIcon size={16} />}
|
||||
>
|
||||
Request raised
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{isNoResultsFound && !isSearchQueryEmpty && (
|
||||
<>
|
||||
<Typography.Text>
|
||||
Our team can help add{' '}
|
||||
<span className="request-data-source-search-query">{searchQuery}</span>{' '}
|
||||
support for you
|
||||
</Typography.Text>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="279"
|
||||
height="2"
|
||||
viewBox="0 0 279 2"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M0 1L279 1"
|
||||
stroke="#7190F9"
|
||||
strokeOpacity="0.2"
|
||||
strokeDasharray="4 4"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{!dataSourceRequestSubmitted && (
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn request-data-source-btn secondary"
|
||||
icon={<Goal size={16} />}
|
||||
onClick={handleRaiseRequest}
|
||||
>
|
||||
Raise request
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{dataSourceRequestSubmitted && (
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn request-data-source-btn success"
|
||||
icon={<CheckIcon size={16} />}
|
||||
>
|
||||
Request raised
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="onboarding-v2">
|
||||
<Layout>
|
||||
@@ -433,6 +597,15 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
</div>
|
||||
|
||||
<div className="header-right-section">
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn invite-teammate-btn outlined"
|
||||
onClick={handleShowInviteTeamMembersModal}
|
||||
icon={<UserPlus size={16} />}
|
||||
>
|
||||
Invite a teammate
|
||||
</Button>
|
||||
|
||||
<LaunchChatSupport
|
||||
attributes={{
|
||||
dataSource: selectedDataSource?.dataSource,
|
||||
@@ -442,7 +615,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
}}
|
||||
eventName={`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.GET_HELP_BUTTON_CLICKED}`}
|
||||
message=""
|
||||
buttonText="Get Help"
|
||||
buttonText="Contact Support"
|
||||
className="periscope-btn get-help-btn outlined"
|
||||
/>
|
||||
</div>
|
||||
@@ -461,7 +634,11 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
</Header>
|
||||
|
||||
<div className="onboarding-product-setup-container">
|
||||
<div className="onboarding-product-setup-container_left-section">
|
||||
<div
|
||||
className={`onboarding-product-setup-container_left-section ${
|
||||
currentStep === 1 ? 'step-id-1' : 'step-id-2'
|
||||
}`}
|
||||
>
|
||||
<div className="perlian-bg" />
|
||||
|
||||
{currentStep === 1 && (
|
||||
@@ -491,6 +668,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
<div className="onboarding-data-source-search">
|
||||
<Input
|
||||
placeholder="Search"
|
||||
maxLength={20}
|
||||
onChange={handleSearch}
|
||||
addonAfter={<SearchOutlined />}
|
||||
/>
|
||||
@@ -525,6 +703,14 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{Object.keys(groupedDataSources).length === 0 && (
|
||||
<div className="no-results-found-container">
|
||||
<Typography.Text>No results for {searchQuery} :/</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!selectedDataSource && renderRequestDataSource()}
|
||||
</div>
|
||||
|
||||
<div className="data-source-categories-filter-container">
|
||||
@@ -534,33 +720,66 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
Filters{' '}
|
||||
</Typography.Title>
|
||||
|
||||
<Typography.Title
|
||||
level={5}
|
||||
className={`onboarding-filters-item-title ${
|
||||
selectedCategory === 'All' ? 'selected' : ''
|
||||
}`}
|
||||
<div
|
||||
key="all"
|
||||
className="onboarding-data-source-category-item"
|
||||
onClick={(): void => handleFilterByCategory('All')}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
handleFilterByCategory('All');
|
||||
}
|
||||
}}
|
||||
>
|
||||
All ({onboardingConfigWithLinks.length})
|
||||
</Typography.Title>
|
||||
<Typography.Title
|
||||
level={5}
|
||||
className={`onboarding-filters-item-title ${
|
||||
selectedCategory === 'All' ? 'selected' : ''
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</Typography.Title>
|
||||
|
||||
<div className="line-divider" />
|
||||
|
||||
<Typography.Text className="onboarding-filters-item-count">
|
||||
{onboardingConfigWithLinks.length}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
{Object.keys(groupedDataSources).map((tag) => (
|
||||
<div key={tag} className="onboarding-data-source-category-item">
|
||||
<div
|
||||
key={tag}
|
||||
className="onboarding-data-source-category-item"
|
||||
onClick={(): void => handleFilterByCategory(tag)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
handleFilterByCategory(tag);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography.Title
|
||||
level={5}
|
||||
className={`onboarding-filters-item-title ${
|
||||
selectedCategory === tag ? 'selected' : ''
|
||||
}`}
|
||||
onClick={(): void => handleFilterByCategory(tag)}
|
||||
>
|
||||
{tag} ({groupedDataSources[tag].length})
|
||||
{tag}
|
||||
</Typography.Title>
|
||||
|
||||
<div className="line-divider" />
|
||||
|
||||
<Typography.Text className="onboarding-filters-item-count">
|
||||
{groupedDataSources[tag].length}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedDataSource &&
|
||||
selectedDataSource?.question &&
|
||||
!isEmpty(selectedDataSource?.question) && (
|
||||
@@ -615,7 +834,6 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedFramework &&
|
||||
selectedFramework?.question &&
|
||||
!isEmpty(selectedFramework?.question) && (
|
||||
@@ -659,7 +877,6 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hasMoreQuestions && showConfigureProduct && (
|
||||
<div className="questionaire-footer" ref={configureProdRef}>
|
||||
<Button
|
||||
@@ -767,39 +984,6 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
</div>
|
||||
|
||||
<div className="onboarding-product-setup-container_right-section">
|
||||
{currentStep === 1 && (
|
||||
<div className="invite-user-section-content">
|
||||
<Button
|
||||
type="default"
|
||||
shape="round"
|
||||
className="invite-user-section-content-button"
|
||||
onClick={handleShowInviteTeamMembersModal}
|
||||
>
|
||||
Invite a team member to help with this step
|
||||
<ArrowRight size={14} />
|
||||
</Button>
|
||||
<div className="need-help-section-content-divider">Or</div>
|
||||
<div className="need-help-section-content">
|
||||
<Typography.Text>
|
||||
Need help with setup? Upgrade now and get expert assistance.
|
||||
</Typography.Text>
|
||||
|
||||
<LaunchChatSupport
|
||||
attributes={{
|
||||
dataSource: selectedDataSource?.dataSource,
|
||||
framework: selectedFramework?.label,
|
||||
environment: selectedEnvironment?.label,
|
||||
currentPage: setupStepItems[currentStep]?.title || '',
|
||||
}}
|
||||
eventName={`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.GET_EXPERT_ASSISTANCE_BUTTON_CLICKED}`}
|
||||
message=""
|
||||
buttonText="Get Expert Assistance"
|
||||
className="periscope-btn get-help-btn rounded-btn outlined"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentStep === 2 && <OnboardingIngestionDetails />}
|
||||
</div>
|
||||
</div>
|
||||
@@ -824,6 +1008,46 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
className="request-data-source-modal"
|
||||
title={<span className="title">Request Data Source</span>}
|
||||
open={showRequestDataSourceModal}
|
||||
closable
|
||||
onCancel={(): void => setShowRequestDataSourceModal(false)}
|
||||
width="640px"
|
||||
footer={[
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn outlined"
|
||||
key="back"
|
||||
onClick={(): void => setShowRequestDataSourceModal(false)}
|
||||
icon={<X size={16} />}
|
||||
>
|
||||
Cancel
|
||||
</Button>,
|
||||
<Button
|
||||
key="submit"
|
||||
type="primary"
|
||||
className="periscope-btn primary"
|
||||
disabled={dataSourceRequest.length <= 0}
|
||||
onClick={handleSubmitDataSourceRequest}
|
||||
icon={<CheckIcon size={16} />}
|
||||
>
|
||||
Submit request
|
||||
</Button>,
|
||||
]}
|
||||
destroyOnClose
|
||||
>
|
||||
<div className="request-data-source-modal-content">
|
||||
<Typography.Text>Enter your request</Typography.Text>
|
||||
<Input
|
||||
placeholder="Eg: Kotlin"
|
||||
className="request-data-source-modal-input"
|
||||
onChange={(e): void => setDataSourceRequest(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</Layout>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function OnboardingIngestionDetails(): JSX.Element {
|
||||
isLoading: isLoadingDeploymentsData,
|
||||
isFetching: isFetchingDeploymentsData,
|
||||
isError: isDeploymentsDataError,
|
||||
} = useGetDeploymentsData();
|
||||
} = useGetDeploymentsData(true);
|
||||
|
||||
const handleCopyKey = (text: string): void => {
|
||||
handleCopyToClipboard(text);
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
|
||||
&__header {
|
||||
background: rgba(11, 12, 14, 0.7);
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
backdrop-filter: blur(20px);
|
||||
padding: 16px 0px 0px 0px;
|
||||
padding: 12px 0px;
|
||||
|
||||
&--sticky {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0px 1rem;
|
||||
// margin-top: 16px;
|
||||
margin-top: 12px;
|
||||
|
||||
background: rgba(11, 12, 14, 0.7);
|
||||
backdrop-filter: blur(20px);
|
||||
@@ -323,7 +324,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.get-help-btn {
|
||||
.get-help-btn,
|
||||
.invite-teammate-btn {
|
||||
font-size: 11px;
|
||||
padding: 6px 16px;
|
||||
border: 1px solid var(--bg-slate-400) !important;
|
||||
@@ -610,15 +612,61 @@
|
||||
display: flex;
|
||||
|
||||
.data-sources-container {
|
||||
flex: 0 0 70%;
|
||||
max-width: 70%;
|
||||
flex: 0 0 80%;
|
||||
max-width: 80%;
|
||||
|
||||
margin-right: 32px;
|
||||
}
|
||||
|
||||
.data-source-categories-filter-container {
|
||||
flex: 0 0 30%;
|
||||
max-width: 30%;
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
|
||||
.onboarding-data-source-category {
|
||||
.onboarding-data-source-category-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.onboarding-filters-item-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.line-divider {
|
||||
height: 1px;
|
||||
margin: 0 16px;
|
||||
flex-grow: 1;
|
||||
border-top: 2px dotted var(--bg-slate-400);
|
||||
}
|
||||
|
||||
.onboarding-filters-item-count {
|
||||
color: var(--text-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 150% */
|
||||
|
||||
background-color: var(--bg-ink-400);
|
||||
border-radius: 4px;
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,8 +691,14 @@
|
||||
max-width: 70%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
|
||||
&.step-id-1 {
|
||||
flex: 0 0 90%;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
// border-right: 1px solid var(--Greyscale-Slate-400, #1d212d);
|
||||
|
||||
.perlian-bg {
|
||||
@@ -678,7 +732,7 @@
|
||||
&_right-section {
|
||||
flex: 1;
|
||||
max-width: 30%;
|
||||
height: calc(100vh - 120px);
|
||||
height: calc(100vh - 130px);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -972,6 +1026,52 @@
|
||||
}
|
||||
}
|
||||
|
||||
.no-results-found-container {
|
||||
.ant-typography {
|
||||
color: rgba(192, 193, 195, 0.6);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
line-height: 18px; /* 150% */
|
||||
letter-spacing: 0.48px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.request-data-source-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
margin: 36px 0;
|
||||
|
||||
display: flex;
|
||||
|
||||
width: fit-content;
|
||||
gap: 24px;
|
||||
|
||||
padding: 12px 12px 12px 16px;
|
||||
border-radius: 6px;
|
||||
background: rgba(171, 189, 255, 0.06);
|
||||
|
||||
.request-data-source-search-query {
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(173, 127, 88, 0.1);
|
||||
background: rgba(173, 127, 88, 0.1);
|
||||
|
||||
color: var(--Sienna-400, #bd9979);
|
||||
font-size: 13px;
|
||||
padding: 2px;
|
||||
line-height: 20px; /* 142.857% */
|
||||
}
|
||||
|
||||
.request-data-source-btn {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--Slate-200, #2c3140);
|
||||
background: var(--Ink-200, #23262e);
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-data-source-category-container {
|
||||
flex: 1;
|
||||
max-width: 30%;
|
||||
@@ -996,7 +1096,7 @@
|
||||
}
|
||||
|
||||
.onboarding-configure-container {
|
||||
height: calc(100vh - 120px);
|
||||
height: calc(100vh - 130px);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1070,7 +1170,8 @@
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.invite-team-member-modal {
|
||||
.invite-team-member-modal,
|
||||
.request-data-source-modal {
|
||||
.ant-modal-content {
|
||||
background-color: var(--bg-ink-500);
|
||||
}
|
||||
@@ -1079,9 +1180,26 @@
|
||||
background-color: var(--bg-ink-500);
|
||||
}
|
||||
|
||||
.invite-team-member-modal-content {
|
||||
.invite-team-member-modal-content,
|
||||
.request-data-source-modal-content {
|
||||
background-color: var(--bg-ink-500);
|
||||
}
|
||||
|
||||
.request-data-source-modal-content {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.request-data-source-modal-input {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.request-data-source-modal {
|
||||
.ant-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.ingestion-setup-details-links {
|
||||
@@ -1262,7 +1380,8 @@
|
||||
}
|
||||
|
||||
.onboarding-v2 {
|
||||
.get-help-btn {
|
||||
.get-help-btn,
|
||||
.invite-teammate-btn {
|
||||
border: 1px solid var(--bg-vanilla-300) !important;
|
||||
color: var(--bg-ink-300) !important;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,11 +13,7 @@ function OrganizationSettings(): JSX.Element {
|
||||
const isNotSSO =
|
||||
!featureFlags?.find((flag) => flag.name === FeatureKeys.SSO)?.active || false;
|
||||
|
||||
const isNoUpSell =
|
||||
!featureFlags?.find((flag) => flag.name === FeatureKeys.DISABLE_UPSELL)
|
||||
?.active || false;
|
||||
|
||||
const isAuthDomain = !isNoUpSell || (isNoUpSell && !isNotSSO);
|
||||
const isAuthDomain = !isNotSSO;
|
||||
|
||||
if (!org) {
|
||||
return <div />;
|
||||
|
||||
@@ -453,7 +453,7 @@ export const Query = memo(function Query({
|
||||
</Col>
|
||||
)}
|
||||
<Col flex="1" className="qb-search-container">
|
||||
{[DataSource.LOGS, DataSource.TRACES].includes(query.dataSource) ? (
|
||||
{query.dataSource === DataSource.LOGS ? (
|
||||
<QueryBuilderSearchV2
|
||||
query={query}
|
||||
onChange={handleChangeTagFilters}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import './QueryBuilderSearchV2.styles.scss';
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
@@ -26,7 +25,6 @@ interface ICustomDropdownProps {
|
||||
exampleQueries: TagFilter[];
|
||||
onChange: (value: TagFilter) => void;
|
||||
currentFilterItem?: ITag;
|
||||
isLogsDataSource: boolean;
|
||||
}
|
||||
|
||||
export default function QueryBuilderSearchDropdown(
|
||||
@@ -40,14 +38,11 @@ export default function QueryBuilderSearchDropdown(
|
||||
exampleQueries,
|
||||
options,
|
||||
onChange,
|
||||
isLogsDataSource,
|
||||
} = props;
|
||||
const userOs = getUserOperatingSystem();
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cx('content', { 'non-logs-data-source': !isLogsDataSource })}
|
||||
>
|
||||
<div className="content">
|
||||
{!currentFilterItem?.key ? (
|
||||
<div className="suggested-filters">Suggested Filters</div>
|
||||
) : !currentFilterItem?.op ? (
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
.query-builder-search {
|
||||
.content {
|
||||
.suggested-filters {
|
||||
color: var(--bg-slate-50);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.88px;
|
||||
text-transform: uppercase;
|
||||
padding: 12px 0px 8px 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.query-builder-search-v2 {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
@@ -27,11 +11,6 @@
|
||||
.rc-virtual-list-holder {
|
||||
height: 115px;
|
||||
}
|
||||
&.non-logs-data-source {
|
||||
.rc-virtual-list-holder {
|
||||
height: 256px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,9 +88,6 @@ interface QueryBuilderSearchV2Props {
|
||||
className?: string;
|
||||
suffixIcon?: React.ReactNode;
|
||||
hardcodedAttributeKeys?: BaseAutocompleteData[];
|
||||
hasPopupContainer?: boolean;
|
||||
rootClassName?: string;
|
||||
maxTagCount?: number | 'responsive';
|
||||
}
|
||||
|
||||
export interface Option {
|
||||
@@ -124,9 +121,6 @@ function QueryBuilderSearchV2(
|
||||
suffixIcon,
|
||||
whereClauseConfig,
|
||||
hardcodedAttributeKeys,
|
||||
hasPopupContainer,
|
||||
rootClassName,
|
||||
maxTagCount,
|
||||
} = props;
|
||||
|
||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||
@@ -695,29 +689,12 @@ function QueryBuilderSearchV2(
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
setDropdownOptions([
|
||||
// Add user typed option if it doesn't exist in the payload
|
||||
...(!isEmpty(tagKey) &&
|
||||
!data?.payload?.attributeKeys?.some((val) => isEqual(val.key, tagKey))
|
||||
? [
|
||||
{
|
||||
label: tagKey,
|
||||
value: {
|
||||
key: tagKey,
|
||||
dataType: DataTypes.EMPTY,
|
||||
type: '',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
// Map existing attribute keys from payload
|
||||
...(data?.payload?.attributeKeys?.map((key) => ({
|
||||
setDropdownOptions(
|
||||
data?.payload?.attributeKeys?.map((key) => ({
|
||||
label: key.key,
|
||||
value: key,
|
||||
})) || []),
|
||||
]);
|
||||
})) || [],
|
||||
);
|
||||
}
|
||||
}
|
||||
if (currentState === DropdownState.OPERATOR) {
|
||||
@@ -934,10 +911,7 @@ function QueryBuilderSearchV2(
|
||||
<div className="query-builder-search-v2">
|
||||
<Select
|
||||
ref={selectRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...(hasPopupContainer ? { getPopupContainer: popupContainer } : {})}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...(maxTagCount ? { maxTagCount } : {})}
|
||||
getPopupContainer={popupContainer}
|
||||
key={queryTags.join('.')}
|
||||
virtual={false}
|
||||
showSearch
|
||||
@@ -969,7 +943,7 @@ function QueryBuilderSearchV2(
|
||||
: '',
|
||||
className,
|
||||
)}
|
||||
rootClassName={cx('query-builder-search', rootClassName)}
|
||||
rootClassName="query-builder-search"
|
||||
disabled={isMetricsDataSource && !query.aggregateAttribute.key}
|
||||
style={selectStyle}
|
||||
onSearch={handleSearch}
|
||||
@@ -990,7 +964,6 @@ function QueryBuilderSearchV2(
|
||||
exampleQueries={suggestionsData?.payload?.example_queries || []}
|
||||
tags={tags}
|
||||
currentFilterItem={currentFilterItem}
|
||||
isLogsDataSource={isLogsDataSource}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
@@ -1026,10 +999,7 @@ QueryBuilderSearchV2.defaultProps = {
|
||||
className: '',
|
||||
suffixIcon: null,
|
||||
whereClauseConfig: {},
|
||||
hasPopupContainer: true,
|
||||
rootClassName: '',
|
||||
hardcodedAttributeKeys: undefined,
|
||||
maxTagCount: undefined,
|
||||
};
|
||||
|
||||
export default QueryBuilderSearchV2;
|
||||
|
||||
@@ -69,6 +69,12 @@
|
||||
opacity: 1;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
&.community-enterprise-user {
|
||||
color: var(--bg-sakura-500);
|
||||
background: rgba(255, 113, 113, 0.1);
|
||||
border: 1px solid var(--bg-sakura-500);
|
||||
}
|
||||
}
|
||||
|
||||
.dockBtn {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import './SideNav.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
@@ -75,7 +75,7 @@ function SideNav(): JSX.Element {
|
||||
|
||||
const [userManagementMenuItems, setUserManagementMenuItems] = useState<
|
||||
UserManagementMenuItems[]
|
||||
>([manageLicenseMenuItem]);
|
||||
>([]);
|
||||
|
||||
const onClickSlackHandler = (): void => {
|
||||
window.open('https://signoz.io/slack', '_blank');
|
||||
@@ -88,8 +88,10 @@ function SideNav(): JSX.Element {
|
||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||
|
||||
const {
|
||||
isCloudUser: isCloudUserVal,
|
||||
isEECloudUser: isEECloudUserVal,
|
||||
isCloudUser,
|
||||
isEnterpriseSelfHostedUser,
|
||||
isCommunityUser,
|
||||
isCommunityEnterpriseUser,
|
||||
} = useGetTenantLicense();
|
||||
|
||||
const { t } = useTranslation('');
|
||||
@@ -103,11 +105,6 @@ function SideNav(): JSX.Element {
|
||||
licenseStatus?.toLocaleLowerCase() ===
|
||||
LICENSE_PLAN_STATUS.VALID.toLocaleLowerCase();
|
||||
|
||||
const isEnterprise = licenses?.licenses?.some(
|
||||
(license: License) =>
|
||||
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.ENTERPRISE_PLAN,
|
||||
);
|
||||
|
||||
const onClickSignozCloud = (): void => {
|
||||
window.open(
|
||||
'https://signoz.io/oss-to-cloud/?utm_source=product_navbar&utm_medium=frontend&utm_campaign=oss_users',
|
||||
@@ -201,14 +198,21 @@ function SideNav(): JSX.Element {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isCloudUserVal) {
|
||||
if (isCloudUser) {
|
||||
setLicenseTag('Cloud');
|
||||
} else if (isEnterprise) {
|
||||
} else if (isEnterpriseSelfHostedUser) {
|
||||
setLicenseTag('Enterprise');
|
||||
} else {
|
||||
setLicenseTag('Free');
|
||||
} else if (isCommunityEnterpriseUser) {
|
||||
setLicenseTag('Enterprise');
|
||||
} else if (isCommunityUser) {
|
||||
setLicenseTag('Community');
|
||||
}
|
||||
}, [isCloudUserVal, isEnterprise]);
|
||||
}, [
|
||||
isCloudUser,
|
||||
isEnterpriseSelfHostedUser,
|
||||
isCommunityEnterpriseUser,
|
||||
isCommunityUser,
|
||||
]);
|
||||
|
||||
const [isCurrentOrgSettings] = useComponentPermission(
|
||||
['current_org_settings'],
|
||||
@@ -280,17 +284,7 @@ function SideNav(): JSX.Element {
|
||||
manageLicenseMenuItem,
|
||||
];
|
||||
|
||||
const isApiMonitoringEnabled = featureFlags?.find(
|
||||
(flag) => flag.name === FeatureKeys.THIRD_PARTY_API,
|
||||
)?.active;
|
||||
|
||||
if (!isApiMonitoringEnabled) {
|
||||
updatedMenuItems = updatedMenuItems.filter(
|
||||
(item) => item.key !== ROUTES.API_MONITORING,
|
||||
);
|
||||
}
|
||||
|
||||
if (isCloudUserVal || isEECloudUserVal) {
|
||||
if (isCloudUser || isEnterpriseSelfHostedUser) {
|
||||
const isOnboardingEnabled =
|
||||
featureFlags?.find((feature) => feature.name === FeatureKeys.ONBOARDING)
|
||||
?.active || false;
|
||||
@@ -317,6 +311,11 @@ function SideNav(): JSX.Element {
|
||||
}
|
||||
|
||||
updatedUserManagementItems = [helpSupportMenuItem];
|
||||
|
||||
// Show manage license menu item for EE cloud users with a active license
|
||||
if (isEnterpriseSelfHostedUser) {
|
||||
updatedUserManagementItems.push(manageLicenseMenuItem);
|
||||
}
|
||||
} else {
|
||||
updatedMenuItems = updatedMenuItems.filter(
|
||||
(item) => item.key !== ROUTES.INTEGRATIONS && item.key !== ROUTES.BILLING,
|
||||
@@ -332,20 +331,21 @@ function SideNav(): JSX.Element {
|
||||
onClick: onClickVersionHandler,
|
||||
};
|
||||
|
||||
updatedUserManagementItems = [
|
||||
versionMenuItem,
|
||||
slackSupportMenuItem,
|
||||
manageLicenseMenuItem,
|
||||
];
|
||||
updatedUserManagementItems = [versionMenuItem, slackSupportMenuItem];
|
||||
|
||||
if (isCommunityEnterpriseUser) {
|
||||
updatedUserManagementItems.push(manageLicenseMenuItem);
|
||||
}
|
||||
}
|
||||
setMenuItems(updatedMenuItems);
|
||||
setUserManagementMenuItems(updatedUserManagementItems);
|
||||
}, [
|
||||
isCommunityEnterpriseUser,
|
||||
currentVersion,
|
||||
featureFlags,
|
||||
isCloudUserVal,
|
||||
isCloudUser,
|
||||
isEnterpriseSelfHostedUser,
|
||||
isCurrentVersionError,
|
||||
isEECloudUserVal,
|
||||
isLatestVersion,
|
||||
licenses?.licenses,
|
||||
onClickVersionHandler,
|
||||
@@ -372,12 +372,31 @@ function SideNav(): JSX.Element {
|
||||
</div>
|
||||
|
||||
{licenseTag && (
|
||||
<div className="license tag nav-item-label">{licenseTag}</div>
|
||||
<Tooltip
|
||||
title={
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
isCommunityUser
|
||||
? 'You are running the community version of SigNoz. You have to install the Enterprise edition in order enable Enterprise features.'
|
||||
: isCommunityEnterpriseUser
|
||||
? 'You do not have an active license present. Add an active license to enable Enterprise features.'
|
||||
: ''
|
||||
}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'license tag nav-item-label',
|
||||
isCommunityEnterpriseUser && 'community-enterprise-user',
|
||||
)}
|
||||
>
|
||||
{licenseTag}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isCloudUserVal && user?.role !== USER_ROLES.VIEWER && (
|
||||
{isCloudUser && user?.role !== USER_ROLES.VIEWER && (
|
||||
<div className="get-started-nav-items">
|
||||
<Button
|
||||
className="get-started-btn"
|
||||
@@ -396,7 +415,7 @@ function SideNav(): JSX.Element {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={cx(`nav-wrapper`, isCloudUserVal && 'nav-wrapper-cloud')}>
|
||||
<div className={cx(`nav-wrapper`, isCloudUser && 'nav-wrapper-cloud')}>
|
||||
<div className="primary-nav-items">
|
||||
{menuItems.map((item, index) => (
|
||||
<NavItem
|
||||
|
||||
@@ -112,7 +112,6 @@ const menuItems: SidebarItem[] = [
|
||||
key: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||
label: 'Infra Monitoring',
|
||||
icon: <Boxes size={16} />,
|
||||
isNew: true,
|
||||
},
|
||||
{
|
||||
key: ROUTES.ALL_DASHBOARD,
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
// Modal base styles
|
||||
.add-span-to-funnel-modal-container {
|
||||
.ant-modal {
|
||||
&-content,
|
||||
&-header {
|
||||
background: var(--bg-ink-500);
|
||||
}
|
||||
|
||||
&-header {
|
||||
border-bottom: none;
|
||||
|
||||
.ant-modal-title {
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
}
|
||||
|
||||
&-body {
|
||||
padding: 14px 16px !important;
|
||||
}
|
||||
}
|
||||
&--details {
|
||||
.ant-modal-content {
|
||||
height: 710px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main modal styles
|
||||
.add-span-to-funnel-modal {
|
||||
// Common button styles
|
||||
%button-base {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: Inter;
|
||||
}
|
||||
|
||||
// Details view styles
|
||||
&--details {
|
||||
.traces-funnel-details {
|
||||
height: unset;
|
||||
|
||||
&__steps-config {
|
||||
width: unset;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.funnel-step-wrapper {
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.steps-content {
|
||||
height: 500px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search section
|
||||
&__search {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
align-items: center;
|
||||
|
||||
&-input {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
background: var(--bg-ink-300);
|
||||
|
||||
.ant-input-prefix {
|
||||
height: 18px;
|
||||
margin-inline-end: 6px;
|
||||
svg {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
&,
|
||||
input {
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.07px;
|
||||
font-weight: 400;
|
||||
background: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: var(--bg-vanilla-400);
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create button
|
||||
&__create-button {
|
||||
@extend %button-base;
|
||||
width: 153px;
|
||||
padding: 4px 8px;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 2px;
|
||||
background: var(--bg-slate-500);
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
}
|
||||
.funnel-item {
|
||||
padding: 8px 16px 12px;
|
||||
&,
|
||||
&:first-child {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
line-height: 20px;
|
||||
}
|
||||
&__details {
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
// List section
|
||||
&__list {
|
||||
max-height: 400px;
|
||||
overflow-y: scroll;
|
||||
.funnels-empty {
|
||||
&__content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.funnels-list {
|
||||
gap: 8px;
|
||||
|
||||
.funnel-item {
|
||||
padding: 8px 16px 12px;
|
||||
|
||||
&__details {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__spinner {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
// Back button
|
||||
&__back-button {
|
||||
@extend %button-base;
|
||||
gap: 6px;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
// Details section
|
||||
&__details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
|
||||
.funnel-configuration__steps {
|
||||
padding: 0;
|
||||
|
||||
.funnel-step {
|
||||
&__content .filters__service-and-span .ant-select {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
&__footer .error {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.inter-step-config {
|
||||
width: calc(100% - 104px);
|
||||
}
|
||||
}
|
||||
.funnel-item__actions-popover {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Light mode styles
|
||||
.lightMode {
|
||||
.add-span-to-funnel-modal-container {
|
||||
.ant-modal {
|
||||
&-content,
|
||||
&-header {
|
||||
background: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
&-title {
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-span-to-funnel-modal {
|
||||
&__search-input {
|
||||
background: var(--bg-vanilla-100);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-500);
|
||||
|
||||
input {
|
||||
color: var(--bg-ink-500);
|
||||
background: var(--bg-vanilla-100);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__create-button {
|
||||
background: var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
&__back-button {
|
||||
color: var(--bg-ink-500);
|
||||
|
||||
&:hover {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
&__details h3 {
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
import './AddSpanToFunnelModal.styles.scss';
|
||||
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { Button, Input, Spin } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
import {
|
||||
useFunnelDetails,
|
||||
useFunnelsList,
|
||||
} from 'hooks/TracesFunnels/useFunnels';
|
||||
import { ArrowLeft, Plus, Search } from 'lucide-react';
|
||||
import FunnelConfiguration from 'pages/TracesFunnelDetails/components/FunnelConfiguration/FunnelConfiguration';
|
||||
import { TracesFunnelsContentRenderer } from 'pages/TracesFunnels';
|
||||
import CreateFunnel from 'pages/TracesFunnels/components/CreateFunnel/CreateFunnel';
|
||||
import { FunnelListItem } from 'pages/TracesFunnels/components/FunnelsList/FunnelsList';
|
||||
import { FunnelProvider } from 'pages/TracesFunnels/FunnelContext';
|
||||
import { ChangeEvent, useMemo, useState } from 'react';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
import { FunnelData } from 'types/api/traceFunnels';
|
||||
|
||||
enum ModalView {
|
||||
LIST = 'list',
|
||||
DETAILS = 'details',
|
||||
}
|
||||
|
||||
function FunnelDetailsView({
|
||||
funnel,
|
||||
span,
|
||||
}: {
|
||||
funnel: FunnelData;
|
||||
span: Span;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className="add-span-to-funnel-modal__details">
|
||||
<FunnelListItem
|
||||
funnel={funnel}
|
||||
shouldRedirectToTracesListOnDeleteSuccess={false}
|
||||
/>
|
||||
<FunnelConfiguration funnel={funnel} isTraceDetailsPage span={span} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
interface AddSpanToFunnelModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
span: Span;
|
||||
}
|
||||
|
||||
function AddSpanToFunnelModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
span,
|
||||
}: AddSpanToFunnelModalProps): JSX.Element {
|
||||
const [activeView, setActiveView] = useState<ModalView>(ModalView.LIST);
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const [selectedFunnelId, setSelectedFunnelId] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState<boolean>(false);
|
||||
|
||||
const handleSearch = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
setSearchQuery(e.target.value);
|
||||
};
|
||||
|
||||
const { data, isLoading, isError, isFetching } = useFunnelsList({
|
||||
searchQuery: '',
|
||||
});
|
||||
|
||||
const filteredData = useMemo(
|
||||
() =>
|
||||
data?.payload
|
||||
?.filter((funnel) =>
|
||||
funnel.funnel_name.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.creation_timestamp).getTime() -
|
||||
new Date(a.creation_timestamp).getTime(),
|
||||
),
|
||||
[data?.payload, searchQuery],
|
||||
);
|
||||
|
||||
const {
|
||||
data: funnelDetails,
|
||||
isLoading: isFunnelDetailsLoading,
|
||||
isFetching: isFunnelDetailsFetching,
|
||||
} = useFunnelDetails({
|
||||
funnelId: selectedFunnelId,
|
||||
});
|
||||
|
||||
const handleFunnelClick = (funnel: FunnelData): void => {
|
||||
setSelectedFunnelId(funnel.id);
|
||||
setActiveView(ModalView.DETAILS);
|
||||
};
|
||||
|
||||
const handleBack = (): void => {
|
||||
setActiveView(ModalView.LIST);
|
||||
setSelectedFunnelId(undefined);
|
||||
};
|
||||
|
||||
const handleCreateNewClick = (): void => {
|
||||
setIsCreateModalOpen(true);
|
||||
};
|
||||
|
||||
const renderListView = (): JSX.Element => (
|
||||
<div className="add-span-to-funnel-modal">
|
||||
{!!filteredData?.length && (
|
||||
<div className="add-span-to-funnel-modal__search">
|
||||
<Input
|
||||
className="add-span-to-funnel-modal__search-input"
|
||||
placeholder="Search by name, description, or tags..."
|
||||
prefix={<Search size={12} />}
|
||||
value={searchQuery}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="add-span-to-funnel-modal__list">
|
||||
<OverlayScrollbar>
|
||||
<TracesFunnelsContentRenderer
|
||||
isError={isError}
|
||||
isLoading={isLoading || isFetching}
|
||||
data={filteredData || []}
|
||||
onCreateFunnel={handleCreateNewClick}
|
||||
onFunnelClick={(funnel: FunnelData): void => handleFunnelClick(funnel)}
|
||||
shouldRedirectToTracesListOnDeleteSuccess={false}
|
||||
/>
|
||||
</OverlayScrollbar>
|
||||
</div>
|
||||
<CreateFunnel
|
||||
isOpen={isCreateModalOpen}
|
||||
onClose={(funnelId): void => {
|
||||
if (funnelId) {
|
||||
setSelectedFunnelId(funnelId);
|
||||
setActiveView(ModalView.DETAILS);
|
||||
}
|
||||
setIsCreateModalOpen(false);
|
||||
}}
|
||||
redirectToDetails={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderDetailsView = ({ span }: { span: Span }): JSX.Element => (
|
||||
<div className="add-span-to-funnel-modal add-span-to-funnel-modal--details">
|
||||
<Button
|
||||
type="text"
|
||||
className="add-span-to-funnel-modal__back-button"
|
||||
onClick={handleBack}
|
||||
>
|
||||
<ArrowLeft size={14} />
|
||||
All funnels
|
||||
</Button>
|
||||
<Spin
|
||||
style={{ height: 400 }}
|
||||
spinning={isFunnelDetailsLoading || isFunnelDetailsFetching}
|
||||
indicator={<LoadingOutlined spin />}
|
||||
>
|
||||
<div className="traces-funnel-details">
|
||||
<div className="traces-funnel-details__steps-config">
|
||||
{selectedFunnelId && funnelDetails?.payload && (
|
||||
<FunnelProvider funnelId={selectedFunnelId}>
|
||||
<FunnelDetailsView funnel={funnelDetails.payload} span={span} />
|
||||
</FunnelProvider>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<SignozModal
|
||||
open={isOpen}
|
||||
onCancel={onClose}
|
||||
width={570}
|
||||
title="Add span to funnel"
|
||||
className={cx('add-span-to-funnel-modal-container', {
|
||||
'add-span-to-funnel-modal-container--details':
|
||||
activeView === ModalView.DETAILS,
|
||||
})}
|
||||
okText="Save Funnel"
|
||||
footer={
|
||||
activeView === ModalView.LIST && !!filteredData?.length ? (
|
||||
<Button
|
||||
type="default"
|
||||
className="add-span-to-funnel-modal__create-button"
|
||||
onClick={handleCreateNewClick}
|
||||
icon={<Plus size={14} />}
|
||||
>
|
||||
Create new funnel
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{activeView === ModalView.LIST
|
||||
? renderListView()
|
||||
: renderDetailsView({ span })}
|
||||
</SignozModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddSpanToFunnelModal;
|
||||
@@ -95,10 +95,6 @@
|
||||
border-radius: 4px;
|
||||
background: rgba(171, 189, 255, 0.06) !important;
|
||||
|
||||
.div-td .span-overview .second-row .add-funnel-button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.span-overview {
|
||||
background: unset !important;
|
||||
|
||||
@@ -235,24 +231,6 @@
|
||||
line-height: 18px; /* 128.571% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
.add-funnel-button {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: opacity 0.1s ease-in-out;
|
||||
|
||||
&__separator {
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
&__button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import cx from 'classnames';
|
||||
import { TableV3 } from 'components/TableV3/TableV3';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||
import AddSpanToFunnelModal from 'container/TraceWaterfall/AddSpanToFunnelModal/AddSpanToFunnelModal';
|
||||
import { IInterestedSpan } from 'container/TraceWaterfall/TraceWaterfall';
|
||||
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
||||
import {
|
||||
@@ -26,7 +25,6 @@ import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
@@ -59,7 +57,6 @@ function SpanOverview({
|
||||
isSpanCollapsed,
|
||||
handleCollapseUncollapse,
|
||||
setSelectedSpan,
|
||||
handleAddSpanToFunnel,
|
||||
selectedSpan,
|
||||
}: {
|
||||
span: Span;
|
||||
@@ -67,7 +64,6 @@ function SpanOverview({
|
||||
handleCollapseUncollapse: (id: string, collapse: boolean) => void;
|
||||
selectedSpan: Span | undefined;
|
||||
setSelectedSpan: Dispatch<SetStateAction<Span | undefined>>;
|
||||
handleAddSpanToFunnel: (span: Span) => void;
|
||||
}): JSX.Element {
|
||||
const isRootSpan = span.level === 0;
|
||||
|
||||
@@ -145,28 +141,6 @@ function SpanOverview({
|
||||
<Typography.Text className="service-name">
|
||||
{span.serviceName}
|
||||
</Typography.Text>
|
||||
{!!span.serviceName && !!span.name && (
|
||||
<div className="add-funnel-button">
|
||||
<span className="add-funnel-button__separator">·</span>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
className="add-funnel-button__button"
|
||||
onClick={(e): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleAddSpanToFunnel(span);
|
||||
}}
|
||||
icon={
|
||||
<img
|
||||
className="add-funnel-button__icon"
|
||||
src="/Icons/funnel-add.svg"
|
||||
alt="funnel-icon"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -236,14 +210,12 @@ function getWaterfallColumns({
|
||||
traceMetadata,
|
||||
selectedSpan,
|
||||
setSelectedSpan,
|
||||
handleAddSpanToFunnel,
|
||||
}: {
|
||||
handleCollapseUncollapse: (id: string, collapse: boolean) => void;
|
||||
uncollapsedNodes: string[];
|
||||
traceMetadata: ITraceMetadata;
|
||||
selectedSpan: Span | undefined;
|
||||
setSelectedSpan: Dispatch<SetStateAction<Span | undefined>>;
|
||||
handleAddSpanToFunnel: (span: Span) => void;
|
||||
}): ColumnDef<Span, any>[] {
|
||||
const waterfallColumns: ColumnDef<Span, any>[] = [
|
||||
columnDefHelper.display({
|
||||
@@ -256,7 +228,6 @@ function getWaterfallColumns({
|
||||
isSpanCollapsed={!uncollapsedNodes.includes(props.row.original.spanId)}
|
||||
selectedSpan={selectedSpan}
|
||||
setSelectedSpan={setSelectedSpan}
|
||||
handleAddSpanToFunnel={handleAddSpanToFunnel}
|
||||
/>
|
||||
),
|
||||
size: 450,
|
||||
@@ -323,17 +294,6 @@ function Success(props: ISuccessProps): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const [isAddSpanToFunnelModalOpen, setIsAddSpanToFunnelModalOpen] = useState(
|
||||
false,
|
||||
);
|
||||
const [selectedSpanToAddToFunnel, setSelectedSpanToAddToFunnel] = useState<
|
||||
Span | undefined
|
||||
>(undefined);
|
||||
const handleAddSpanToFunnel = useCallback((span: Span): void => {
|
||||
setIsAddSpanToFunnelModalOpen(true);
|
||||
setSelectedSpanToAddToFunnel(span);
|
||||
}, []);
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
getWaterfallColumns({
|
||||
@@ -342,7 +302,6 @@ function Success(props: ISuccessProps): JSX.Element {
|
||||
traceMetadata,
|
||||
selectedSpan,
|
||||
setSelectedSpan,
|
||||
handleAddSpanToFunnel,
|
||||
}),
|
||||
[
|
||||
handleCollapseUncollapse,
|
||||
@@ -350,7 +309,6 @@ function Success(props: ISuccessProps): JSX.Element {
|
||||
traceMetadata,
|
||||
selectedSpan,
|
||||
setSelectedSpan,
|
||||
handleAddSpanToFunnel,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -422,13 +380,6 @@ function Success(props: ISuccessProps): JSX.Element {
|
||||
virtualiserRef={virtualizerRef}
|
||||
setColumnWidths={setTraceFlamegraphStatsWidth}
|
||||
/>
|
||||
{selectedSpanToAddToFunnel && (
|
||||
<AddSpanToFunnelModal
|
||||
span={selectedSpanToAddToFunnel}
|
||||
isOpen={isAddSpanToFunnelModalOpen}
|
||||
onClose={(): void => setIsAddSpanToFunnelModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import { DeploymentsDataProps } from 'types/api/customDomain/types';
|
||||
|
||||
export const useGetDeploymentsData = (): UseQueryResult<
|
||||
AxiosResponse<DeploymentsDataProps>,
|
||||
AxiosError
|
||||
> =>
|
||||
export const useGetDeploymentsData = (
|
||||
isEnabled: boolean,
|
||||
): UseQueryResult<AxiosResponse<DeploymentsDataProps>, AxiosError> =>
|
||||
useQuery<AxiosResponse<DeploymentsDataProps>, AxiosError>({
|
||||
queryKey: ['getDeploymentsData'],
|
||||
queryFn: () => getDeploymentsData(),
|
||||
enabled: isEnabled,
|
||||
});
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import useDebounce from 'hooks/useDebounce';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { useFunnelContext } from 'pages/TracesFunnels/FunnelContext';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { FunnelData, FunnelStepData } from 'types/api/traceFunnels';
|
||||
|
||||
import { useUpdateFunnelSteps } from './useFunnels';
|
||||
|
||||
interface UseFunnelConfiguration {
|
||||
isPopoverOpen: boolean;
|
||||
setIsPopoverOpen: (isPopoverOpen: boolean) => void;
|
||||
steps: FunnelStepData[];
|
||||
}
|
||||
|
||||
// Add this helper function
|
||||
const normalizeSteps = (steps: FunnelStepData[]): FunnelStepData[] => {
|
||||
if (steps.some((step) => !step.filters)) return steps;
|
||||
|
||||
return steps.map((step) => ({
|
||||
...step,
|
||||
filters: {
|
||||
...step.filters,
|
||||
items: step.filters.items.map((item) => ({
|
||||
id: '',
|
||||
key: item.key,
|
||||
value: item.value,
|
||||
op: item.op,
|
||||
})),
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export default function useFunnelConfiguration({
|
||||
funnel,
|
||||
}: {
|
||||
funnel: FunnelData;
|
||||
}): UseFunnelConfiguration {
|
||||
const { notifications } = useNotifications();
|
||||
const {
|
||||
steps,
|
||||
initialSteps,
|
||||
setHasIncompleteStepFields,
|
||||
setHasAllEmptyStepFields,
|
||||
} = useFunnelContext();
|
||||
|
||||
// State management
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
const debouncedSteps = useDebounce(steps, 200);
|
||||
|
||||
const [lastValidatedSteps, setLastValidatedSteps] = useState<FunnelStepData[]>(
|
||||
initialSteps,
|
||||
);
|
||||
|
||||
// Mutation hooks
|
||||
const updateStepsMutation = useUpdateFunnelSteps(funnel.id, notifications);
|
||||
|
||||
// Derived state
|
||||
const lastSavedStepsStateRef = useRef<FunnelStepData[]>(steps);
|
||||
|
||||
const hasStepsChanged = useCallback(() => {
|
||||
const normalizedLastSavedSteps = normalizeSteps(
|
||||
lastSavedStepsStateRef.current,
|
||||
);
|
||||
const normalizedDebouncedSteps = normalizeSteps(debouncedSteps);
|
||||
return !isEqual(normalizedDebouncedSteps, normalizedLastSavedSteps);
|
||||
}, [debouncedSteps]);
|
||||
|
||||
const hasStepServiceOrSpanNameChanged = useCallback(
|
||||
(prevSteps: FunnelStepData[], nextSteps: FunnelStepData[]): boolean => {
|
||||
if (prevSteps.length !== nextSteps.length) return true;
|
||||
return prevSteps.some((step, index) => {
|
||||
const nextStep = nextSteps[index];
|
||||
return (
|
||||
step.service_name !== nextStep.service_name ||
|
||||
step.span_name !== nextStep.span_name
|
||||
);
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// Mutation payload preparation
|
||||
const getUpdatePayload = useCallback(
|
||||
() => ({
|
||||
funnel_id: funnel.id,
|
||||
steps: debouncedSteps,
|
||||
updated_timestamp: Date.now(),
|
||||
}),
|
||||
[funnel.id, debouncedSteps],
|
||||
);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { selectedTime } = useFunnelContext();
|
||||
|
||||
const validateStepsQueryKey = useMemo(
|
||||
() => [REACT_QUERY_KEY.VALIDATE_FUNNEL_STEPS, funnel.id, selectedTime],
|
||||
[funnel.id, selectedTime],
|
||||
);
|
||||
useEffect(() => {
|
||||
if (hasStepsChanged()) {
|
||||
updateStepsMutation.mutate(getUpdatePayload(), {
|
||||
onSuccess: (data) => {
|
||||
const updatedFunnelSteps = data?.payload?.steps;
|
||||
|
||||
if (!updatedFunnelSteps) return;
|
||||
|
||||
queryClient.setQueryData(
|
||||
[REACT_QUERY_KEY.GET_FUNNEL_DETAILS, funnel.id],
|
||||
(oldData: any) => ({
|
||||
...oldData,
|
||||
payload: {
|
||||
...oldData.payload,
|
||||
steps: updatedFunnelSteps,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
lastSavedStepsStateRef.current = updatedFunnelSteps;
|
||||
|
||||
const hasIncompleteStepFields = updatedFunnelSteps.some(
|
||||
(step) => step.service_name === '' || step.span_name === '',
|
||||
);
|
||||
|
||||
const hasAllEmptyStepsData = updatedFunnelSteps.every(
|
||||
(step) => step.service_name === '' && step.span_name === '',
|
||||
);
|
||||
|
||||
setHasIncompleteStepFields(hasIncompleteStepFields);
|
||||
setHasAllEmptyStepFields(hasAllEmptyStepsData);
|
||||
|
||||
// Only validate if service_name or span_name changed
|
||||
if (
|
||||
!hasIncompleteStepFields &&
|
||||
hasStepServiceOrSpanNameChanged(lastValidatedSteps, debouncedSteps)
|
||||
) {
|
||||
queryClient.refetchQueries(validateStepsQueryKey);
|
||||
setLastValidatedSteps(debouncedSteps);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
debouncedSteps,
|
||||
getUpdatePayload,
|
||||
hasStepServiceOrSpanNameChanged,
|
||||
hasStepsChanged,
|
||||
lastValidatedSteps,
|
||||
queryClient,
|
||||
validateStepsQueryKey,
|
||||
]);
|
||||
|
||||
return {
|
||||
isPopoverOpen,
|
||||
setIsPopoverOpen,
|
||||
steps,
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user