Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7e0f5b96a | ||
|
|
a26ebb742a | ||
|
|
9d1305f174 | ||
|
|
ab514cc0f2 | ||
|
|
1f44f089e0 | ||
|
|
174fc107c2 | ||
|
|
06a55ccdd6 | ||
|
|
a3731e4c4e | ||
|
|
e183cace75 | ||
|
|
23490ca7f8 | ||
|
|
9f71e732c7 | ||
|
|
23d6287594 | ||
|
|
2624ce4007 | ||
|
|
3d5134b43c | ||
|
|
c657f96032 | ||
|
|
c18fff6ae8 | ||
|
|
28142764af | ||
|
|
2fa265ff2e | ||
|
|
dca0b11acd | ||
|
|
bad80def90 | ||
|
|
8965b9b503 | ||
|
|
05076968c9 | ||
|
|
7c8afc2e1c | ||
|
|
c8a1a8600e | ||
|
|
45cb1eb38f | ||
|
|
8ebb76bd0c | ||
|
|
309ffa4989 | ||
|
|
d787298600 | ||
|
|
7998d474e2 | ||
|
|
7b8ff5a285 | ||
|
|
cb22aef36f |
@@ -144,6 +144,8 @@ Moreover, SigNoz has few more advanced features wrt Jaeger:
|
||||
- SigNoz Logs management are based on ClickHouse, a columnar OLAP datastore which makes aggregate log analytics queries much more efficient
|
||||
- 50% lower resource requirement compared to Elastic during ingestion
|
||||
|
||||
We have published benchmarks comparing Elastic with SigNoz. Check it out [here](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)
|
||||
|
||||
<p>  </p>
|
||||
|
||||
### SigNoz vs Loki
|
||||
@@ -152,6 +154,8 @@ Moreover, SigNoz has few more advanced features wrt Jaeger:
|
||||
- SigNoz supports indexes over high cardinality data and has no limitations on the number of indexes, while Loki reaches max streams with a few indexes added to it.
|
||||
- Searching over a huge volume of data is difficult and slow in Loki compared to SigNoz
|
||||
|
||||
We have published benchmarks comparing Loki with SigNoz. Check it out [here](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)
|
||||
|
||||
<br /><br />
|
||||
|
||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributors.svg" width="50px" />
|
||||
|
||||
@@ -137,7 +137,7 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.16.0
|
||||
image: signoz/query-service:0.16.2
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
@@ -166,7 +166,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.16.0
|
||||
image: signoz/frontend:0.16.2
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@@ -179,7 +179,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.66.4
|
||||
image: signoz/signoz-otel-collector:0.66.5
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
user: root # required for reading docker container logs
|
||||
volumes:
|
||||
@@ -208,7 +208,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:0.66.4
|
||||
image: signoz/signoz-otel-collector:0.66.5
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
otel-collector:
|
||||
container_name: otel-collector
|
||||
image: signoz/signoz-otel-collector:0.66.4
|
||||
image: signoz/signoz-otel-collector:0.66.5
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
# user: root # required for reading docker container logs
|
||||
volumes:
|
||||
@@ -67,7 +67,7 @@ services:
|
||||
|
||||
otel-collector-metrics:
|
||||
container_name: otel-collector-metrics
|
||||
image: signoz/signoz-otel-collector:0.66.4
|
||||
image: signoz/signoz-otel-collector:0.66.5
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
|
||||
@@ -153,7 +153,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.16.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.16.2}
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
# ports:
|
||||
@@ -181,7 +181,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.16.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.16.2}
|
||||
container_name: frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -193,7 +193,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.4}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.5}
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
user: root # required for reading docker container logs
|
||||
volumes:
|
||||
@@ -219,7 +219,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.4}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.5}
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
|
||||
@@ -228,7 +228,7 @@ wait_for_containers_start() {
|
||||
|
||||
# The while loop is important because for-loops don't work for dynamic values
|
||||
while [[ $timeout -gt 0 ]]; do
|
||||
status_code="$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3301/api/v1/services/list || true)"
|
||||
status_code="$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:3301/api/v1/health?live=1" || true)"
|
||||
if [[ status_code -eq 200 ]]; then
|
||||
break
|
||||
else
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"go.signoz.io/signoz/ee/query-service/license"
|
||||
baseapp "go.signoz.io/signoz/pkg/query-service/app"
|
||||
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
rules "go.signoz.io/signoz/pkg/query-service/rules"
|
||||
"go.signoz.io/signoz/pkg/query-service/version"
|
||||
)
|
||||
@@ -96,7 +97,7 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router) {
|
||||
router.HandleFunc("/api/v1/complete/google",
|
||||
baseapp.OpenAccess(ah.receiveGoogleAuth)).
|
||||
Methods(http.MethodGet)
|
||||
|
||||
|
||||
router.HandleFunc("/api/v1/orgs/{orgId}/domains",
|
||||
baseapp.AdminAccess(ah.listDomainsByOrg)).
|
||||
Methods(http.MethodGet)
|
||||
@@ -127,5 +128,11 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router) {
|
||||
|
||||
func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
|
||||
version := version.GetVersion()
|
||||
ah.WriteJSON(w, r, map[string]string{"version": version, "ee": "Y"})
|
||||
versionResponse := basemodel.GetVersionResponse{
|
||||
Version: version,
|
||||
EE: "Y",
|
||||
SetupCompleted: ah.SetupCompleted,
|
||||
}
|
||||
|
||||
ah.WriteJSON(w, r, versionResponse)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"go.signoz.io/signoz/ee/query-service/constants"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
@@ -87,9 +88,16 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// get invite object
|
||||
invite, err := baseauth.ValidateInvite(ctx, req)
|
||||
if err != nil || invite == nil {
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to validate invite token", err)
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if invite == nil {
|
||||
zap.S().Errorf("failed to validate invite token: it is either empty or invalid", err)
|
||||
RespondError(w, model.BadRequest(basemodel.ErrSignupFailed{}), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// get auth domain from email domain
|
||||
@@ -190,7 +198,7 @@ func handleSsoError(w http.ResponseWriter, r *http.Request, redirectURL string)
|
||||
}
|
||||
|
||||
// receiveGoogleAuth completes google OAuth response and forwards a request
|
||||
// to front-end to sign user in
|
||||
// to front-end to sign user in
|
||||
func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request) {
|
||||
redirectUri := constants.GetDefaultSiteURL()
|
||||
ctx := context.Background()
|
||||
@@ -221,15 +229,15 @@ func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request)
|
||||
// upgrade redirect url from the relay state for better accuracy
|
||||
redirectUri = fmt.Sprintf("%s://%s%s", parsedState.Scheme, parsedState.Host, "/login")
|
||||
|
||||
// fetch domain by parsing relay state.
|
||||
// fetch domain by parsing relay state.
|
||||
domain, err := ah.AppDao().GetDomainFromSsoResponse(ctx, parsedState)
|
||||
if err != nil {
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
// now that we have domain, use domain to fetch sso settings.
|
||||
// prepare google callback handler using parsedState -
|
||||
// now that we have domain, use domain to fetch sso settings.
|
||||
// prepare google callback handler using parsedState -
|
||||
// which contains redirect URL (front-end endpoint)
|
||||
callbackHandler, err := domain.PrepareGoogleOAuthProvider(parsedState)
|
||||
|
||||
@@ -239,7 +247,7 @@ func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request)
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
nextPage, err := ah.AppDao().PrepareSsoRedirect(ctx, redirectUri, identity.Email)
|
||||
if err != nil {
|
||||
zap.S().Errorf("[receiveGoogleAuth] failed to generate redirect URI after successful login ", domain.String(), zap.Error(err))
|
||||
@@ -250,15 +258,12 @@ func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request)
|
||||
http.Redirect(w, r, nextPage, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// receiveSAML completes a SAML request and gets user logged in
|
||||
func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) {
|
||||
// this is the source url that initiated the login request
|
||||
redirectUri := constants.GetDefaultSiteURL()
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
if !ah.CheckFeature(model.SSO) {
|
||||
zap.S().Errorf("[receiveSAML] sso requested but feature unavailable %s in org domain %s", model.SSO)
|
||||
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently)
|
||||
@@ -287,13 +292,13 @@ func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) {
|
||||
// upgrade redirect url from the relay state for better accuracy
|
||||
redirectUri = fmt.Sprintf("%s://%s%s", parsedState.Scheme, parsedState.Host, "/login")
|
||||
|
||||
// fetch domain by parsing relay state.
|
||||
// fetch domain by parsing relay state.
|
||||
domain, err := ah.AppDao().GetDomainFromSsoResponse(ctx, parsedState)
|
||||
if err != nil {
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
sp, err := domain.PrepareSamlRequest(parsedState)
|
||||
if err != nil {
|
||||
zap.S().Errorf("[receiveSAML] failed to prepare saml request for domain (%s): %v", domain.String(), err)
|
||||
@@ -327,6 +332,6 @@ func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) {
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
http.Redirect(w, r, nextPage, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ module.exports = {
|
||||
'react/no-array-index-key': 'error',
|
||||
'linebreak-style': [
|
||||
'error',
|
||||
process.platform === 'win32' ? 'windows' : 'unix',
|
||||
process.env.platform === 'win32' ? 'windows' : 'unix',
|
||||
],
|
||||
'@typescript-eslint/default-param-last': 'off',
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const config: Config.InitialOptions = {
|
||||
useESM: true,
|
||||
},
|
||||
},
|
||||
testMatch: ['<rootDir>/src/**/?(*.)(test).(ts|js)?(x)'],
|
||||
testMatch: ['<rootDir>/src/**/*?(*.)(test).(ts|js)?(x)'],
|
||||
preset: 'ts-jest/presets/js-with-ts-esm',
|
||||
transform: {
|
||||
'^.+\\.(ts|tsx)?$': 'ts-jest',
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"event-source-polyfill": "1.0.31",
|
||||
"file-loader": "6.1.1",
|
||||
"flat": "^5.0.2",
|
||||
"fontfaceobserver": "2.3.0",
|
||||
"history": "4.10.1",
|
||||
"html-webpack-plugin": "5.1.0",
|
||||
"i18next": "^21.6.12",
|
||||
@@ -127,6 +128,7 @@
|
||||
"@types/d3-tip": "^3.5.5",
|
||||
"@types/event-source-polyfill": "^1.0.0",
|
||||
"@types/flat": "^5.0.2",
|
||||
"@types/fontfaceobserver": "2.1.0",
|
||||
"@types/jest": "^27.5.1",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/mini-css-extract-plugin": "^2.5.1",
|
||||
@@ -137,7 +139,6 @@
|
||||
"@types/react-redux": "^7.1.11",
|
||||
"@types/react-resizable": "3.0.3",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"@types/redux": "^3.6.0",
|
||||
"@types/styled-components": "^5.1.4",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@types/vis": "^4.21.21",
|
||||
@@ -174,6 +175,7 @@
|
||||
"lint-staged": "^12.3.7",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"prettier": "2.2.1",
|
||||
"react-hooks-testing-library": "0.6.0",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"react-resizable": "3.0.4",
|
||||
"ts-jest": "^27.1.4",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { notification } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import loginApi from 'api/user/login';
|
||||
import { Logout } from 'api/utils';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -47,7 +47,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const currentRoute = mapRoutes.get('current');
|
||||
|
||||
@@ -157,12 +157,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
|
||||
// NOTE: disabling this rule as there is no need to have div
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
interface PrivateRouteProps {
|
||||
|
||||
@@ -3,6 +3,7 @@ import NotFound from 'components/NotFound';
|
||||
import Spinner from 'components/Spinner';
|
||||
import AppLayout from 'container/AppLayout';
|
||||
import { useThemeConfig } from 'hooks/useDarkMode';
|
||||
import { NotificationProvider } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { Suspense } from 'react';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
@@ -15,26 +16,28 @@ function App(): JSX.Element {
|
||||
|
||||
return (
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
<Router history={history}>
|
||||
<PrivateRoute>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<NotificationProvider>
|
||||
<Router history={history}>
|
||||
<PrivateRoute>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</PrivateRoute>
|
||||
</Router>
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</PrivateRoute>
|
||||
</Router>
|
||||
</NotificationProvider>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ const getFilters = async (
|
||||
maxDuration: String((duration.duration || [])[0] || ''),
|
||||
minDuration: String((duration.duration || [])[1] || ''),
|
||||
exclude,
|
||||
spanKind: props.spanKind,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -10,7 +10,7 @@ const getSpans = async (
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const updatedSelectedTags = props.selectedTags.map((e) => ({
|
||||
Key: e.Key[0],
|
||||
Key: e.Key,
|
||||
Operator: e.Operator,
|
||||
StringValues: e.StringValues,
|
||||
NumberValues: e.NumberValues,
|
||||
@@ -44,6 +44,7 @@ const getSpans = async (
|
||||
maxDuration: String((duration.duration || [])[0] || ''),
|
||||
minDuration: String((duration.duration || [])[1] || ''),
|
||||
exclude,
|
||||
spanKind: props.spanKind,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ const getSpanAggregate = async (
|
||||
});
|
||||
|
||||
const updatedSelectedTags = props.selectedTags.map((e) => ({
|
||||
Key: e.Key[0],
|
||||
Key: e.Key,
|
||||
Operator: e.Operator,
|
||||
StringValues: e.StringValues,
|
||||
NumberValues: e.NumberValues,
|
||||
@@ -48,6 +48,7 @@ const getSpanAggregate = async (
|
||||
maxDuration: String((duration.duration || [])[0] || ''),
|
||||
minDuration: String((duration.duration || [])[1] || ''),
|
||||
exclude,
|
||||
spanKind: props.spanKind,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -32,6 +32,7 @@ const getTagFilters = async (
|
||||
maxDuration: String((duration.duration || [])[0] || ''),
|
||||
minDuration: String((duration.duration || [])[1] || ''),
|
||||
exclude,
|
||||
spanKind: props.spanKind,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -15,6 +15,7 @@ const getTagValue = async (
|
||||
Key: props.tagKey.Key,
|
||||
Type: props.tagKey.Type,
|
||||
},
|
||||
spanKind: props.spanKind,
|
||||
});
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
@@ -6,7 +6,10 @@ export const LegendsContainer = styled.div`
|
||||
|
||||
* {
|
||||
::-webkit-scrollbar {
|
||||
width: 0.5rem;
|
||||
width: 0.3rem;
|
||||
}
|
||||
::-webkit-scrollbar:horizontal {
|
||||
height: 0.3rem;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
@@ -18,5 +21,8 @@ export const LegendsContainer = styled.div`
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: ${themeColors.matterhornGrey};
|
||||
}
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { notification, Popover } from 'antd';
|
||||
import { Popover } from 'antd';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
@@ -7,7 +8,7 @@ function CopyClipboardHOC({
|
||||
children,
|
||||
}: CopyClipboardHOCProps): JSX.Element {
|
||||
const [value, setCopy] = useCopyToClipboard();
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
useEffect(() => {
|
||||
if (value.value) {
|
||||
notifications.success({
|
||||
@@ -22,7 +23,6 @@ function CopyClipboardHOC({
|
||||
|
||||
return (
|
||||
<span onClick={onClick} onKeyDown={onClick} role="button" tabIndex={0}>
|
||||
{NotificationElement}
|
||||
<Popover
|
||||
placement="top"
|
||||
content={<span style={{ fontSize: '0.9rem' }}>Copy to clipboard</span>}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { blue, grey, orange } from '@ant-design/colors';
|
||||
import { CopyFilled, ExpandAltOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider, notification, Row, Typography } from 'antd';
|
||||
import { Button, Divider, Row, Typography } from 'antd';
|
||||
import { map } from 'd3';
|
||||
import dayjs from 'dayjs';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
// utils
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
// interfaces
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
// components
|
||||
import AddToQueryHOC from '../AddToQueryHOC';
|
||||
import CopyClipboardHOC from '../CopyClipboardHOC';
|
||||
// styles
|
||||
import { Container, LogContainer, Text, TextContainer } from './styles';
|
||||
import { isValidLogField } from './util';
|
||||
|
||||
@@ -36,6 +41,7 @@ function LogGeneralField({ fieldKey, fieldValue }: LogFieldProps): JSX.Element {
|
||||
</TextContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function LogSelectedField({
|
||||
fieldKey = '',
|
||||
fieldValue = '',
|
||||
@@ -69,17 +75,19 @@ function LogSelectedField({
|
||||
);
|
||||
}
|
||||
|
||||
interface LogItemProps {
|
||||
interface ListLogViewProps {
|
||||
logData: ILog;
|
||||
}
|
||||
function LogItem({ logData }: LogItemProps): JSX.Element {
|
||||
function ListLogView({ logData }: ListLogViewProps): JSX.Element {
|
||||
const {
|
||||
fields: { selected },
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
||||
|
||||
const [, setCopy] = useCopyToClipboard();
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const handleDetailedView = useCallback(() => {
|
||||
dispatch({
|
||||
@@ -97,7 +105,6 @@ function LogItem({ logData }: LogItemProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{NotificationElement}
|
||||
<div>
|
||||
<div>
|
||||
{'{'}
|
||||
@@ -152,4 +159,4 @@ function LogItem({ logData }: LogItemProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export default LogItem;
|
||||
export default ListLogView;
|
||||
5
frontend/src/components/Logs/RawLogView/config.ts
Normal file
5
frontend/src/components/Logs/RawLogView/config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const rawLineStyle: React.CSSProperties = {
|
||||
marginBottom: 0,
|
||||
fontFamily: "'Fira Code', monospace",
|
||||
fontWeight: 300,
|
||||
};
|
||||
48
frontend/src/components/Logs/RawLogView/index.tsx
Normal file
48
frontend/src/components/Logs/RawLogView/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { ExpandAltOutlined } from '@ant-design/icons';
|
||||
import { Typography } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
// hooks
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
// interfaces
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import { rawLineStyle } from './config';
|
||||
// styles
|
||||
import { ExpandIconWrapper, RawLogViewContainer } from './styles';
|
||||
|
||||
interface RawLogViewProps {
|
||||
data: ILog;
|
||||
linesPerRow: number;
|
||||
onClickExpand: (log: ILog) => void;
|
||||
}
|
||||
|
||||
function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
const { data, linesPerRow, onClickExpand } = props;
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const text = useMemo(
|
||||
() => `${dayjs(data.timestamp / 1e6).format()} | ${data.body}`,
|
||||
[data.timestamp, data.body],
|
||||
);
|
||||
|
||||
const ellipsis = useMemo(() => ({ rows: linesPerRow }), [linesPerRow]);
|
||||
|
||||
const handleClickExpand = useCallback(() => {
|
||||
onClickExpand(data);
|
||||
}, [onClickExpand, data]);
|
||||
|
||||
return (
|
||||
<RawLogViewContainer wrap={false} align="middle" $isDarkMode={isDarkMode}>
|
||||
<ExpandIconWrapper flex="30px" onClick={handleClickExpand}>
|
||||
<ExpandAltOutlined />
|
||||
</ExpandIconWrapper>
|
||||
<Typography.Paragraph style={rawLineStyle} ellipsis={ellipsis}>
|
||||
{text}
|
||||
</Typography.Paragraph>
|
||||
</RawLogViewContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default RawLogView;
|
||||
24
frontend/src/components/Logs/RawLogView/styles.ts
Normal file
24
frontend/src/components/Logs/RawLogView/styles.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { blue } from '@ant-design/colors';
|
||||
import { Col, Row } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const RawLogViewContainer = styled(Row)<{ $isDarkMode: boolean }>`
|
||||
width: 100%;
|
||||
font-weight: 700;
|
||||
font-size: 0.625rem;
|
||||
line-height: 1.25rem;
|
||||
|
||||
transition: background-color 0.2s ease-in;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ $isDarkMode }): string =>
|
||||
$isDarkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0, 0, 0, 0.1)'};
|
||||
}
|
||||
`;
|
||||
|
||||
export const ExpandIconWrapper = styled(Col)`
|
||||
color: ${blue[6]};
|
||||
padding: 0.25rem 0.375rem;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
`;
|
||||
12
frontend/src/components/Logs/TableView/config.ts
Normal file
12
frontend/src/components/Logs/TableView/config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TableProps } from 'antd';
|
||||
|
||||
export const defaultCellStyle: React.CSSProperties = {
|
||||
paddingTop: 4,
|
||||
paddingBottom: 6,
|
||||
paddingRight: 8,
|
||||
paddingLeft: 8,
|
||||
};
|
||||
|
||||
export const tableScroll: TableProps<Record<string, unknown>>['scroll'] = {
|
||||
x: true,
|
||||
};
|
||||
106
frontend/src/components/Logs/TableView/index.tsx
Normal file
106
frontend/src/components/Logs/TableView/index.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { ExpandAltOutlined } from '@ant-design/icons';
|
||||
import { Table, Typography } from 'antd';
|
||||
import { ColumnsType, ColumnType } from 'antd/es/table';
|
||||
import dayjs from 'dayjs';
|
||||
// utils
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import React, { useMemo } from 'react';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
// interfaces
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
// styles
|
||||
import { ExpandIconWrapper } from '../RawLogView/styles';
|
||||
// config
|
||||
import { defaultCellStyle, tableScroll } from './config';
|
||||
|
||||
type ColumnTypeRender<T = unknown> = ReturnType<
|
||||
NonNullable<ColumnType<T>['render']>
|
||||
>;
|
||||
|
||||
type LogsTableViewProps = {
|
||||
logs: ILog[];
|
||||
fields: IField[];
|
||||
linesPerRow: number;
|
||||
onClickExpand: (log: ILog) => void;
|
||||
};
|
||||
|
||||
function LogsTableView(props: LogsTableViewProps): JSX.Element {
|
||||
const { logs, fields, linesPerRow, onClickExpand } = props;
|
||||
|
||||
const flattenLogData = useMemo(() => logs.map((log) => FlatLogData(log)), [
|
||||
logs,
|
||||
]);
|
||||
|
||||
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
||||
const fieldColumns: ColumnsType<Record<string, unknown>> = fields.map(
|
||||
({ name }) => ({
|
||||
title: name,
|
||||
dataIndex: name,
|
||||
key: name,
|
||||
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
|
||||
props: {
|
||||
style: defaultCellStyle,
|
||||
},
|
||||
children: (
|
||||
<Typography.Paragraph ellipsis={{ rows: linesPerRow }}>
|
||||
{field}
|
||||
</Typography.Paragraph>
|
||||
),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'id',
|
||||
key: 'expand',
|
||||
// https://github.com/ant-design/ant-design/discussions/36886
|
||||
render: (_, item): ColumnTypeRender<Record<string, unknown>> => ({
|
||||
props: {
|
||||
style: defaultCellStyle,
|
||||
},
|
||||
children: (
|
||||
<ExpandIconWrapper
|
||||
onClick={(): void => {
|
||||
onClickExpand((item as unknown) as ILog);
|
||||
}}
|
||||
>
|
||||
<ExpandAltOutlined />
|
||||
</ExpandIconWrapper>
|
||||
),
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'Timestamp',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
// https://github.com/ant-design/ant-design/discussions/36886
|
||||
render: (field): ColumnTypeRender<Record<string, unknown>> => {
|
||||
const date = dayjs(field / 1e6).format();
|
||||
return {
|
||||
props: {
|
||||
style: defaultCellStyle,
|
||||
},
|
||||
children: <span>{date}</span>,
|
||||
};
|
||||
},
|
||||
},
|
||||
...fieldColumns,
|
||||
];
|
||||
}, [fields, linesPerRow, onClickExpand]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={flattenLogData}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
bordered
|
||||
scroll={tableScroll}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogsTableView;
|
||||
@@ -17,13 +17,6 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
|
||||
[],
|
||||
);
|
||||
|
||||
const draggableOpts = useMemo(
|
||||
() => ({
|
||||
enableUserSelectHack,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
if (!width) {
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return <th {...restProps} />;
|
||||
@@ -35,7 +28,7 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
|
||||
height={0}
|
||||
handle={handle}
|
||||
onResize={onResize}
|
||||
draggableOpts={draggableOpts}
|
||||
draggableOpts={enableUserSelectHack}
|
||||
>
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<th {...restProps} />
|
||||
|
||||
@@ -4,4 +4,6 @@ export enum LOCALSTORAGE {
|
||||
AUTH_TOKEN = 'AUTH_TOKEN',
|
||||
REFRESH_AUTH_TOKEN = 'REFRESH_AUTH_TOKEN',
|
||||
THEME = 'THEME',
|
||||
LOGS_VIEW_MODE = 'LOGS_VIEW_MODE',
|
||||
LOGS_LINES_PER_ROW = 'LOGS_LINES_PER_ROW',
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { Button, notification } from 'antd';
|
||||
import { Button } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -17,7 +18,7 @@ import Delete from './Delete';
|
||||
|
||||
function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
|
||||
const { t } = useTranslation(['channels']);
|
||||
const [notifications, Element] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
const [channels, setChannels] = useState<Channels[]>(allChannels);
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [action] = useComponentPermission(['new_alert_action'], role);
|
||||
@@ -63,12 +64,7 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{Element}
|
||||
<ResizeTable columns={columns} dataSource={channels} rowKey="id" />
|
||||
</>
|
||||
);
|
||||
return <ResizeTable columns={columns} dataSource={channels} rowKey="id" />;
|
||||
}
|
||||
|
||||
interface AlertChannelsProps {
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
notification,
|
||||
Space,
|
||||
TableProps,
|
||||
Tooltip,
|
||||
@@ -18,6 +17,7 @@ import getErrorCounts from 'api/errors/getErrorCounts';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import ROUTES from 'constants/routes';
|
||||
import dayjs from 'dayjs';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import history from 'lib/history';
|
||||
@@ -127,7 +127,7 @@ function AllErrors(): JSX.Element {
|
||||
enabled: !loading,
|
||||
},
|
||||
]);
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.error) {
|
||||
@@ -386,24 +386,21 @@ function AllErrors(): JSX.Element {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<ResizeTable
|
||||
columns={columns}
|
||||
tableLayout="fixed"
|
||||
dataSource={data?.payload as Exception[]}
|
||||
rowKey="firstSeen"
|
||||
loading={isLoading || false || errorCountResponse.status === 'loading'}
|
||||
pagination={{
|
||||
pageSize: getUpdatedPageSize,
|
||||
responsive: true,
|
||||
current: getUpdatedOffset / 10 + 1,
|
||||
position: ['bottomLeft'],
|
||||
total: errorCountResponse.data?.payload || 0,
|
||||
}}
|
||||
onChange={onChangeHandler}
|
||||
/>
|
||||
</>
|
||||
<ResizeTable
|
||||
columns={columns}
|
||||
tableLayout="fixed"
|
||||
dataSource={data?.payload as Exception[]}
|
||||
rowKey="firstSeen"
|
||||
loading={isLoading || false || errorCountResponse.status === 'loading'}
|
||||
pagination={{
|
||||
pageSize: getUpdatedPageSize,
|
||||
responsive: true,
|
||||
current: getUpdatedOffset / 10 + 1,
|
||||
position: ['bottomLeft'],
|
||||
total: errorCountResponse.data?.payload || 0,
|
||||
}}
|
||||
onChange={onChangeHandler}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { notification } from 'antd';
|
||||
import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
|
||||
import getFeaturesFlags from 'api/features/getFeatureFlags';
|
||||
import getUserLatestVersion from 'api/user/getLatestVersion';
|
||||
@@ -6,6 +5,7 @@ import getUserVersion from 'api/user/getVersion';
|
||||
import Header from 'container/Header';
|
||||
import SideNav from 'container/SideNav';
|
||||
import TopNav from 'container/TopNav';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { ReactNode, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueries } from 'react-query';
|
||||
@@ -91,7 +91,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
const latestVersionCounter = useRef(0);
|
||||
const latestConfigCounter = useRef(0);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -153,6 +153,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
type: UPDATE_CURRENT_VERSION,
|
||||
payload: {
|
||||
currentVersion: getUserVersionResponse.data.payload.version,
|
||||
ee: getUserVersionResponse.data.payload.ee,
|
||||
setupCompleted: getUserVersionResponse.data.payload.setupCompleted,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -228,7 +230,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
{NotificationElement}
|
||||
{isToDisplayLayout && <Header />}
|
||||
<Layout>
|
||||
{isToDisplayLayout && <SideNav />}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Form, notification } from 'antd';
|
||||
import { Form } from 'antd';
|
||||
import createPagerApi from 'api/channels/createPager';
|
||||
import createSlackApi from 'api/channels/createSlack';
|
||||
import createWebhookApi from 'api/channels/createWebhook';
|
||||
@@ -7,6 +7,7 @@ import testSlackApi from 'api/channels/testSlack';
|
||||
import testWebhookApi from 'api/channels/testWebhook';
|
||||
import ROUTES from 'constants/routes';
|
||||
import FormAlertChannels from 'container/FormAlertChannels';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -58,7 +59,7 @@ function CreateAlertChannels({
|
||||
});
|
||||
const [savingState, setSavingState] = useState<boolean>(false);
|
||||
const [testingState, setTestingState] = useState<boolean>(false);
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const [type, setType] = useState<ChannelType>(preType);
|
||||
const onTypeChangeHandler = useCallback(
|
||||
@@ -336,7 +337,6 @@ function CreateAlertChannels({
|
||||
onSaveHandler,
|
||||
savingState,
|
||||
testingState,
|
||||
NotificationElement,
|
||||
title: t('page_title_create'),
|
||||
initialValue: {
|
||||
type,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Form, notification } from 'antd';
|
||||
import { Form } from 'antd';
|
||||
import editPagerApi from 'api/channels/editPager';
|
||||
import editSlackApi from 'api/channels/editSlack';
|
||||
import editWebhookApi from 'api/channels/editWebhook';
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
WebhookType,
|
||||
} from 'container/CreateAlertChannels/config';
|
||||
import FormAlertChannels from 'container/FormAlertChannels';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -36,7 +37,7 @@ function EditAlertChannels({
|
||||
});
|
||||
const [savingState, setSavingState] = useState<boolean>(false);
|
||||
const [testingState, setTestingState] = useState<boolean>(false);
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const [type, setType] = useState<ChannelType>(
|
||||
@@ -281,7 +282,6 @@ function EditAlertChannels({
|
||||
onSaveHandler,
|
||||
testingState,
|
||||
savingState,
|
||||
NotificationElement,
|
||||
title: t('page_title_edit'),
|
||||
initialValue,
|
||||
editing: true,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Button, Divider, notification, Space, Typography } from 'antd';
|
||||
import { Button, Divider, Space, Typography } from 'antd';
|
||||
import getNextPrevId from 'api/errors/getNextPrevId';
|
||||
import Editor from 'components/Editor';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { getNanoSeconds } from 'container/AllError/utils';
|
||||
import dayjs from 'dayjs';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { urlKey } from 'pages/ErrorDetails/utils';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
@@ -80,7 +81,7 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
||||
[],
|
||||
);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onClickErrorIdHandler = async (
|
||||
id: string,
|
||||
@@ -121,7 +122,6 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<Typography>{errorDetail.exceptionType}</Typography>
|
||||
<Typography>{errorDetail.exceptionMessage}</Typography>
|
||||
<Divider />
|
||||
|
||||
@@ -31,7 +31,6 @@ function FormAlertChannels({
|
||||
onSaveHandler,
|
||||
savingState,
|
||||
testingState,
|
||||
NotificationElement,
|
||||
title,
|
||||
initialValue,
|
||||
editing = false,
|
||||
@@ -53,8 +52,6 @@ function FormAlertChannels({
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
|
||||
<Title level={3}>{title}</Title>
|
||||
|
||||
<Form initialValues={initialValue} layout="vertical" form={formInstance}>
|
||||
@@ -126,10 +123,6 @@ interface FormAlertChannelsProps {
|
||||
onTestHandler: (props: ChannelType) => void;
|
||||
testingState: boolean;
|
||||
savingState: boolean;
|
||||
NotificationElement: React.ReactElement<
|
||||
unknown,
|
||||
string | React.JSXElementConstructor<unknown>
|
||||
>;
|
||||
title: string;
|
||||
initialValue: Store;
|
||||
// editing indicates if the form is opened in edit mode
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { notification, Select } from 'antd';
|
||||
import { Select } from 'antd';
|
||||
import getChannels from 'api/channels/getAll';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -20,7 +21,7 @@ function ChannelSelect({
|
||||
|
||||
const { loading, payload, error, errorMessage } = useFetch(getChannels);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const handleChange = (value: string[]): void => {
|
||||
onSelectChannels(value);
|
||||
@@ -50,22 +51,19 @@ function ChannelSelect({
|
||||
return children;
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<StyledSelect
|
||||
status={error ? 'error' : ''}
|
||||
mode="multiple"
|
||||
style={{ width: '100%' }}
|
||||
placeholder={t('placeholder_channel_select')}
|
||||
value={currentValue}
|
||||
onChange={(value): void => {
|
||||
handleChange(value as string[]);
|
||||
}}
|
||||
optionLabelProp="label"
|
||||
>
|
||||
{renderOptions()}
|
||||
</StyledSelect>
|
||||
</>
|
||||
<StyledSelect
|
||||
status={error ? 'error' : ''}
|
||||
mode="multiple"
|
||||
style={{ width: '100%' }}
|
||||
placeholder={t('placeholder_channel_select')}
|
||||
value={currentValue}
|
||||
onChange={(value): void => {
|
||||
handleChange(value as string[]);
|
||||
}}
|
||||
optionLabelProp="label"
|
||||
>
|
||||
{renderOptions()}
|
||||
</StyledSelect>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, notification, Tabs } from 'antd';
|
||||
import { Button, Tabs } from 'antd';
|
||||
import MetricsBuilderFormula from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/formula';
|
||||
import MetricsBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query';
|
||||
import {
|
||||
IQueryBuilderFormulaHandleChange,
|
||||
IQueryBuilderQueryHandleChange,
|
||||
} from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
@@ -163,7 +164,7 @@ function QuerySection({
|
||||
...allQueries,
|
||||
});
|
||||
};
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const addMetricQuery = useCallback(() => {
|
||||
if (Object.keys(metricQueries).length > 5) {
|
||||
@@ -351,7 +352,6 @@ function QuerySection({
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<StepHeading> {t('alert_form_step1')}</StepHeading>
|
||||
<FormContainer>
|
||||
<div style={{ display: 'flex' }}>{renderTabs(alertType)}</div>
|
||||
|
||||
@@ -104,7 +104,7 @@ function RuleOptions({
|
||||
<Option value="5m0s">{t('option_5min')}</Option>
|
||||
<Option value="10m0s">{t('option_10min')}</Option>
|
||||
<Option value="15m0s">{t('option_15min')}</Option>
|
||||
<Option value="60m0s">{t('option_60min')}</Option>
|
||||
<Option value="1h0m0s">{t('option_60min')}</Option>
|
||||
<Option value="4h0m0s">{t('option_4hours')}</Option>
|
||||
<Option value="24h0m0s">{t('option_24hours')}</Option>
|
||||
</InlineSelect>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons';
|
||||
import { Col, FormInstance, Modal, notification, Typography } from 'antd';
|
||||
import { Col, FormInstance, Modal, Typography } from 'antd';
|
||||
import saveAlertApi from 'api/alerts/save';
|
||||
import testAlertApi from 'api/alerts/testAlert';
|
||||
import ROUTES from 'constants/routes';
|
||||
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
|
||||
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -190,7 +191,7 @@ function FormAlertRules({
|
||||
});
|
||||
}
|
||||
};
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const validatePromParams = useCallback((): boolean => {
|
||||
let retval = true;
|
||||
@@ -483,7 +484,6 @@ function FormAlertRules({
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
{Element}
|
||||
<PanelContainer>
|
||||
<StyledLeftContainer flex="5 1 600px">
|
||||
|
||||
@@ -132,7 +132,7 @@ export const toChartInterval = (evalWindow: string | undefined): Time => {
|
||||
return '15min';
|
||||
case '30m0s':
|
||||
return '30min';
|
||||
case '60m0s':
|
||||
case '1h0m0s':
|
||||
return '1hr';
|
||||
case '4h0m0s':
|
||||
return '4hr';
|
||||
|
||||
@@ -16,6 +16,7 @@ export const SpanLine = styled.div<Props>`
|
||||
top: 50%;
|
||||
position: absolute;
|
||||
`;
|
||||
|
||||
export const SpanBorder = styled.div<Props>`
|
||||
background: ${({ bgColor }): string => bgColor};
|
||||
border-radius: 5px;
|
||||
@@ -34,27 +35,18 @@ export const SpanWrapper = styled.div`
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
min-height: 2rem;
|
||||
|
||||
/* &:before {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
border-bottom: 1px solid #303030;
|
||||
position: absolute;
|
||||
left: -30px;
|
||||
width: 30px;
|
||||
z-index: 0;
|
||||
} */
|
||||
`;
|
||||
interface SpanTextProps extends Pick<Props, 'leftOffset'> {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const SpanText = styled(Typography)<SpanTextProps>`
|
||||
export const SpanText = styled(Typography.Paragraph)<SpanTextProps>`
|
||||
&&& {
|
||||
left: ${({ leftOffset }): string => `${leftOffset}%`};
|
||||
top: 65%;
|
||||
position: absolute;
|
||||
color: ${({ isDarkMode }): string => (isDarkMode ? '##ACACAC' : '#666')};
|
||||
width: max-content;
|
||||
color: ${({ isDarkMode }): string => (isDarkMode ? '#ACACAC' : '#666')};
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Divider,
|
||||
Modal,
|
||||
notification,
|
||||
Row,
|
||||
Spin,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { Button, Card, Col, Divider, Modal, Row, Spin, Typography } from 'antd';
|
||||
import setRetentionApi from 'api/settings/setRetention';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import find from 'lodash-es/find';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -172,7 +163,7 @@ function GeneralSettings({
|
||||
logsTtlValuesPayload.status === 'pending' ? 1000 : null,
|
||||
);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onModalToggleHandler = (type: TTTLType): void => {
|
||||
if (type === 'metrics') setModalMetrics((modal) => !modal);
|
||||
@@ -593,7 +584,6 @@ function GeneralSettings({
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
{Element}
|
||||
<Col xs={24} md={22} xl={20} xxl={18} style={{ margin: 'auto' }}>
|
||||
<ErrorTextContainer>
|
||||
|
||||
@@ -67,7 +67,7 @@ function GridCardGraph({
|
||||
|
||||
const queryResponse = useQuery(
|
||||
[
|
||||
`GetMetricsQueryRange-${widget.timePreferance}-${globalSelectedInterval}-${widget.id}`,
|
||||
`GetMetricsQueryRange-${widget?.timePreferance}-${globalSelectedInterval}-${widget.id}`,
|
||||
{
|
||||
widget,
|
||||
maxTime,
|
||||
@@ -78,7 +78,7 @@ function GridCardGraph({
|
||||
],
|
||||
() =>
|
||||
GetMetricQueryRange({
|
||||
selectedTime: widget.timePreferance,
|
||||
selectedTime: widget?.timePreferance,
|
||||
graphType: widget.panelTypes,
|
||||
query: widget.query,
|
||||
globalSelectedInterval,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */
|
||||
import { notification } from 'antd';
|
||||
|
||||
import updateDashboardApi from 'api/dashboard/update';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -76,7 +77,9 @@ function GridGraph(props: Props): JSX.Element {
|
||||
const startTimestamp = Math.trunc(start);
|
||||
const endTimestamp = Math.trunc(end);
|
||||
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
@@ -204,7 +207,7 @@ function GridGraph(props: Props): JSX.Element {
|
||||
[widgets, onDragSelect],
|
||||
);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onEmptyWidgetHandler = useCallback(async () => {
|
||||
try {
|
||||
@@ -276,21 +279,18 @@ function GridGraph(props: Props): JSX.Element {
|
||||
}, [layouts, onEmptyWidgetHandler, t, toggleAddWidget, notifications]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<GraphLayoutContainer
|
||||
{...{
|
||||
addPanelLoading,
|
||||
layouts,
|
||||
onAddPanelHandler,
|
||||
onLayoutChangeHandler,
|
||||
onLayoutSaveHandler,
|
||||
saveLayoutState,
|
||||
widgets,
|
||||
setLayout,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
<GraphLayoutContainer
|
||||
{...{
|
||||
addPanelLoading,
|
||||
layouts,
|
||||
onAddPanelHandler,
|
||||
onLayoutChangeHandler,
|
||||
onLayoutSaveHandler,
|
||||
saveLayoutState,
|
||||
widgets,
|
||||
setLayout,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button, Form, Input, notification } from 'antd';
|
||||
import { Button, Form, Input } from 'antd';
|
||||
import getFeaturesFlags from 'api/features/getFeatureFlags';
|
||||
import apply from 'api/licenses/apply';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { QueryObserverResult, RefetchOptions, useQuery } from 'react-query';
|
||||
@@ -27,7 +28,7 @@ function ApplyLicenseForm({
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onFinish = async (values: unknown | { key: string }): Promise<void> => {
|
||||
const params = values as { key: string };
|
||||
@@ -77,7 +78,6 @@ function ApplyLicenseForm({
|
||||
|
||||
return (
|
||||
<ApplyFormContainer>
|
||||
{NotificationElement}
|
||||
<ApplyForm layout="inline" onFinish={onFinish}>
|
||||
<LicenseInput labelAlign="left" name="key">
|
||||
<Input
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { notification, Typography } from 'antd';
|
||||
import { Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import useInterval from 'hooks/useInterval';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -31,7 +32,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
role,
|
||||
);
|
||||
|
||||
const [notificationsApi, NotificationElement] = notification.useNotification();
|
||||
const { notifications: notificationsApi } = useNotifications();
|
||||
|
||||
useInterval(() => {
|
||||
(async (): Promise<void> => {
|
||||
@@ -51,8 +52,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
history.push(ROUTES.ALERTS_NEW);
|
||||
}, []);
|
||||
|
||||
const [notifications, Element] = notification.useNotification();
|
||||
|
||||
const onEditHandler = (id: string): void => {
|
||||
history.push(`${ROUTES.EDIT_ALERTS}?ruleId=${id}`);
|
||||
};
|
||||
@@ -144,7 +143,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
Edit
|
||||
</ColumnButton>
|
||||
|
||||
<DeleteAlert notifications={notifications} setData={setData} id={id} />
|
||||
<DeleteAlert notifications={notificationsApi} setData={setData} id={id} />
|
||||
</>
|
||||
),
|
||||
});
|
||||
@@ -152,9 +151,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
{Element}
|
||||
|
||||
<ButtonContainer>
|
||||
<TextToolTip
|
||||
{...{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { notification } from 'antd';
|
||||
import patchAlert from 'api/alerts/patch';
|
||||
import { State } from 'hooks/useFetch';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useState } from 'react';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
import { PayloadProps as PatchPayloadProps } from 'types/api/alerts/patch';
|
||||
@@ -20,7 +20,7 @@ function ToggleAlertState({
|
||||
payload: undefined,
|
||||
});
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const defaultErrorMessage = 'Something went wrong';
|
||||
|
||||
@@ -90,17 +90,14 @@ function ToggleAlertState({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<ColumnButton
|
||||
disabled={apiStatus.loading || false}
|
||||
loading={apiStatus.loading || false}
|
||||
onClick={(): Promise<void> => onToggleHandler(id, !disabled)}
|
||||
type="link"
|
||||
>
|
||||
{disabled ? 'Enable' : 'Disable'}
|
||||
</ColumnButton>
|
||||
</>
|
||||
<ColumnButton
|
||||
disabled={apiStatus.loading || false}
|
||||
loading={apiStatus.loading || false}
|
||||
onClick={(): Promise<void> => onToggleHandler(id, !disabled)}
|
||||
type="link"
|
||||
>
|
||||
{disabled ? 'Enable' : 'Disable'}
|
||||
</ColumnButton>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { notification, Space } from 'antd';
|
||||
import { Space } from 'antd';
|
||||
import getAll from 'api/alerts/getAll';
|
||||
import ReleaseNote from 'components/ReleaseNote';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
@@ -17,7 +18,7 @@ function ListAlertRules(): JSX.Element {
|
||||
cacheTime: 0,
|
||||
});
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
|
||||
@@ -29,26 +30,18 @@ function ListAlertRules(): JSX.Element {
|
||||
|
||||
// api failed to load the data
|
||||
if (isError) {
|
||||
return (
|
||||
<div>
|
||||
{NotificationElement}
|
||||
{data?.error || t('something_went_wrong')}
|
||||
</div>
|
||||
);
|
||||
return <div>{data?.error || t('something_went_wrong')}</div>;
|
||||
}
|
||||
|
||||
// api is successful but error is present
|
||||
if (status === 'success' && data.statusCode >= 400) {
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<ListAlert
|
||||
{...{
|
||||
allAlertRules: [],
|
||||
refetch,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
<ListAlert
|
||||
{...{
|
||||
allAlertRules: [],
|
||||
refetch,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,7 +52,6 @@ function ListAlertRules(): JSX.Element {
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
{NotificationElement}
|
||||
<ReleaseNote path={location.pathname} />
|
||||
<ListAlert
|
||||
{...{
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import { red } from '@ant-design/colors';
|
||||
import { ExclamationCircleTwoTone } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
notification,
|
||||
Space,
|
||||
Typography,
|
||||
Upload,
|
||||
UploadProps,
|
||||
} from 'antd';
|
||||
import { Button, Modal, Space, Typography, Upload, UploadProps } from 'antd';
|
||||
import createDashboard from 'api/dashboard/create';
|
||||
import Editor from 'components/Editor';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -41,7 +34,7 @@ function ImportJSON({
|
||||
|
||||
const [editorValue, setEditorValue] = useState<string>('');
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onChangeHandler: UploadProps['onChange'] = (info) => {
|
||||
const { fileList } = info;
|
||||
@@ -132,61 +125,58 @@ function ImportJSON({
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<Modal
|
||||
open={isImportJSONModalVisible}
|
||||
centered
|
||||
maskClosable
|
||||
destroyOnClose
|
||||
width="70vw"
|
||||
onCancel={onModalHandler}
|
||||
title={
|
||||
<>
|
||||
<Typography.Title level={4}>{t('import_json')}</Typography.Title>
|
||||
<Typography>{t('import_dashboard_by_pasting')}</Typography>
|
||||
</>
|
||||
}
|
||||
footer={
|
||||
<FooterContainer>
|
||||
<Button
|
||||
disabled={editorValue.length === 0}
|
||||
onClick={onClickLoadJsonHandler}
|
||||
loading={dashboardCreating}
|
||||
>
|
||||
{t('load_json')}
|
||||
</Button>
|
||||
{isCreateDashboardError && getErrorNode(t('error_loading_json'))}
|
||||
</FooterContainer>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Space direction="horizontal">
|
||||
<Upload
|
||||
accept=".json"
|
||||
showUploadList={false}
|
||||
multiple={false}
|
||||
onChange={onChangeHandler}
|
||||
beforeUpload={(): boolean => false}
|
||||
action="none"
|
||||
data={jsonData}
|
||||
>
|
||||
<Button type="primary">{t('upload_json_file')}</Button>
|
||||
</Upload>
|
||||
{isUploadJSONError && <>{getErrorNode(t('error_upload_json'))}</>}
|
||||
</Space>
|
||||
<Modal
|
||||
open={isImportJSONModalVisible}
|
||||
centered
|
||||
maskClosable
|
||||
destroyOnClose
|
||||
width="70vw"
|
||||
onCancel={onModalHandler}
|
||||
title={
|
||||
<>
|
||||
<Typography.Title level={4}>{t('import_json')}</Typography.Title>
|
||||
<Typography>{t('import_dashboard_by_pasting')}</Typography>
|
||||
</>
|
||||
}
|
||||
footer={
|
||||
<FooterContainer>
|
||||
<Button
|
||||
disabled={editorValue.length === 0}
|
||||
onClick={onClickLoadJsonHandler}
|
||||
loading={dashboardCreating}
|
||||
>
|
||||
{t('load_json')}
|
||||
</Button>
|
||||
{isCreateDashboardError && getErrorNode(t('error_loading_json'))}
|
||||
</FooterContainer>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Space direction="horizontal">
|
||||
<Upload
|
||||
accept=".json"
|
||||
showUploadList={false}
|
||||
multiple={false}
|
||||
onChange={onChangeHandler}
|
||||
beforeUpload={(): boolean => false}
|
||||
action="none"
|
||||
data={jsonData}
|
||||
>
|
||||
<Button type="primary">{t('upload_json_file')}</Button>
|
||||
</Upload>
|
||||
{isUploadJSONError && <>{getErrorNode(t('error_upload_json'))}</>}
|
||||
</Space>
|
||||
|
||||
<EditorContainer>
|
||||
<Typography.Paragraph>{t('paste_json_below')}</Typography.Paragraph>
|
||||
<Editor
|
||||
onChange={(newValue): void => setEditorValue(newValue)}
|
||||
value={editorValue}
|
||||
language="json"
|
||||
/>
|
||||
</EditorContainer>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
<EditorContainer>
|
||||
<Typography.Paragraph>{t('paste_json_below')}</Typography.Paragraph>
|
||||
<Editor
|
||||
onChange={(newValue): void => setEditorValue(newValue)}
|
||||
value={editorValue}
|
||||
language="json"
|
||||
/>
|
||||
</EditorContainer>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,12 @@ import {
|
||||
import { Button, Divider, Select } from 'antd';
|
||||
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||
import { defaultSelectStyle } from 'pages/Logs/config';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
GET_NEXT_LOG_LINES,
|
||||
GET_PREVIOUS_LOG_LINES,
|
||||
@@ -21,8 +24,6 @@ import { ILogsReducer } from 'types/reducer/logs';
|
||||
import { ITEMS_PER_PAGE_OPTIONS } from './config';
|
||||
import { Container } from './styles';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
function LogControls(): JSX.Element | null {
|
||||
const {
|
||||
logLinesPerPage,
|
||||
@@ -34,13 +35,14 @@ function LogControls(): JSX.Element | null {
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const handleLogLinesPerPageChange = (e: number): void => {
|
||||
dispatch({
|
||||
type: SET_LOG_LINES_PER_PAGE,
|
||||
payload: {
|
||||
logLinesPerPage: e,
|
||||
logsLinesPerPage: e,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -52,13 +54,17 @@ function LogControls(): JSX.Element | null {
|
||||
globalTime.maxTime,
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: RESET_ID_START_AND_END,
|
||||
payload: getGlobalTime(globalTime.selectedTime, {
|
||||
maxTime,
|
||||
minTime,
|
||||
}),
|
||||
const updatedGlobalTime = getGlobalTime(globalTime.selectedTime, {
|
||||
maxTime,
|
||||
minTime,
|
||||
});
|
||||
|
||||
if (updatedGlobalTime) {
|
||||
dispatch({
|
||||
type: RESET_ID_START_AND_END,
|
||||
payload: updatedGlobalTime,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleNavigatePrevious = (): void => {
|
||||
@@ -117,12 +123,16 @@ function LogControls(): JSX.Element | null {
|
||||
Next <RightOutlined />
|
||||
</Button>
|
||||
<Select
|
||||
style={defaultSelectStyle}
|
||||
loading={isLoading}
|
||||
value={logLinesPerPage}
|
||||
onChange={handleLogLinesPerPageChange}
|
||||
>
|
||||
{ITEMS_PER_PAGE_OPTIONS.map((count) => (
|
||||
<Option key={count} value={count}>{`${count} / page`}</Option>
|
||||
<Select.Option
|
||||
key={count}
|
||||
value={count}
|
||||
>{`${count} / page`}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Container>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Button, Input, notification, Space, Tooltip, Typography } from 'antd';
|
||||
import { Button, Input, Space, Tooltip, Typography } from 'antd';
|
||||
import getUserVersion from 'api/user/getVersion';
|
||||
import loginApi from 'api/user/login';
|
||||
import loginPrecheckApi from 'api/user/loginPrecheck';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { PayloadProps as PrecheckResultType } from 'types/api/user/loginPrecheck';
|
||||
|
||||
import { FormContainer, FormWrapper, Label, ParentContainer } from './styles';
|
||||
@@ -42,7 +45,27 @@ function Login({
|
||||
const [precheckInProcess, setPrecheckInProcess] = useState(false);
|
||||
const [precheckComplete, setPrecheckComplete] = useState(false);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const getUserVersionResponse = useQuery({
|
||||
queryFn: getUserVersion,
|
||||
queryKey: 'getUserVersion',
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
getUserVersionResponse.isFetched &&
|
||||
getUserVersionResponse.data &&
|
||||
getUserVersionResponse.data.payload
|
||||
) {
|
||||
const { setupCompleted } = getUserVersionResponse.data.payload;
|
||||
if (!setupCompleted) {
|
||||
// no org account registered yet, re-route user to sign up first
|
||||
history.push(ROUTES.SIGN_UP);
|
||||
}
|
||||
}
|
||||
}, [getUserVersionResponse]);
|
||||
|
||||
useEffect(() => {
|
||||
if (withPassword === 'Y') {
|
||||
@@ -185,7 +208,6 @@ function Login({
|
||||
|
||||
return (
|
||||
<FormWrapper>
|
||||
{NotificationElement}
|
||||
<FormContainer onSubmit={onSubmitHandler}>
|
||||
<Title level={4}>{t('login_page_title')}</Title>
|
||||
<ParentContainer>
|
||||
@@ -255,20 +277,6 @@ function Login({
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
|
||||
{!canSelfRegister && (
|
||||
<Typography.Paragraph italic style={{ color: '#ACACAC' }}>
|
||||
{t('prompt_create_account')}{' '}
|
||||
<Typography.Link
|
||||
onClick={(): void => {
|
||||
history.push(ROUTES.SIGN_UP);
|
||||
}}
|
||||
style={{ fontWeight: 700 }}
|
||||
>
|
||||
{t('create_an_account')}
|
||||
</Typography.Link>
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
|
||||
{canSelfRegister && (
|
||||
<Typography.Paragraph italic style={{ color: '#ACACAC' }}>
|
||||
{t('prompt_if_admin')}{' '}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
|
||||
import { Input } from 'antd';
|
||||
import { Col, Input } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
@@ -9,7 +9,7 @@ import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { ICON_STYLE } from './config';
|
||||
import FieldItem from './FieldItem';
|
||||
import { CategoryContainer, Container, FieldContainer } from './styles';
|
||||
import { CategoryContainer, FieldContainer } from './styles';
|
||||
import { IHandleInterestProps, IHandleRemoveInterestProps } from './types';
|
||||
import { onHandleAddInterest, onHandleRemoveInterest } from './utils';
|
||||
|
||||
@@ -58,7 +58,7 @@ function LogsFilters(): JSX.Element {
|
||||
);
|
||||
|
||||
return (
|
||||
<Container flex="450px">
|
||||
<Col flex="250px">
|
||||
<Input
|
||||
placeholder="Filter Values"
|
||||
onInput={handleSearch}
|
||||
@@ -110,7 +110,7 @@ function LogsFilters(): JSX.Element {
|
||||
))}
|
||||
</FieldContainer>
|
||||
</CategoryContainer>
|
||||
</Container>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { blue, grey } from '@ant-design/colors';
|
||||
import { Col, Typography } from 'antd';
|
||||
import { Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled(Col)`
|
||||
padding-top: 0.3rem;
|
||||
min-width: 15.625rem;
|
||||
max-width: 21.875rem;
|
||||
`;
|
||||
|
||||
export const CategoryContainer = styled.div`
|
||||
margin: 1rem 0;
|
||||
padding-left: 0.2rem;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { notification } from 'antd';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { flatten } from 'lodash-es';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -36,7 +36,7 @@ function SearchFields({
|
||||
|
||||
const keyPrefixRef = useRef(hashCode(JSON.stringify(fieldsQuery)));
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
const updatedFieldsQuery = createParsedQueryStructure([
|
||||
@@ -102,7 +102,6 @@ function SearchFields({
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<QueryBuilder
|
||||
key={keyPrefixRef.current}
|
||||
keyPrefix={keyPrefixRef.current}
|
||||
|
||||
@@ -48,6 +48,7 @@ function SearchFilter({
|
||||
AppState,
|
||||
ILogsReducer
|
||||
>((state) => state.logs);
|
||||
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
@@ -185,8 +186,8 @@ function SearchFilter({
|
||||
onChange={(e): void => {
|
||||
const { value } = e.target;
|
||||
setSearchText(value);
|
||||
debouncedupdateQueryString(value);
|
||||
}}
|
||||
onSearch={debouncedupdateQueryString}
|
||||
allowClear
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
@@ -1,19 +1,53 @@
|
||||
import { Typography } from 'antd';
|
||||
import LogItem from 'components/Logs/LogItem';
|
||||
import { Card, Typography } from 'antd';
|
||||
// components
|
||||
import ListLogView from 'components/Logs/ListLogView';
|
||||
import RawLogView from 'components/Logs/RawLogView';
|
||||
import LogsTableView from 'components/Logs/TableView';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { contentStyle } from 'container/Trace/Search/config';
|
||||
import useFontFaceObserver from 'hooks/useFontObserver';
|
||||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
// interfaces
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
// styles
|
||||
import { Container, Heading } from './styles';
|
||||
|
||||
function LogsTable(): JSX.Element {
|
||||
const { logs, isLoading, liveTail } = useSelector<AppState, ILogsReducer>(
|
||||
(state) => state.logs,
|
||||
export type LogViewMode = 'raw' | 'table' | 'list';
|
||||
|
||||
type LogsTableProps = {
|
||||
viewMode: LogViewMode;
|
||||
linesPerRow: number;
|
||||
onClickExpand: (logData: ILog) => void;
|
||||
};
|
||||
|
||||
function LogsTable(props: LogsTableProps): JSX.Element {
|
||||
const { viewMode, onClickExpand, linesPerRow } = props;
|
||||
|
||||
useFontFaceObserver(
|
||||
[
|
||||
{
|
||||
family: 'Fira Code',
|
||||
weight: '300',
|
||||
},
|
||||
],
|
||||
viewMode === 'raw',
|
||||
{
|
||||
timeout: 5000,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
logs,
|
||||
fields: { selected },
|
||||
isLoading,
|
||||
liveTail,
|
||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||
|
||||
const isLiveTail = useMemo(() => logs.length === 0 && liveTail === 'PLAYING', [
|
||||
logs?.length,
|
||||
liveTail,
|
||||
@@ -27,29 +61,63 @@ function LogsTable(): JSX.Element {
|
||||
const getItemContent = useCallback(
|
||||
(index: number): JSX.Element => {
|
||||
const log = logs[index];
|
||||
return <LogItem key={log.id} logData={log} />;
|
||||
|
||||
if (viewMode === 'raw') {
|
||||
return (
|
||||
<RawLogView
|
||||
key={log.id}
|
||||
data={log}
|
||||
linesPerRow={linesPerRow}
|
||||
onClickExpand={onClickExpand}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <ListLogView key={log.id} logData={log} />;
|
||||
},
|
||||
[logs],
|
||||
[logs, linesPerRow, viewMode, onClickExpand],
|
||||
);
|
||||
|
||||
const renderContent = useMemo(() => {
|
||||
if (viewMode === 'table') {
|
||||
return (
|
||||
<LogsTableView
|
||||
logs={logs}
|
||||
fields={selected}
|
||||
linesPerRow={linesPerRow}
|
||||
onClickExpand={onClickExpand}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card bodyStyle={contentStyle}>
|
||||
<Virtuoso
|
||||
useWindowScroll
|
||||
totalCount={logs.length}
|
||||
itemContent={getItemContent}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}, [getItemContent, linesPerRow, logs, onClickExpand, selected, viewMode]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner height={20} tip="Getting Logs" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container flex="auto">
|
||||
<Heading>
|
||||
<Typography.Text>Event</Typography.Text>
|
||||
</Heading>
|
||||
<Container>
|
||||
{viewMode !== 'table' && (
|
||||
<Heading>
|
||||
<Typography.Text>Event</Typography.Text>
|
||||
</Heading>
|
||||
)}
|
||||
|
||||
{isLiveTail && <Typography>Getting live logs...</Typography>}
|
||||
|
||||
{isNoLogs && <Typography>No log lines found</Typography>}
|
||||
{isNoLogs && <Typography>No logs lines found</Typography>}
|
||||
|
||||
<Virtuoso
|
||||
useWindowScroll
|
||||
totalCount={logs.length}
|
||||
itemContent={getItemContent}
|
||||
/>
|
||||
{renderContent}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { Card, Col } from 'antd';
|
||||
import { Card } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled(Col)`
|
||||
export const Container = styled.div`
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
`;
|
||||
|
||||
export const Heading = styled(Card)`
|
||||
margin-bottom: 0.1rem;
|
||||
height: 32px;
|
||||
.ant-card-body {
|
||||
padding: 0.3rem 0.5rem;
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ export const externalCallDurationByAddress = ({
|
||||
};
|
||||
|
||||
interface ExternalCallDurationByAddressProps extends ExternalCallProps {
|
||||
legend: '{{address}}';
|
||||
legend: string;
|
||||
}
|
||||
|
||||
export interface ExternalCallProps {
|
||||
|
||||
@@ -26,7 +26,7 @@ export const operationPerSec = ({
|
||||
{
|
||||
id: '',
|
||||
key: 'operation',
|
||||
op: 'MATCH',
|
||||
op: 'IN',
|
||||
value: topLevelOperations,
|
||||
},
|
||||
...tagFilterItems,
|
||||
@@ -56,7 +56,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: 'operation',
|
||||
op: 'MATCH',
|
||||
op: 'IN',
|
||||
value: topLevelOperations,
|
||||
},
|
||||
{
|
||||
@@ -78,7 +78,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: 'operation',
|
||||
op: 'MATCH',
|
||||
op: 'IN',
|
||||
value: topLevelOperations,
|
||||
},
|
||||
...tagFilterItems,
|
||||
|
||||
@@ -79,11 +79,11 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
|
||||
type="default"
|
||||
size="small"
|
||||
id="database_call_rps_button"
|
||||
onClick={onViewTracePopupClick(
|
||||
onClick={onViewTracePopupClick({
|
||||
servicename,
|
||||
selectedTraceTags,
|
||||
selectedTimeStamp,
|
||||
)}
|
||||
timestamp: selectedTimeStamp,
|
||||
})}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
@@ -114,11 +114,11 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
|
||||
type="default"
|
||||
size="small"
|
||||
id="database_call_avg_duration_button"
|
||||
onClick={onViewTracePopupClick(
|
||||
onClick={onViewTracePopupClick({
|
||||
servicename,
|
||||
selectedTraceTags,
|
||||
selectedTimeStamp,
|
||||
)}
|
||||
timestamp: selectedTimeStamp,
|
||||
})}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
|
||||
@@ -6,8 +6,11 @@ import {
|
||||
externalCallErrorPercent,
|
||||
externalCallRpsByAddress,
|
||||
} from 'container/MetricsApplication/MetricsPageQueries/ExternalQueries';
|
||||
import { resourceAttributesToTagFilterItems } from 'lib/resourceAttributes';
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
convertRawQueriesToTraceSelectedTags,
|
||||
resourceAttributesToTagFilterItems,
|
||||
} from 'lib/resourceAttributes';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -15,8 +18,13 @@ import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import MetricReducer from 'types/reducer/metrics';
|
||||
|
||||
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
|
||||
import { legend } from './constant';
|
||||
import { Button } from './styles';
|
||||
import { onGraphClickHandler, onViewTracePopupClick } from './util';
|
||||
|
||||
function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||
|
||||
const { servicename } = useParams<{ servicename?: string }>();
|
||||
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
|
||||
(state) => state.metrics,
|
||||
@@ -27,8 +35,6 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
[resourceAttributeQueries],
|
||||
);
|
||||
|
||||
const legend = '{{address}}';
|
||||
|
||||
const externalCallErrorWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -36,7 +42,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
promQL: [],
|
||||
metricsBuilder: externalCallErrorPercent({
|
||||
servicename,
|
||||
legend,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
}),
|
||||
clickHouse: [],
|
||||
@@ -44,6 +50,14 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
[getWidgetQueryBuilder, servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const selectedTraceTags = useMemo(
|
||||
() =>
|
||||
JSON.stringify(
|
||||
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
|
||||
),
|
||||
[resourceAttributeQueries],
|
||||
);
|
||||
|
||||
const externalCallDurationWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -65,7 +79,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
promQL: [],
|
||||
metricsBuilder: externalCallRpsByAddress({
|
||||
servicename,
|
||||
legend,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
}),
|
||||
clickHouse: [],
|
||||
@@ -80,7 +94,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
promQL: [],
|
||||
metricsBuilder: externalCallDurationByAddress({
|
||||
servicename,
|
||||
legend,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
}),
|
||||
clickHouse: [],
|
||||
@@ -92,6 +106,19 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
<>
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="external_call_error_percentage_button"
|
||||
onClick={onViewTracePopupClick({
|
||||
servicename,
|
||||
selectedTraceTags,
|
||||
timestamp: selectedTimeStamp,
|
||||
isExternalCall: true,
|
||||
})}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
<Card>
|
||||
<GraphTitle>External Call Error Percentage</GraphTitle>
|
||||
<GraphContainer>
|
||||
@@ -100,12 +127,35 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
fullViewOptions={false}
|
||||
widget={externalCallErrorWidget}
|
||||
yAxisUnit="%"
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
onGraphClickHandler(setSelectedTimeStamp)(
|
||||
ChartEvent,
|
||||
activeElements,
|
||||
chart,
|
||||
data,
|
||||
'external_call_error_percentage',
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="external_call_duration_button"
|
||||
onClick={onViewTracePopupClick({
|
||||
servicename,
|
||||
selectedTraceTags,
|
||||
timestamp: selectedTimeStamp,
|
||||
isExternalCall: true,
|
||||
})}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
|
||||
<Card>
|
||||
<GraphTitle>External Call duration</GraphTitle>
|
||||
<GraphContainer>
|
||||
@@ -114,6 +164,15 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
fullViewOptions={false}
|
||||
widget={externalCallDurationWidget}
|
||||
yAxisUnit="ms"
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
onGraphClickHandler(setSelectedTimeStamp)(
|
||||
ChartEvent,
|
||||
activeElements,
|
||||
chart,
|
||||
data,
|
||||
'external_call_duration',
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
@@ -122,6 +181,19 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="external_call_rps_by_address_button"
|
||||
onClick={onViewTracePopupClick({
|
||||
servicename,
|
||||
selectedTraceTags,
|
||||
timestamp: selectedTimeStamp,
|
||||
isExternalCall: true,
|
||||
})}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
<Card>
|
||||
<GraphTitle>External Call RPS(by Address)</GraphTitle>
|
||||
<GraphContainer>
|
||||
@@ -130,12 +202,35 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
fullViewOptions={false}
|
||||
widget={externalCallRPSWidget}
|
||||
yAxisUnit="reqps"
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
onGraphClickHandler(setSelectedTimeStamp)(
|
||||
ChartEvent,
|
||||
activeElements,
|
||||
chart,
|
||||
data,
|
||||
'external_call_rps_by_address',
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="external_call_duration_by_address_button"
|
||||
onClick={onViewTracePopupClick({
|
||||
servicename,
|
||||
selectedTraceTags,
|
||||
timestamp: selectedTimeStamp,
|
||||
isExternalCall: true,
|
||||
})}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
|
||||
<Card>
|
||||
<GraphTitle>External Call duration(by Address)</GraphTitle>
|
||||
<GraphContainer>
|
||||
@@ -144,6 +239,15 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
|
||||
fullViewOptions={false}
|
||||
widget={externalCallDurationAddressWidget}
|
||||
yAxisUnit="ms"
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
onGraphClickHandler(setSelectedTimeStamp)(
|
||||
ChartEvent,
|
||||
activeElements,
|
||||
chart,
|
||||
data,
|
||||
'external_call_duration_by_address',
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
||||
import Graph from 'components/Graph';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
@@ -29,7 +30,29 @@ import { onGraphClickHandler, onViewTracePopupClick } from './util';
|
||||
function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||
const { servicename } = useParams<{ servicename?: string }>();
|
||||
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||
|
||||
const handleSetTimeStamp = useCallback((selectTime: number) => {
|
||||
setSelectedTimeStamp(selectTime);
|
||||
}, []);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const handleGraphClick = useCallback(
|
||||
(type: string): ClickHandlerType => (
|
||||
ChartEvent: ChartEvent,
|
||||
activeElements: ActiveElement[],
|
||||
chart: Chart,
|
||||
data: ChartData,
|
||||
): void => {
|
||||
onGraphClickHandler(handleSetTimeStamp)(
|
||||
ChartEvent,
|
||||
activeElements,
|
||||
chart,
|
||||
data,
|
||||
type,
|
||||
);
|
||||
},
|
||||
[handleSetTimeStamp],
|
||||
);
|
||||
|
||||
const {
|
||||
topOperations,
|
||||
@@ -82,14 +105,16 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||
const startTimestamp = Math.trunc(start);
|
||||
const endTimestamp = Math.trunc(end);
|
||||
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const onErrorTrackHandler = (timestamp: number): void => {
|
||||
const currentTime = timestamp;
|
||||
const tPlusOne = timestamp + 1 * 60 * 1000;
|
||||
const tPlusOne = timestamp + 60 * 1000;
|
||||
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
||||
@@ -102,6 +127,52 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||
);
|
||||
};
|
||||
|
||||
const generalChartDataProperties = useCallback(
|
||||
(title: string, colorIndex: number) => ({
|
||||
borderColor: colors[colorIndex],
|
||||
label: title,
|
||||
showLine: true,
|
||||
borderWidth: 1.5,
|
||||
spanGaps: true,
|
||||
pointRadius: 2,
|
||||
pointHoverRadius: 4,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const dataSets = useMemo(
|
||||
() => [
|
||||
{
|
||||
data: serviceOverview.map((e) =>
|
||||
parseFloat(convertToNanoSecondsToSecond(e.p99)),
|
||||
),
|
||||
...generalChartDataProperties('p99 Latency', 0),
|
||||
},
|
||||
{
|
||||
data: serviceOverview.map((e) =>
|
||||
parseFloat(convertToNanoSecondsToSecond(e.p95)),
|
||||
),
|
||||
...generalChartDataProperties('p95 Latency', 1),
|
||||
},
|
||||
{
|
||||
data: serviceOverview.map((e) =>
|
||||
parseFloat(convertToNanoSecondsToSecond(e.p50)),
|
||||
),
|
||||
...generalChartDataProperties('p50 Latency', 2),
|
||||
},
|
||||
],
|
||||
[generalChartDataProperties, serviceOverview],
|
||||
);
|
||||
|
||||
const data = useMemo(
|
||||
() => ({
|
||||
datasets: dataSets,
|
||||
labels: serviceOverview.map(
|
||||
(e) => new Date(parseFloat(convertToNanoSecondsToSecond(e.timestamp))),
|
||||
),
|
||||
}),
|
||||
[serviceOverview, dataSets],
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Row gutter={24}>
|
||||
@@ -110,11 +181,11 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||
type="default"
|
||||
size="small"
|
||||
id="Service_button"
|
||||
onClick={onViewTracePopupClick(
|
||||
onClick={onViewTracePopupClick({
|
||||
servicename,
|
||||
selectedTraceTags,
|
||||
selectedTimeStamp,
|
||||
)}
|
||||
timestamp: selectedTimeStamp,
|
||||
})}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
@@ -122,58 +193,11 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||
<GraphTitle>Latency</GraphTitle>
|
||||
<GraphContainer>
|
||||
<Graph
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
onGraphClickHandler(setSelectedTimeStamp)(
|
||||
ChartEvent,
|
||||
activeElements,
|
||||
chart,
|
||||
data,
|
||||
'Service',
|
||||
);
|
||||
}}
|
||||
animate={false}
|
||||
onClickHandler={handleGraphClick('Service')}
|
||||
name="service_latency"
|
||||
type="line"
|
||||
data={{
|
||||
datasets: [
|
||||
{
|
||||
data: serviceOverview.map((e) =>
|
||||
parseFloat(convertToNanoSecondsToSecond(e.p99)),
|
||||
),
|
||||
borderColor: colors[0],
|
||||
label: 'p99 Latency',
|
||||
showLine: true,
|
||||
borderWidth: 1.5,
|
||||
spanGaps: true,
|
||||
pointRadius: 1.5,
|
||||
},
|
||||
{
|
||||
data: serviceOverview.map((e) =>
|
||||
parseFloat(convertToNanoSecondsToSecond(e.p95)),
|
||||
),
|
||||
borderColor: colors[1],
|
||||
label: 'p95 Latency',
|
||||
showLine: true,
|
||||
borderWidth: 1.5,
|
||||
spanGaps: true,
|
||||
pointRadius: 1.5,
|
||||
},
|
||||
{
|
||||
data: serviceOverview.map((e) =>
|
||||
parseFloat(convertToNanoSecondsToSecond(e.p50)),
|
||||
),
|
||||
borderColor: colors[2],
|
||||
label: 'p50 Latency',
|
||||
showLine: true,
|
||||
borderWidth: 1.5,
|
||||
spanGaps: true,
|
||||
pointRadius: 1.5,
|
||||
},
|
||||
],
|
||||
labels: serviceOverview.map(
|
||||
(e) =>
|
||||
new Date(parseFloat(convertToNanoSecondsToSecond(e.timestamp))),
|
||||
),
|
||||
}}
|
||||
data={data}
|
||||
yAxisUnit="ms"
|
||||
onDragSelect={onDragSelect}
|
||||
/>
|
||||
@@ -186,11 +210,11 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||
type="default"
|
||||
size="small"
|
||||
id="Rate_button"
|
||||
onClick={onViewTracePopupClick(
|
||||
onClick={onViewTracePopupClick({
|
||||
servicename,
|
||||
selectedTraceTags,
|
||||
selectedTimeStamp,
|
||||
)}
|
||||
timestamp: selectedTimeStamp,
|
||||
})}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
@@ -200,15 +224,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||
<FullView
|
||||
name="operations_per_sec"
|
||||
fullViewOptions={false}
|
||||
onClickHandler={(event, element, chart, data): void => {
|
||||
onGraphClickHandler(setSelectedTimeStamp)(
|
||||
event,
|
||||
element,
|
||||
chart,
|
||||
data,
|
||||
'Rate',
|
||||
);
|
||||
}}
|
||||
onClickHandler={handleGraphClick('Rate')}
|
||||
widget={operationPerSecWidget}
|
||||
yAxisUnit="ops"
|
||||
onDragSelect={onDragSelect}
|
||||
@@ -236,15 +252,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||
<FullView
|
||||
name="error_percentage_%"
|
||||
fullViewOptions={false}
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
onGraphClickHandler(setSelectedTimeStamp)(
|
||||
ChartEvent,
|
||||
activeElements,
|
||||
chart,
|
||||
data,
|
||||
'Error',
|
||||
);
|
||||
}}
|
||||
onClickHandler={handleGraphClick('Error')}
|
||||
widget={errorPercentageWidget}
|
||||
yAxisUnit="%"
|
||||
onDragSelect={onDragSelect}
|
||||
@@ -267,4 +275,12 @@ interface DashboardProps {
|
||||
getWidgetQueryBuilder: (query: Widgets['query']) => Widgets;
|
||||
}
|
||||
|
||||
type ClickHandlerType = (
|
||||
ChartEvent: ChartEvent,
|
||||
activeElements: ActiveElement[],
|
||||
chart: Chart,
|
||||
data: ChartData,
|
||||
type?: string,
|
||||
) => void;
|
||||
|
||||
export default Application;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const legend = {
|
||||
address: '{{address}}',
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import { Tags } from 'types/reducer/trace';
|
||||
|
||||
export const dbSystemTags: Tags[] = [
|
||||
{
|
||||
Key: ['db.system.(string)'],
|
||||
Key: 'db.system.(string)',
|
||||
StringValues: [''],
|
||||
NumberValues: [],
|
||||
BoolValues: [],
|
||||
@@ -14,14 +14,21 @@ export const dbSystemTags: Tags[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export function onViewTracePopupClick(
|
||||
servicename: string | undefined,
|
||||
selectedTraceTags: string,
|
||||
timestamp: number,
|
||||
): VoidFunction {
|
||||
interface OnViewTracePopupClickProps {
|
||||
servicename: string | undefined;
|
||||
selectedTraceTags: string;
|
||||
timestamp: number;
|
||||
isExternalCall?: boolean;
|
||||
}
|
||||
export function onViewTracePopupClick({
|
||||
selectedTraceTags,
|
||||
servicename,
|
||||
timestamp,
|
||||
isExternalCall,
|
||||
}: OnViewTracePopupClickProps): VoidFunction {
|
||||
return (): void => {
|
||||
const currentTime = timestamp;
|
||||
const tPlusOne = timestamp + 1 * 60 * 1000;
|
||||
const tPlusOne = timestamp + 60 * 1000;
|
||||
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
||||
@@ -30,13 +37,17 @@ export function onViewTracePopupClick(
|
||||
history.replace(
|
||||
`${
|
||||
ROUTES.TRACE
|
||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1`,
|
||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1${
|
||||
isExternalCall ? '&spanKind=3' : ''
|
||||
}`,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function onGraphClickHandler(
|
||||
setSelectedTimeStamp: React.Dispatch<React.SetStateAction<number>>,
|
||||
setSelectedTimeStamp: (
|
||||
n: number,
|
||||
) => void | React.Dispatch<React.SetStateAction<number>>,
|
||||
) {
|
||||
return async (
|
||||
event: ChartEvent,
|
||||
@@ -49,7 +60,7 @@ export function onGraphClickHandler(
|
||||
const points = chart.getElementsAtEventForMode(
|
||||
event.native,
|
||||
'nearest',
|
||||
{ intersect: true },
|
||||
{ intersect: false },
|
||||
true,
|
||||
);
|
||||
const id = `${from}_button`;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button, notification, Space, Typography } from 'antd';
|
||||
import { Button, Space, Typography } from 'antd';
|
||||
import changeMyPassword from 'api/user/changeMyPassword';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { isPasswordNotValidMessage, isPasswordValid } from 'pages/SignUp/utils';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -23,7 +24,7 @@ function PasswordContainer(): JSX.Element {
|
||||
ns: 'settings',
|
||||
});
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPassword && !isPasswordValid(currentPassword)) {
|
||||
@@ -82,7 +83,6 @@ function PasswordContainer(): JSX.Element {
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size="large">
|
||||
{NotificationElement}
|
||||
<Typography.Title level={3}>
|
||||
{t('change_password', {
|
||||
ns: 'settings',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button, notification, Space, Typography } from 'antd';
|
||||
import { Button, Space, Typography } from 'antd';
|
||||
import editUser from 'api/user/editUser';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -21,7 +22,7 @@ function UpdateName(): JSX.Element {
|
||||
const [changedName, setChangedName] = useState<string>(user?.name || '');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
if (!user || !org) {
|
||||
return <div />;
|
||||
@@ -72,7 +73,6 @@ function UpdateName(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{NotificationElement}
|
||||
<Space direction="vertical" size="middle">
|
||||
<Typography>Name</Typography>
|
||||
<NameInput
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { notification } from 'antd';
|
||||
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
@@ -22,7 +23,7 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element {
|
||||
(state) => state.dashboards,
|
||||
);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const [selectedDashboard] = dashboards;
|
||||
const { data } = selectedDashboard;
|
||||
@@ -57,7 +58,6 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{NotificationElement}
|
||||
{menuItems.map(({ name, Icon, display }) => (
|
||||
<Card
|
||||
onClick={(event): void => {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { blue, red } from '@ant-design/colors';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Modal, notification, Row, Space, Tag } from 'antd';
|
||||
import { Button, Modal, Row, Space, Tag } from 'antd';
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
@@ -26,7 +27,7 @@ function VariablesSetting({
|
||||
(state) => state.dashboards,
|
||||
);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const [selectedDashboard] = dashboards;
|
||||
|
||||
@@ -143,7 +144,6 @@ function VariablesSetting({
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
{variableViewMode ? (
|
||||
<VariableItem
|
||||
variableData={{ ...variableEditData } as IDashboardVariable}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { notification, Row } from 'antd';
|
||||
import { Row } from 'antd';
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { map, sortBy } from 'lodash-es';
|
||||
import React, { useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
@@ -26,7 +27,7 @@ function DashboardVariableSelection({
|
||||
|
||||
const [update, setUpdate] = useState<boolean>(false);
|
||||
const [lastUpdatedVar, setLastUpdatedVar] = useState<string>('');
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onVarChanged = (name: string): void => {
|
||||
setLastUpdatedVar(name);
|
||||
@@ -62,7 +63,6 @@ function DashboardVariableSelection({
|
||||
|
||||
return (
|
||||
<Row style={{ gap: '1rem' }}>
|
||||
{NotificationElement}
|
||||
{map(sortBy(Object.keys(variables)), (variableName) => (
|
||||
<VariableItem
|
||||
key={`${variableName}${variables[variableName].modificationUUID}`}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button, Modal, notification, Typography } from 'antd';
|
||||
import { Button, Modal, Typography } from 'antd';
|
||||
import Editor from 'components/Editor';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
@@ -32,7 +33,7 @@ function ShareModal({
|
||||
const [isViewJSON, setIsViewJSON] = useState<boolean>(false);
|
||||
const { t } = useTranslation(['dashboard', 'common']);
|
||||
const [state, setCopy] = useCopyToClipboard();
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (state.error) {
|
||||
@@ -84,34 +85,28 @@ function ShareModal({
|
||||
}, [isViewJSON, jsonValue, selectedData, selectedDataCleaned, setCopy, t]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<Modal
|
||||
open={isJSONModalVisible}
|
||||
onCancel={(): void => {
|
||||
onToggleHandler();
|
||||
setIsViewJSON(false);
|
||||
}}
|
||||
width="70vw"
|
||||
centered
|
||||
title={t('share', {
|
||||
ns: 'common',
|
||||
})}
|
||||
okText={t('download_json')}
|
||||
cancelText={t('cancel')}
|
||||
destroyOnClose
|
||||
footer={GetFooterComponent}
|
||||
>
|
||||
{!isViewJSON ? (
|
||||
<Typography>{t('export_dashboard')}</Typography>
|
||||
) : (
|
||||
<Editor
|
||||
onChange={(value): void => setJSONValue(value)}
|
||||
value={jsonValue}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
<Modal
|
||||
open={isJSONModalVisible}
|
||||
onCancel={(): void => {
|
||||
onToggleHandler();
|
||||
setIsViewJSON(false);
|
||||
}}
|
||||
width="70vw"
|
||||
centered
|
||||
title={t('share', {
|
||||
ns: 'common',
|
||||
})}
|
||||
okText={t('download_json')}
|
||||
cancelText={t('cancel')}
|
||||
destroyOnClose
|
||||
footer={GetFooterComponent}
|
||||
>
|
||||
{!isViewJSON ? (
|
||||
<Typography>{t('export_dashboard')}</Typography>
|
||||
) : (
|
||||
<Editor onChange={(value): void => setJSONValue(value)} value={jsonValue} />
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { notification } from 'antd';
|
||||
import {
|
||||
QueryBuilderFormulaTemplate,
|
||||
QueryBuilderQueryTemplate,
|
||||
} from 'constants/dashboard';
|
||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import GetFormulaName from 'lib/query/GetFormulaName';
|
||||
import GetQueryName from 'lib/query/GetQueryName';
|
||||
import React from 'react';
|
||||
@@ -37,7 +37,7 @@ function QueryBuilderQueryContainer({
|
||||
metricsBuilderQueries,
|
||||
selectedGraph,
|
||||
}: IQueryBuilderQueryContainerProps): JSX.Element | null {
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
const handleQueryBuilderQueryChange = ({
|
||||
queryIndex,
|
||||
aggregateFunction,
|
||||
@@ -156,7 +156,6 @@ function QueryBuilderQueryContainer({
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
{metricsBuilderQueries.queryBuilder.map((q, idx) => (
|
||||
<MetricsBuilder
|
||||
key={q.name}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/* eslint-disable prefer-regex-literals */
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Form, Input, Modal, notification, Typography } from 'antd';
|
||||
import { Button, Form, Input, Modal, Typography } from 'antd';
|
||||
import { useForm } from 'antd/es/form/Form';
|
||||
import createDomainApi from 'api/SAML/postDomain';
|
||||
import { FeatureKeys } from 'constants/featureKeys';
|
||||
import useFeatureFlag from 'hooks/useFeatureFlag';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -21,7 +22,7 @@ function AddDomain({ refetch }: Props): JSX.Element {
|
||||
|
||||
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onCreateHandler = async (): Promise<void> => {
|
||||
try {
|
||||
@@ -51,7 +52,6 @@ function AddDomain({ refetch }: Props): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<Container>
|
||||
<Typography.Title level={3}>
|
||||
{t('authenticated_domains', {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button, Form, notification, Space } from 'antd';
|
||||
import { Button, Form, Space } from 'antd';
|
||||
import { useForm } from 'antd/lib/form/Form';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AuthDomain, GOOGLE_AUTH, SAML } from 'types/api/SAML/listDomain';
|
||||
@@ -31,7 +32,7 @@ function EditSSO({
|
||||
|
||||
const { t } = useTranslation(['common']);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onFinishHandler = useCallback(() => {
|
||||
form
|
||||
@@ -75,7 +76,6 @@ function EditSSO({
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
>
|
||||
{NotificationElement}
|
||||
{renderFormInputs(record)}
|
||||
<Space
|
||||
style={{ width: '100%', justifyContent: 'flex-end' }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LockTwoTone } from '@ant-design/icons';
|
||||
import { Button, Modal, notification, Space, Typography } from 'antd';
|
||||
import { Button, Modal, Space, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import deleteDomain from 'api/SAML/deleteDomain';
|
||||
import listAllDomain from 'api/SAML/listAllDomain';
|
||||
@@ -9,6 +9,7 @@ import TextToolTip from 'components/TextToolTip';
|
||||
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
|
||||
import { FeatureKeys } from 'constants/featureKeys';
|
||||
import useFeatureFlag from 'hooks/useFeatureFlag';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
@@ -57,7 +58,7 @@ function AuthDomains(): JSX.Element {
|
||||
enabled: org !== null,
|
||||
});
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const assignSsoMethod = useCallback(
|
||||
(typ: AuthDomain['ssoType']): void => {
|
||||
@@ -254,7 +255,6 @@ function AuthDomains(): JSX.Element {
|
||||
if (!isLoading && data?.payload?.length === 0) {
|
||||
return (
|
||||
<Space direction="vertical" size="middle">
|
||||
{NotificationElement}
|
||||
<AddDomain refetch={refetch} />
|
||||
|
||||
<Modal
|
||||
@@ -286,7 +286,6 @@ function AuthDomains(): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<Modal
|
||||
centered
|
||||
title="Configure Authentication Method"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button, Form, Input, notification } from 'antd';
|
||||
import { Button, Form, Input } from 'antd';
|
||||
import editOrg from 'api/user/editOrg';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -21,7 +22,7 @@ function DisplayName({
|
||||
const { name } = (org || [])[index];
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onSubmit = async ({ name: orgName }: OnSubmitProps): Promise<void> => {
|
||||
try {
|
||||
@@ -76,7 +77,6 @@ function DisplayName({
|
||||
onFinish={onSubmit}
|
||||
autoComplete="off"
|
||||
>
|
||||
{NotificationElement}
|
||||
<Form.Item name="name" label="Display name" rules={[{ required: true }]}>
|
||||
<Input size="large" placeholder={t('signoz')} disabled={isLoading} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { CopyOutlined } from '@ant-design/icons';
|
||||
import { Button, Input, notification, Select, Space, Tooltip } from 'antd';
|
||||
import { Button, Input, Select, Space, Tooltip } from 'antd';
|
||||
import getResetPasswordToken from 'api/user/getResetPasswordToken';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
@@ -36,7 +37,7 @@ function EditMembersDetails({
|
||||
[],
|
||||
);
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (state.error) {
|
||||
@@ -91,7 +92,6 @@ function EditMembersDetails({
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size="large">
|
||||
{NotificationElement}
|
||||
<Space direction="horizontal">
|
||||
<Title>Email address</Title>
|
||||
<Input
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Modal, notification, Space, Typography } from 'antd';
|
||||
import { Button, Modal, Space, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import deleteUser from 'api/user/deleteUser';
|
||||
import editUserApi from 'api/user/editUser';
|
||||
@@ -6,6 +6,7 @@ import getOrgUser from 'api/user/getOrgUser';
|
||||
import updateRole from 'api/user/updateRole';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import dayjs from 'dayjs';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
@@ -40,7 +41,7 @@ function UserFunction({
|
||||
const { t } = useTranslation(['common']);
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState<boolean>(false);
|
||||
const [isUpdateLoading, setIsUpdateLoading] = useState<boolean>(false);
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onUpdateDetailsHandler = (): void => {
|
||||
setDataSource((data) => {
|
||||
@@ -164,7 +165,6 @@ function UserFunction({
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<Space direction="horizontal">
|
||||
<Typography.Link
|
||||
onClick={(): void => onModalToggleHandler(setIsModalVisible, true)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Modal, notification, Space, Typography } from 'antd';
|
||||
import { Button, Modal, Space, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import deleteInvite from 'api/user/deleteInvite';
|
||||
import getPendingInvites from 'api/user/getPendingInvites';
|
||||
@@ -7,6 +7,7 @@ import sendInvite from 'api/user/sendInvite';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { INVITE_MEMBERS_HASH } from 'constants/app';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
@@ -26,7 +27,7 @@ function PendingInvitesContainer(): JSX.Element {
|
||||
const [isInvitingMembers, setIsInvitingMembers] = useState<boolean>(false);
|
||||
const { t } = useTranslation(['organizationsettings', 'common']);
|
||||
const [state, setText] = useCopyToClipboard();
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (state.error) {
|
||||
@@ -229,7 +230,6 @@ function PendingInvitesContainer(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{NotificationElement}
|
||||
<Modal
|
||||
title={t('invite_team_members')}
|
||||
open={isInviteTeamMemberModalOpen}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Button, Input, notification, Typography } from 'antd';
|
||||
import { Button, Input, Typography } from 'antd';
|
||||
import resetPasswordApi from 'api/user/resetPassword';
|
||||
import { Logout } from 'api/utils';
|
||||
import WelcomeLeftContainer from 'components/WelcomeLeftContainer';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { Label } from 'pages/SignUp/styles';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@@ -24,7 +25,7 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
|
||||
const { search } = useLocation();
|
||||
const params = new URLSearchParams(search);
|
||||
const token = params.get('token');
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
@@ -83,73 +84,70 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<WelcomeLeftContainer version={version}>
|
||||
<>
|
||||
{NotificationElement}
|
||||
<FormWrapper>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Title level={4}>Reset Your Password</Title>
|
||||
<FormWrapper>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Title level={4}>Reset Your Password</Title>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="Password">Password</Label>
|
||||
<Input.Password
|
||||
value={password}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setPassword);
|
||||
}}
|
||||
required
|
||||
id="currentPassword"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="ConfirmPassword">Confirm Password</Label>
|
||||
<Input.Password
|
||||
value={confirmPassword}
|
||||
onChange={(e): void => {
|
||||
const updateValue = e.target.value;
|
||||
setState(updateValue, setConfirmPassword);
|
||||
if (password !== updateValue) {
|
||||
setConfirmPasswordError(true);
|
||||
} else {
|
||||
setConfirmPasswordError(false);
|
||||
}
|
||||
}}
|
||||
required
|
||||
id="UpdatePassword"
|
||||
/>
|
||||
|
||||
{confirmPasswordError && (
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
style={{
|
||||
color: '#D89614',
|
||||
marginTop: '0.50rem',
|
||||
}}
|
||||
>
|
||||
Passwords don’t match. Please try again
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
data-attr="signup"
|
||||
loading={loading}
|
||||
disabled={
|
||||
loading ||
|
||||
!password ||
|
||||
!confirmPassword ||
|
||||
confirmPasswordError ||
|
||||
token === null
|
||||
<div>
|
||||
<Label htmlFor="Password">Password</Label>
|
||||
<Input.Password
|
||||
value={password}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setPassword);
|
||||
}}
|
||||
required
|
||||
id="currentPassword"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="ConfirmPassword">Confirm Password</Label>
|
||||
<Input.Password
|
||||
value={confirmPassword}
|
||||
onChange={(e): void => {
|
||||
const updateValue = e.target.value;
|
||||
setState(updateValue, setConfirmPassword);
|
||||
if (password !== updateValue) {
|
||||
setConfirmPasswordError(true);
|
||||
} else {
|
||||
setConfirmPasswordError(false);
|
||||
}
|
||||
}}
|
||||
required
|
||||
id="UpdatePassword"
|
||||
/>
|
||||
|
||||
{confirmPasswordError && (
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
style={{
|
||||
color: '#D89614',
|
||||
marginTop: '0.50rem',
|
||||
}}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</form>
|
||||
</FormWrapper>
|
||||
</>
|
||||
Passwords don’t match. Please try again
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
data-attr="signup"
|
||||
loading={loading}
|
||||
disabled={
|
||||
loading ||
|
||||
!password ||
|
||||
!confirmPassword ||
|
||||
confirmPasswordError ||
|
||||
token === null
|
||||
}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</form>
|
||||
</FormWrapper>
|
||||
</WelcomeLeftContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,11 @@ function Timeline({
|
||||
},0)`}
|
||||
key={`${interval.label + interval.percentage + index}`}
|
||||
>
|
||||
<text y={13} fill={isDarkMode ? 'white' : 'black'}>
|
||||
<text
|
||||
y={13}
|
||||
x={index === intervals.length - 1 ? -10 : 0}
|
||||
fill={isDarkMode ? 'white' : 'black'}
|
||||
>
|
||||
{interval.label}
|
||||
</text>
|
||||
<line
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Checkbox, notification, Tooltip, Typography } from 'antd';
|
||||
import { Checkbox, Tooltip, Typography } from 'antd';
|
||||
import getFilters from 'api/trace/getFilters';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
@@ -23,6 +24,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
filter,
|
||||
userSelectedFilter,
|
||||
isFilterExclude,
|
||||
spanKind,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
@@ -38,7 +40,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
(userSelectedFilter.get(name) || []).find((e) => e === keyValue) !==
|
||||
undefined;
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const onCheckHandler = async (): Promise<void> => {
|
||||
@@ -90,6 +92,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
start: String(globalTime.minTime),
|
||||
getFilters: filterToFetchData.filter((e) => e !== name),
|
||||
isFilterExclude: preIsFilterExclude,
|
||||
spanKind,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
@@ -124,6 +127,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
order: spansAggregate.order,
|
||||
orderParam: spansAggregate.orderParam,
|
||||
pageSize: spansAggregate.pageSize,
|
||||
spanKind,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -163,7 +167,6 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<CheckBoxContainer>
|
||||
{NotificationElement}
|
||||
<Checkbox
|
||||
disabled={isLoading || filterLoading}
|
||||
onClick={onCheckHandler}
|
||||
|
||||
@@ -30,6 +30,7 @@ function Duration(): JSX.Element {
|
||||
selectedTags,
|
||||
userSelectedFilter,
|
||||
isFilterExclude,
|
||||
spanKind,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
@@ -88,6 +89,7 @@ function Duration(): JSX.Element {
|
||||
other: Object.fromEntries(preSelectedFilter),
|
||||
start: String(globalTime.minTime),
|
||||
isFilterExclude,
|
||||
spanKind,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
@@ -113,6 +115,7 @@ function Duration(): JSX.Element {
|
||||
order: spansAggregate.order,
|
||||
pageSize: spansAggregate.pageSize,
|
||||
orderParam: spansAggregate.orderParam,
|
||||
spanKind,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Input, notification } from 'antd';
|
||||
import { Input } from 'antd';
|
||||
import getFilters from 'api/trace/getFilters';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
@@ -21,6 +22,7 @@ function TraceID(): JSX.Element {
|
||||
selectedTags,
|
||||
userSelectedFilter,
|
||||
isFilterExclude,
|
||||
spanKind,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
@@ -28,7 +30,7 @@ function TraceID(): JSX.Element {
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [userEnteredValue, setUserEnteredValue] = useState<string>('');
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
useEffect(() => {
|
||||
setUserEnteredValue(selectedFilter.get('traceID')?.[0] || '');
|
||||
}, [selectedFilter]);
|
||||
@@ -51,6 +53,7 @@ function TraceID(): JSX.Element {
|
||||
start: String(globalTime.minTime),
|
||||
getFilters: filterToFetchData,
|
||||
isFilterExclude,
|
||||
spanKind,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
@@ -76,6 +79,7 @@ function TraceID(): JSX.Element {
|
||||
order: spansAggregate.order,
|
||||
pageSize: spansAggregate.pageSize,
|
||||
orderParam: spansAggregate.orderParam,
|
||||
spanKind,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -109,7 +113,6 @@ function TraceID(): JSX.Element {
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{NotificationElement}
|
||||
<Search
|
||||
placeholder="Filter by Trace ID"
|
||||
onSearch={onSearch}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Card, Divider, notification, Typography } from 'antd';
|
||||
import { Card, Divider, Typography } from 'antd';
|
||||
import getFilters from 'api/trace/getFilters';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
@@ -36,6 +37,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
filter,
|
||||
isFilterExclude,
|
||||
userSelectedFilter,
|
||||
spanKind,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const { name: PanelName, isOpen: IsPanelOpen } = props;
|
||||
@@ -53,7 +55,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
|
||||
const defaultErrorMessage = 'Something went wrong';
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const onExpandHandler: React.MouseEventHandler<HTMLDivElement> = async (e) => {
|
||||
@@ -74,6 +76,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
getFilters: updatedFilterData,
|
||||
other: Object.fromEntries(getprepdatedSelectedFilter),
|
||||
isFilterExclude,
|
||||
spanKind,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
@@ -106,6 +109,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
order: spansAggregate.order,
|
||||
pageSize: spansAggregate.pageSize,
|
||||
orderParam: spansAggregate.orderParam,
|
||||
spanKind,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -159,6 +163,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
order: spansAggregate.order,
|
||||
pageSize: spansAggregate.pageSize,
|
||||
orderParam: spansAggregate.orderParam,
|
||||
spanKind,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -194,6 +199,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
getFilters: filterToFetchData,
|
||||
other: Object.fromEntries(updatedFilter),
|
||||
isFilterExclude: postIsFilterExclude,
|
||||
spanKind,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200 && response.payload) {
|
||||
@@ -212,6 +218,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
order: spansAggregate.order,
|
||||
pageSize: spansAggregate.pageSize,
|
||||
orderParam: spansAggregate.orderParam,
|
||||
spanKind,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -297,7 +304,6 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
{PanelName !== 'duration' && <Divider plain style={{ margin: 0 }} />}
|
||||
|
||||
<Card bordered={false}>
|
||||
|
||||
@@ -16,7 +16,7 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
|
||||
|
||||
const { index, setLocalSelectedTags, tag } = props;
|
||||
|
||||
const [selectedKey, setSelectedKey] = useState<string>(tag.Key[0] || '');
|
||||
const [selectedKey, setSelectedKey] = useState<string>(tag.Key || '');
|
||||
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
@@ -27,6 +27,7 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
|
||||
globalTime.maxTime,
|
||||
traces.selectedFilter,
|
||||
traces.isFilterExclude,
|
||||
traces.spanKind,
|
||||
],
|
||||
{
|
||||
queryFn: () =>
|
||||
@@ -35,6 +36,7 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
|
||||
end: globalTime.maxTime,
|
||||
other: Object.fromEntries(traces.selectedFilter),
|
||||
isFilterExclude: traces.isFilterExclude,
|
||||
spanKind: traces.spanKind,
|
||||
}),
|
||||
cacheTime: 120000,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Select } from 'antd';
|
||||
import { BaseOptionType } from 'antd/es/select';
|
||||
import getTagValue from 'api/trace/getTagValue';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
getTagValueOptions,
|
||||
onTagValueChange,
|
||||
selectOptions,
|
||||
separateTagValues,
|
||||
TagValueTypes,
|
||||
} from './utils';
|
||||
|
||||
@@ -30,6 +31,8 @@ function TagValue(props: TagValueProps): JSX.Element {
|
||||
BoolValues: selectedBoolValues,
|
||||
} = tag;
|
||||
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const [localTagValue, setLocalTagValue] = useState<TagValueTypes[]>(
|
||||
getInitialLocalValue(
|
||||
selectedNumberValues,
|
||||
@@ -45,7 +48,14 @@ function TagValue(props: TagValueProps): JSX.Element {
|
||||
const tagType = useMemo(() => extractTagType(tagKey), [tagKey]);
|
||||
|
||||
const { isLoading, data } = useQuery(
|
||||
['tagKey', globalReducer.minTime, globalReducer.maxTime, tagKey, tagType],
|
||||
[
|
||||
'tagKey',
|
||||
globalReducer.minTime,
|
||||
globalReducer.maxTime,
|
||||
tagKey,
|
||||
tagType,
|
||||
traces.spanKind,
|
||||
],
|
||||
{
|
||||
queryFn: () =>
|
||||
getTagValue({
|
||||
@@ -55,6 +65,7 @@ function TagValue(props: TagValueProps): JSX.Element {
|
||||
Key: extractTagKey(tagKey),
|
||||
Type: tagType,
|
||||
},
|
||||
spanKind: traces.spanKind,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -71,66 +82,27 @@ function TagValue(props: TagValueProps): JSX.Element {
|
||||
[index, selectedKey, selectedOperator, setLocalSelectedTags],
|
||||
);
|
||||
|
||||
const onSetLocalValue = useCallback(() => {
|
||||
setLocalTagValue([]);
|
||||
}, []);
|
||||
|
||||
const onSelectedHandler = useCallback(
|
||||
(value: unknown) => {
|
||||
if (
|
||||
typeof value === 'number' ||
|
||||
(typeof value === 'string' && !Number.isNaN(Number(value)) && value !== ' ')
|
||||
) {
|
||||
setLocalTagValue([value]);
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
StringValues: [],
|
||||
NumberValues: [Number(value)],
|
||||
BoolValues: [],
|
||||
},
|
||||
...tags.slice(index + 1, tags.length),
|
||||
]);
|
||||
} else if (
|
||||
typeof value === 'boolean' ||
|
||||
value === 'true' ||
|
||||
value === 'false'
|
||||
) {
|
||||
setLocalTagValue([value]);
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
StringValues: [],
|
||||
NumberValues: [],
|
||||
BoolValues: [value === 'true' || value === true],
|
||||
},
|
||||
...tags.slice(index + 1, tags.length),
|
||||
]);
|
||||
} else if (typeof value === 'string') {
|
||||
setLocalTagValue([value]);
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
StringValues: [value],
|
||||
NumberValues: [],
|
||||
BoolValues: [],
|
||||
},
|
||||
...tags.slice(index + 1, tags.length),
|
||||
]);
|
||||
}
|
||||
},
|
||||
[index, selectedKey, selectedOperator, setLocalSelectedTags],
|
||||
);
|
||||
|
||||
const onChangeHandler = useCallback(
|
||||
(value: unknown) => onTagValueChange(value, setLocalTagValue),
|
||||
[],
|
||||
(value: unknown) => {
|
||||
const updatedValues = onTagValueChange(value);
|
||||
setLocalTagValue(updatedValues);
|
||||
const { boolValues, numberValues, stringValues } = separateTagValues(
|
||||
updatedValues,
|
||||
selectedKey,
|
||||
);
|
||||
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, index),
|
||||
{
|
||||
...tags[index],
|
||||
BoolValues: boolValues,
|
||||
NumberValues: numberValues,
|
||||
StringValues: stringValues,
|
||||
},
|
||||
...tags.slice(index + 1),
|
||||
]);
|
||||
},
|
||||
[index, setLocalSelectedTags, selectedKey],
|
||||
);
|
||||
|
||||
const getFilterOptions = useCallback(
|
||||
@@ -149,14 +121,11 @@ function TagValue(props: TagValueProps): JSX.Element {
|
||||
options={getTagValueOptions(data?.payload, tagType)}
|
||||
mode="tags"
|
||||
allowClear
|
||||
onClear={onSetLocalValue}
|
||||
onDeselect={onSetLocalValue}
|
||||
showSearch
|
||||
filterOption={getFilterOptions}
|
||||
disabled={isLoading || tagValueDisabled}
|
||||
value={localTagValue}
|
||||
onChange={onChangeHandler}
|
||||
onSelect={onSelectedHandler}
|
||||
>
|
||||
{selectOptions(data?.payload, tagType)?.map((suggestion) => (
|
||||
<Select.Option key={suggestion.toString()} value={suggestion}>
|
||||
@@ -176,4 +145,4 @@ interface TagValueProps {
|
||||
tagKey: string;
|
||||
}
|
||||
|
||||
export default TagValue;
|
||||
export default memo(TagValue);
|
||||
|
||||
@@ -150,12 +150,12 @@ function SingleTags(props: AllTagsProps): JSX.Element {
|
||||
}
|
||||
</SelectComponent>
|
||||
|
||||
{selectedKey[0] ? (
|
||||
{selectedKey ? (
|
||||
<TagValue
|
||||
index={index}
|
||||
tag={tag}
|
||||
setLocalSelectedTags={setLocalSelectedTags}
|
||||
tagKey={selectedKey[0]}
|
||||
tagKey={selectedKey}
|
||||
/>
|
||||
) : (
|
||||
<SelectComponent />
|
||||
|
||||
@@ -50,29 +50,62 @@ export const extractTagKey = (tagKey: string): string => {
|
||||
return '';
|
||||
};
|
||||
|
||||
export function onTagValueChange(
|
||||
values: unknown,
|
||||
setLocalValue: React.Dispatch<React.SetStateAction<TagValueTypes[]>>,
|
||||
): void {
|
||||
if (Array.isArray(values) && values.length > 0) {
|
||||
if (typeof values[0] === 'number' || typeof values[0] === 'boolean') {
|
||||
setLocalValue(values);
|
||||
} else if (typeof values[0] === 'string') {
|
||||
if (values[0] === 'true' || values[0] === 'false') {
|
||||
setLocalValue([values[0] === 'true']);
|
||||
} else if (values[0] !== ' ' && !Number.isNaN(Number(values[0]))) {
|
||||
setLocalValue([Number(values[0])]);
|
||||
} else {
|
||||
setLocalValue([values[0]]);
|
||||
}
|
||||
}
|
||||
export function onTagValueChange(values: unknown): TagValueTypes[] {
|
||||
const stringValues = values as string[];
|
||||
|
||||
if (!Array.isArray(stringValues) || stringValues.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return values as TagValueTypes[];
|
||||
}
|
||||
|
||||
export function separateTagValues(
|
||||
values: TagValueTypes[],
|
||||
selectedKey: string,
|
||||
): { boolValues: boolean[]; numberValues: number[]; stringValues: string[] } {
|
||||
if (selectedKey.includes('.(bool)')) {
|
||||
const boolValues = values.filter(
|
||||
(value) => typeof value === 'boolean',
|
||||
) as boolean[];
|
||||
|
||||
return {
|
||||
boolValues,
|
||||
numberValues: [],
|
||||
stringValues: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (selectedKey.includes('.(number)')) {
|
||||
const numberValues = values
|
||||
.filter((value) => typeof value === 'number' || !Number.isNaN(Number(value)))
|
||||
.map((value) => Number(value)) as number[];
|
||||
return {
|
||||
boolValues: [],
|
||||
numberValues,
|
||||
stringValues: [],
|
||||
};
|
||||
}
|
||||
|
||||
const stringValues = values.filter(
|
||||
(value) =>
|
||||
typeof value === 'string' &&
|
||||
value !== 'true' &&
|
||||
value !== 'false' &&
|
||||
Number.isNaN(Number(value)),
|
||||
) as string[];
|
||||
|
||||
return {
|
||||
boolValues: [],
|
||||
numberValues: [],
|
||||
stringValues,
|
||||
};
|
||||
}
|
||||
|
||||
export function disableTagValue(
|
||||
selectedOperator: OperatorValues,
|
||||
setLocalValue: React.Dispatch<React.SetStateAction<TagValueTypes[]>>,
|
||||
selectedKeys: string[],
|
||||
selectedKeys: string,
|
||||
setLocalSelectedTags: React.Dispatch<React.SetStateAction<Tags[]>>,
|
||||
index: number,
|
||||
): boolean {
|
||||
@@ -93,22 +126,16 @@ export function disableTagValue(
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getInitialLocalValue(
|
||||
selectedNumberValues: number[],
|
||||
selectedBoolValues: boolean[],
|
||||
selectedStringValues: string[],
|
||||
): TagValueTypes[] {
|
||||
if (selectedStringValues && selectedStringValues.length > 0) {
|
||||
return selectedStringValues;
|
||||
}
|
||||
if (selectedNumberValues && selectedNumberValues.length > 0) {
|
||||
return selectedNumberValues;
|
||||
}
|
||||
if (selectedBoolValues && selectedBoolValues.length > 0) {
|
||||
return selectedBoolValues;
|
||||
}
|
||||
return selectedStringValues;
|
||||
return [
|
||||
...selectedBoolValues,
|
||||
...selectedNumberValues,
|
||||
...selectedStringValues,
|
||||
];
|
||||
}
|
||||
|
||||
export function getTagValueOptions(
|
||||
@@ -169,9 +196,9 @@ export function selectOptions(
|
||||
return [];
|
||||
}
|
||||
|
||||
export function mapOperators(selectedKey: string[]): AllMenuProps[] {
|
||||
export function mapOperators(selectedKey: string): AllMenuProps[] {
|
||||
return AllMenu.filter((e) =>
|
||||
e?.supportedTypes?.includes(extractTagType(selectedKey[0])),
|
||||
e?.supportedTypes?.includes(extractTagType(selectedKey)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,7 +219,7 @@ export function onTagKeySelect(
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, index),
|
||||
{
|
||||
Key: [value],
|
||||
Key: value,
|
||||
Operator: tag.Operator,
|
||||
StringValues: tag.StringValues,
|
||||
NumberValues: tag.NumberValues,
|
||||
|
||||
@@ -39,7 +39,7 @@ function AllTags({
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags,
|
||||
{
|
||||
Key: [],
|
||||
Key: '',
|
||||
Operator: 'Equals',
|
||||
StringValues: [],
|
||||
NumberValues: [],
|
||||
@@ -94,7 +94,7 @@ function AllTags({
|
||||
<CurrentTagsContainer>
|
||||
{localSelectedTags.map((tags, index) => (
|
||||
<Tags
|
||||
key={tags.Key.join(',')}
|
||||
key={tags.Key}
|
||||
tag={tags}
|
||||
index={index}
|
||||
onCloseHandler={(): void => onCloseHandler(index)}
|
||||
|
||||
@@ -67,6 +67,7 @@ function Search({
|
||||
order: traces.spansAggregate.order,
|
||||
pageSize: traces.spansAggregate.pageSize,
|
||||
orderParam: traces.spansAggregate.orderParam,
|
||||
spanKind: traces.spanKind,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export const parseQueryToTags = (query: string): PayloadProps<Tags> => {
|
||||
// If the operator is Exists or NotExists, then return the tag object without values
|
||||
if (operator === 'Exists' || operator === 'NotExists') {
|
||||
return {
|
||||
Key: [tagName],
|
||||
Key: tagName,
|
||||
StringValues: [],
|
||||
NumberValues: [],
|
||||
BoolValues: [],
|
||||
@@ -97,7 +97,7 @@ export const parseQueryToTags = (query: string): PayloadProps<Tags> => {
|
||||
|
||||
// Return the tag object
|
||||
return {
|
||||
Key: [tagName],
|
||||
Key: tagName,
|
||||
StringValues,
|
||||
NumberValues,
|
||||
BoolValues,
|
||||
@@ -120,31 +120,31 @@ export const parseTagsToQuery = (tags: Tags): PayloadProps<string> => {
|
||||
const payload = tags
|
||||
.map(({ StringValues, NumberValues, BoolValues, Key, Operator }) => {
|
||||
// Check if the key of the tag is undefined
|
||||
if (!Key[0]) {
|
||||
if (!Key) {
|
||||
isError = true;
|
||||
}
|
||||
if (Operator === 'Exists' || Operator === 'NotExists') {
|
||||
return `${Key[0]} ${Operator}`;
|
||||
return `${Key} ${Operator}`;
|
||||
}
|
||||
// Check if the tag has string values
|
||||
if (StringValues.length > 0) {
|
||||
// Format the string values and join them with a ','
|
||||
const formattedStringValues = formatValues(StringValues);
|
||||
return `${Key[0]} ${Operator} (${formattedStringValues})`;
|
||||
return `${Key} ${Operator} (${formattedStringValues})`;
|
||||
}
|
||||
|
||||
// Check if the tag has number values
|
||||
if (NumberValues.length > 0) {
|
||||
// Format the number values and join them with a ','
|
||||
const formattedNumberValues = formatValues(NumberValues);
|
||||
return `${Key[0]} ${Operator} (${formattedNumberValues})`;
|
||||
return `${Key} ${Operator} (${formattedNumberValues})`;
|
||||
}
|
||||
|
||||
// Check if the tag has boolean values
|
||||
if (BoolValues.length > 0) {
|
||||
// Format the boolean values and join them with a ','
|
||||
const formattedBoolValues = formatValues(BoolValues);
|
||||
return `${Key[0]} ${Operator} (${formattedBoolValues})`;
|
||||
return `${Key} ${Operator} (${formattedBoolValues})`;
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
@@ -40,6 +40,7 @@ function TraceGraphFilter(): JSX.Element {
|
||||
globalTime.maxTime,
|
||||
traces.selectedFilter,
|
||||
traces.isFilterExclude,
|
||||
traces.spanKind,
|
||||
],
|
||||
{
|
||||
queryFn: () =>
|
||||
@@ -48,8 +49,10 @@ function TraceGraphFilter(): JSX.Element {
|
||||
end: globalTime.maxTime,
|
||||
other: Object.fromEntries(traces.selectedFilter),
|
||||
isFilterExclude: traces.isFilterExclude,
|
||||
spanKind: traces.spanKind,
|
||||
}),
|
||||
cacheTime: 120000,
|
||||
enabled: traces.filter.size > 0,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { notification } from 'antd';
|
||||
import getTriggeredApi from 'api/alerts/getTriggered';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { State } from 'hooks/useFetch';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PayloadProps } from 'types/api/alerts/getTriggered';
|
||||
@@ -17,7 +17,7 @@ function TriggeredAlerts(): JSX.Element {
|
||||
payload: [],
|
||||
});
|
||||
const { t } = useTranslation(['common']);
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
@@ -69,21 +69,11 @@ function TriggeredAlerts(): JSX.Element {
|
||||
}, [groupState.error, groupState.errorMessage, t, notifications]);
|
||||
|
||||
if (groupState.error) {
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<TriggerComponent allAlerts={[]} />
|
||||
</>
|
||||
);
|
||||
return <TriggerComponent allAlerts={[]} />;
|
||||
}
|
||||
|
||||
if (groupState.loading || groupState.payload === undefined) {
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<Spinner height="75vh" tip="Loading Alerts..." />
|
||||
</>
|
||||
);
|
||||
return <Spinner height="75vh" tip="Loading Alerts..." />;
|
||||
}
|
||||
|
||||
// commented the reduce() call as we no longer use /alerts/groups
|
||||
@@ -95,12 +85,7 @@ function TriggeredAlerts(): JSX.Element {
|
||||
// return [...acc, ...curr.alerts];
|
||||
// }, initialAlerts);
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<TriggerComponent allAlerts={groupState.payload} />
|
||||
</>
|
||||
);
|
||||
return <TriggerComponent allAlerts={groupState.payload} />;
|
||||
}
|
||||
|
||||
export default TriggeredAlerts;
|
||||
|
||||
69
frontend/src/hooks/useFontObserver.tsx
Normal file
69
frontend/src/hooks/useFontObserver.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import FontFaceObserver from 'fontfaceobserver';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export interface FontFace {
|
||||
family: string;
|
||||
weight?:
|
||||
| `light`
|
||||
| `normal`
|
||||
| `bold`
|
||||
| `bolder`
|
||||
| `100`
|
||||
| `200`
|
||||
| `300`
|
||||
| `400`
|
||||
| `500`
|
||||
| `600`
|
||||
| `700`
|
||||
| `800`
|
||||
| `900`;
|
||||
style?: `normal` | `italic` | `oblique`;
|
||||
stretch?:
|
||||
| `normal`
|
||||
| `ultra-condensed`
|
||||
| `extra-condensed`
|
||||
| `condensed`
|
||||
| `semi-condensed`
|
||||
| `semi-expanded`
|
||||
| `expanded`
|
||||
| `extra-expanded`
|
||||
| `ultra-expanded`;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
testString?: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
showErrors: boolean;
|
||||
}
|
||||
|
||||
function useFontFaceObserver(
|
||||
fontFaces: FontFace[] = [],
|
||||
isEnabled = true,
|
||||
{ testString, timeout }: Options = {},
|
||||
{ showErrors }: Config = { showErrors: false },
|
||||
): boolean {
|
||||
const [isResolved, setIsResolved] = useState(false);
|
||||
const fontFacesString = JSON.stringify(fontFaces);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEnabled) {
|
||||
const promises = JSON.parse(fontFacesString).map(
|
||||
({ family, weight, style, stretch }: FontFace) =>
|
||||
new FontFaceObserver(family, {
|
||||
weight,
|
||||
style,
|
||||
stretch,
|
||||
}).load(testString, timeout),
|
||||
);
|
||||
|
||||
Promise.all(promises).then(() => setIsResolved(true));
|
||||
}
|
||||
}, [fontFacesString, testString, timeout, showErrors, isEnabled]);
|
||||
|
||||
return isResolved;
|
||||
}
|
||||
|
||||
export default useFontFaceObserver;
|
||||
43
frontend/src/hooks/useNotifications.tsx
Normal file
43
frontend/src/hooks/useNotifications.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { notification } from 'antd';
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import React, { createContext, useContext, useMemo } from 'react';
|
||||
|
||||
type Notification = {
|
||||
notifications: NotificationInstance;
|
||||
};
|
||||
|
||||
const defaultNotification: Notification = {
|
||||
notifications: {
|
||||
success: (): void => {},
|
||||
error: (): void => {},
|
||||
info: (): void => {},
|
||||
warning: (): void => {},
|
||||
open: (): void => {},
|
||||
destroy: (): void => {},
|
||||
},
|
||||
};
|
||||
|
||||
export const NotificationContext = createContext<Notification>(
|
||||
defaultNotification,
|
||||
);
|
||||
|
||||
export function NotificationProvider({
|
||||
children,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
}): JSX.Element {
|
||||
const [notificationApi, NotificationElement] = notification.useNotification();
|
||||
const notifications = useMemo(() => ({ notifications: notificationApi }), [
|
||||
notificationApi,
|
||||
]);
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider value={notifications}>
|
||||
{NotificationElement}
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useNotifications = (): Notification =>
|
||||
useContext(NotificationContext);
|
||||
@@ -50,8 +50,14 @@
|
||||
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
|
||||
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
|
||||
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Fira+Code"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; box-sizing: border-box;">
|
||||
<body style="margin: 0; padding: 0; box-sizing: border-box">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
@@ -37,7 +37,7 @@ export const convertRawQueriesToTraceSelectedTags = (
|
||||
queries: IResourceAttributeQuery[],
|
||||
): Tags[] =>
|
||||
queries.map((query) => ({
|
||||
Key: [convertMetricKeyToTrace(query.tagKey)],
|
||||
Key: convertMetricKeyToTrace(query.tagKey),
|
||||
Operator: convertOperatorLabelToTraceOperator(query.operator),
|
||||
StringValues: query.tagValue,
|
||||
NumberValues: [],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { notification } from 'antd';
|
||||
import get from 'api/alerts/get';
|
||||
import Spinner from 'components/Spinner';
|
||||
import ROUTES from 'constants/routes';
|
||||
import EditRulesContainer from 'container/EditRules';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -26,7 +26,7 @@ function EditRules(): JSX.Element {
|
||||
enabled: isValidRuleId,
|
||||
});
|
||||
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isValidRuleId) {
|
||||
@@ -42,12 +42,7 @@ function EditRules(): JSX.Element {
|
||||
ruleId == null ||
|
||||
(data?.payload?.data === undefined && !isLoading)
|
||||
) {
|
||||
return (
|
||||
<div>
|
||||
{NotificationElement}
|
||||
{data?.error || t('something_went_wrong')}
|
||||
</div>
|
||||
);
|
||||
return <div>{data?.error || t('something_went_wrong')}</div>;
|
||||
}
|
||||
|
||||
if (isLoading || !data?.payload) {
|
||||
@@ -55,13 +50,10 @@ function EditRules(): JSX.Element {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
<EditRulesContainer
|
||||
ruleId={parseInt(ruleId, 10)}
|
||||
initialValue={data.payload.data}
|
||||
/>
|
||||
</>
|
||||
<EditRulesContainer
|
||||
ruleId={parseInt(ruleId, 10)}
|
||||
initialValue={data.payload.data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
28
frontend/src/pages/Logs/PopoverContent.tsx
Normal file
28
frontend/src/pages/Logs/PopoverContent.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { InputNumber, Row, Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
interface PopoverContentProps {
|
||||
linesPerRow: number;
|
||||
handleLinesPerRowChange: (l: unknown) => void;
|
||||
}
|
||||
|
||||
function PopoverContent({
|
||||
linesPerRow,
|
||||
handleLinesPerRowChange,
|
||||
}: PopoverContentProps): JSX.Element {
|
||||
return (
|
||||
<Row align="middle">
|
||||
<Space align="center">
|
||||
<Typography>Max lines per Row </Typography>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={10}
|
||||
value={linesPerRow}
|
||||
onChange={handleLinesPerRowChange}
|
||||
/>
|
||||
</Space>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default PopoverContent;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user