Compare commits

..

21 Commits

Author SHA1 Message Date
Shivanshu Raj Shrivastava
99cbd023b5 feat: adds server and handler changes
Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
2025-05-13 16:20:02 +05:30
Nageshbansal
dfca5b13c0 fix: OSS telemetry for the number of services (#7908)
Fixes the OSS telemetry for sending the `Number of services` events.
2025-05-13 16:18:59 +05:30
Aditya Singh
ad392e81ff Fix: Update query_range api from v3 to v4 (Logs and Traces) (#7906)
* fix: change query range api from v3 to v4

* test: update test cases

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>
2025-05-13 09:54:37 +00:00
Yunus M
92ceefccee Update pull_request_template.md (#7865) 2025-05-13 09:41:16 +00:00
Vikrant Gupta
9cc4e1b56f chore(frontend): update the api folder structure (#7901)
* chore(api): update the api folder structure according to rest principles

* chore(api): update the api folder structure according to rest principles
2025-05-12 18:14:58 +05:30
Ekansh Gupta
3758ee7451 fix: changed the keys in the default quick filters to actual keys in … (#7863)
* fix: changed the keys in the default quick filters to actual keys in the v3.attributekeys

* fix: changed the keys in the default quick filters to actual keys in the v3.attributekeys

* fix: changed the keys in the default quick filters to actual keys in the v3.attributekeys

* fix: changed the keys in the default quick filters to actual keys in the v3.attributekeys

* fix: changed the keys in the default quick filters to actual keys in the v3.attributekeys
2025-05-12 16:20:47 +05:30
Vibhu Pandey
02b605d109 feat(analytics): add analytics package (#7808)
- add analytics package
2025-05-12 14:32:13 +05:30
Amlan Kumar Nandy
eb86aabf3e chore: improvements to the all attributes section in metric details (#7879) 2025-05-12 05:24:02 +00:00
Amlan Kumar Nandy
8810693bda chore: persist one chart per query toggle across refreshes and exports (#7884) 2025-05-12 05:16:13 +00:00
Shaheer Kochai
6334e09a60 feat: Funnel Details Page Base Structure (#7364)
* feat: funnels list page basic UI

* feat: get funnels list data from mock API, and handle data, loading and empty states

* feat: implement funnel rename

* chore: move useFunnels to hooks/TracesFunnels

* feat: create traces funnels details basic page + funnel -> details redirection

* fix: properly display created at in funnels list item + preventDefault

* chore: add tab bar to trace funnel details page

* chore: traces funnel details page overall skeleton

* chore: traces funnel details results skeleton

* fix: hide step count for add button only

* feat: funnel details page steps and configuration (#7424)

* chore: add a new tab for traces funnels

* feat: funnels list page basic UI

* feat: get funnels list data from mock API, and handle data, loading and empty states

* feat: implement funnel rename

* refactor: overall improvements

* feat: implement sorting in traces funnels list page

* feat: add sort column key and order to url params

* chore: move useFunnels to hooks/TracesFunnels

* feat: implement traces funnels search and refactor search and sort by extracting to custom hooks

* chore: overall improvements to rename trace funnel modal

* chore: make the rename input auto-focusable

* feat: handle create funnel modal

* feat: delete funnel modal and functionality

* fix: fix the layout shift in funnel item caused by getContainer={false}

* chore: overall improvements and use live api in traces funnels

* feat: create traces funnels details basic page + funnel -> details redirection

* fix: funnels traces light mode UI

* fix: properly display created at in funnels list item + preventDefault

* refactor: extract FunnelItemPopover into a separate component

* chore: hide funnel tab from traces explorer

* chore: add check to display trace funnels tab only in dev environment

* chore: improve funnels modals light mode

* chore: overall improvements

* fix: properly pass funnel details link

* chore: address PR review changes

* chore: add tab bar to trace funnel details page

* feat: funnel step UI with service, span, and where filters

* feat: build radio button component

* refactor: use the SignozRadioButton in funnel results -> step transitions radio buttons

* feat: inter step config (i.e. latency type) UI

* chore: improve steps header styles by removing divider width

* feat: funnel steps title, description, popover UI + pass data from API

* chore: update FilterSelect component to conditionally add url params and accept on change

* fix: fix funnel step where clause and update the state variables for filters

* chore: add support for isMultiple and fix the type in FilterSelect

* feat: centralize the steps state management in StepsContent

* fix: move steps state up + pass steps count from state

* feat: implement auto save for updating the steps whenever any step changes

* feat: implement auto save for validating steps if service name or span names change

* feat: impelement funnel step removal

* feat: implement add details modal for funnel steps

* fix: fix the overflowing time range picker

* feat: funnel details empty state

* feat: add support for saving funnel description

* chore: overall improvements

* fix: fix the light mode styles

* fix: fix the failing build + broken search UI

* refactor: remove the reference of useLocation from traceFunnel item in TraceModulePage constant

* fix: fix the issue of update steps getting triggered on initial render if we have filters

* fix: fix the edge case of stale state causing filters to be re-added after removing

* feat: funnel details page results (#7451)

* feat: funnel metrics table component

* feat: funnel metrics and steps transition metrics components UI

* feat: funnel table component

* feat: slowest traces and traces with error components

* fix: overall light theme fixes

* fix: fix the warning

* chore: add empty and loading states to FunnelMetricsTable

* feat: get overall funnel metrics from the API

* fix: fix the empty state of funnel metrics table

* feat: get data for slowest traces and traces with errors

* fix: link trace id to trace details page

* fix: get data for funnel step transition metrics and refactor the existing data fetching logic

* refactor: add funnel context + overall refactoring and optimizations

* refactor: move steps states to funnel context + handle empty and run funnel disabled states

* feat: handle run funnel

* fix: improve empty state

* chore: rename isValidateStepsMutationLoading -> isValidateStepsLoading

* chore: improve query key

* fix: display loading state if funnel results are fetching

* refactor: move steps validation fetching and states to the context API

* fix: display loading state in funnel results while steps validation is fetching

* fix: call validate steps API only on changing the service name or span name of any step

* refactor: move validateStepsQuery key out of useEffect and update the dependencies

* chore: centralize hasIncompleteSteps and run validate only if steps have service and spans

* fix: handle all empty fields state + overall improvements

* fix: handle long where query tags

* feat: build the funnel result graph component

* feat: build the funnel result graph component

* feat: handle loading, error, empty states in funnel graph

* fix: don't display change percentage if % is 0

* refactor: overall improvements

* feat: get funnel steps graph data from API + move logic to custom hook

* fix: improve empty and error states

* fix: handle funnel graph legends width using css

* fix: redirect to trace funnels list page on clicking delete from funnel details

* fix: update the query cache while updating steps

* fix: implement debounced search for funnel list search

* fix: refetch steps graph data query on clicking run funnel / sync button

* fix: improve the step footer spacing

* chore: add gap between divider to inter-step-config

* fix: handle loading state while fetching

* feat: add span to funnel flow (from trace details page) (#7477)

* chore: display add to funnel icon on hovering any span in trace details page

* chore: add className to funnel item actions popover

* feat: add funnels tab to trace details v2 tab bar

* feat: add span to funnel flow

* chore: hide actions popover button from funnel item in span -> funnel flows

* chore: improve the funnel details UI in add span to funnel modal

* fix: display empty state + don't redirect to funnels list on delete success + overall improvements

* chore: add null check

* fix: display add to funnel button based on feature flag

* fix: display funnels tab in trace details based on feature flag

* fix: remove maxTagCount

* feat: change ms to ns

* chore: address review comments

* chore: remove feature flag and display trace funnels only in dev envirnoment

* fix: handle restoring steps if updating funnel steps fail

* refactor: update the get and delete funnel endpoints to adjust to the BE changes (#7697)

* refactor: address review comments

* fix: handle nested funnel response structure to fix missing funnel_id… (#7740)

* fix: handle nested funnel response structure to fix missing funnel_id in updates

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* chore: remove console.og

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* chore: revert explicitly passing funnelId to updateFunnelSteps

---------

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
Co-authored-by: ahmadshaheer <ashaheerki@gmail.com>

* chore: fix api endpoint

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* refactor: incorporate the recent funnel details API changes (#7760)

* chore: trace funnels feedback changes (#7772)

* chore: change the copy from x traces to valid traces found / not found

* chore: add open funnel button in add span to funnel modal

* feat: display buttons for adding step details and funnel description + copy to clipboard

* feat: highlight funnel graph column based on selected (total / error span) from the legend items

* chore: trace funnel changes (#7780)

* refactor: handle funnels list search on frontend

* refactor: use funnel steps update API for adding / updating step title and description

* feat: allow selecting user's typed option in trace funnel service and span name dropdowns

* chore: properly render the -> between steps in funnel results

* fix: sync funnel step name with add details modal text fields

---------

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
Co-authored-by: Yunus M <myounis.ar@live.com>
Co-authored-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
2025-05-12 10:16:26 +05:30
Vikrant Gupta
1d379931b2 chore(integration-test): remove the outdated-setup (#7887)
* chore(integration-test): remove the outdated-setup

* chore(integration-test): remove the outdated-setup
2025-05-11 17:10:55 +05:30
dependabot[bot]
815a6d13c5 chore(deps): bump @babel/helpers from 7.21.0 to 7.26.10 in /frontend (#7272)
Bumps [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) from 7.21.0 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-helpers)

---
updated-dependencies:
- dependency-name: "@babel/helpers"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-11 16:38:45 +05:30
Vibhu Pandey
59af9d1c2f chore(go): upgrade to 1.23 (#7885) 2025-05-11 14:09:24 +05:30
dependabot[bot]
19d24da147 chore(deps): bump @babel/runtime from 7.21.0 to 7.26.10 in /frontend (#7289)
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.21.0 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 23:28:01 +05:30
Vibhu Pandey
cd1c9ddf11 fix(dashboards): fix lock/unlock functionality (#7880)
Co-authored-by: Vikrant Gupta <vikrant@signoz.io>
2025-05-09 16:44:57 +00:00
Sahil Khan
7ff3286c9c fix: added prismjs 1.30.0 in resolutions (#7872) 2025-05-09 21:47:22 +05:30
Amlan Kumar Nandy
27830742f9 fix: resolve typescript issues in metrics explorer (#7878) 2025-05-09 18:55:43 +05:30
Vikrant Gupta
39f07e7477 chore(error): update the channels module to use the new api errors (#7856)
* chore(error): integrate new errors for channels create and test

* chore(error): update all the channel APIs

* chore(error): update the edit org http issue

* chore(error): fix create channel test

* chore(error): fix create channel test

* chore(error): fix create channel test

* chore(error): fix create channel test

* chore(error): remove console logs
2025-05-09 07:56:47 +00:00
Amlan Kumar Nandy
0ab50da7b0 chore: change graph list layout to grid in explorer view (#7852) 2025-05-09 05:38:51 +00:00
Amlan Kumar Nandy
c03541cd6c chore: improve error handling and loading states in summary view of metrics explorer (#7862) 2025-05-09 04:41:41 +00:00
Amlan Kumar Nandy
727a039eb9 fix: fix 'open in explorer' functionality in metrics explorer (#7873) 2025-05-09 11:34:32 +07:00
253 changed files with 3485 additions and 4572 deletions

View File

@@ -1,17 +1,74 @@
### Summary
## 📄 Summary
<!-- ✍️ A clear and concise description...-->
<!-- Describe the purpose of the PR in a few sentences. What does it fix/add/update? -->
#### Related Issues / PR's
---
<!-- ✍️ Add the issues being resolved here and related PR's where applicable -->
## ✅ Changes
#### Screenshots
- [ ] Feature: Brief description
- [ ] Bug fix: Brief description
NA
---
<!-- ✍️ Add screenshots of before and after changes where applicable-->
## 🏷️ Required: Add Relevant Labels
#### Affected Areas and Manually Tested Areas
> ⚠️ **Manually add appropriate labels in the PR sidebar**
Please select one or more labels (as applicable):
<!-- ✍️ Add details of blast radius and dev testing areas where applicable-->
ex:
- `frontend`
- `backend`
- `devops`
- `bug`
- `enhancement`
- `ui`
- `test`
---
## 👥 Reviewers
> Tag the relevant teams for review:
- [ ] @SigNoz/frontend
- [ ] @SigNoz/backend
- [ ] @SigNoz/devops
---
## 🧪 How to Test
<!-- Describe how reviewers can test this PR -->
1. ...
2. ...
3. ...
---
## 🔍 Related Issues
<!-- Reference any related issues (e.g. Fixes #123, Closes #456) -->
Closes #
---
## 📸 Screenshots / Screen Recording (if applicable / mandatory for UI related changes)
<!-- Add screenshots or GIFs to help visualize changes -->
---
## 📋 Checklist
- [ ] Dev Review
- [ ] Test cases added (Unit/ Integration / E2E)
- [ ] Manually tested the changes
---
## 👀 Notes for Reviewers
<!-- Anything reviewers should keep in mind while reviewing -->

View File

@@ -62,6 +62,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
GO_NAME: signoz-community
GO_INPUT_ARTIFACT_CACHE_KEY: community-jsbuild-${{ github.sha }}
GO_INPUT_ARTIFACT_PATH: frontend/build

View File

@@ -94,6 +94,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
GO_INPUT_ARTIFACT_CACHE_KEY: enterprise-jsbuild-${{ github.sha }}
GO_INPUT_ARTIFACT_PATH: frontend/build
GO_BUILD_CONTEXT: ./ee/query-service

View File

@@ -91,6 +91,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
GO_INPUT_ARTIFACT_CACHE_KEY: staging-jsbuild-${{ github.sha }}
GO_INPUT_ARTIFACT_PATH: frontend/build
GO_BUILD_CONTEXT: ./ee/query-service

View File

@@ -18,6 +18,7 @@ jobs:
with:
PRIMUS_REF: main
GO_TEST_CONTEXT: ./...
GO_VERSION: 1.23
fmt:
if: |
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
@@ -26,6 +27,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
lint:
if: |
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
@@ -34,6 +36,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
build:
if: |
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
@@ -45,7 +48,7 @@ jobs:
- name: go-install
uses: actions/setup-go@v5
with:
go-version: "1.22"
go-version: "1.23"
- name: qemu-install
uses: docker/setup-qemu-action@v3
- name: aarch64-install

View File

@@ -58,7 +58,7 @@ jobs:
- name: setup-go
uses: actions/setup-go@v5
with:
go-version: "1.22"
go-version: "1.23"
- name: cross-compilation-tools
if: matrix.os == 'ubuntu-latest'
run: |
@@ -122,7 +122,7 @@ jobs:
- name: setup-go
uses: actions/setup-go@v5
with:
go-version: "1.22"
go-version: "1.23"
# copy the caches from build
- name: get-sha

View File

@@ -73,7 +73,7 @@ jobs:
- name: setup-go
uses: actions/setup-go@v5
with:
go-version: "1.22"
go-version: "1.23"
- name: cross-compilation-tools
if: matrix.os == 'ubuntu-latest'
run: |
@@ -136,7 +136,7 @@ jobs:
- name: setup-go
uses: actions/setup-go@v5
with:
go-version: "1.22"
go-version: "1.23"
# copy the caches from build
- name: get-sha

2
.gitignore vendored
View File

@@ -60,9 +60,7 @@ ee/query-service/db
e2e/node_modules/
e2e/test-results/
e2e/playwright-report/
e2e/blob-report/
e2e/playwright/.cache/
e2e/.auth
# go

View File

@@ -164,3 +164,9 @@ alertmanager:
maintenance_interval: 15m
# Retention of the notification logs.
retention: 120h
##################### Analytics #####################
analytics:
# Whether to enable analytics.
enabled: false

View File

@@ -1,4 +1,4 @@
FROM golang:1.22-bullseye
FROM golang:1.23-bullseye
ARG OS="linux"
ARG TARGETARCH

View File

@@ -41,8 +41,8 @@ func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request
return
}
dashboard, err := dashboards.GetDashboard(r.Context(), claims.OrgID, uuid)
if err != nil {
dashboard, apiErr := dashboards.GetDashboard(r.Context(), claims.OrgID, uuid)
if apiErr != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to get dashboard"))
return
}
@@ -53,8 +53,8 @@ func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request
}
// Lock/Unlock the dashboard
err = dashboards.LockUnlockDashboard(r.Context(), claims.OrgID, uuid, lock)
if err != nil {
apiErr = dashboards.LockUnlockDashboard(r.Context(), claims.OrgID, uuid, lock)
if apiErr != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to lock/unlock dashboard"))
return
}

View File

@@ -322,6 +322,7 @@ 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{"*"},

View File

@@ -62,6 +62,13 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.TraceFunnels,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var EnterprisePlan = basemodel.FeatureSet{
@@ -114,4 +121,11 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.TraceFunnels,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}

View File

@@ -1,6 +1,10 @@
# Ignore artifacts:
build
coverage
public/
# Ignore all MD files:
**/*.md
**/*.md
# Ignore all JSON files:
**/*.json

View File

@@ -3,5 +3,4 @@ BUNDLE_ANALYSER="true"
FRONTEND_API_ENDPOINT="http://localhost:8080/"
INTERCOM_APP_ID="intercom-app-id"
PLAYWRIGHT_TEST_BASE_URL="http://localhost:8080"
CI="1"

View File

@@ -30,11 +30,6 @@ const config: Config.InitialOptions = {
testPathIgnorePatterns: ['/node_modules/', '/public/'],
moduleDirectories: ['node_modules', 'src'],
testEnvironment: 'jest-environment-jsdom',
testEnvironmentOptions: {
'jest-playwright': {
browsers: ['chromium', 'firefox', 'webkit'],
},
},
coverageThreshold: {
global: {
statements: 80,

View File

@@ -15,10 +15,6 @@
"jest:coverage": "jest --coverage",
"jest:watch": "jest --watch",
"postinstall": "yarn i18n:generate-hash && (is-ci || yarn husky:configure)",
"playwright": "NODE_ENV=testing playwright test --config=./playwright.config.ts",
"playwright:local:debug": "PWDEBUG=console yarn playwright --headed --browser=chromium",
"playwright:codegen:local": "playwright codegen http://localhost:3301",
"playwright:codegen:local:auth": "yarn playwright:codegen:local --load-storage=tests/auth.json",
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
"commitlint": "commitlint --edit $1",
"test": "jest",
@@ -164,7 +160,6 @@
"@commitlint/config-conventional": "^16.2.4",
"@faker-js/faker": "9.3.0",
"@jest/globals": "^27.5.1",
"@playwright/test": "^1.22.0",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.4.0",
"@testing-library/user-event": "14.4.3",
@@ -258,6 +253,7 @@
"http-proxy-middleware": "3.0.3",
"cross-spawn": "7.0.5",
"cookie": "^0.7.1",
"serialize-javascript": "6.0.2"
"serialize-javascript": "6.0.2",
"prismjs": "1.30.0"
}
}

View File

@@ -1,23 +0,0 @@
import { PlaywrightTestConfig } from '@playwright/test';
import dotenv from 'dotenv';
dotenv.config();
const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
preserveOutput: 'always',
name: 'Signoz',
testDir: './tests',
use: {
trace: 'retain-on-failure',
baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:3301',
},
updateSnapshots: 'all',
fullyParallel: !!process.env.CI,
quiet: false,
testMatch: ['**/*.spec.ts'],
reporter: process.env.CI ? 'github' : 'list',
};
export default config;

View File

@@ -9,7 +9,7 @@ done
# create temporary tsconfig which includes only passed files
str="{
\"extends\": \"./tsconfig.json\",
\"include\": [ \"src/typings/**/*.ts\",\"src/**/*.d.ts\", \"./babel.config.js\", \"./jest.config.ts\", \"./.eslintrc.js\",\"./__mocks__\",\"./conf/default.conf\",\"./public\",\"./tests\",\"./playwright.config.ts\",\"./commitlint.config.ts\",\"./webpack.config.js\",\"./webpack.config.prod.js\",\"./jest.setup.ts\",\"./**/*.d.ts\",$files]
\"include\": [ \"src/typings/**/*.ts\",\"src/**/*.d.ts\", \"./babel.config.js\", \"./jest.config.ts\", \"./.eslintrc.js\",\"./__mocks__\",\"./public\",\"./tests\",\"./commitlint.config.ts\",\"./webpack.config.js\",\"./webpack.config.prod.js\",\"./jest.setup.ts\",\"./**/*.d.ts\",$files]
}"
echo $str > tsconfig.tmp

View File

@@ -1,6 +1,6 @@
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import getOrgUser from 'api/user/getOrgUser';
import getOrgUser from 'api/v1/user/getOrgUser';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';

View File

@@ -1,9 +1,9 @@
import { AxiosError } from 'axios';
import { ErrorV2 } from 'types/api';
import { ErrorV2Resp } from 'types/api';
import APIError from 'types/api/error';
// reference - https://axios-http.com/docs/handling_errors
export function ErrorResponseHandlerV2(error: AxiosError<ErrorV2>): never {
export function ErrorResponseHandlerV2(error: AxiosError<ErrorV2Resp>): never {
const { response, request } = error;
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
@@ -11,10 +11,10 @@ export function ErrorResponseHandlerV2(error: AxiosError<ErrorV2>): never {
throw new APIError({
httpStatusCode: response.status || 500,
error: {
code: response.data.code,
message: response.data.message,
url: response.data.url,
errors: response.data.errors,
code: response.data.error.code,
message: response.data.error.message,
url: response.data.error.url,
errors: response.data.error.errors,
},
});
}

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createEmail';
const create = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post('/channels', {
const response = await axios.post<PayloadProps>('/channels', {
name: props.name,
email_configs: [
{
@@ -21,13 +21,12 @@ const create = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createMsTeams';
const create = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post('/channels', {
const response = await axios.post<PayloadProps>('/channels', {
name: props.name,
msteamsv2_configs: [
{
@@ -21,13 +21,12 @@ const create = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createOpsgenie';
const create = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post('/channels', {
const response = await axios.post<PayloadProps>('/channels', {
name: props.name,
opsgenie_configs: [
{
@@ -24,13 +24,12 @@ const create = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createPager';
const create = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post('/channels', {
const response = await axios.post<PayloadProps>('/channels', {
name: props.name,
pagerduty_configs: [
{
@@ -29,13 +29,12 @@ const create = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createSlack';
const create = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post('/channels', {
const response = await axios.post<PayloadProps>('/channels', {
name: props.name,
slack_configs: [
{
@@ -22,13 +22,12 @@ const create = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,12 +1,12 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createWebhook';
const create = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
let httpConfig = {};
const username = props.username ? props.username.trim() : '';
@@ -28,7 +28,7 @@ const create = async (
};
}
const response = await axios.post('/channels', {
const response = await axios.post<PayloadProps>('/channels', {
name: props.name,
webhook_configs: [
{
@@ -40,13 +40,12 @@ const create = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,23 +1,22 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/delete';
const deleteChannel = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.delete(`/channels/${props.id}`);
const response = await axios.delete<PayloadProps>(`/channels/${props.id}`);
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/editEmail';
const editEmail = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.put(`/channels/${props.id}`, {
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
name: props.name,
email_configs: [
{
@@ -21,13 +21,12 @@ const editEmail = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/editMsTeams';
const editMsTeams = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.put(`/channels/${props.id}`, {
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
name: props.name,
msteamsv2_configs: [
{
@@ -21,13 +21,12 @@ const editMsTeams = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorResponse, ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/editOpsgenie';
const editOpsgenie = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.put(`/channels/${props.id}`, {
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
name: props.name,
opsgenie_configs: [
{
@@ -25,13 +25,12 @@ const editOpsgenie = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
return ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/editPager';
const editPager = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.put(`/channels/${props.id}`, {
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
name: props.name,
pagerduty_configs: [
{
@@ -29,13 +29,12 @@ const editPager = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/editSlack';
const editSlack = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.put(`/channels/${props.id}`, {
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
name: props.name,
slack_configs: [
{
@@ -22,13 +22,12 @@ const editSlack = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,12 +1,12 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/editWebhook';
const editWebhook = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
let httpConfig = {};
const username = props.username ? props.username.trim() : '';
@@ -28,7 +28,7 @@ const editWebhook = async (
};
}
const response = await axios.put(`/channels/${props.id}`, {
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
name: props.name,
webhook_configs: [
{
@@ -40,13 +40,12 @@ const editWebhook = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,23 +1,21 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/get';
import { Channels } from 'types/api/channels/getAll';
const get = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const get = async (props: Props): Promise<SuccessResponseV2<Channels>> => {
try {
const response = await axios.get(`/channels/${props.id}`);
const response = await axios.get<PayloadProps>(`/channels/${props.id}`);
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,23 +1,20 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps } from 'types/api/channels/getAll';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { Channels, PayloadProps } from 'types/api/channels/getAll';
const getAll = async (): Promise<
SuccessResponse<PayloadProps> | ErrorResponse
> => {
const getAll = async (): Promise<SuccessResponseV2<Channels[]>> => {
try {
const response = await axios.get('/channels');
const response = await axios.get<PayloadProps>('/channels');
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createEmail';
const testEmail = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post('/testChannel', {
const response = await axios.post<PayloadProps>('/testChannel', {
name: props.name,
email_configs: [
{
@@ -21,13 +21,12 @@ const testEmail = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createMsTeams';
const testMsTeams = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post('/testChannel', {
const response = await axios.post<PayloadProps>('/testChannel', {
name: props.name,
msteamsv2_configs: [
{
@@ -21,13 +21,12 @@ const testMsTeams = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createOpsgenie';
const testOpsgenie = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post('/testChannel', {
const response = await axios.post<PayloadProps>('/testChannel', {
name: props.name,
opsgenie_configs: [
{
@@ -24,13 +24,12 @@ const testOpsgenie = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createPager';
const testPager = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post('/testChannel', {
const response = await axios.post<PayloadProps>('/testChannel', {
name: props.name,
pagerduty_configs: [
{
@@ -29,13 +29,12 @@ const testPager = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createSlack';
const testSlack = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post('/testChannel', {
const response = await axios.post<PayloadProps>('/testChannel', {
name: props.name,
slack_configs: [
{
@@ -22,13 +22,12 @@ const testSlack = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -1,12 +1,12 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createWebhook';
const testWebhook = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
let httpConfig = {};
const username = props.username ? props.username.trim() : '';
@@ -28,7 +28,7 @@ const testWebhook = async (
};
}
const response = await axios.post('/testChannel', {
const response = await axios.post<PayloadProps>('/testChannel', {
name: props.name,
webhook_configs: [
{
@@ -40,13 +40,12 @@ const testWebhook = async (
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
throw error;
}
};

View File

@@ -2,7 +2,7 @@
/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/no-explicit-any */
import getLocalStorageApi from 'api/browser/localstorage/get';
import loginApi from 'api/user/login';
import loginApi from 'api/v1/user/login';
import afterLogin from 'AppRoutes/utils';
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { ENVIRONMENT } from 'constants/env';

View File

@@ -222,6 +222,13 @@ export const getFunnelOverview = async (
};
};
export interface SlowTracesPayload {
start_time: number;
end_time: number;
step_a_order: number;
step_b_order: number;
}
export interface SlowTraceData {
status: string;
data: Array<{
@@ -236,7 +243,7 @@ export interface SlowTraceData {
export const getFunnelSlowTraces = async (
funnelId: string,
payload: FunnelOverviewPayload,
payload: SlowTracesPayload,
signal?: AbortSignal,
): Promise<SuccessResponse<SlowTraceData> | ErrorResponse> => {
const response = await axios.post(
@@ -254,6 +261,12 @@ export const getFunnelSlowTraces = async (
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;
@@ -269,10 +282,10 @@ export interface ErrorTraceData {
export const getFunnelErrorTraces = async (
funnelId: string,
payload: FunnelOverviewPayload,
payload: ErrorTracesPayload,
signal?: AbortSignal,
): Promise<SuccessResponse<ErrorTraceData> | ErrorResponse> => {
const response = await axios.post(
const response: AxiosResponse = await axios.post(
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/error-traces`,
payload,
{
@@ -324,37 +337,3 @@ export const getFunnelSteps = async (
payload: response.data,
};
};
export interface FunnelStepsOverviewPayload {
start_time: number;
end_time: number;
step_start?: number;
step_end?: number;
}
export interface FunnelStepsOverviewResponse {
status: string;
data: Array<{
timestamp: string;
data: Record<string, unknown>;
}>;
}
export const getFunnelStepsOverview = async (
funnelId: string,
payload: FunnelStepsOverviewPayload,
signal?: AbortSignal,
): Promise<SuccessResponse<FunnelStepsOverviewResponse> | ErrorResponse> => {
const response = await axios.post(
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/steps/overview`,
payload,
{ signal },
);
return {
statusCode: 200,
error: null,
message: '',
payload: response.data,
};
};

View File

@@ -1,7 +1,7 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2, SuccessResponseV2 } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/login';
const login = async (
@@ -17,7 +17,7 @@ const login = async (
data: response.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2>);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
// this line is never reached but ts isn't detecting the never type properly for the ErrorResponseHandlerV2
throw error;
}

View File

@@ -8,7 +8,7 @@ const getPreference = async (): Promise<
SuccessResponse<PayloadProps> | ErrorResponse
> => {
try {
const response = await axios.get(`/userPreferences`);
const response = await axios.get(`/user/preferences`);
return {
statusCode: 200,

View File

@@ -1,11 +1,11 @@
.dropdown-button {
color: #fff;
color: #fff;
}
.dropdown-button--dark {
color: #000;
color: #000;
}
.dropdown-icon {
font-size: 1.2rem;
}
font-size: 1.2rem;
}

View File

@@ -4,19 +4,14 @@ export const getYAxisFormattedValue = (
value: string,
format: string,
): string => {
let decimalPrecision: number | undefined;
const parsedValue = getValueFormat(format)(
parseFloat(value),
undefined,
undefined,
undefined,
);
try {
const parsedValue = getValueFormat(format)(
parseFloat(value),
undefined,
undefined,
undefined,
);
if (!parsedValue?.text) {
return `${parseFloat(value)}`;
}
let decimalPrecision: number | undefined;
const decimalSplitted = parsedValue.text.split('.');
if (decimalSplitted.length === 1) {
decimalPrecision = 0;
@@ -46,9 +41,9 @@ export const getYAxisFormattedValue = (
),
);
} catch (error) {
console.error('Error in getYAxisFormattedValue:', error);
return `${parseFloat(value)}`;
console.error(error);
}
return `${parseFloat(value)}`;
};
export const getToolTipValue = (value: string, format?: string): string => {

View File

@@ -1,14 +1,15 @@
.learn-more {
display: flex;
align-items: center;
gap: 4px;
padding: 0;
font-family: Inter;
font-size: 14px;
font-weight: 500;
line-height: 18px; /* 128.571% */
letter-spacing: -0.07px;
&,&:hover {
color: var(--bg-robin-400) !important;
}
display: flex;
align-items: center;
gap: 4px;
padding: 0;
font-family: Inter;
font-size: 14px;
font-weight: 500;
line-height: 18px; /* 128.571% */
letter-spacing: -0.07px;
&,
&:hover {
color: var(--bg-robin-400) !important;
}
}

View File

@@ -110,6 +110,7 @@ exports[`Quick Filters renders correctly with default props 1`] = `
class="left-action"
>
<svg
aria-hidden="true"
class="lucide lucide-chevron-down"
cursor="pointer"
fill="none"
@@ -347,6 +348,7 @@ exports[`Quick Filters renders correctly with default props 1`] = `
class="left-action"
>
<svg
aria-hidden="true"
class="lucide lucide-chevron-right"
cursor="pointer"
fill="none"

View File

@@ -1,10 +1,9 @@
.label-column {
display: flex;
flex-wrap: wrap;
display: flex;
flex-wrap: wrap;
.label-column--tag {
white-space: normal;
margin: 0.2rem 0.2rem;
}
.label-column--tag {
white-space: normal;
margin: 0.2rem 0.2rem;
}
}

View File

@@ -1,3 +1,3 @@
.overlay--text-wrap {
white-space: pre-wrap;
}
white-space: pre-wrap;
}

View File

@@ -8,4 +8,6 @@ export enum FeatureKeys {
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
ONBOARDING_V3 = 'ONBOARDING_V3',
THIRD_PARTY_API = 'THIRD_PARTY_API',
TRACE_FUNNELS = 'TRACE_FUNNELS',
}

View File

@@ -75,7 +75,6 @@ export const REACT_QUERY_KEY = {
VALIDATE_FUNNEL_STEPS: 'VALIDATE_FUNNEL_STEPS',
UPDATE_FUNNEL_STEP_DETAILS: 'UPDATE_FUNNEL_STEP_DETAILS',
GET_FUNNEL_OVERVIEW: 'GET_FUNNEL_OVERVIEW',
GET_FUNNEL_STEPS_OVERVIEW: 'GET_FUNNEL_STEPS_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',

View File

@@ -10,7 +10,7 @@ import { useAppContext } from 'providers/App/App';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router-dom';
import { Channels, PayloadProps } from 'types/api/channels/getAll';
import { Channels } from 'types/api/channels/getAll';
import Delete from './Delete';
@@ -68,7 +68,7 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
}
interface AlertChannelsProps {
allChannels: PayloadProps;
allChannels: Channels[];
}
export default AlertChannels;

View File

@@ -4,6 +4,7 @@ import deleteChannel from 'api/channels/delete';
import { Dispatch, SetStateAction, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
function Delete({ notifications, setChannels, id }: DeleteProps): JSX.Element {
const { t } = useTranslation(['channels']);
@@ -12,30 +13,20 @@ function Delete({ notifications, setChannels, id }: DeleteProps): JSX.Element {
const onClickHandler = async (): Promise<void> => {
try {
setLoading(true);
const response = await deleteChannel({
await deleteChannel({
id,
});
if (response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_delete_success'),
});
setChannels((preChannels) => preChannels.filter((e) => e.id !== id));
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_delete_unexp_error'),
});
}
notifications.success({
message: 'Success',
description: t('channel_delete_success'),
});
setChannels((preChannels) => preChannels.filter((e) => e.id !== id));
setLoading(false);
} catch (error) {
notifications.error({
message: 'Error',
description:
error instanceof Error
? error.toString()
: t('channel_delete_unexp_error'),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
setLoading(false);
}

View File

@@ -1,15 +1,7 @@
import ROUTES from 'constants/routes';
import AlertChannels from 'container/AllAlertChannels';
import { allAlertChannels } from 'mocks-server/__mockdata__/alerts';
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
jest.mock('hooks/useFetch', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
payload: allAlertChannels,
})),
}));
const successNotification = jest.fn();
jest.mock('hooks/useNotifications', () => ({
__esModule: true,
@@ -29,8 +21,11 @@ jest.mock('react-router-dom', () => ({
}));
describe('Alert Channels Settings List page', () => {
beforeEach(() => {
beforeEach(async () => {
render(<AlertChannels />);
await waitFor(() =>
expect(screen.getByText('sending_channels_note')).toBeInTheDocument(),
);
});
afterEach(() => {
jest.restoreAllMocks();

View File

@@ -1,15 +1,8 @@
/* eslint-disable sonarjs/no-identical-functions */
import ROUTES from 'constants/routes';
import AlertChannels from 'container/AllAlertChannels';
import { allAlertChannels } from 'mocks-server/__mockdata__/alerts';
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
jest.mock('hooks/useFetch', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
payload: allAlertChannels,
})),
}));
const successNotification = jest.fn();
jest.mock('hooks/useNotifications', () => ({
__esModule: true,
@@ -34,27 +27,31 @@ jest.mock('react-router-dom', () => ({
}));
describe('Alert Channels Settings List page (Normal User)', () => {
beforeEach(() => {
beforeEach(async () => {
render(<AlertChannels />);
await waitFor(() =>
expect(screen.getByText('sending_channels_note')).toBeInTheDocument(),
);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Should display the Alert Channels page properly', () => {
it('Should check if "The alerts will be sent to all the configured channels." is visible ', () => {
expect(screen.getByText('sending_channels_note')).toBeInTheDocument();
it('Should check if "The alerts will be sent to all the configured channels." is visible ', async () => {
await waitFor(() =>
expect(screen.getByText('sending_channels_note')).toBeInTheDocument(),
);
});
it('Should check if "New Alert Channel" Button is visble and disabled', () => {
it('Should check if "New Alert Channel" Button is visble and disabled', async () => {
const newAlertButton = screen.getByRole('button', {
name: 'plus button_new_channel',
});
expect(newAlertButton).toBeInTheDocument();
await waitFor(() => expect(newAlertButton).toBeInTheDocument());
expect(newAlertButton).toBeDisabled();
});
it('Should check if the help icon is visible and displays "tooltip_notification_channels ', async () => {
const helpIcon = screen.getByLabelText('question-circle');
fireEvent.mouseOver(helpIcon);
await waitFor(() => {
@@ -64,13 +61,13 @@ describe('Alert Channels Settings List page (Normal User)', () => {
});
});
describe('Should check if the channels table is properly displayed', () => {
it('Should check if the table columns are properly displayed', () => {
it('Should check if the table columns are properly displayed', async () => {
expect(screen.getByText('column_channel_name')).toBeInTheDocument();
expect(screen.getByText('column_channel_type')).toBeInTheDocument();
expect(screen.queryByText('column_channel_action')).not.toBeInTheDocument();
});
it('Should check if the data in the table is displayed properly', () => {
it('Should check if the data in the table is displayed properly', async () => {
expect(screen.getByText('Dummy-Channel')).toBeInTheDocument();
expect(screen.getAllByText('slack')[0]).toBeInTheDocument();
expect(screen.queryByText('column_channel_edit')).not.toBeInTheDocument();

View File

@@ -119,12 +119,7 @@ describe('Create Alert Channel', () => {
fireEvent.click(saveButton);
await waitFor(() =>
expect(errorNotification).toHaveBeenCalledWith({
description: 'Something went wrong',
message: 'Error',
}),
);
await waitFor(() => expect(errorNotification).toHaveBeenCalled());
});
it('Should check if clicking on Test button shows "An alert has been sent to this channel" success message if testing passes', async () => {
server.use(
@@ -158,12 +153,7 @@ describe('Create Alert Channel', () => {
fireEvent.click(testButton);
await waitFor(() =>
expect(errorNotification).toHaveBeenCalledWith({
message: 'Error',
description: 'channel_test_failed',
}),
);
await waitFor(() => expect(errorNotification).toHaveBeenCalled());
});
});
describe('New Alert Channel Cascading Fields Based on Channel Type', () => {

View File

@@ -6,12 +6,15 @@ import Spinner from 'components/Spinner';
import TextToolTip from 'components/TextToolTip';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
import history from 'lib/history';
import { isUndefined } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
import { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { SuccessResponseV2 } from 'types/api';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import AlertChannelsComponent from './AlertChannels';
import { Button, ButtonContainer, RightActionContainer } from './styles';
@@ -29,21 +32,26 @@ function AlertChannels(): JSX.Element {
history.push(ROUTES.CHANNELS_NEW);
}, []);
const { loading, payload, error, errorMessage } = useFetch(getAll);
const { isLoading, data, error } = useQuery<
SuccessResponseV2<Channels[]>,
APIError
>(['getChannels'], {
queryFn: () => getAll(),
});
useEffect(() => {
if (!isUndefined(payload)) {
if (!isUndefined(data?.data)) {
logEvent('Alert Channel: Channel list page visited', {
number: payload?.length,
number: data?.data?.length,
});
}
}, [payload]);
}, [data?.data]);
if (error) {
return <Typography>{errorMessage}</Typography>;
return <Typography>{error.getErrorMessage()}</Typography>;
}
if (loading || payload === undefined) {
if (isLoading || isUndefined(data?.data)) {
return <Spinner tip={t('loading_channels_message')} height="90vh" />;
}
@@ -78,7 +86,7 @@ function AlertChannels(): JSX.Element {
</RightActionContainer>
</ButtonContainer>
<AlertChannelsComponent allChannels={payload} />
<AlertChannelsComponent allChannels={data?.data || []} />
</>
);
}

View File

@@ -9,8 +9,8 @@ import manageCreditCardApi from 'api/billing/manage';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import getUserLatestVersion from 'api/user/getLatestVersion';
import getUserVersion from 'api/user/getVersion';
import getUserLatestVersion from 'api/v1/version/getLatestVersion';
import getUserVersion from 'api/v1/version/getVersion';
import cx from 'classnames';
import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';

View File

@@ -35,13 +35,13 @@
font-weight: 500;
line-height: 16px;
padding: 8px 17px;
&.primary {
background: var(--bg-robin-500);
border: none;
color: var(--bg-vanilla-100);
}
&.secondary {
display: flex;
align-items: center;

View File

@@ -18,6 +18,7 @@ import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import APIError from 'types/api/error';
import {
ChannelType,
@@ -136,28 +137,17 @@ function CreateAlertChannels({
setSavingState(true);
try {
const response = await createSlackApi(prepareSlackRequest());
if (response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
await createSlackApi(prepareSlackRequest());
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
message: (error as APIError).error.error.code,
description: (error as APIError).error.error.message,
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
@@ -204,27 +194,17 @@ function CreateAlertChannels({
setSavingState(true);
try {
const request = prepareWebhookRequest();
const response = await createWebhookApi(request);
if (response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
await createWebhookApi(request);
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
@@ -264,34 +244,19 @@ function CreateAlertChannels({
try {
if (request) {
const response = await createPagerApi(request);
if (response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
await createPagerApi(request);
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
@@ -313,30 +278,18 @@ function CreateAlertChannels({
const onOpsgenieHandler = useCallback(async () => {
setSavingState(true);
try {
const response = await createOpsgenie(prepareOpsgenieRequest());
if (response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
await createOpsgenie(prepareOpsgenieRequest());
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
@@ -359,27 +312,17 @@ function CreateAlertChannels({
setSavingState(true);
try {
const request = prepareEmailRequest();
const response = await createEmail(request);
if (response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
await createEmail(request);
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
@@ -402,28 +345,17 @@ function CreateAlertChannels({
setSavingState(true);
try {
const response = await createMsTeamsApi(prepareMsTeamsRequest());
if (response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
await createMsTeamsApi(prepareMsTeamsRequest());
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
@@ -481,31 +413,30 @@ function CreateAlertChannels({
setTestingState(true);
try {
let request;
let response;
switch (channelType) {
case ChannelType.Webhook:
request = prepareWebhookRequest();
response = await testWebhookApi(request);
await testWebhookApi(request);
break;
case ChannelType.Slack:
request = prepareSlackRequest();
response = await testSlackApi(request);
await testSlackApi(request);
break;
case ChannelType.Pagerduty:
request = preparePagerRequest();
if (request) response = await testPagerApi(request);
if (request) await testPagerApi(request);
break;
case ChannelType.MsTeams:
request = prepareMsTeamsRequest();
response = await testMsTeamsApi(request);
await testMsTeamsApi(request);
break;
case ChannelType.Opsgenie:
request = prepareOpsgenieRequest();
response = await testOpsGenie(request);
await testOpsGenie(request);
break;
case ChannelType.Email:
request = prepareEmailRequest();
response = await testEmail(request);
await testEmail(request);
break;
default:
notifications.error({
@@ -516,30 +447,28 @@ function CreateAlertChannels({
return;
}
if (response && response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_test_done'),
});
} else {
notifications.error({
message: 'Error',
description: t('channel_test_failed'),
});
}
notifications.success({
message: 'Success',
description: t('channel_test_done'),
});
logEvent('Alert Channel: Test notification', {
type: channelType,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'true',
status:
response && response.statusCode === 200 ? 'Test success' : 'Test failed',
status: 'Test success',
});
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_test_unexpected'),
message: (error as APIError).error.error.code,
description: (error as APIError).error.error.message,
});
logEvent('Alert Channel: Test notification', {
type: channelType,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'true',
status: 'Test failed',
});
}

View File

@@ -1,4 +1,4 @@
.download-button {
display: flex;
display: flex;
align-items: center;
}
}

View File

@@ -29,6 +29,7 @@ import history from 'lib/history';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import APIError from 'types/api/error';
function EditAlertChannels({
initialValue,
@@ -93,9 +94,8 @@ function EditAlertChannels({
return { status: 'failed', statusMessage: t('webhook_url_required') };
}
const response = await editSlackApi(prepareSlackRequest());
if (response.statusCode === 200) {
try {
await editSlackApi(prepareSlackRequest());
notifications.success({
message: 'Success',
description: t('channel_edit_done'),
@@ -103,16 +103,19 @@ function EditAlertChannels({
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} catch (error) {
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return {
status: 'failed',
statusMessage:
(error as APIError).getErrorMessage() || t('channel_edit_failed'),
};
} finally {
setSavingState(false);
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareSlackRequest, t, notifications, selectedConfig]);
const prepareWebhookRequest = useCallback(() => {
@@ -150,9 +153,8 @@ function EditAlertChannels({
return { status: 'failed', statusMessage: t('username_no_password') };
}
const response = await editWebhookApi(prepareWebhookRequest());
if (response.statusCode === 200) {
try {
await editWebhookApi(prepareWebhookRequest());
notifications.success({
message: 'Success',
description: t('channel_edit_done'),
@@ -160,14 +162,19 @@ function EditAlertChannels({
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} catch (error) {
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return {
status: 'failed',
statusMessage:
(error as APIError).getErrorMessage() || t('channel_edit_failed'),
};
} finally {
setSavingState(false);
}
showError(response.error || t('channel_edit_failed'));
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareWebhookRequest, t, notifications, selectedConfig]);
const prepareEmailRequest = useCallback(
@@ -185,25 +192,28 @@ function EditAlertChannels({
const onEmailEditHandler = useCallback(async () => {
setSavingState(true);
const request = prepareEmailRequest();
const response = await editEmail(request);
if (response.statusCode === 200) {
try {
await editEmail(request);
notifications.success({
message: 'Success',
description: t('channel_edit_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} catch (error) {
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return {
status: 'failed',
statusMessage:
(error as APIError).getErrorMessage() || t('channel_edit_failed'),
};
} finally {
setSavingState(false);
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareEmailRequest, t, notifications]);
const preparePagerRequest = useCallback(
@@ -237,27 +247,28 @@ function EditAlertChannels({
setSavingState(false);
return { status: 'failed', statusMessage: validationError };
}
const response = await editPagerApi(preparePagerRequest());
if (response.statusCode === 200) {
try {
await editPagerApi(preparePagerRequest());
notifications.success({
message: 'Success',
description: t('channel_edit_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} catch (error) {
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return {
status: 'failed',
statusMessage:
(error as APIError).getErrorMessage() || t('channel_edit_failed'),
};
} finally {
setSavingState(false);
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [preparePagerRequest, notifications, selectedConfig, t]);
const prepareOpsgenieRequest = useCallback(
@@ -284,28 +295,27 @@ function EditAlertChannels({
setSavingState(false);
return { status: 'failed', statusMessage: t('api_key_required') };
}
const response = await editOpsgenie(prepareOpsgenieRequest());
if (response.statusCode === 200) {
try {
await editOpsgenie(prepareOpsgenieRequest());
notifications.success({
message: 'Success',
description: t('channel_edit_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} catch (error) {
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return {
status: 'failed',
statusMessage:
(error as APIError).getErrorMessage() || t('channel_edit_failed'),
};
} finally {
setSavingState(false);
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareOpsgenieRequest, t, notifications, selectedConfig]);
const prepareMsTeamsRequest = useCallback(
@@ -332,27 +342,27 @@ function EditAlertChannels({
return { status: 'failed', statusMessage: t('webhook_url_required') };
}
const response = await editMsTeamsApi(prepareMsTeamsRequest());
if (response.statusCode === 200) {
try {
await editMsTeamsApi(prepareMsTeamsRequest());
notifications.success({
message: 'Success',
description: t('channel_edit_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} catch (error) {
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
return {
status: 'failed',
statusMessage:
(error as APIError).getErrorMessage() || t('channel_edit_failed'),
};
} finally {
setSavingState(false);
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareMsTeamsRequest, t, notifications, selectedConfig]);
const onSaveHandler = useCallback(
@@ -396,31 +406,30 @@ function EditAlertChannels({
setTestingState(true);
try {
let request;
let response;
switch (channelType) {
case ChannelType.Webhook:
request = prepareWebhookRequest();
response = await testWebhookApi(request);
await testWebhookApi(request);
break;
case ChannelType.Slack:
request = prepareSlackRequest();
response = await testSlackApi(request);
await testSlackApi(request);
break;
case ChannelType.Pagerduty:
request = preparePagerRequest();
if (request) response = await testPagerApi(request);
if (request) await testPagerApi(request);
break;
case ChannelType.MsTeams:
request = prepareMsTeamsRequest();
if (request) response = await testMsTeamsApi(request);
if (request) await testMsTeamsApi(request);
break;
case ChannelType.Opsgenie:
request = prepareOpsgenieRequest();
if (request) response = await testOpsgenie(request);
if (request) await testOpsgenie(request);
break;
case ChannelType.Email:
request = prepareEmailRequest();
if (request) response = await testEmail(request);
if (request) await testEmail(request);
break;
default:
notifications.error({
@@ -431,29 +440,28 @@ function EditAlertChannels({
return;
}
if (response && response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_test_done'),
});
} else {
notifications.error({
message: 'Error',
description: t('channel_test_failed'),
});
}
notifications.success({
message: 'Success',
description: t('channel_test_done'),
});
logEvent('Alert Channel: Test notification', {
type: channelType,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'false',
status:
response && response.statusCode === 200 ? 'Test success' : 'Test failed',
status: 'Test success',
});
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_test_failed'),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
logEvent('Alert Channel: Test notification', {
type: channelType,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'false',
status: 'Test failed',
});
}
setTestingState(false);

View File

@@ -1,55 +1,54 @@
.explorer-option-droppable-container {
position: fixed;
position: fixed;
bottom: 0;
width: -webkit-fill-available;
height: 24px;
display: flex;
justify-content: center;
border-radius: 10px 10px 0px 0px;
// box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
// backdrop-filter: blur(20px);
width: -webkit-fill-available;
height: 24px;
display: flex;
justify-content: center;
border-radius: 10px 10px 0px 0px;
// box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
// backdrop-filter: blur(20px);
.explorer-actions-btn {
display: flex;
gap: 8px;
margin-right: 8px;
.explorer-actions-btn {
display: flex;
gap: 8px;
margin-right: 8px;
.action-btn {
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px 10px 0px 0px;
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(20px);
height: 24px !important;
border: none;
}
}
.action-btn {
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px 10px 0px 0px;
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(20px);
height: 24px !important;
border: none;
}
}
.explorer-show-btn {
border-radius: 10px 10px 0px 0px;
border: 1px solid var(--bg-slate-400);
background: rgba(22, 24, 29, 0.40);
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(20px);
align-self: center;
padding: 8px 12px;
height: 24px !important;
.explorer-show-btn {
border-radius: 10px 10px 0px 0px;
border: 1px solid var(--bg-slate-400);
background: rgba(22, 24, 29, 0.4);
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(20px);
align-self: center;
padding: 8px 12px;
height: 24px !important;
.menu-bar {
border-radius: 50px;
background: var(--bg-slate-200);
height: 4px;
width: 50px;
}
}
.menu-bar {
border-radius: 50px;
background: var(--bg-slate-200);
height: 4px;
width: 50px;
}
}
}
.lightMode {
.explorer-option-droppable-container {
.explorer-show-btn {
background: var(--bg-vanilla-200);
}
}
}
.explorer-option-droppable-container {
.explorer-show-btn {
background: var(--bg-vanilla-200);
}
}
}

View File

@@ -2,17 +2,20 @@ import './FormAlertRules.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Form, Select, Switch, Tooltip } from 'antd';
import getChannels from 'api/channels/getAll';
import getAll from 'api/channels/getAll';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
import { useAppContext } from 'providers/App/App';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { SuccessResponseV2 } from 'types/api';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef, Labels } from 'types/api/alerts/def';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
import { popupContainer } from 'utils/selectPopupContainer';
@@ -42,7 +45,13 @@ function BasicInfo({
}: BasicInfoProps): JSX.Element {
const { t } = useTranslation('alerts');
const channels = useFetch(getChannels);
const { isLoading, data, error, isError, refetch } = useQuery<
SuccessResponseV2<Channels[]>,
APIError
>(['getChannels'], {
queryFn: () => getAll(),
});
const { user } = useAppContext();
const [addNewChannelPermission] = useComponentPermission(
['add_new_channel'],
@@ -72,7 +81,7 @@ function BasicInfo({
});
};
const noChannels = channels.payload?.length === 0;
const noChannels = data?.data?.length === 0;
const handleCreateNewChannels = useCallback(() => {
logEvent('Alert: Create notification channel button clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
@@ -84,18 +93,18 @@ function BasicInfo({
const hasLoggedEvent = useRef(false);
useEffect(() => {
if (!channels.loading && isNewRule && !hasLoggedEvent.current) {
if (!isLoading && isNewRule && !hasLoggedEvent.current) {
logEvent('Alert: New alert creation page visited', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
numberOfChannels: channels?.payload?.length,
numberOfChannels: data?.data?.length,
});
hasLoggedEvent.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [channels.loading]);
}, [isLoading]);
const refetchChannels = async (): Promise<void> => {
await channels.refetch();
await refetch();
};
return (
@@ -192,7 +201,7 @@ function BasicInfo({
<Switch
checked={shouldBroadCastToAllChannels}
onChange={handleBroadcastToAllChannels}
disabled={noChannels || !!channels.loading}
disabled={noChannels || !!isLoading}
data-testid="alert-broadcast-to-all-channels"
/>
</Tooltip>
@@ -220,7 +229,10 @@ function BasicInfo({
disabled={shouldBroadCastToAllChannels}
currentValue={alertDef.preferredChannels}
handleCreateNewChannels={handleCreateNewChannels}
channels={channels}
channels={data?.data || []}
isLoading={isLoading}
hasError={isError}
error={error as APIError}
onSelectChannels={(preferredChannels): void => {
setAlertDef({
...alertDef,

View File

@@ -1,12 +1,12 @@
import { PlusOutlined } from '@ant-design/icons';
import { Select, Spin } from 'antd';
import useComponentPermission from 'hooks/useComponentPermission';
import { State } from 'hooks/useFetch';
import { useNotifications } from 'hooks/useNotifications';
import { useAppContext } from 'providers/App/App';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { PayloadProps } from 'types/api/channels/getAll';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import { StyledCreateChannelOption, StyledSelect } from './styles';
@@ -15,7 +15,10 @@ export interface ChannelSelectProps {
currentValue?: string[];
onSelectChannels: (s: string[]) => void;
onDropdownOpen: () => void;
channels: State<PayloadProps | undefined>;
isLoading: boolean;
channels: Channels[];
hasError: boolean;
error: APIError;
handleCreateNewChannels: () => void;
}
@@ -25,6 +28,9 @@ function ChannelSelect({
onSelectChannels,
onDropdownOpen,
channels,
isLoading,
hasError,
error,
handleCreateNewChannels,
}: ChannelSelectProps): JSX.Element | null {
// init namespace for translations
@@ -40,10 +46,10 @@ function ChannelSelect({
onSelectChannels(value);
};
if (channels.error && channels.errorMessage !== '') {
if (hasError) {
notifications.error({
message: 'Error',
description: channels.errorMessage,
message: error.getErrorCode(),
description: error.getErrorMessage(),
});
}
@@ -56,7 +62,7 @@ function ChannelSelect({
const renderOptions = (): ReactNode[] => {
const children: ReactNode[] = [];
if (!channels.loading && addNewChannelPermission) {
if (!isLoading && addNewChannelPermission) {
children.push(
<Select.Option key="add-new-channel" value="add-new-channel">
<StyledCreateChannelOption>
@@ -67,15 +73,11 @@ function ChannelSelect({
);
}
if (
channels.loading ||
channels.payload === undefined ||
channels.payload.length === 0
) {
if (isLoading || channels.length === 0) {
return children;
}
channels.payload.forEach((o) => {
channels.forEach((o) => {
children.push(
<Select.Option key={o.id} value={o.name}>
{o.name}
@@ -89,13 +91,13 @@ function ChannelSelect({
return (
<StyledSelect
disabled={disabled}
status={channels.error ? 'error' : ''}
status={hasError ? 'error' : ''}
mode="multiple"
style={{ width: '100%' }}
placeholder={t('placeholder_channel_select')}
data-testid="alert-channel-select"
value={currentValue}
notFoundContent={channels.loading && <Spin size="small" />}
notFoundContent={isLoading && <Spin size="small" />}
onDropdownVisibleChange={(open): void => {
if (open) {
onDropdownOpen();

View File

@@ -1,31 +1,31 @@
.context-log-renderer {
.virtuoso-list {
overflow-y: hidden !important;
.virtuoso-list {
overflow-y: hidden !important;
&::-webkit-scrollbar {
width: 0.3rem;
height: 0.3rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--bg-slate-300);
}
&::-webkit-scrollbar-thumb {
background: var(--bg-slate-300);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--bg-slate-200);
}
.ant-row {
width: fit-content;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--bg-slate-200);
}
.ant-row {
width: fit-content;
}
}
&__item {
width: 100%;
.raw-log-content {
cursor: pointer;
}
}
&__item {
width: 100%;
.raw-log-content {
cursor: pointer;
}
}
}

View File

@@ -1,23 +1,23 @@
.log-context-container {
border: 1px solid var(--bg-slate-400);
flex: 1;
position: relative;
overflow: scroll;
overflow-x: hidden;
border: 1px solid var(--bg-slate-400);
flex: 1;
position: relative;
overflow: scroll;
overflow-x: hidden;
&::-webkit-scrollbar {
width: 0.3rem;
}
&::-webkit-scrollbar {
width: 0.3rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--bg-slate-300);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--bg-slate-200);
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--bg-slate-300);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--bg-slate-200);
}
}

View File

@@ -1,34 +1,34 @@
.empty-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.infra-metrics-container {
.views-tabs {
margin-bottom: 1rem;
}
.views-tabs {
margin-bottom: 1rem;
}
}
.infra-metrics-card {
margin: 1rem 0;
height: 300px;
padding: 10px;
margin: 1rem 0;
height: 300px;
padding: 10px;
.ant-card-body {
padding: 0;
}
.ant-card-body {
padding: 0;
}
.chart-container {
width: 100%;
height: 100%;
}
.chart-container {
width: 100%;
height: 100%;
}
.no-data-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.no-data-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}

View File

@@ -1,3 +1,3 @@
.log-context-container {
border: 1px solid var(--bg-slate-400);
}
border: 1px solid var(--bg-slate-400);
}

View File

@@ -1,9 +1,9 @@
import { Button, Form, Input, Space, Tooltip, Typography } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import getUserVersion from 'api/user/getVersion';
import loginApi from 'api/user/login';
import loginPrecheckApi from 'api/user/loginPrecheck';
import loginPrecheckApi from 'api/v1/login/loginPrecheck';
import loginApi from 'api/v1/user/login';
import getUserVersion from 'api/v1/version/getVersion';
import afterLogin from 'AppRoutes/utils';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';

View File

@@ -6,7 +6,7 @@ import { getQueryStats, WsDataEvent } from 'api/common/getQueryStats';
import logEvent from 'api/common/logEvent';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { LOCALSTORAGE } from 'constants/localStorage';
import { AVAILABLE_EXPORT_PANEL_TYPES } from 'constants/panelTypes';
@@ -247,7 +247,7 @@ function LogsExplorerViews({
} = useGetExplorerQueryRange(
listChartQuery,
PANEL_TYPES.TIME_SERIES,
DEFAULT_ENTITY_VERSION,
ENTITY_VERSION_V4,
{
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
},
@@ -265,7 +265,7 @@ function LogsExplorerViews({
} = useGetExplorerQueryRange(
requestData,
panelType,
DEFAULT_ENTITY_VERSION,
ENTITY_VERSION_V4,
{
keepPreviousData: true,
enabled: !isLimit && !!requestData,

View File

@@ -2,30 +2,33 @@
// @ts-ignore
// @ts-nocheck
import { QueryTypes, ConditionalOperators, ValidTypeSequence, ValidTypeValue } from 'lib/logql/tokens';
import {
QueryTypes,
ConditionalOperators,
ValidTypeSequence,
ValidTypeValue,
} from 'lib/logql/tokens';
export interface QueryFields {
type: keyof typeof QueryTypes;
value: string | string[];
}
export function fieldsQueryIsvalid(queryFields: QueryFields[]): boolean {
let lastOp: string;
let result = true;
queryFields.forEach((q, idx)=> {
queryFields.forEach((q, idx) => {
if (!q.value || q.value === null || q.value === '') result = false;
if (Array.isArray(q.value) && q.value.length === 0 ) result = false;
const nextOp = idx < queryFields.length ? queryFields[idx+1]: undefined;
if (!ValidTypeSequence(lastOp?.type, q?.type, nextOp?.type)) result = false
if (Array.isArray(q.value) && q.value.length === 0) result = false;
const nextOp = idx < queryFields.length ? queryFields[idx + 1] : undefined;
if (!ValidTypeSequence(lastOp?.type, q?.type, nextOp?.type)) result = false;
if (!ValidTypeValue(lastOp?.value, q.value)) result = false;
lastOp = q;
});
return result
return result;
}
export const queryKOVPair = (): QueryFields[] => [
@@ -43,7 +46,11 @@ export const queryKOVPair = (): QueryFields[] => [
},
];
export const initQueryKOVPair = (name?: string = null, op?: string = null , value?: string | string[] = null ): QueryFields[] => [
export const initQueryKOVPair = (
name?: string = null,
op?: string = null,
value?: string | string[] = null,
): QueryFields[] => [
{
type: QueryTypes.QUERY_KEY,
value: name,
@@ -58,12 +65,14 @@ export const initQueryKOVPair = (name?: string = null, op?: string = null , val
},
];
export const prepareConditionOperator = (op?: string = ConditionalOperators.AND): QueryFields => {
export const prepareConditionOperator = (
op?: string = ConditionalOperators.AND,
): QueryFields => {
return {
type: QueryTypes.CONDITIONAL_OPERATOR,
value: op,
}
}
};
};
export const createParsedQueryStructure = (parsedQuery = []) => {
if (!parsedQuery.length) {

View File

@@ -1,5 +1,5 @@
.graph-controls-panel {
position: absolute;
position: absolute;
z-index: 999;
display: none;
width: 110px;
@@ -22,4 +22,4 @@
.ant-btn-link:hover {
color: var(--bg-vanilla-100);
}
}
}

View File

@@ -1,9 +1,9 @@
.top-operation {
position: relative;
.top-operation--download {
position: absolute;
top: 15px;
right: 0px;
z-index: 1;
}
}
position: relative;
.top-operation--download {
position: absolute;
top: 15px;
right: 0px;
z-index: 1;
}
}

View File

@@ -55,6 +55,8 @@
}
.explore-content {
margin-top: 10px;
.ant-space {
margin-top: 10px;
margin-bottom: 20px;
@@ -75,16 +77,14 @@
}
.time-series-container {
display: flex;
gap: 10px;
display: grid;
grid-template-columns: repeat(
auto-fit,
minmax(min(100%, calc(50% - 8px)), 1fr)
);
gap: 16px;
width: 100%;
height: fit-content;
overflow-y: scroll;
.time-series-view {
min-width: 80%;
width: 80%;
}
}
.related-metrics-container {

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