diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 41c8600991..7855aa30ee 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -27,6 +27,27 @@ jobs: run: | make build-frontend-amd64 + build-frontend-ee: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Create .env file + run: echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env + - name: Install dependencies + run: cd frontend && yarn install + - name: Run ESLint + run: cd frontend && npm run lint + - name: Run Jest + run: cd frontend && npm run jest + - name: TSC + run: yarn tsc + working-directory: ./frontend + - name: Build frontend docker image + shell: bash + run: | + make build-frontend-amd64 + build-query-service: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index c198442ae5..754f611783 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -123,3 +123,49 @@ jobs: fi - name: Build and push docker image run: make build-push-frontend + + image-build-and-push-frontend-ee: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Create .env file + run: echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env + - name: Install dependencies + working-directory: frontend + run: yarn install + - name: Run Prettier + working-directory: frontend + run: npm run prettify + continue-on-error: true + - name: Run ESLint + working-directory: frontend + run: npm run lint + continue-on-error: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + version: latest + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: benjlevesque/short-sha@v2.2 + id: short-sha + - name: Get branch name + id: branch-name + uses: tj-actions/branch-names@v5.1 + - name: Set docker tag environment + run: | + if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then + tag="${{ steps.branch-name.outputs.tag }}" + tag="${tag:1}" + echo "DOCKER_TAG=${tag}-ee" >> $GITHUB_ENV + elif [ '${{ steps.branch-name.outputs.current_branch }}' == 'main' ]; then + echo "DOCKER_TAG=latest-ee" >> $GITHUB_ENV + else + echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}-ee" >> $GITHUB_ENV + fi + - name: Build and push docker image + run: make build-push-frontend diff --git a/.gitignore b/.gitignore index 3627f21481..829e51d9d1 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,7 @@ frontend/src/constants/env.ts **/locust-scripts/__pycache__/ **/__debug_bin -frontend/*.env +frontend/.env pkg/query-service/signoz.db pkg/query-service/tests/test-deploy/data/ diff --git a/Makefile b/Makefile index bef8c8bce7..1761acd26d 100644 --- a/Makefile +++ b/Makefile @@ -151,3 +151,4 @@ test: go test ./pkg/query-service/app/querier/... go test ./pkg/query-service/converter/... go test ./pkg/query-service/formatter/... + go test ./pkg/query-service/tests/integration/... \ No newline at end of file diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index cf39bf49c4..20bc9cc1fa 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -131,7 +131,7 @@ services: # - ./data/clickhouse-3/:/var/lib/clickhouse/ alertmanager: - image: signoz/alertmanager:0.23.3 + image: signoz/alertmanager:0.23.4 volumes: - ./data/alertmanager:/data command: @@ -144,8 +144,12 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.28.0 - command: [ "-config=/root/config/prometheus.yml" ] + image: signoz/query-service:0.29.0 + command: + [ + "-config=/root/config/prometheus.yml", + "--prefer-delta=true" + ] # ports: # - "6060:6060" # pprof port # - "8080:8080" # query-service port @@ -180,7 +184,7 @@ services: <<: *clickhouse-depend frontend: - image: signoz/frontend:0.28.0 + image: signoz/frontend:0.29.0 deploy: restart_policy: condition: on-failure @@ -193,7 +197,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/signoz-otel-collector:0.79.6 + image: signoz/signoz-otel-collector:0.79.7 command: [ "--config=/etc/otel-collector-config.yaml", @@ -226,7 +230,7 @@ services: <<: *clickhouse-depend otel-collector-metrics: - image: signoz/signoz-otel-collector:0.79.6 + image: signoz/signoz-otel-collector:0.79.7 command: [ "--config=/etc/otel-collector-metrics-config.yaml", diff --git a/deploy/docker/clickhouse-setup/docker-compose-core.yaml b/deploy/docker/clickhouse-setup/docker-compose-core.yaml index f18356c372..b32dbe9c65 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-core.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-core.yaml @@ -34,7 +34,7 @@ services: alertmanager: container_name: signoz-alertmanager - image: signoz/alertmanager:0.23.3 + image: signoz/alertmanager:0.23.4 volumes: - ./data/alertmanager:/data depends_on: @@ -48,7 +48,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` otel-collector: container_name: signoz-otel-collector - image: signoz/signoz-otel-collector:0.79.6 + image: signoz/signoz-otel-collector:0.79.7 command: [ "--config=/etc/otel-collector-config.yaml", @@ -78,7 +78,7 @@ services: otel-collector-metrics: container_name: signoz-otel-collector-metrics - image: signoz/signoz-otel-collector:0.79.6 + image: signoz/signoz-otel-collector:0.79.7 command: [ "--config=/etc/otel-collector-metrics-config.yaml", diff --git a/deploy/docker/clickhouse-setup/docker-compose-local.yaml b/deploy/docker/clickhouse-setup/docker-compose-local.yaml index cce46073f3..2c7b9a5c46 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-local.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-local.yaml @@ -22,7 +22,11 @@ services: - ./prometheus.yml:/root/config/prometheus.yml - ../dashboards:/root/config/dashboards - ./data/signoz/:/var/lib/signoz/ - command: [ "-config=/root/config/prometheus.yml" ] + command: + [ + "-config=/root/config/prometheus.yml", + "--prefer-delta=true" + ] ports: - "6060:6060" - "8080:8080" diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 0f96583036..1a543db348 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -147,7 +147,7 @@ services: # - ./user_scripts:/var/lib/clickhouse/user_scripts/ alertmanager: - image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.3} + image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.4} container_name: signoz-alertmanager volumes: - ./data/alertmanager:/data @@ -162,9 +162,13 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:${DOCKER_TAG:-0.28.0} + image: signoz/query-service:${DOCKER_TAG:-0.29.0} container_name: signoz-query-service - command: [ "-config=/root/config/prometheus.yml" ] + command: + [ + "-config=/root/config/prometheus.yml", + "--prefer-delta=true" + ] # ports: # - "6060:6060" # pprof port # - "8080:8080" # query-service port @@ -197,7 +201,7 @@ services: <<: *clickhouse-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.28.0} + image: signoz/frontend:${DOCKER_TAG:-0.29.0} container_name: signoz-frontend restart: on-failure depends_on: @@ -209,7 +213,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.6} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.7} container_name: signoz-otel-collector command: [ @@ -240,7 +244,7 @@ services: <<: *clickhouse-depend otel-collector-metrics: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.6} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.7} container_name: signoz-otel-collector-metrics command: [ diff --git a/ee/query-service/Dockerfile b/ee/query-service/Dockerfile index af75187057..258b0869f7 100644 --- a/ee/query-service/Dockerfile +++ b/ee/query-service/Dockerfile @@ -22,7 +22,7 @@ RUN cd ee/query-service \ # use a minimal alpine image -FROM alpine:3.7 +FROM alpine:3.16.7 # Add Maintainer Info LABEL maintainer="signoz" diff --git a/ee/query-service/app/api/auth.go b/ee/query-service/app/api/auth.go index e013b87b29..60da4e125b 100644 --- a/ee/query-service/app/api/auth.go +++ b/ee/query-service/app/api/auth.go @@ -107,7 +107,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) { RespondError(w, model.InternalError(basemodel.ErrSignupFailed{}), nil) } - precheckResp := &model.PrecheckResponse{ + precheckResp := &basemodel.PrecheckResponse{ SSO: false, IsUser: false, } diff --git a/ee/query-service/app/api/metrics.go b/ee/query-service/app/api/metrics.go index 4b1a8e49dd..81af7035b7 100644 --- a/ee/query-service/app/api/metrics.go +++ b/ee/query-service/app/api/metrics.go @@ -137,8 +137,8 @@ func (ah *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request var s basemodel.Series s.QueryName = name s.Labels = v.Metric.Copy().Map() - for _, p := range v.Points { - s.Points = append(s.Points, basemodel.MetricPoint{Timestamp: p.T, Value: p.V}) + for _, p := range v.Floats { + s.Points = append(s.Points, basemodel.MetricPoint{Timestamp: p.T, Value: p.F}) } seriesList = append(seriesList, &s) } diff --git a/ee/query-service/app/server.go b/ee/query-service/app/server.go index 9f3a08a394..aee2c87160 100644 --- a/ee/query-service/app/server.go +++ b/ee/query-service/app/server.go @@ -548,7 +548,7 @@ func (s *Server) Start() error { go func() { zap.S().Info("Starting OpAmp Websocket server", zap.String("addr", baseconst.OpAmpWsEndpoint)) - err := opamp.InitalizeServer(baseconst.OpAmpWsEndpoint, &opAmpModel.AllAgents) + err := opamp.InitializeAndStartServer(baseconst.OpAmpWsEndpoint, &opAmpModel.AllAgents) if err != nil { zap.S().Info("opamp ws server failed to start", err) s.unavailableChannel <- healthcheck.Unavailable diff --git a/ee/query-service/dao/interface.go b/ee/query-service/dao/interface.go index 2303bb72d4..1a8f3b2460 100644 --- a/ee/query-service/dao/interface.go +++ b/ee/query-service/dao/interface.go @@ -21,7 +21,6 @@ type ModelDao interface { DB() *sqlx.DB // auth methods - PrecheckLogin(ctx context.Context, email, sourceUrl string) (*model.PrecheckResponse, basemodel.BaseApiError) CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError) PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError) GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*model.OrgDomain, error) diff --git a/ee/query-service/dao/sqlite/auth.go b/ee/query-service/dao/sqlite/auth.go index e06c073997..664323eaaf 100644 --- a/ee/query-service/dao/sqlite/auth.go +++ b/ee/query-service/dao/sqlite/auth.go @@ -120,10 +120,10 @@ func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, base // PrecheckLogin is called when the login or signup page is loaded // to check sso login is to be prompted -func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*model.PrecheckResponse, basemodel.BaseApiError) { +func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*basemodel.PrecheckResponse, basemodel.BaseApiError) { // assume user is valid unless proven otherwise - resp := &model.PrecheckResponse{IsUser: true, CanSelfRegister: false} + resp := &basemodel.PrecheckResponse{IsUser: true, CanSelfRegister: false} // check if email is a valid user userPayload, baseApiErr := m.GetUserByEmail(ctx, email) diff --git a/ee/query-service/model/auth.go b/ee/query-service/model/auth.go index 8c3447a00d..9ad83cb398 100644 --- a/ee/query-service/model/auth.go +++ b/ee/query-service/model/auth.go @@ -4,18 +4,9 @@ import ( basemodel "go.signoz.io/signoz/pkg/query-service/model" ) -// PrecheckResponse contains login precheck response -type PrecheckResponse struct { - SSO bool `json:"sso"` - SsoUrl string `json:"ssoUrl"` - CanSelfRegister bool `json:"canSelfRegister"` - IsUser bool `json:"isUser"` - SsoError string `json:"ssoError"` -} - // GettableInvitation overrides base object and adds precheck into // response type GettableInvitation struct { *basemodel.InvitationResponseObject - Precheck *PrecheckResponse `json:"precheck"` + Precheck *basemodel.PrecheckResponse `json:"precheck"` } diff --git a/ee/query-service/model/plans.go b/ee/query-service/model/plans.go index e3a8b44cc6..caa2a4df68 100644 --- a/ee/query-service/model/plans.go +++ b/ee/query-service/model/plans.go @@ -9,6 +9,8 @@ const Basic = "BASIC_PLAN" const Pro = "PRO_PLAN" const Enterprise = "ENTERPRISE_PLAN" const DisableUpsell = "DISABLE_UPSELL" +const Onboarding = "ONBOARDING" +const ChatSupport = "CHAT_SUPPORT" var BasicPlan = basemodel.FeatureSet{ basemodel.Feature{ @@ -276,4 +278,18 @@ var EnterprisePlan = basemodel.FeatureSet{ UsageLimit: -1, Route: "", }, + basemodel.Feature{ + Name: Onboarding, + Active: true, + Usage: 0, + UsageLimit: -1, + Route: "", + }, + basemodel.Feature{ + Name: ChatSupport, + Active: true, + Usage: 0, + UsageLimit: -1, + Route: "", + }, } diff --git a/frontend/.dockerignore b/frontend/.dockerignore index 2e3d78bb02..a106e6a11e 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -1,5 +1,4 @@ node_modules .vscode build -.env .git diff --git a/frontend/example.env b/frontend/example.env new file mode 100644 index 0000000000..0ddbfb2837 --- /dev/null +++ b/frontend/example.env @@ -0,0 +1,7 @@ +NODE_ENV="development" +BUNDLE_ANALYSER="true" +FRONTEND_API_ENDPOINT="http://localhost:3301/" +INTERCOM_APP_ID="intercom-app-id" + +PLAYWRIGHT_TEST_BASE_URL="http://localhost:3301" +CI="1" \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 3a5e7035a4..8e013ce78a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,27 +29,31 @@ "dependencies": { "@ant-design/colors": "6.0.0", "@ant-design/icons": "4.8.0", - "@grafana/data": "^8.4.3", + "@grafana/data": "^9.5.2", + "@mdx-js/loader": "2.3.0", + "@mdx-js/react": "2.3.0", "@monaco-editor/react": "^4.3.1", + "@uiw/react-md-editor": "3.23.5", "@xstate/react": "^3.0.0", "ansi-to-html": "0.7.2", "antd": "5.0.5", "antd-table-saveas-excel": "2.2.1", "axios": "^0.21.0", "babel-eslint": "^10.1.0", - "babel-jest": "^26.6.0", - "babel-loader": "8.1.0", + "babel-jest": "^29.6.4", + "babel-loader": "9.1.3", "babel-plugin-named-asset-import": "^0.3.7", "babel-preset-minify": "^0.5.1", - "babel-preset-react-app": "^10.0.0", + "babel-preset-react-app": "^10.0.1", "chart.js": "3.9.1", "chartjs-adapter-date-fns": "^2.0.0", "chartjs-plugin-annotation": "^1.4.0", + "classnames": "2.3.2", "color": "^4.2.1", "color-alpha": "1.1.3", "cross-env": "^7.0.3", "css-loader": "4.3.0", - "css-minimizer-webpack-plugin": "^3.2.0", + "css-minimizer-webpack-plugin": "5.0.1", "dayjs": "^1.10.7", "dompurify": "3.0.0", "dotenv": "8.2.0", @@ -58,7 +62,7 @@ "file-loader": "6.1.1", "fontfaceobserver": "2.3.0", "history": "4.10.1", - "html-webpack-plugin": "5.1.0", + "html-webpack-plugin": "5.5.0", "i18next": "^21.6.12", "i18next-browser-languagedetector": "^6.1.3", "i18next-http-backend": "^1.3.2", @@ -75,7 +79,7 @@ "react-dnd-html5-backend": "16.0.1", "react-dom": "18.2.0", "react-drag-listview": "2.0.0", - "react-force-graph": "^1.41.0", + "react-force-graph": "^1.43.0", "react-grid-layout": "^1.3.4", "react-helmet-async": "1.3.0", "react-i18next": "^11.16.1", @@ -89,7 +93,7 @@ "redux-thunk": "^2.3.0", "stream": "^0.0.2", "style-loader": "1.3.0", - "styled-components": "^5.2.1", + "styled-components": "^5.3.11", "terser-webpack-plugin": "^5.2.5", "timestamp-nano": "^1.0.0", "ts-node": "^10.2.1", @@ -97,8 +101,8 @@ "typescript": "^4.0.5", "uuid": "^8.3.2", "web-vitals": "^0.2.4", - "webpack": "^5.23.0", - "webpack-dev-server": "^4.3.1", + "webpack": "5.88.2", + "webpack-dev-server": "^4.15.1", "xstate": "^4.31.0" }, "browserslist": { @@ -114,13 +118,13 @@ ] }, "devDependencies": { - "@babel/core": "^7.12.3", - "@babel/plugin-proposal-class-properties": "^7.12.13", + "@babel/core": "^7.22.11", + "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-syntax-jsx": "^7.12.13", - "@babel/preset-env": "^7.12.17", + "@babel/preset-env": "^7.22.14", "@babel/preset-react": "^7.12.13", - "@babel/preset-typescript": "^7.12.17", - "@commitlint/cli": "^16.2.4", + "@babel/preset-typescript": "^7.21.4", + "@commitlint/cli": "^16.3.0", "@commitlint/config-conventional": "^16.2.4", "@jest/globals": "^27.5.1", "@playwright/test": "^1.22.0", @@ -149,22 +153,21 @@ "@types/styled-components": "^5.1.4", "@types/uuid": "^8.3.1", "@types/webpack": "^5.28.0", - "@types/webpack-dev-server": "^4.3.0", - "@typescript-eslint/eslint-plugin": "^4.28.2", - "@typescript-eslint/parser": "^4.28.2", - "@welldone-software/why-did-you-render": "6.2.1", + "@types/webpack-dev-server": "^4.7.2", + "@typescript-eslint/eslint-plugin": "^4.33.0", + "@typescript-eslint/parser": "^4.33.0", "autoprefixer": "^9.0.0", "babel-plugin-styled-components": "^1.12.0", "compression-webpack-plugin": "9.0.0", "copy-webpack-plugin": "^8.1.0", "critters-webpack-plugin": "^3.0.1", - "eslint": "^7.30.0", + "eslint": "^7.32.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^16.1.4", "eslint-config-prettier": "^8.3.0", "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^26.1.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jest": "^26.9.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.0.0", @@ -175,15 +178,17 @@ "eslint-plugin-sonarjs": "^0.12.0", "husky": "^7.0.4", "is-ci": "^3.0.1", - "jest-playwright-preset": "^1.7.0", + "jest-playwright-preset": "^1.7.2", "jest-styled-components": "^7.0.8", - "lint-staged": "^12.3.7", + "lint-staged": "^12.5.0", "portfinder-sync": "^0.0.2", "prettier": "2.2.1", "react-hooks-testing-library": "0.6.0", "react-hot-loader": "^4.13.0", "react-resizable": "3.0.4", - "ts-jest": "^27.1.4", + "sass": "1.66.1", + "sass-loader": "13.3.2", + "ts-jest": "^27.1.5", "ts-node": "^10.2.1", "typescript-plugin-css-modules": "5.0.1", "webpack-bundle-analyzer": "^4.5.0", @@ -196,6 +201,9 @@ }, "resolutions": { "@types/react": "18.0.26", - "@types/react-dom": "18.0.10" + "@types/react-dom": "18.0.10", + "debug": "4.3.4", + "semver": "7.5.4", + "xml2js": "0.5.0" } } diff --git a/frontend/public/Logos/cmd-terminal.svg b/frontend/public/Logos/cmd-terminal.svg new file mode 100644 index 0000000000..9eb82fbb25 --- /dev/null +++ b/frontend/public/Logos/cmd-terminal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/Logos/docker.svg b/frontend/public/Logos/docker.svg new file mode 100644 index 0000000000..ff2b2b4381 --- /dev/null +++ b/frontend/public/Logos/docker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/Logos/kubernetes.svg b/frontend/public/Logos/kubernetes.svg new file mode 100644 index 0000000000..86e288be34 --- /dev/null +++ b/frontend/public/Logos/kubernetes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/Logos/node-js.svg b/frontend/public/Logos/node-js.svg new file mode 100644 index 0000000000..9c2d5c64a5 --- /dev/null +++ b/frontend/public/Logos/node-js.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/Logos/software-window.svg b/frontend/public/Logos/software-window.svg new file mode 100644 index 0000000000..60bf068531 --- /dev/null +++ b/frontend/public/Logos/software-window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/Logos/syslogs.svg b/frontend/public/Logos/syslogs.svg new file mode 100644 index 0000000000..40f9055577 --- /dev/null +++ b/frontend/public/Logos/syslogs.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/public/locales/en-GB/explorer.json b/frontend/public/locales/en-GB/explorer.json new file mode 100644 index 0000000000..b4ffa6148c --- /dev/null +++ b/frontend/public/locales/en-GB/explorer.json @@ -0,0 +1,3 @@ +{ + "name_of_the_view": "Name of the view" +} \ No newline at end of file diff --git a/frontend/public/locales/en-GB/signup.json b/frontend/public/locales/en-GB/signup.json index f5657a07d2..9e0d586549 100644 --- a/frontend/public/locales/en-GB/signup.json +++ b/frontend/public/locales/en-GB/signup.json @@ -8,7 +8,7 @@ "label_orgname": "Organization Name", "placeholder_orgname": "Your Company", "prompt_keepme_posted": "Keep me updated on new SigNoz features", - "prompt_anonymise": "Anonymise my usage date. We collect data to measure product usage", + "prompt_anonymise": "Anonymise my usage data. We collect data to measure product usage", "failed_confirm_password": "Passwords don’t match. Please try again", "unexpected_error": "Something went wrong", "failed_to_initiate_login": "Signup completed but failed to initiate login", diff --git a/frontend/public/locales/en-GB/titles.json b/frontend/public/locales/en-GB/titles.json index f6ba0b816c..8b078211e3 100644 --- a/frontend/public/locales/en-GB/titles.json +++ b/frontend/public/locales/en-GB/titles.json @@ -1,13 +1,13 @@ { "SIGN_UP": "SigNoz | Sign Up", "LOGIN": "SigNoz | Login", + "GET_STARTED": "SigNoz | Get Started", "SERVICE_METRICS": "SigNoz | Service Metrics", "SERVICE_MAP": "SigNoz | Service Map", "TRACE": "SigNoz | Trace", "TRACE_DETAIL": "SigNoz | Trace Detail", "TRACES_EXPLORER": "SigNoz | Traces Explorer", "SETTINGS": "SigNoz | Settings", - "INSTRUMENTATION": "SigNoz | Get Started", "USAGE_EXPLORER": "SigNoz | Usage Explorer", "APPLICATION": "SigNoz | Home", "ALL_DASHBOARD": "SigNoz | All Dashboards", diff --git a/frontend/public/locales/en/explorer.json b/frontend/public/locales/en/explorer.json new file mode 100644 index 0000000000..b4ffa6148c --- /dev/null +++ b/frontend/public/locales/en/explorer.json @@ -0,0 +1,3 @@ +{ + "name_of_the_view": "Name of the view" +} \ No newline at end of file diff --git a/frontend/public/locales/en/signup.json b/frontend/public/locales/en/signup.json index f5657a07d2..9e0d586549 100644 --- a/frontend/public/locales/en/signup.json +++ b/frontend/public/locales/en/signup.json @@ -8,7 +8,7 @@ "label_orgname": "Organization Name", "placeholder_orgname": "Your Company", "prompt_keepme_posted": "Keep me updated on new SigNoz features", - "prompt_anonymise": "Anonymise my usage date. We collect data to measure product usage", + "prompt_anonymise": "Anonymise my usage data. We collect data to measure product usage", "failed_confirm_password": "Passwords don’t match. Please try again", "unexpected_error": "Something went wrong", "failed_to_initiate_login": "Signup completed but failed to initiate login", diff --git a/frontend/public/locales/en/titles.json b/frontend/public/locales/en/titles.json index f6ba0b816c..2a1036dc57 100644 --- a/frontend/public/locales/en/titles.json +++ b/frontend/public/locales/en/titles.json @@ -3,11 +3,11 @@ "LOGIN": "SigNoz | Login", "SERVICE_METRICS": "SigNoz | Service Metrics", "SERVICE_MAP": "SigNoz | Service Map", + "GET_STARTED": "SigNoz | Get Started", "TRACE": "SigNoz | Trace", "TRACE_DETAIL": "SigNoz | Trace Detail", "TRACES_EXPLORER": "SigNoz | Traces Explorer", "SETTINGS": "SigNoz | Settings", - "INSTRUMENTATION": "SigNoz | Get Started", "USAGE_EXPLORER": "SigNoz | Usage Explorer", "APPLICATION": "SigNoz | Home", "ALL_DASHBOARD": "SigNoz | All Dashboards", diff --git a/frontend/src/AppRoutes/index.tsx b/frontend/src/AppRoutes/index.tsx index a4ccb0d0f0..11f9d79d51 100644 --- a/frontend/src/AppRoutes/index.tsx +++ b/frontend/src/AppRoutes/index.tsx @@ -1,20 +1,77 @@ import { ConfigProvider } from 'antd'; import NotFound from 'components/NotFound'; import Spinner from 'components/Spinner'; +import { FeatureKeys } from 'constants/features'; +import ROUTES from 'constants/routes'; import AppLayout from 'container/AppLayout'; import { useThemeConfig } from 'hooks/useDarkMode'; +import useGetFeatureFlag from 'hooks/useGetFeatureFlag'; import { NotificationProvider } from 'hooks/useNotifications'; import { ResourceProvider } from 'hooks/useResourceAttribute'; import history from 'lib/history'; import { QueryBuilderProvider } from 'providers/QueryBuilder'; -import { Suspense } from 'react'; +import { Suspense, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { Route, Router, Switch } from 'react-router-dom'; +import { Dispatch } from 'redux'; +import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { UPDATE_FEATURE_FLAG_RESPONSE } from 'types/actions/app'; +import AppReducer from 'types/reducer/app'; import PrivateRoute from './Private'; -import routes from './routes'; +import defaultRoutes from './routes'; function App(): JSX.Element { const themeConfig = useThemeConfig(); + const [routes, setRoutes] = useState(defaultRoutes); + const { isLoggedIn: isLoggedInState, user } = useSelector< + AppState, + AppReducer + >((state) => state.app); + + const dispatch = useDispatch>(); + + const { hostname } = window.location; + + const featureResponse = useGetFeatureFlag((allFlags) => { + const isOnboardingEnabled = + allFlags.find((flag) => flag.name === FeatureKeys.ONBOARDING)?.active || + false; + + const isChatSupportEnabled = + allFlags.find((flag) => flag.name === FeatureKeys.CHAT_SUPPORT)?.active || + false; + + dispatch({ + type: UPDATE_FEATURE_FLAG_RESPONSE, + payload: { + featureFlag: allFlags, + refetch: featureResponse.refetch, + }, + }); + + if ( + !isOnboardingEnabled || + !(hostname && hostname.endsWith('signoz.cloud')) + ) { + const newRoutes = routes.filter( + (route) => route?.path !== ROUTES.GET_STARTED, + ); + + setRoutes(newRoutes); + } + + if (isLoggedInState && isChatSupportEnabled) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + window.Intercom('boot', { + app_id: process.env.INTERCOM_APP_ID, + email: user?.email || '', + name: user?.name || '', + }); + } + }); return ( diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index 9b72092342..e13282542f 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -44,6 +44,10 @@ export const GettingStarted = Loadable( () => import(/* webpackChunkName: "GettingStarted" */ 'pages/GettingStarted'), ); +export const Onboarding = Loadable( + () => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPage'), +); + export const DashboardPage = Loadable( () => import(/* webpackChunkName: "DashboardPage" */ 'pages/Dashboard'), ); diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index ed19a96d6d..36081197e5 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -1,5 +1,4 @@ import ROUTES from 'constants/routes'; -import DashboardWidget from 'pages/DashboardWidget'; import { RouteProps } from 'react-router-dom'; import { @@ -8,10 +7,10 @@ import { CreateAlertChannelAlerts, CreateNewAlerts, DashboardPage, + DashboardWidget, EditAlertChannelsAlerts, EditRulesPage, ErrorDetails, - GettingStarted, LicensePage, ListAllALertsPage, LiveLogs, @@ -21,6 +20,7 @@ import { LogsIndexToFields, MySettings, NewDashboardPage, + Onboarding, OrganizationSettings, PasswordReset, PipelinePage, @@ -46,6 +46,13 @@ const routes: AppRoutes[] = [ isPrivate: false, key: 'SIGN_UP', }, + { + path: ROUTES.GET_STARTED, + exact: true, + component: Onboarding, + isPrivate: true, + key: 'GET_STARTED', + }, { component: LogsIndexToFields, path: ROUTES.LOGS_INDEX_FIELDS, @@ -95,13 +102,6 @@ const routes: AppRoutes[] = [ isPrivate: true, key: 'USAGE_EXPLORER', }, - { - path: ROUTES.INSTRUMENTATION, - exact: true, - component: GettingStarted, - isPrivate: true, - key: 'INSTRUMENTATION', - }, { path: ROUTES.ALL_DASHBOARD, exact: true, diff --git a/frontend/src/api/dashboard/getAll.ts b/frontend/src/api/dashboard/getAll.ts index abd3e8fc93..aafe44b3ed 100644 --- a/frontend/src/api/dashboard/getAll.ts +++ b/frontend/src/api/dashboard/getAll.ts @@ -1,24 +1,8 @@ import axios from 'api'; -import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; -import { AxiosError } from 'axios'; -import { ErrorResponse, SuccessResponse } from 'types/api'; -import { PayloadProps } from 'types/api/dashboard/getAll'; +import { ApiResponse } from 'types/api'; +import { Dashboard } from 'types/api/dashboard/getAll'; -const getAll = async (): Promise< - SuccessResponse | ErrorResponse -> => { - try { - const response = await axios.get('/dashboards'); - - return { - statusCode: 200, - error: null, - message: response.data.message, - payload: response.data.data, - }; - } catch (error) { - return ErrorResponseHandler(error as AxiosError); - } -}; - -export default getAll; +export const getAllDashboardList = (): Promise => + axios + .get>('/dashboards') + .then((res) => res.data.data); diff --git a/frontend/src/api/features/getFeatureFlags.ts b/frontend/src/api/features/getFeatureFlags.ts index 16f6b17c05..2ce37b99e1 100644 --- a/frontend/src/api/features/getFeatureFlags.ts +++ b/frontend/src/api/features/getFeatureFlags.ts @@ -1,24 +1,10 @@ import axios from 'api'; -import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; -import { AxiosError } from 'axios'; -import { ErrorResponse, SuccessResponse } from 'types/api'; -import { PayloadProps } from 'types/api/features/getFeaturesFlags'; +import { ApiResponse } from 'types/api'; +import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags'; -const getFeaturesFlags = async (): Promise< - SuccessResponse | ErrorResponse -> => { - try { - const response = await axios.get(`/featureFlags`); - - return { - statusCode: 200, - error: null, - message: response.data.status, - payload: response.data.data, - }; - } catch (error) { - return ErrorResponseHandler(error as AxiosError); - } -}; +const getFeaturesFlags = (): Promise => + axios + .get>(`/featureFlags`) + .then((response) => response.data.data); export default getFeaturesFlags; diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 584e3b4868..9739f24e49 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -79,7 +79,6 @@ const interceptorRejected = async ( // when refresh token is expired if (response.status === 401 && response.config.url === '/login') { - console.log('logging out '); Logout(); } } diff --git a/frontend/src/api/logs/AddToSelectedField.ts b/frontend/src/api/logs/AddToSelectedField.ts index 55be186f5b..b2672f7dc4 100644 --- a/frontend/src/api/logs/AddToSelectedField.ts +++ b/frontend/src/api/logs/AddToSelectedField.ts @@ -4,7 +4,7 @@ import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; import { PayloadProps, Props } from 'types/api/logs/addToSelectedFields'; -const AddToSelectedFields = async ( +const addToSelectedFields = async ( props: Props, ): Promise | ErrorResponse> => { try { @@ -16,8 +16,8 @@ const AddToSelectedFields = async ( payload: data.data, }; } catch (error) { - return ErrorResponseHandler(error as AxiosError); + return Promise.reject(ErrorResponseHandler(error as AxiosError)); } }; -export default AddToSelectedFields; +export default addToSelectedFields; diff --git a/frontend/src/api/logs/RemoveFromSelectedField.ts b/frontend/src/api/logs/RemoveFromSelectedField.ts index c33ff9fca1..f417565595 100644 --- a/frontend/src/api/logs/RemoveFromSelectedField.ts +++ b/frontend/src/api/logs/RemoveFromSelectedField.ts @@ -4,7 +4,7 @@ import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; import { PayloadProps, Props } from 'types/api/logs/addToSelectedFields'; -const RemoveSelectedField = async ( +const removeSelectedField = async ( props: Props, ): Promise | ErrorResponse> => { try { @@ -16,8 +16,8 @@ const RemoveSelectedField = async ( payload: data.data, }; } catch (error) { - return ErrorResponseHandler(error as AxiosError); + return Promise.reject(ErrorResponseHandler(error as AxiosError)); } }; -export default RemoveSelectedField; +export default removeSelectedField; diff --git a/frontend/src/api/user/signup.ts b/frontend/src/api/user/signup.ts index 09aabebf61..fcb483dffb 100644 --- a/frontend/src/api/user/signup.ts +++ b/frontend/src/api/user/signup.ts @@ -2,14 +2,12 @@ import axios from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; -import * as loginPrecheck from 'types/api/user/loginPrecheck'; +import { PayloadProps } from 'types/api/user/loginPrecheck'; import { Props } from 'types/api/user/signup'; const signup = async ( props: Props, -): Promise< - SuccessResponse | ErrorResponse -> => { +): Promise | ErrorResponse> => { try { const response = await axios.post(`/register`, { ...props, diff --git a/frontend/src/api/utils.ts b/frontend/src/api/utils.ts index f74c1c6831..2d31836733 100644 --- a/frontend/src/api/utils.ts +++ b/frontend/src/api/utils.ts @@ -60,5 +60,9 @@ export const Logout = (): void => { }, }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + window.Intercom('shutdown'); + history.push(ROUTES.LOGIN); }; diff --git a/frontend/src/components/ExplorerCard/ExplorerCard.tsx b/frontend/src/components/ExplorerCard/ExplorerCard.tsx index a7dda9ce95..0944023b6c 100644 --- a/frontend/src/components/ExplorerCard/ExplorerCard.tsx +++ b/frontend/src/components/ExplorerCard/ExplorerCard.tsx @@ -1,6 +1,5 @@ import { DeleteOutlined, - DownOutlined, MoreOutlined, SaveOutlined, ShareAltOutlined, @@ -13,13 +12,14 @@ import { MenuProps, Popover, Row, + Select, Space, Typography, } from 'antd'; import axios from 'axios'; import TextToolTip from 'components/TextToolTip'; import { SOMETHING_WENT_WRONG } from 'constants/api'; -import { querySearchParams } from 'constants/queryBuilderQueryNames'; +import { QueryParams } from 'constants/query'; import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useDeleteView } from 'hooks/saveViews/useDeleteView'; @@ -28,7 +28,7 @@ import { useUpdateView } from 'hooks/saveViews/useUpdateView'; import useErrorNotification from 'hooks/useErrorNotification'; import { useNotifications } from 'hooks/useNotifications'; import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useState } from 'react'; import { useCopyToClipboard } from 'react-use'; import { ExploreHeaderToolTip, SaveButtonText } from './constants'; @@ -40,7 +40,7 @@ import { OffSetCol, } from './styles'; import { ExplorerCardProps } from './types'; -import { deleteViewHandler, isQueryUpdatedInView } from './utils'; +import { deleteViewHandler } from './utils'; function ExplorerCard({ sourcepage, @@ -48,7 +48,6 @@ function ExplorerCard({ }: ExplorerCardProps): JSX.Element { const [isOpen, setIsOpen] = useState(false); const [, setCopyUrl] = useCopyToClipboard(); - const [isQueryUpdated, setIsQueryUpdated] = useState(false); const { notifications } = useNotifications(); const onCopyUrlHandler = (): void => { @@ -59,10 +58,11 @@ function ExplorerCard({ }; const { - stagedQuery, currentQuery, panelType, redirectWithQueryBuilderData, + updateAllQueriesOperators, + isStagedQueryUpdated, } = useQueryBuilder(); const { @@ -75,18 +75,15 @@ function ExplorerCard({ useErrorNotification(error); - const handlePopOverClose = (): void => { - setIsOpen(false); - }; - - const handleOpenChange = (newOpen: boolean): void => { + const handleOpenChange = (newOpen = false): void => { setIsOpen(newOpen); }; - const viewName = - useGetSearchQueryParam(querySearchParams.viewName) || 'Query Builder'; + const viewName = useGetSearchQueryParam(QueryParams.viewName) || ''; - const viewKey = useGetSearchQueryParam(querySearchParams.viewKey) || ''; + const viewKey = useGetSearchQueryParam(QueryParams.viewKey) || ''; + + const isQueryUpdated = isStagedQueryUpdated(viewsData?.data?.data, viewKey); const { mutateAsync: updateViewAsync } = useUpdateView({ compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType), @@ -104,7 +101,7 @@ function ExplorerCard({ }); }; - const onDeleteHandler = useCallback(() => { + const onDeleteHandler = (): void => deleteViewHandler({ deleteViewAsync, notifications, @@ -113,15 +110,9 @@ function ExplorerCard({ refetchAllView, viewId: viewKey, viewKey, + updateAllQueriesOperators, + sourcePage: sourcepage, }); - }, [ - deleteViewAsync, - notifications, - panelType, - redirectWithQueryBuilderData, - refetchAllView, - viewKey, - ]); const onUpdateQueryHandler = (): void => { updateViewAsync( @@ -134,7 +125,6 @@ function ExplorerCard({ }, { onSuccess: () => { - setIsQueryUpdated(false); notifications.success({ message: 'View Updated Successfully', }); @@ -147,56 +137,16 @@ function ExplorerCard({ ); }; - useEffect(() => { - setIsQueryUpdated( - isQueryUpdatedInView({ - data: viewsData?.data?.data, - stagedQuery, - viewKey, - currentPanelType: panelType, - }), - ); - }, [ - currentQuery, - viewsData?.data?.data, - stagedQuery, - stagedQuery?.builder.queryData, - viewKey, - panelType, - ]); - - const menu = useMemo( - (): MenuProps => ({ - items: viewsData?.data?.data?.map((view) => ({ - key: view.uuid, - label: ( - - ), - })), - }), - [refetchAllView, viewKey, viewsData?.data?.data], - ); - - const moreOptionMenu = useMemo( - (): MenuProps => ({ - items: [ - { - key: 'delete', - label: Delete, - onClick: onDeleteHandler, - icon: , - }, - ], - }), - [onDeleteHandler], - ); + const moreOptionMenu: MenuProps = { + items: [ + { + key: 'delete', + label: Delete, + onClick: onDeleteHandler, + icon: , + }, + ], + }; const saveButtonType = isQueryUpdated ? 'default' : 'primary'; const saveButtonIcon = isQueryUpdated ? null : ; @@ -207,7 +157,7 @@ function ExplorerCard({ - {viewName} + Query Builder - + {viewsData?.data.data && viewsData?.data.data.length && ( - {/* Saved Views */} - } - trigger={['click']} - overlayStyle={DropDownOverlay} + showSearch + placeholder="Select a view" + dropdownStyle={DropDownOverlay} + dropdownMatchSelectWidth={false} + optionLabelProp="value" + value={viewName || undefined} > - Select View - + {viewsData?.data.data.map((view) => ( + + + + ))} + )} {isQueryUpdated && ( @@ -246,7 +209,7 @@ function ExplorerCard({ content={ } diff --git a/frontend/src/components/ExplorerCard/MenuItemGenerator.tsx b/frontend/src/components/ExplorerCard/MenuItemGenerator.tsx index 2b101385e9..77a143adf9 100644 --- a/frontend/src/components/ExplorerCard/MenuItemGenerator.tsx +++ b/frontend/src/components/ExplorerCard/MenuItemGenerator.tsx @@ -17,9 +17,15 @@ function MenuItemGenerator({ uuid, viewData, refetchAllView, + sourcePage, }: MenuItemLabelGeneratorProps): JSX.Element { - const { panelType, redirectWithQueryBuilderData } = useQueryBuilder(); + const { + panelType, + redirectWithQueryBuilderData, + updateAllQueriesOperators, + } = useQueryBuilder(); const { handleExplorerTabChange } = useHandleExplorerTabChange(); + const { notifications } = useNotifications(); const { mutateAsync: deleteViewAsync } = useDeleteView(uuid); @@ -34,6 +40,8 @@ function MenuItemGenerator({ refetchAllView, viewId: uuid, viewKey, + updateAllQueriesOperators, + sourcePage, }); }; diff --git a/frontend/src/components/ExplorerCard/SaveViewWithName.tsx b/frontend/src/components/ExplorerCard/SaveViewWithName.tsx index 3f77a769c8..7e83e54003 100644 --- a/frontend/src/components/ExplorerCard/SaveViewWithName.tsx +++ b/frontend/src/components/ExplorerCard/SaveViewWithName.tsx @@ -1,13 +1,13 @@ -import { Card, Input, Typography } from 'antd'; +import { Card, Form, Input, Typography } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useSaveView } from 'hooks/saveViews/useSaveView'; import { useNotifications } from 'hooks/useNotifications'; import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery'; -import { ChangeEvent, useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { SaveButton } from './styles'; -import { SaveViewWithNameProps } from './types'; +import { SaveViewFormProps, SaveViewWithNameProps } from './types'; import { saveViewHandler } from './utils'; function SaveViewWithName({ @@ -15,7 +15,8 @@ function SaveViewWithName({ handlePopOverClose, refetchAllView, }: SaveViewWithNameProps): JSX.Element { - const [name, setName] = useState(''); + const [form] = Form.useForm(); + const { t } = useTranslation(['explorer']); const { currentQuery, panelType, @@ -25,19 +26,12 @@ function SaveViewWithName({ const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType); const { isLoading, mutateAsync: saveViewAsync } = useSaveView({ - viewName: name, + viewName: form.getFieldValue('viewName'), compositeQuery, sourcePage, extraData: '', }); - const onChangeHandler = useCallback( - (e: ChangeEvent): void => { - setName(e.target.value); - }, - [], - ); - const onSaveHandler = (): void => { saveViewHandler({ compositeQuery, @@ -49,18 +43,32 @@ function SaveViewWithName({ refetchAllView, saveViewAsync, sourcePage, - viewName: name, - setName, + viewName: form.getFieldValue('viewName'), + form, }); }; return ( - Name of the View - - - Save - + {t('name_of_the_view')} +
+ + + + + Save + +
); } diff --git a/frontend/src/components/ExplorerCard/constants.ts b/frontend/src/components/ExplorerCard/constants.ts index 8caffb366c..ee06168796 100644 --- a/frontend/src/components/ExplorerCard/constants.ts +++ b/frontend/src/components/ExplorerCard/constants.ts @@ -1,3 +1,5 @@ +import { QueryParams } from 'constants/query'; + export const ExploreHeaderToolTip = { url: 'https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=new-query-builder', @@ -8,3 +10,5 @@ export const SaveButtonText = { SAVE_AS_NEW_VIEW: 'Save as new view', SAVE_VIEW: 'Save view', }; + +export type QuerySearchParamNames = QueryParams.viewName | QueryParams.viewKey; diff --git a/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx b/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx index c7289756d7..7efc8a65cd 100644 --- a/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx +++ b/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx @@ -62,15 +62,12 @@ describe('ExplorerCard', () => { const screen = render( Mock Children, ); - const selectButton = screen.getByText('Select View'); + const selectPlaceholder = screen.getByText('Select a view'); - fireEvent.click(selectButton); - - const spanElement = screen.getByRole('img', { - name: 'down', + fireEvent.mouseDown(selectPlaceholder); + const viewNameText = await screen.getAllByText('View 1'); + viewNameText.forEach((element) => { + expect(element).toBeInTheDocument(); }); - fireEvent.click(spanElement); - const viewNameText = await screen.findByText('View 2'); - expect(viewNameText).toBeInTheDocument(); }); }); diff --git a/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx b/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx index de4f9c06a7..c869024f19 100644 --- a/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx +++ b/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx @@ -1,6 +1,7 @@ import { render, screen } from '@testing-library/react'; import ROUTES from 'constants/routes'; import MockQueryClientProvider from 'providers/test/MockQueryClientProvider'; +import { DataSource } from 'types/common/queryBuilder'; import { viewMockData } from '../__mock__/viewData'; import MenuItemGenerator from '../MenuItemGenerator'; @@ -12,6 +13,12 @@ jest.mock('react-router-dom', () => ({ }), })); +jest.mock('antd/es/form/Form', () => ({ + useForm: jest.fn().mockReturnValue({ + onFinish: jest.fn(), + }), +})); + describe('MenuItemGenerator', () => { it('should render MenuItemGenerator component', () => { const screen = render( @@ -23,6 +30,7 @@ describe('MenuItemGenerator', () => { uuid={viewMockData[0].uuid} refetchAllView={jest.fn()} viewData={viewMockData} + sourcePage={DataSource.TRACES} /> , ); @@ -40,6 +48,7 @@ describe('MenuItemGenerator', () => { uuid={viewMockData[0].uuid} refetchAllView={jest.fn()} viewData={viewMockData} + sourcePage={DataSource.TRACES} /> , ); diff --git a/frontend/src/components/ExplorerCard/types.ts b/frontend/src/components/ExplorerCard/types.ts index bda7f702b9..9f4eed3f32 100644 --- a/frontend/src/components/ExplorerCard/types.ts +++ b/frontend/src/components/ExplorerCard/types.ts @@ -1,7 +1,7 @@ +import { FormInstance } from 'antd'; import { NotificationInstance } from 'antd/es/notification/interface'; import { AxiosResponse } from 'axios'; import { PANEL_TYPES } from 'constants/queryBuilder'; -import { SetStateAction } from 'react'; import { UseMutateAsyncFunction } from 'react-query'; import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; @@ -38,6 +38,10 @@ export interface SaveViewWithNameProps { refetchAllView: VoidFunction; } +export interface SaveViewFormProps { + viewName: string; +} + export interface MenuItemLabelGeneratorProps { viewName: string; viewKey: string; @@ -45,6 +49,7 @@ export interface MenuItemLabelGeneratorProps { uuid: string; viewData: ViewProps[]; refetchAllView: VoidFunction; + sourcePage: ExplorerCardProps['sourcepage']; } export interface SaveViewHandlerProps { @@ -63,7 +68,7 @@ export interface SaveViewHandlerProps { >; handlePopOverClose: SaveViewWithNameProps['handlePopOverClose']; redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData']; - setName: (value: SetStateAction) => void; + form: FormInstance; } export interface DeleteViewHandlerProps { @@ -74,4 +79,6 @@ export interface DeleteViewHandlerProps { panelType: PANEL_TYPES | null; viewKey: string; viewId: string; + updateAllQueriesOperators: QueryBuilderContextType['updateAllQueriesOperators']; + sourcePage: ExplorerCardProps['sourcepage']; } diff --git a/frontend/src/components/ExplorerCard/utils.ts b/frontend/src/components/ExplorerCard/utils.ts index e846eb4c6a..2fd33b3c7d 100644 --- a/frontend/src/components/ExplorerCard/utils.ts +++ b/frontend/src/components/ExplorerCard/utils.ts @@ -1,11 +1,8 @@ import { NotificationInstance } from 'antd/es/notification/interface'; import axios from 'axios'; import { SOMETHING_WENT_WRONG } from 'constants/api'; -import { initialQueriesMap } from 'constants/queryBuilder'; -import { - queryParamNamesMap, - querySearchParams, -} from 'constants/queryBuilderQueryNames'; +import { QueryParams } from 'constants/query'; +import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi'; import isEqual from 'lodash-es/isEqual'; @@ -108,7 +105,7 @@ export const saveViewHandler = ({ extraData, redirectWithQueryBuilderData, panelType, - setName, + form, }: SaveViewHandlerProps): void => { saveViewAsync( { @@ -121,9 +118,9 @@ export const saveViewHandler = ({ onSuccess: (data) => { refetchAllView(); redirectWithQueryBuilderData(mapQueryDataFromApi(compositeQuery), { - [queryParamNamesMap.panelTypes]: panelType, - [querySearchParams.viewName]: viewName, - [querySearchParams.viewKey]: data.data.data, + [QueryParams.panelTypes]: panelType, + [QueryParams.viewName]: viewName, + [QueryParams.viewKey]: data.data.data, }); notifications.success({ message: 'View Saved Successfully', @@ -134,7 +131,7 @@ export const saveViewHandler = ({ }, onSettled: () => { handlePopOverClose(); - setName(''); + form.resetFields(); }, }, ); @@ -148,15 +145,24 @@ export const deleteViewHandler = ({ panelType, viewKey, viewId, + updateAllQueriesOperators, + sourcePage, }: DeleteViewHandlerProps): void => { deleteViewAsync(viewKey, { onSuccess: () => { if (viewId === viewKey) { - redirectWithQueryBuilderData(initialQueriesMap.traces, { - [querySearchParams.viewName]: 'Query Builder', - [queryParamNamesMap.panelTypes]: panelType, - [querySearchParams.viewKey]: '', - }); + redirectWithQueryBuilderData( + updateAllQueriesOperators( + initialQueriesMap.traces, + panelType || PANEL_TYPES.LIST, + sourcePage, + ), + { + [QueryParams.viewName]: '', + [QueryParams.panelTypes]: panelType, + [QueryParams.viewKey]: '', + }, + ); } notifications.success({ message: 'View Deleted Successfully', diff --git a/frontend/src/components/Graph/Plugin/DragSelect.ts b/frontend/src/components/Graph/Plugin/DragSelect.ts index a22c6da91d..400b870b87 100644 --- a/frontend/src/components/Graph/Plugin/DragSelect.ts +++ b/frontend/src/components/Graph/Plugin/DragSelect.ts @@ -1,5 +1,5 @@ import { Chart, ChartTypeRegistry, Plugin } from 'chart.js'; -import * as ChartHelpers from 'chart.js/helpers'; +import { getRelativePosition } from 'chart.js/helpers'; // utils import { ChartEventHandler, mergeDefaultOptions } from './utils'; @@ -45,7 +45,7 @@ function createMousedownHandler( return (ev): void => { const { left, right } = chart.chartArea; - let { x: startDragPositionX } = ChartHelpers.getRelativePosition(ev, chart); + let { x: startDragPositionX } = getRelativePosition(ev, chart); if (left > startDragPositionX) { startDragPositionX = left; @@ -74,7 +74,7 @@ function createMousemoveHandler( const { left, right } = chart.chartArea; - let { x: dragPositionX } = ChartHelpers.getRelativePosition(ev, chart); + let { x: dragPositionX } = getRelativePosition(ev, chart); if (left > dragPositionX) { dragPositionX = left; @@ -99,7 +99,7 @@ function createMouseupHandler( return (ev): void => { const { left, right } = chart.chartArea; - let { x: endRelativePostionX } = ChartHelpers.getRelativePosition(ev, chart); + let { x: endRelativePostionX } = getRelativePosition(ev, chart); if (left > endRelativePostionX) { endRelativePostionX = left; diff --git a/frontend/src/components/Graph/Plugin/IntersectionCursor.ts b/frontend/src/components/Graph/Plugin/IntersectionCursor.ts index 99b4e50011..a3d9a4d6a4 100644 --- a/frontend/src/components/Graph/Plugin/IntersectionCursor.ts +++ b/frontend/src/components/Graph/Plugin/IntersectionCursor.ts @@ -1,5 +1,5 @@ import { Chart, ChartEvent, ChartTypeRegistry, Plugin } from 'chart.js'; -import * as ChartHelpers from 'chart.js/helpers'; +import { getRelativePosition } from 'chart.js/helpers'; // utils import { ChartEventHandler, mergeDefaultOptions } from './utils'; @@ -42,7 +42,7 @@ function createMousemoveHandler( return (ev: ChartEvent | MouseEvent): void => { const { left, right, top, bottom } = chart.chartArea; - let { x, y } = ChartHelpers.getRelativePosition(ev, chart); + let { x, y } = getRelativePosition(ev, chart); if (left > x) { x = left; diff --git a/frontend/src/components/Styled/index.ts b/frontend/src/components/Styled/index.ts index 579e15a499..b999485859 100644 --- a/frontend/src/components/Styled/index.ts +++ b/frontend/src/components/Styled/index.ts @@ -1,4 +1,17 @@ -import * as AntD from 'antd'; +import { + Button, + ButtonProps, + Col, + ColProps, + Divider, + DividerProps, + Row, + RowProps, + Space, + SpaceProps, + TabsProps, + Typography, +} from 'antd'; import { TextProps } from 'antd/lib/typography/Text'; import { TitleProps } from 'antd/lib/typography/Title'; import { HTMLAttributes } from 'react'; @@ -9,43 +22,43 @@ import { IStyledClass } from './types'; const styledClass = (props: IStyledClass): FlattenSimpleInterpolation | null => props.styledclass || null; -type TStyledCol = AntD.ColProps & IStyledClass; -const StyledCol = styled(AntD.Col)` +type TStyledCol = ColProps & IStyledClass; +const StyledCol = styled(Col)` ${styledClass} `; -type TStyledRow = AntD.RowProps & IStyledClass; -const StyledRow = styled(AntD.Row)` +type TStyledRow = RowProps & IStyledClass; +const StyledRow = styled(Row)` ${styledClass} `; -type TStyledDivider = AntD.DividerProps & IStyledClass; -const StyledDivider = styled(AntD.Divider)` +type TStyledDivider = DividerProps & IStyledClass; +const StyledDivider = styled(Divider)` ${styledClass} `; -type TStyledSpace = AntD.SpaceProps & IStyledClass; -const StyledSpace = styled(AntD.Space)` +type TStyledSpace = SpaceProps & IStyledClass; +const StyledSpace = styled(Space)` ${styledClass} `; -type TStyledTabs = AntD.TabsProps & IStyledClass; -const StyledTabs = styled(AntD.Divider)` +type TStyledTabs = TabsProps & IStyledClass; +const StyledTabs = styled(Divider)` ${styledClass} `; -type TStyledButton = AntD.ButtonProps & IStyledClass; -const StyledButton = styled(AntD.Button)` +type TStyledButton = ButtonProps & IStyledClass; +const StyledButton = styled(Button)` ${styledClass} `; -const { Text } = AntD.Typography; +const { Text } = Typography; type TStyledTypographyText = TextProps & IStyledClass; const StyledTypographyText = styled(Text)` ${styledClass} `; -const { Title } = AntD.Typography; +const { Title } = Typography; type TStyledTypographyTitle = TitleProps & IStyledClass; const StyledTypographyTitle = styled(Title)` ${styledClass} diff --git a/frontend/src/constants/card.ts b/frontend/src/constants/card.ts new file mode 100644 index 0000000000..1832c1b7e8 --- /dev/null +++ b/frontend/src/constants/card.ts @@ -0,0 +1,4 @@ +export const CARD_BODY_STYLE = { + padding: '0', + height: '100%', +}; diff --git a/frontend/src/constants/features.ts b/frontend/src/constants/features.ts index fc267c0e7c..396843db2d 100644 --- a/frontend/src/constants/features.ts +++ b/frontend/src/constants/features.ts @@ -17,4 +17,6 @@ export enum FeatureKeys { DISABLE_UPSELL = 'DISABLE_UPSELL', USE_SPAN_METRICS = 'USE_SPAN_METRICS', OSS = 'OSS', + ONBOARDING = 'ONBOARDING', + CHAT_SUPPORT = 'CHAT_SUPPORT', } diff --git a/frontend/src/constants/localStorage.ts b/frontend/src/constants/localStorage.ts index 450546abb4..b17ae3d34c 100644 --- a/frontend/src/constants/localStorage.ts +++ b/frontend/src/constants/localStorage.ts @@ -11,4 +11,7 @@ export enum LOCALSTORAGE { GRAPH_VISIBILITY_STATES = 'GRAPH_VISIBILITY_STATES', TRACES_LIST_COLUMNS = 'TRACES_LIST_COLUMNS', LOGS_LIST_COLUMNS = 'LOGS_LIST_COLUMNS', + LOGGED_IN_USER_NAME = 'LOGGED_IN_USER_NAME', + LOGGED_IN_USER_EMAIL = 'LOGGED_IN_USER_EMAIL', + CHAT_SUPPORT = 'CHAT_SUPPORT', } diff --git a/frontend/src/constants/query.ts b/frontend/src/constants/query.ts index 3d5f1150e3..a35f0223a7 100644 --- a/frontend/src/constants/query.ts +++ b/frontend/src/constants/query.ts @@ -18,4 +18,12 @@ export enum QueryParams { q = 'q', activeLogId = 'activeLogId', timeRange = 'timeRange', + compositeQuery = 'compositeQuery', + panelTypes = 'panelTypes', + pageSize = 'pageSize', + viewMode = 'viewMode', + selectedFields = 'selectedFields', + linesPerRow = 'linesPerRow', + viewName = 'viewName', + viewKey = 'viewKey', } diff --git a/frontend/src/constants/queryBuilderQueryNames.ts b/frontend/src/constants/queryBuilderQueryNames.ts deleted file mode 100644 index fd7311364b..0000000000 --- a/frontend/src/constants/queryBuilderQueryNames.ts +++ /dev/null @@ -1,26 +0,0 @@ -type QueryParamNames = - | 'compositeQuery' - | 'panelTypes' - | 'pageSize' - | 'viewMode' - | 'selectedFields' - | 'linesPerRow'; - -export type QuerySearchParamNames = 'viewName' | 'viewKey'; - -export const queryParamNamesMap: Record = { - compositeQuery: 'compositeQuery', - panelTypes: 'panelTypes', - pageSize: 'pageSize', - viewMode: 'viewMode', - selectedFields: 'selectedFields', - linesPerRow: 'linesPerRow', -}; - -export const querySearchParams: Record< - QuerySearchParamNames, - QuerySearchParamNames -> = { - viewName: 'viewName', - viewKey: 'viewKey', -}; diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts index c4997dd45b..1f984ebd46 100644 --- a/frontend/src/constants/reactQueryKeys.ts +++ b/frontend/src/constants/reactQueryKeys.ts @@ -2,4 +2,6 @@ export const REACT_QUERY_KEY = { GET_ALL_LICENCES: 'GET_ALL_LICENCES', GET_QUERY_RANGE: 'GET_QUERY_RANGE', GET_ALL_DASHBOARDS: 'GET_ALL_DASHBOARDS', + GET_TRIGGERED_ALERTS: 'GET_TRIGGERED_ALERTS', + GET_FEATURES_FLAGS: 'GET_FEATURES_FLAGS', }; diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index ada1875c0c..c910d0d25e 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -7,7 +7,7 @@ const ROUTES = { TRACE_DETAIL: '/trace/:id', TRACES_EXPLORER: '/traces-explorer', SETTINGS: '/settings', - INSTRUMENTATION: '/get-started', + GET_STARTED: '/get-started', USAGE_EXPLORER: '/usage-explorer', APPLICATION: '/services', ALL_DASHBOARD: '/dashboard', diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 7574e59143..b4dddf1a7b 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -1,7 +1,7 @@ import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs'; -import getFeaturesFlags from 'api/features/getFeatureFlags'; import getUserLatestVersion from 'api/user/getLatestVersion'; import getUserVersion from 'api/user/getVersion'; +import ROUTES from 'constants/routes'; import Header from 'container/Header'; import SideNav from 'container/SideNav'; import TopNav from 'container/TopNav'; @@ -19,26 +19,25 @@ import { UPDATE_CONFIGS, UPDATE_CURRENT_ERROR, UPDATE_CURRENT_VERSION, - UPDATE_FEATURE_FLAG_RESPONSE, UPDATE_LATEST_VERSION, UPDATE_LATEST_VERSION_ERROR, } from 'types/actions/app'; import AppReducer from 'types/reducer/app'; -import { ChildrenContainer, Layout } from './styles'; +import { ChildrenContainer, Layout, LayoutContent } from './styles'; import { getRouteKey } from './utils'; function AppLayout(props: AppLayoutProps): JSX.Element { const { isLoggedIn, user } = useSelector( (state) => state.app, ); + const { pathname } = useLocation(); const { t } = useTranslation(['titles']); const [ getUserVersionResponse, getUserLatestVersionResponse, - getFeaturesResponse, getDynamicConfigsResponse, ] = useQueries([ { @@ -51,10 +50,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element { queryKey: ['getUserLatestVersion', user?.accessJwt], enabled: isLoggedIn, }, - { - queryFn: getFeaturesFlags, - queryKey: ['getFeatureFlags', user?.accessJwt], - }, { queryFn: getDynamicConfigs, queryKey: ['getDynamicConfigs', user?.accessJwt], @@ -62,10 +57,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element { ]); useEffect(() => { - if (getFeaturesResponse.status === 'idle') { - getFeaturesResponse.refetch(); - } - if (getUserLatestVersionResponse.status === 'idle' && isLoggedIn) { getUserLatestVersionResponse.refetch(); } @@ -73,14 +64,10 @@ function AppLayout(props: AppLayoutProps): JSX.Element { if (getUserVersionResponse.status === 'idle' && isLoggedIn) { getUserVersionResponse.refetch(); } - if (getFeaturesResponse.status === 'idle') { - getFeaturesResponse.refetch(); - } if (getDynamicConfigsResponse.status === 'idle') { getDynamicConfigsResponse.refetch(); } }, [ - getFeaturesResponse, getUserLatestVersionResponse, getUserVersionResponse, isLoggedIn, @@ -194,42 +181,17 @@ function AppLayout(props: AppLayoutProps): JSX.Element { getUserLatestVersionResponse.isFetched, getUserVersionResponse.isFetched, getUserLatestVersionResponse.isSuccess, - getFeaturesResponse.isFetched, - getFeaturesResponse.isSuccess, - getFeaturesResponse.data, getDynamicConfigsResponse.data, getDynamicConfigsResponse.isFetched, getDynamicConfigsResponse.isSuccess, notifications, ]); - useEffect(() => { - if ( - getFeaturesResponse.isFetched && - getFeaturesResponse.isSuccess && - getFeaturesResponse.data && - getFeaturesResponse.data.payload - ) { - dispatch({ - type: UPDATE_FEATURE_FLAG_RESPONSE, - payload: { - featureFlag: getFeaturesResponse.data.payload, - refetch: getFeaturesResponse.refetch, - }, - }); - } - }, [ - dispatch, - getFeaturesResponse.data, - getFeaturesResponse.isFetched, - getFeaturesResponse.isSuccess, - getFeaturesResponse.refetch, - ]); - const isToDisplayLayout = isLoggedIn; const routeKey = useMemo(() => getRouteKey(pathname), [pathname]); const pageTitle = t(routeKey); + const renderFullScreen = pathname === ROUTES.GET_STARTED; return ( @@ -239,13 +201,13 @@ function AppLayout(props: AppLayoutProps): JSX.Element { {isToDisplayLayout &&
} - {isToDisplayLayout && } - + {isToDisplayLayout && !renderFullScreen && } + - {isToDisplayLayout && } + {isToDisplayLayout && !renderFullScreen && } {children} - + ); diff --git a/frontend/src/container/AppLayout/styles.ts b/frontend/src/container/AppLayout/styles.ts index 1f48b54033..d3a030bf1f 100644 --- a/frontend/src/container/AppLayout/styles.ts +++ b/frontend/src/container/AppLayout/styles.ts @@ -7,9 +7,14 @@ export const Layout = styled(LayoutComponent)` position: relative; min-height: calc(100vh - 4rem); overflow: hidden; + height: 100%; } `; +export const LayoutContent = styled(LayoutComponent.Content)` + overflow-y: auto; +`; + export const ChildrenContainer = styled.div` margin: 0 1rem; display: flex; diff --git a/frontend/src/container/ErrorDetails/config.ts b/frontend/src/container/ErrorDetails/config.ts new file mode 100644 index 0000000000..5ce48c48d7 --- /dev/null +++ b/frontend/src/container/ErrorDetails/config.ts @@ -0,0 +1,8 @@ +export const keyToExclude = [ + 'exceptionStacktrace', + 'exceptionType', + 'errorId', + 'timestamp', + 'exceptionMessage', + 'exceptionEscaped', +]; diff --git a/frontend/src/container/ErrorDetails/index.tsx b/frontend/src/container/ErrorDetails/index.tsx index 561114a289..ff27641c64 100644 --- a/frontend/src/container/ErrorDetails/index.tsx +++ b/frontend/src/container/ErrorDetails/index.tsx @@ -5,6 +5,7 @@ import { ResizeTable } from 'components/ResizeTable'; import { getNanoSeconds } from 'container/AllError/utils'; import dayjs from 'dayjs'; import { useNotifications } from 'hooks/useNotifications'; +import createQueryParams from 'lib/createQueryParams'; import history from 'lib/history'; import { urlKey } from 'pages/ErrorDetails/utils'; import { useMemo, useState } from 'react'; @@ -13,12 +14,13 @@ import { useQuery } from 'react-query'; import { useLocation } from 'react-router-dom'; import { PayloadProps as GetByErrorTypeAndServicePayload } from 'types/api/errors/getByErrorTypeAndService'; +import { keyToExclude } from './config'; import { DashedContainer, EditorContainer, EventContainer } from './styles'; function ErrorDetails(props: ErrorDetailsProps): JSX.Element { const { idPayload } = props; const { t } = useTranslation(['errorDetails', 'common']); - const { search } = useLocation(); + const { search, pathname } = useLocation(); const params = useMemo(() => new URLSearchParams(search), [search]); @@ -69,18 +71,6 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element { [], ); - const keyToExclude = useMemo( - () => [ - 'exceptionStacktrace', - 'exceptionType', - 'errorId', - 'timestamp', - 'exceptionMessage', - 'exceptionEscaped', - ], - [], - ); - const { notifications } = useNotifications(); const onClickErrorIdHandler = async ( @@ -95,11 +85,13 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element { return; } - history.replace( - `${history.location.pathname}?&groupId=${ - idPayload.groupID - }×tamp=${getNanoSeconds(timestamp)}&errorId=${id}`, - ); + const queryParams = { + groupId: idPayload.groupID, + timestamp: getNanoSeconds(timestamp), + errorId: id, + }; + + history.replace(`${pathname}?${createQueryParams(queryParams)}`); } catch (error) { notifications.error({ message: t('something_went_wrong'), diff --git a/frontend/src/container/ExportPanel/ExportPanel.tsx b/frontend/src/container/ExportPanel/ExportPanel.tsx index 24e9ed210e..eda643dde9 100644 --- a/frontend/src/container/ExportPanel/ExportPanel.tsx +++ b/frontend/src/container/ExportPanel/ExportPanel.tsx @@ -44,10 +44,10 @@ function ExportPanel({ isLoading, onExport }: ExportPanelProps): JSX.Element { onError: handleError, }); - const options = useMemo(() => getSelectOptions(data?.payload || []), [data]); + const options = useMemo(() => getSelectOptions(data || []), [data]); const handleExportClick = useCallback((): void => { - const currentSelectedDashboard = data?.payload?.find( + const currentSelectedDashboard = data?.find( ({ uuid }) => uuid === selectedDashboardId, ); diff --git a/frontend/src/container/ExportPanel/index.tsx b/frontend/src/container/ExportPanel/index.tsx index 8447814fa7..c3751c7e58 100644 --- a/frontend/src/container/ExportPanel/index.tsx +++ b/frontend/src/container/ExportPanel/index.tsx @@ -1,6 +1,6 @@ import { AlertOutlined, AreaChartOutlined } from '@ant-design/icons'; import { Button, Modal, Space } from 'antd'; -import { queryParamNamesMap } from 'constants/queryBuilderQueryNames'; +import { QueryParams } from 'constants/query'; import ROUTES from 'constants/routes'; import history from 'lib/history'; import { useCallback, useState } from 'react'; @@ -22,9 +22,9 @@ function ExportPanel({ const onCreateAlertsHandler = useCallback(() => { history.push( - `${ROUTES.ALERTS_NEW}?${ - queryParamNamesMap.compositeQuery - }=${encodeURIComponent(JSON.stringify(query))}`, + `${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent( + JSON.stringify(query), + )}`, ); }, [query]); diff --git a/frontend/src/container/FormAlertRules/ChartPreview/utils.ts b/frontend/src/container/FormAlertRules/ChartPreview/utils.ts index 0dbcc21603..dd9406b275 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/utils.ts +++ b/frontend/src/container/FormAlertRules/ChartPreview/utils.ts @@ -20,6 +20,10 @@ export function covertIntoDataFormats({ sourceUnit, targetUnit, }: IUnit): number { + if (sourceUnit === undefined || targetUnit === undefined) { + return value; + } + if (Object.values(BooleanFormats).includes(sourceUnit as BooleanFormats)) { return 1; } diff --git a/frontend/src/container/GridGraphLayout/Graph/WidgetGraphComponent.tsx b/frontend/src/container/GridGraphLayout/Graph/WidgetGraphComponent.tsx index 4d3d6867ca..f40d1e4190 100644 --- a/frontend/src/container/GridGraphLayout/Graph/WidgetGraphComponent.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/WidgetGraphComponent.tsx @@ -4,6 +4,7 @@ import { Events } from 'constants/events'; import GridPanelSwitch from 'container/GridPanelSwitch'; import { useChartMutable } from 'hooks/useChartMutable'; import { useNotifications } from 'hooks/useNotifications'; +import createQueryParams from 'lib/createQueryParams'; import history from 'lib/history'; import { isEmpty, isEqual } from 'lodash-es'; import { @@ -18,6 +19,7 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { connect, useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; import { bindActionCreators } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; import { DeleteWidget } from 'store/actions/dashboard/deleteWidget'; @@ -61,6 +63,7 @@ function WidgetGraphComponent({ const [hovered, setHovered] = useState(false); const { notifications } = useNotifications(); const { t } = useTranslation(['common']); + const { pathname } = useLocation(); const { graphVisibilityStates: localstoredVisibilityStates } = useMemo( () => @@ -190,11 +193,11 @@ function WidgetGraphComponent({ message: 'Panel cloned successfully, redirecting to new copy.', }); - setTimeout(() => { - history.push( - `${history.location.pathname}/new?graphType=${widget?.panelTypes}&widgetId=${uuid}`, - ); - }, 1500); + const queryParams = { + graphType: widget?.panelTypes, + widgetId: uuid, + }; + history.push(`${pathname}/new?${createQueryParams(queryParams)}`); }); } }; diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx index 0e0829ecce..b5c4b3ffbf 100644 --- a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx +++ b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx @@ -8,7 +8,7 @@ import { } from '@ant-design/icons'; import { Dropdown, MenuProps, Tooltip, Typography } from 'antd'; import Spinner from 'components/Spinner'; -import { queryParamNamesMap } from 'constants/queryBuilderQueryNames'; +import { QueryParams } from 'constants/query'; import ROUTES from 'constants/routes'; import useComponentPermission from 'hooks/useComponentPermission'; import history from 'lib/history'; @@ -73,7 +73,7 @@ function WidgetHeader({ history.push( `${window.location.pathname}/new?widgetId=${widgetId}&graphType=${ widget.panelTypes - }&${queryParamNamesMap.compositeQuery}=${encodeURIComponent( + }&${QueryParams.compositeQuery}=${encodeURIComponent( JSON.stringify(widget.query), )}`, ); @@ -81,9 +81,9 @@ function WidgetHeader({ const onCreateAlertsHandler = useCallback(() => { history.push( - `${ROUTES.ALERTS_NEW}?${ - queryParamNamesMap.compositeQuery - }=${encodeURIComponent(JSON.stringify(widget.query))}`, + `${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent( + JSON.stringify(widget.query), + )}`, ); }, [widget]); diff --git a/frontend/src/container/Header/index.tsx b/frontend/src/container/Header/index.tsx index c5b39c4740..ae98295ada 100644 --- a/frontend/src/container/Header/index.tsx +++ b/frontend/src/container/Header/index.tsx @@ -123,7 +123,6 @@ function HeaderContainer(): JSX.Element { Try Signoz Cloud )} - searchItem.data.tags).filter(Boolean), ), (tag) => ({ name: tag }), diff --git a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx index fb13155841..39d3c02a23 100644 --- a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx +++ b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx @@ -10,7 +10,11 @@ import AppActions from 'types/actions'; import { Data } from '../index'; import { TableLinkText } from './styles'; -function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element { +function DeleteButton({ + deleteDashboard, + id, + refetchDashboardList, +}: DeleteButtonProps): JSX.Element { const [modal, contextHolder] = Modal.useModal(); const openConfirmationDialog = useCallback((): void => { @@ -20,13 +24,14 @@ function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element { onOk() { deleteDashboard({ uuid: id, + refetch: refetchDashboardList, }); }, okText: 'Delete', okButtonProps: { danger: true }, centered: true, }); - }, [id, modal, deleteDashboard]); + }, [modal, deleteDashboard, id, refetchDashboardList]); return ( <> @@ -51,13 +56,22 @@ const mapDispatchToProps = ( deleteDashboard: bindActionCreators(DeleteDashboard, dispatch), }); -type DeleteButtonProps = Data & DispatchProps; +export type DeleteButtonProps = Data & DispatchProps; const WrapperDeleteButton = connect(null, mapDispatchToProps)(DeleteButton); // This is to avoid the type collision function Wrapper(props: Data): JSX.Element { - const { createdBy, description, id, key, lastUpdatedTime, name, tags } = props; + const { + createdBy, + description, + id, + key, + refetchDashboardList, + lastUpdatedTime, + name, + tags, + } = props; return ( ); diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx index bcd5d81fca..669ee75942 100644 --- a/frontend/src/container/ListOfDashboard/index.tsx +++ b/frontend/src/container/ListOfDashboard/index.tsx @@ -14,6 +14,7 @@ import { ResizeTable } from 'components/ResizeTable'; import TextToolTip from 'components/TextToolTip'; import ROUTES from 'constants/routes'; import SearchFilter from 'container/ListOfDashboard/SearchFilter'; +import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard'; import useComponentPermission from 'hooks/useComponentPermission'; import history from 'lib/history'; import { @@ -25,6 +26,7 @@ import { useState, } from 'react'; import { useTranslation } from 'react-i18next'; +import { UseQueryResult } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { generatePath } from 'react-router-dom'; import { AppState } from 'store/reducers'; @@ -32,20 +34,24 @@ import AppActions from 'types/actions'; import { GET_ALL_DASHBOARD_SUCCESS } from 'types/actions/dashboard'; import { Dashboard } from 'types/api/dashboard/getAll'; import AppReducer from 'types/reducer/app'; -import DashboardReducer from 'types/reducer/dashboards'; import ImportJSON from './ImportJSON'; import { ButtonContainer, NewDashboardButton, TableContainer } from './styles'; import Createdby from './TableComponents/CreatedBy'; import DateComponent from './TableComponents/Date'; -import DeleteButton from './TableComponents/DeleteButton'; +import DeleteButton, { + DeleteButtonProps, +} from './TableComponents/DeleteButton'; import Name from './TableComponents/Name'; import Tags from './TableComponents/Tags'; function ListOfAllDashboard(): JSX.Element { - const { dashboards, loading } = useSelector( - (state) => state.dashboards, - ); + const { + data: dashboardListResponse = [], + isLoading: isDashboardListLoading, + refetch: refetchDashboardList, + } = useGetAllDashboard(); + const dispatch = useDispatch>(); const { role } = useSelector((state) => state.app); @@ -66,8 +72,10 @@ function ListOfAllDashboard(): JSX.Element { const [filteredDashboards, setFilteredDashboards] = useState(); useEffect(() => { - setFilteredDashboards(dashboards); - }, [dashboards]); + if (dashboardListResponse.length) { + setFilteredDashboards(dashboardListResponse); + } + }, [dashboardListResponse]); const [newDashboardState, setNewDashboardState] = useState({ loading: false, @@ -125,22 +133,43 @@ function ListOfAllDashboard(): JSX.Element { title: 'Action', dataIndex: '', width: 40, - render: DeleteButton, + render: ({ + createdBy, + description, + id, + key, + lastUpdatedTime, + name, + tags, + }: DeleteButtonProps) => ( + + ), }); } return tableColumns; - }, [action]); + }, [action, refetchDashboardList]); - const data: Data[] = (filteredDashboards || dashboards).map((e) => ({ - createdBy: e.created_at, - description: e.data.description || '', - id: e.uuid, - lastUpdatedTime: e.updated_at, - name: e.data.title, - tags: e.data.tags || [], - key: e.uuid, - })); + const data: Data[] = + filteredDashboards?.map((e) => ({ + createdBy: e.created_at, + description: e.data.description || '', + id: e.uuid, + lastUpdatedTime: e.updated_at, + name: e.data.title, + tags: e.data.tags || [], + key: e.uuid, + refetchDashboardList, + })) || []; const onNewDashboardHandler = useCallback(async () => { try { @@ -209,7 +238,7 @@ function ListOfAllDashboard(): JSX.Element { menuItems.push({ key: t('create_dashboard').toString(), label: t('create_dashboard'), - disabled: loading, + disabled: isDashboardListLoading, onClick: onNewDashboardHandler, }); } @@ -228,7 +257,7 @@ function ListOfAllDashboard(): JSX.Element { }); return menuItems; - }, [createNewDashboard, loading, onNewDashboardHandler, t]); + }, [createNewDashboard, isDashboardListLoading, onNewDashboardHandler, t]); const menu: MenuProps = useMemo( () => ({ @@ -250,7 +279,11 @@ function ListOfAllDashboard(): JSX.Element { }} /> {newDashboard && ( - + } type="primary" @@ -266,7 +299,7 @@ function ListOfAllDashboard(): JSX.Element { ), [ newDashboard, - loading, + isDashboardListLoading, menu, newDashboardState.loading, newDashboardState.error, @@ -278,9 +311,9 @@ function ListOfAllDashboard(): JSX.Element { {GetHeader} - {!loading && ( + {!isDashboardListLoading && ( )} @@ -300,7 +333,7 @@ function ListOfAllDashboard(): JSX.Element { showHeader bordered sticky - loading={loading} + loading={isDashboardListLoading} dataSource={data} showSorterTooltip /> @@ -317,6 +350,7 @@ export interface Data { createdBy: string; lastUpdatedTime: string; id: string; + refetchDashboardList: UseQueryResult['refetch']; } export default ListOfAllDashboard; diff --git a/frontend/src/container/LiveLogs/BackButton/index.tsx b/frontend/src/container/LiveLogs/BackButton/index.tsx index 8386a0208d..5dee8d9546 100644 --- a/frontend/src/container/LiveLogs/BackButton/index.tsx +++ b/frontend/src/container/LiveLogs/BackButton/index.tsx @@ -1,10 +1,10 @@ import { ArrowLeftOutlined } from '@ant-design/icons'; import { Button } from 'antd'; +import { QueryParams } from 'constants/query'; import { initialQueryBuilderFormValuesMap, PANEL_TYPES, } from 'constants/queryBuilder'; -import { queryParamNamesMap } from 'constants/queryBuilderQueryNames'; import ROUTES from 'constants/routes'; import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; @@ -17,7 +17,7 @@ import { constructCompositeQuery } from '../constants'; function BackButton(): JSX.Element { const history = useHistory(); - const { updateAllQueriesOperators, resetQuery } = useQueryBuilder(); + const { updateAllQueriesOperators } = useQueryBuilder(); const compositeQuery = useGetCompositeQueryParam(); @@ -36,14 +36,12 @@ function BackButton(): JSX.Element { DataSource.LOGS, ); - resetQuery(updatedQuery); - const JSONCompositeQuery = encodeURIComponent(JSON.stringify(updatedQuery)); - const path = `${ROUTES.LOGS_EXPLORER}?${queryParamNamesMap.compositeQuery}=${JSONCompositeQuery}`; + const path = `${ROUTES.LOGS_EXPLORER}?${QueryParams.compositeQuery}=${JSONCompositeQuery}`; history.push(path); - }, [history, compositeQuery, resetQuery, updateAllQueriesOperators]); + }, [history, compositeQuery, updateAllQueriesOperators]); return (