Compare commits

..

1 Commits

Author SHA1 Message Date
Native
b0bbb9d318 fix: background color for full screen in light mode 2023-12-07 01:53:35 +05:30
117 changed files with 530 additions and 2620 deletions

4
.github/CODEOWNERS vendored
View File

@@ -8,4 +8,8 @@
/frontend/src/container/NewWidget/RightContainer/types.ts @srikanthccv
/deploy/ @prashant-shahi
/sample-apps/ @prashant-shahi
**/query-service/ @srikanthccv
Makefile @srikanthccv
go.* @srikanthccv
.git* @srikanthccv
.github @prashant-shahi

View File

@@ -1,32 +0,0 @@
name: Code Coverage
on:
push:
branches:
- develop
- main
- release/v*
pull_request:
branches:
- develop
- main
- release/v*
jobs:
coverage:
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
contents: write
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- uses: jwalton/gh-find-current-pr@v1
id: findPr
- uses: ArtiomTr/jest-coverage-report-action@v2
with:
package-manager: yarn
working-directory: frontend
test-script: yarn jest:coverage
github-token: ${{ secrets.GITHUB_TOKEN }}
output: comment
prnumber: ${{ steps.findPr.outputs.number }}

View File

@@ -34,7 +34,7 @@ jobs:
id: short-sha
- name: Get branch name
id: branch-name
uses: tj-actions/branch-names@v7.0.7
uses: tj-actions/branch-names@v5.1
- name: Set docker tag environment
run: |
if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then
@@ -78,7 +78,7 @@ jobs:
id: short-sha
- name: Get branch name
id: branch-name
uses: tj-actions/branch-names@v7.0.7
uses: tj-actions/branch-names@v5.1
- name: Set docker tag environment
run: |
if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then
@@ -127,7 +127,7 @@ jobs:
id: short-sha
- name: Get branch name
id: branch-name
uses: tj-actions/branch-names@v7.0.7
uses: tj-actions/branch-names@v5.1
- name: Set docker tag environment
run: |
if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then
@@ -176,7 +176,7 @@ jobs:
id: short-sha
- name: Get branch name
id: branch-name
uses: tj-actions/branch-names@v7.0.7
uses: tj-actions/branch-names@v5.1
- name: Set docker tag environment
run: |
if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then

View File

@@ -1,7 +1,7 @@
version: "3.9"
x-clickhouse-defaults: &clickhouse-defaults
image: clickhouse/clickhouse-server:23.11.1-alpine
image: clickhouse/clickhouse-server:23.7.3-alpine
tty: true
deploy:
restart_policy:
@@ -146,7 +146,7 @@ services:
condition: on-failure
query-service:
image: signoz/query-service:0.35.1
image: signoz/query-service:0.34.4
command:
[
"-config=/root/config/prometheus.yml",
@@ -186,7 +186,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:0.35.1
image: signoz/frontend:0.34.4
deploy:
restart_policy:
condition: on-failure
@@ -199,7 +199,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
image: signoz/signoz-otel-collector:0.88.3
image: signoz/signoz-otel-collector:0.88.1
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -237,7 +237,7 @@ services:
- query-service
otel-collector-migrator:
image: signoz/signoz-schema-migrator:0.88.3
image: signoz/signoz-schema-migrator:0.88.1
deploy:
restart_policy:
condition: on-failure
@@ -250,7 +250,7 @@ services:
# - clickhouse-3
otel-collector-metrics:
image: signoz/signoz-otel-collector:0.88.3
image: signoz/signoz-otel-collector:0.88.1
command:
[
"--config=/etc/otel-collector-metrics-config.yaml",

View File

@@ -66,7 +66,7 @@ services:
- --storage.path=/data
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.3}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.1}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -81,7 +81,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
otel-collector:
container_name: signoz-otel-collector
image: signoz/signoz-otel-collector:0.88.3
image: signoz/signoz-otel-collector:0.88.1
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -118,7 +118,7 @@ services:
otel-collector-metrics:
container_name: signoz-otel-collector-metrics
image: signoz/signoz-otel-collector:0.88.3
image: signoz/signoz-otel-collector:0.88.1
command:
[
"--config=/etc/otel-collector-metrics-config.yaml",

View File

@@ -3,7 +3,7 @@ version: "2.4"
x-clickhouse-defaults: &clickhouse-defaults
restart: on-failure
# addding non LTS version due to this fix https://github.com/ClickHouse/ClickHouse/commit/32caf8716352f45c1b617274c7508c86b7d1afab
image: clickhouse/clickhouse-server:23.11.1-alpine
image: clickhouse/clickhouse-server:23.7.3-alpine
tty: true
depends_on:
- zookeeper-1
@@ -164,7 +164,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.35.1}
image: signoz/query-service:${DOCKER_TAG:-0.34.4}
container_name: signoz-query-service
command:
[
@@ -203,7 +203,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.35.1}
image: signoz/frontend:${DOCKER_TAG:-0.34.4}
container_name: signoz-frontend
restart: on-failure
depends_on:
@@ -215,7 +215,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.3}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.1}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -229,7 +229,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.3}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.1}
container_name: signoz-otel-collector
command:
[
@@ -269,7 +269,7 @@ services:
condition: service_healthy
otel-collector-metrics:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.3}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.1}
container_name: signoz-otel-collector-metrics
command:
[

View File

@@ -12,7 +12,6 @@ import (
"github.com/gorilla/mux"
"go.signoz.io/signoz/ee/query-service/model"
"go.signoz.io/signoz/pkg/query-service/auth"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
@@ -48,18 +47,8 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
req.CreatedAt = time.Now().Unix()
req.Token = generatePATToken()
// default expiry is 30 days
if req.ExpiresAt == 0 {
req.ExpiresAt = time.Now().AddDate(0, 0, 30).Unix()
}
// max expiry is 1 year
if req.ExpiresAt > time.Now().AddDate(1, 0, 0).Unix() {
req.ExpiresAt = time.Now().AddDate(1, 0, 0).Unix()
}
zap.S().Debugf("Got PAT request: %+v", req)
var apierr basemodel.BaseApiError
if req, apierr = ah.AppDao().CreatePAT(ctx, req); apierr != nil {
if apierr := ah.AppDao().CreatePAT(ctx, &req); apierr != nil {
RespondError(w, apierr, nil)
return
}

View File

@@ -480,7 +480,7 @@ func (s *Server) analyticsMiddleware(next http.Handler) http.Handler {
}
}
if _, ok := telemetry.EnabledPaths()[path]; ok {
if _, ok := telemetry.IgnoredPaths()[path]; !ok {
userEmail, err := auth.GetEmailFromJwt(r.Context())
if err == nil {
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data, userEmail)

View File

@@ -33,7 +33,7 @@ type ModelDao interface {
DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError
GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError)
CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError)
CreatePAT(ctx context.Context, p *model.PAT) basemodel.BaseApiError
GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError)
GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError)
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError)

View File

@@ -3,15 +3,14 @@ package sqlite
import (
"context"
"fmt"
"strconv"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError) {
result, err := m.DB().ExecContext(ctx,
func (m *modelDao) CreatePAT(ctx context.Context, p *model.PAT) basemodel.BaseApiError {
_, err := m.DB().ExecContext(ctx,
"INSERT INTO personal_access_tokens (user_id, token, name, created_at, expires_at) VALUES ($1, $2, $3, $4, $5)",
p.UserID,
p.Token,
@@ -20,15 +19,9 @@ func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basem
p.ExpiresAt)
if err != nil {
zap.S().Errorf("Failed to insert PAT in db, err: %v", zap.Error(err))
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
return model.InternalError(fmt.Errorf("PAT insertion failed"))
}
id, err := result.LastInsertId()
if err != nil {
zap.S().Errorf("Failed to get last inserted id, err: %v", zap.Error(err))
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
}
p.Id = strconv.Itoa(int(id))
return p, nil
return nil
}
func (m *modelDao) ListPATs(ctx context.Context, userID string) ([]model.PAT, basemodel.BaseApiError) {
@@ -97,7 +90,7 @@ func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.U
u.org_id,
u.group_id
FROM users u, personal_access_tokens p
WHERE u.id = p.user_id and p.token=? and p.expires_at >= strftime('%s', 'now');`
WHERE u.id = p.user_id and p.token=?;`
if err := m.DB().Select(&users, query, token); err != nil {
return nil, model.InternalError(fmt.Errorf("failed to fetch user from PAT, err: %v", err))

View File

@@ -6,5 +6,5 @@ type PAT struct {
Token string `json:"token" db:"token"`
Name string `json:"name" db:"name"`
CreatedAt int64 `json:"createdAt" db:"created_at"`
ExpiresAt int64 `json:"expiresAt" db:"expires_at"`
ExpiresAt int64 `json:"expiresAt" db:"expires_at"` // unused as of now
}

View File

@@ -52,14 +52,14 @@ var BasicPlan = basemodel.FeatureSet{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: 20,
UsageLimit: 5,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: 10,
UsageLimit: 5,
Route: "",
},
basemodel.Feature{

View File

@@ -86,7 +86,6 @@ module.exports = {
},
],
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
'no-plusplus': 'off',
'jsx-a11y/label-has-associated-control': [
'error',
{
@@ -110,6 +109,7 @@ module.exports = {
// eslint rules need to remove
'@typescript-eslint/no-shadow': 'off',
'import/no-cycle': 'off',
'prettier/prettier': [
'error',
{},

View File

@@ -2,19 +2,3 @@
. "$(dirname "$0")/_/husky.sh"
cd frontend && yarn run commitlint --edit $1
branch="$(git rev-parse --abbrev-ref HEAD)"
color_red="$(tput setaf 1)"
bold="$(tput bold)"
reset="$(tput sgr0)"
if [ "$branch" = "main" ]; then
echo "${color_red}${bold}You can't commit directly to the main branch${reset}"
exit 1
fi
if [ "$branch" = "develop" ]; then
echo "${color_red}${bold}You can't commit directly to the develop branch${reset}"
exit 1
fi

View File

@@ -22,7 +22,7 @@ const config: Config.InitialOptions = {
'^.+\\.(js|jsx)$': 'babel-jest',
},
transformIgnorePatterns: [
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios)/)',
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend)/)',
],
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
testPathIgnorePatterns: ['/node_modules/', '/public/'],

View File

@@ -29,9 +29,6 @@
"dependencies": {
"@ant-design/colors": "6.0.0",
"@ant-design/icons": "4.8.0",
"@dnd-kit/core": "6.1.0",
"@dnd-kit/modifiers": "7.0.0",
"@dnd-kit/sortable": "8.0.0",
"@grafana/data": "^9.5.2",
"@mdx-js/loader": "2.3.0",
"@mdx-js/react": "2.3.0",
@@ -41,7 +38,7 @@
"ansi-to-html": "0.7.2",
"antd": "5.11.0",
"antd-table-saveas-excel": "2.2.1",
"axios": "1.6.2",
"axios": "^0.21.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^29.6.4",
"babel-loader": "9.1.3",
@@ -90,7 +87,7 @@
"react-helmet-async": "1.3.0",
"react-i18next": "^11.16.1",
"react-markdown": "8.0.7",
"react-query": "3.39.3",
"react-query": "^3.34.19",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0",
"react-syntax-highlighter": "15.5.0",

View File

@@ -21,9 +21,5 @@
"error_while_updating_variable": "Error while updating variable",
"dashboard_has_been_updated": "Dashboard has been updated",
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?",
"delete_dashboard_success": "{{name}} dashboard deleted successfully",
"dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.",
"dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
"your_graph_build_with": "Your graph built with",
"dashboar_ok_confirm": "query will be saved. Press OK to confirm."
"delete_dashboard_success": "{{name}} dashboard deleted successfully"
}

View File

@@ -24,9 +24,5 @@
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?",
"locked_dashboard_delete_tooltip_admin_author": "Dashboard is locked. Please unlock the dashboard to enable delete.",
"locked_dashboard_delete_tooltip_editor": "Dashboard is locked. Please contact admin to delete the dashboard.",
"delete_dashboard_success": "{{name}} dashboard deleted successfully",
"dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.",
"dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
"your_graph_build_with": "Your graph built with",
"dashboar_ok_confirm": "query will be saved. Press OK to confirm."
"delete_dashboard_success": "{{name}} dashboard deleted successfully"
}

View File

@@ -1,9 +1,10 @@
import cacheBursting from 'i18n-translations-hash.json';
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';
import cacheBursting from '../../i18n-translations-hash.json';
i18n
// load translation using http -> see /public/locales
.use(Backend)

View File

@@ -1,4 +1,4 @@
import { AxiosError, AxiosResponse } from 'axios';
import { AxiosError } from 'axios';
import { ErrorResponse } from 'types/api';
import { ErrorStatusCode } from 'types/common';
@@ -10,7 +10,7 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
const statusCode = response.status as ErrorStatusCode;
if (statusCode >= 400 && statusCode < 500) {
const { data } = response as AxiosResponse;
const { data } = response;
if (statusCode === 404) {
return {

View File

@@ -3,9 +3,9 @@ import { ApiResponse } from 'types/api';
import { Props } from 'types/api/dashboard/get';
import { Dashboard } from 'types/api/dashboard/getAll';
const getDashboard = (props: Props): Promise<Dashboard> =>
const get = (props: Props): Promise<Dashboard> =>
axios
.get<ApiResponse<Dashboard>>(`/dashboards/${props.uuid}`)
.then((res) => res.data.data);
export default getDashboard;
export default get;

View File

@@ -4,7 +4,7 @@
import getLocalStorageApi from 'api/browser/localstorage/get';
import loginApi from 'api/user/login';
import afterLogin from 'AppRoutes/utils';
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import { ENVIRONMENT } from 'constants/env';
import { LOCALSTORAGE } from 'constants/localStorage';
import store from 'store';
@@ -17,16 +17,14 @@ const interceptorsResponse = (
): Promise<AxiosResponse<any>> => Promise.resolve(value);
const interceptorsRequestResponse = (
value: InternalAxiosRequestConfig,
): InternalAxiosRequestConfig => {
value: AxiosRequestConfig,
): AxiosRequestConfig => {
const token =
store.getState().app.user?.accessJwt ||
getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN) ||
'';
if (value && value.headers) {
value.headers.Authorization = token ? `Bearer ${token}` : '';
}
value.headers.Authorization = token ? `Bearer ${token}` : '';
return value;
};
@@ -94,8 +92,8 @@ const instance = axios.create({
baseURL: `${ENVIRONMENT.baseURL}${apiV1}`,
});
instance.interceptors.request.use(interceptorsRequestResponse);
instance.interceptors.response.use(interceptorsResponse, interceptorRejected);
instance.interceptors.request.use(interceptorsRequestResponse);
export const AxiosAlertManagerInstance = axios.create({
baseURL: `${ENVIRONMENT.baseURL}${apiAlertManager}`,

View File

@@ -9,10 +9,9 @@ import {
export const getMetricsQueryRange = async (
props: QueryRangePayload,
signal: AbortSignal,
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
try {
const response = await axios.post('/query_range', props, { signal });
const response = await axios.post('/query_range', props);
return {
statusCode: 200,

View File

@@ -5,7 +5,6 @@ import { QueryParams } from 'constants/query';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import isEqual from 'lodash-es/isEqual';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import {
DeleteViewHandlerProps,
@@ -36,45 +35,6 @@ export const getViewDetailsUsingViewKey: GetViewDetailsUsingViewKey = (
return undefined;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const omitIdFromQuery = (query: Query | null): any => ({
...query,
builder: {
...query?.builder,
queryData: query?.builder.queryData.map((queryData) => {
const { id, ...rest } = queryData.aggregateAttribute;
const newAggregateAttribute = rest;
const newGroupByAttributes = queryData.groupBy.map((groupByAttribute) => {
const { id, ...rest } = groupByAttribute;
return rest;
});
const newItems = queryData.filters.items.map((item) => {
const { id, ...newItem } = item;
if (item.key) {
const { id, ...rest } = item.key;
return {
...newItem,
key: rest,
};
}
return newItem;
});
return {
...queryData,
aggregateAttribute: newAggregateAttribute,
groupBy: newGroupByAttributes,
filters: {
...queryData.filters,
items: newItems,
},
limit: queryData.limit ? queryData.limit : 0,
offset: queryData.offset ? queryData.offset : 0,
pageSize: queryData.pageSize ? queryData.pageSize : 0,
};
}),
},
});
export const isQueryUpdatedInView = ({
viewKey,
data,
@@ -88,7 +48,43 @@ export const isQueryUpdatedInView = ({
const { query, panelType } = currentViewDetails;
// Omitting id from aggregateAttribute and groupBy
const updatedCurrentQuery = omitIdFromQuery(stagedQuery);
const updatedCurrentQuery = {
...stagedQuery,
builder: {
...stagedQuery?.builder,
queryData: stagedQuery?.builder.queryData.map((queryData) => {
const { id, ...rest } = queryData.aggregateAttribute;
const newAggregateAttribute = rest;
const newGroupByAttributes = queryData.groupBy.map((groupByAttribute) => {
const { id, ...rest } = groupByAttribute;
return rest;
});
const newItems = queryData.filters.items.map((item) => {
const { id, ...newItem } = item;
if (item.key) {
const { id, ...rest } = item.key;
return {
...newItem,
key: rest,
};
}
return newItem;
});
return {
...queryData,
aggregateAttribute: newAggregateAttribute,
groupBy: newGroupByAttributes,
filters: {
...queryData.filters,
items: newItems,
},
limit: queryData.limit ? queryData.limit : 0,
offset: queryData.offset ? queryData.offset : 0,
pageSize: queryData.pageSize ? queryData.pageSize : 0,
};
}),
},
};
return (
panelType !== currentPanelType ||

View File

@@ -1,9 +1,8 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './Uplot.styles.scss';
import './uplot.scss';
import { Typography } from 'antd';
import { ToggleGraphProps } from 'components/Graph/types';
import { LineChart } from 'lucide-react';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import {
forwardRef,
@@ -128,16 +127,6 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
}
}, [data, resetScales, create]);
if (data && data[0] && data[0]?.length === 0) {
return (
<div className="uplot-no-data not-found">
<LineChart size={48} strokeWidth={0.5} />
<Typography>No Data</Typography>
</div>
);
}
return (
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<div className="uplot-graph-container" ref={targetRef}>

View File

@@ -13,11 +13,3 @@
height: 100%;
width: 100%;
}
.uplot-no-data {
position: relative;
display: flex;
width: 100%;
flex-direction: column;
gap: 8px;
}

View File

@@ -10,7 +10,7 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -18,7 +18,6 @@ import { AlertDef } from 'types/api/alerts/def';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getTimeRange } from 'utils/getTimeRange';
import { ChartContainer, FailedMessageContainer } from './styles';
import { getThresholdLabel } from './utils';
@@ -33,7 +32,6 @@ export interface ChartPreviewProps {
alertDef?: AlertDef;
userQueryKey?: string;
allowSelectedIntervalForStepGen?: boolean;
yAxisUnit: string;
}
function ChartPreview({
@@ -46,17 +44,12 @@ function ChartPreview({
userQueryKey,
allowSelectedIntervalForStepGen = false,
alertDef,
yAxisUnit,
}: ChartPreviewProps): JSX.Element | null {
const { t } = useTranslation('alerts');
const threshold = alertDef?.condition.target || 0;
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const canQuery = useMemo((): boolean => {
if (!query || query == null) {
@@ -106,13 +99,6 @@ function ChartPreview({
const graphRef = useRef<HTMLDivElement>(null);
useEffect((): void => {
const { startTime, endTime } = getTimeRange(queryResponse);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [maxTime, minTime, globalSelectedInterval, queryResponse]);
const chartData = getUPlotChartData(queryResponse?.data?.payload);
const containerDimensions = useResizeObserver(graphRef);
@@ -126,11 +112,9 @@ function ChartPreview({
() =>
getUPlotChartOptions({
id: 'alert_legend_widget',
yAxisUnit,
yAxisUnit: query?.unit,
apiResponse: queryResponse?.data?.payload,
dimensions: containerDimensions,
minTimeScale,
maxTimeScale,
isDarkMode,
thresholds: [
{
@@ -145,18 +129,16 @@ function ChartPreview({
optionName,
threshold,
alertDef?.condition.targetUnit,
yAxisUnit,
query?.unit,
)})`,
thresholdUnit: alertDef?.condition.targetUnit,
},
],
}),
[
yAxisUnit,
query?.unit,
queryResponse?.data?.payload,
containerDimensions,
minTimeScale,
maxTimeScale,
isDarkMode,
threshold,
t,
@@ -186,7 +168,7 @@ function ChartPreview({
name={name || 'Chart Preview'}
panelData={queryResponse.data?.payload.data.newResult.data.result || []}
query={query || initialQueriesMap.metrics}
yAxisUnit={yAxisUnit}
yAxisUnit={query?.unit}
/>
</div>
)}

View File

@@ -61,20 +61,8 @@ export const getThresholdLabel = (
unit === MiscellaneousFormats.PercentUnit ||
yAxisUnit === MiscellaneousFormats.PercentUnit
) {
if (unit === MiscellaneousFormats.Percent) {
return `${value}%`;
}
return `${value * 100}%`;
}
if (
unit === MiscellaneousFormats.Percent ||
yAxisUnit === MiscellaneousFormats.Percent
) {
if (unit === MiscellaneousFormats.PercentUnit) {
return `${value * 100}%`;
}
return `${value}%`;
}
return `${value} ${optionName}`;
};

View File

@@ -82,7 +82,6 @@ function FormAlertRules({
// alertDef holds the form values to be posted
const [alertDef, setAlertDef] = useState<AlertDef>(initialValue);
const [yAxisUnit, setYAxisUnit] = useState<string>(currentQuery.unit || '');
// initQuery contains initial query when component was mounted
const initQuery = useMemo(() => initialValue.condition.compositeQuery, [
@@ -401,7 +400,6 @@ function FormAlertRules({
query={stagedQuery}
selectedInterval={globalSelectedInterval}
alertDef={alertDef}
yAxisUnit={yAxisUnit || ''}
/>
);
@@ -417,7 +415,6 @@ function FormAlertRules({
query={stagedQuery}
alertDef={alertDef}
selectedInterval={globalSelectedInterval}
yAxisUnit={yAxisUnit || ''}
/>
);
@@ -430,8 +427,7 @@ function FormAlertRules({
currentQuery.queryType === EQueryType.QUERY_BUILDER &&
alertType !== AlertTypes.METRICS_BASED_ALERT;
const onUnitChangeHandler = (value: string): void => {
setYAxisUnit(value);
const onUnitChangeHandler = (): void => {
// reset target unit
setAlertDef((def) => ({
...def,
@@ -461,10 +457,7 @@ function FormAlertRules({
renderPromAndChQueryChartPreview()}
<StepContainer>
<BuilderUnitsFilter
onChange={onUnitChangeHandler}
yAxisUnit={yAxisUnit}
/>
<BuilderUnitsFilter onChange={onUnitChangeHandler} />
</StepContainer>
<QuerySection

View File

@@ -1,4 +1,3 @@
import { Tooltip } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { LabelContainer } from '../styles';
@@ -24,9 +23,7 @@ function Label({
disabled={disabled}
onClick={onClickHandler}
>
<Tooltip title={label} placement="topLeft">
{getAbbreviatedLabel(label)}
</Tooltip>
{getAbbreviatedLabel(label)}
</LabelContainer>
);
}

View File

@@ -23,7 +23,6 @@ import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import uPlot from 'uplot';
import { getTimeRange } from 'utils/getTimeRange';
import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './contants';
import GraphManager from './GraphManager';
@@ -93,21 +92,6 @@ function FullView({
const isDarkMode = useIsDarkMode();
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
useEffect((): void => {
const { startTime, endTime } = getTimeRange(response);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [maxTime, minTime, globalSelectedInterval, response]);
useEffect(() => {
if (!response.isFetching && fullViewRef.current) {
const width = fullViewRef.current?.clientWidth
@@ -130,8 +114,6 @@ function FullView({
graphsVisibilityStates,
setGraphsVisibilityStates,
thresholds: widget.thresholds,
minTimeScale,
maxTimeScale,
});
setChartOptions(newChartOptions);

View File

@@ -1,5 +1,4 @@
import { Skeleton, Typography } from 'antd';
import cx from 'classnames';
import { ToggleGraphProps } from 'components/Graph/types';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query';
@@ -299,10 +298,7 @@ function WidgetGraphComponent({
</div>
{queryResponse.isLoading && <Skeleton />}
{queryResponse.isSuccess && (
<div
className={cx('widget-graph-container', widget.panelTypes)}
ref={graphRef}
>
<div style={{ height: '90%' }} ref={graphRef}>
<GridPanelSwitch
panelType={widget.panelTypes}
data={data}

View File

@@ -15,7 +15,6 @@ import { useDispatch, useSelector } from 'react-redux';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getTimeRange } from 'utils/getTimeRange';
import EmptyWidget from '../EmptyWidget';
import { MenuItemKeys } from '../WidgetHeader/contants';
@@ -35,8 +34,6 @@ function GridCardGraph({
const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>();
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const onDragSelect = useCallback(
(start: number, end: number): void => {
@@ -65,16 +62,16 @@ function GridCardGraph({
}
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
const updatedQuery = useStepInterval(widget?.query);
const isEmptyWidget =
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const updatedQuery = useStepInterval(widget?.query);
const isEmptyWidget =
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
const queryResponse = useGetQueryRange(
{
selectedTime: widget?.timePreferance,
@@ -106,13 +103,6 @@ function GridCardGraph({
const containerDimensions = useResizeObserver(graphRef);
useEffect((): void => {
const { startTime, endTime } = getTimeRange(queryResponse);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [maxTime, minTime, globalSelectedInterval, queryResponse]);
const chartData = getUPlotChartData(queryResponse?.data?.payload, fillSpans);
const isDarkMode = useIsDarkMode();
@@ -133,8 +123,6 @@ function GridCardGraph({
yAxisUnit: widget?.yAxisUnit,
onClickHandler,
thresholds: widget.thresholds,
minTimeScale,
maxTimeScale,
}),
[
widget?.id,
@@ -145,8 +133,6 @@ function GridCardGraph({
isDarkMode,
onDragSelect,
onClickHandler,
minTimeScale,
maxTimeScale,
],
);

View File

@@ -1,15 +1,12 @@
.fullscreen-grid-container {
overflow: auto;
.react-grid-layout {
border: none !important;
}
}
.widget-graph-container {
height: 100%;
&.graph {
height: calc(100% - 30px);
}
.fullscreen-grid-container--light {
@extend .fullscreen-grid-container;
background-color: #f5f5f5;
}

View File

@@ -3,7 +3,6 @@ import './GridCardLayout.styles.scss';
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import { useIsDarkMode } from 'hooks/useDarkMode';
@@ -142,7 +141,14 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
)}
</ButtonContainer>
<FullScreen handle={handle} className="fullscreen-grid-container">
<FullScreen
handle={handle}
className={
isDarkMode
? 'fullscreen-grid-container'
: 'fullscreen-grid-container--light'
}
>
<ReactGridLayout
cols={12}
rowHeight={100}
@@ -156,7 +162,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
onLayoutChange={setLayouts}
draggableHandle=".drag-handle"
layout={layouts}
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
>
{layouts.map((layout) => {
const { i: id } = layout;

View File

@@ -2,12 +2,9 @@
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
height: 30px;
width: 100%;
padding: 0.5rem;
box-sizing: border-box;
font-size: 14px;
font-weight: 600;
}
.widget-header-title {
@@ -22,10 +19,6 @@
visibility: hidden;
border: none;
box-shadow: none;
cursor: pointer;
font: 14px;
font-weight: 600;
padding: 8px;
}
.widget-header-hover {

View File

@@ -10,7 +10,7 @@ import {
MoreOutlined,
WarningOutlined,
} from '@ant-design/icons';
import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
import { Button, Dropdown, MenuProps, Tooltip, Typography } from 'antd';
import Spinner from 'components/Spinner';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -199,7 +199,9 @@ function WidgetHeader({
</Tooltip>
)}
<Dropdown menu={menu} trigger={['hover']} placement="bottomRight">
<MoreOutlined
<Button
type="default"
icon={<MoreOutlined />}
className={`widget-header-more-options ${
parentHover ? 'widget-header-hover' : ''
}`}

View File

@@ -2,7 +2,7 @@ import { Button as ButtonComponent, Card as CardComponent, Space } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { StyledCSS } from 'container/GantChart/Trace/styles';
import RGL, { WidthProvider } from 'react-grid-layout';
import styled, { css } from 'styled-components';
import styled, { css, FlattenSimpleInterpolation } from 'styled-components';
const ReactGridLayoutComponent = WidthProvider(RGL);
@@ -17,8 +17,14 @@ export const Card = styled(CardComponent)<CardProps>`
}
.ant-card-body {
height: calc(100% - 40px);
height: 90%;
padding: 0;
${({ $panelType }): FlattenSimpleInterpolation =>
$panelType === PANEL_TYPES.TABLE
? css`
padding-top: 1.8rem;
`
: css``}
}
`;

View File

@@ -8,18 +8,5 @@
.upgrade-link {
padding: 0px;
padding-right: 4px;
display: inline !important;
color: white;
text-decoration: underline;
text-decoration-color: white;
text-decoration-thickness: 2px;
text-underline-offset: 2px;
&:hover {
color: white;
text-decoration: underline;
text-decoration-color: white;
text-decoration-thickness: 2px;
text-underline-offset: 2px;
}
}

View File

@@ -1,6 +1,3 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/anchor-is-valid */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import './Header.styles.scss';
import {
@@ -138,17 +135,16 @@ function HeaderContainer(): JSX.Element {
<>
{showTrialExpiryBanner && (
<div className="trial-expiry-banner">
You are in free trial period. Your free trial will end on{' '}
You are in free trial period. Your free trial will end on
<span>
{getFormattedDate(licenseData?.payload?.trialEnd || Date.now())}.
</span>
{role === 'ADMIN' ? (
<span>
{' '}
Please{' '}
<a className="upgrade-link" onClick={handleUpgrade}>
Please
<Button className="upgrade-link" type="link" onClick={handleUpgrade}>
upgrade
</a>
</Button>
to continue using SigNoz features.
</span>
) : (

View File

@@ -1,19 +0,0 @@
import { TopOperationList } from '../TopOperationsTable';
interface TopOperation {
numCalls: number;
errorCount: number;
}
export const getTopOperationList = ({
errorCount,
numCalls,
}: TopOperation): TopOperationList =>
({
p50: 0,
errorCount,
name: 'test',
numCalls,
p95: 0,
p99: 0,
} as TopOperationList);

View File

@@ -1,70 +0,0 @@
import { getTopOperationList } from './__mocks__/getTopOperation';
import { TopOperationList } from './TopOperationsTable';
import {
convertedTracesToDownloadData,
getErrorRate,
getNearestHighestBucketValue,
} from './utils';
describe('Error Rate', () => {
test('should return correct error rate', () => {
const list: TopOperationList = getTopOperationList({
errorCount: 10,
numCalls: 100,
});
expect(getErrorRate(list)).toBe(10);
});
test('should handle no errors gracefully', () => {
const list = getTopOperationList({ errorCount: 0, numCalls: 100 });
expect(getErrorRate(list)).toBe(0);
});
test('should handle zero calls', () => {
const list = getTopOperationList({ errorCount: 0, numCalls: 0 });
expect(getErrorRate(list)).toBe(0);
});
});
describe('getNearestHighestBucketValue', () => {
test('should return nearest higher bucket value', () => {
expect(getNearestHighestBucketValue(50, [10, 20, 30, 40, 60, 70])).toBe('60');
});
test('should return +Inf for value higher than any bucket', () => {
expect(getNearestHighestBucketValue(80, [10, 20, 30, 40, 60, 70])).toBe(
'+Inf',
);
});
test('should return the first bucket for value lower than all buckets', () => {
expect(getNearestHighestBucketValue(5, [10, 20, 30, 40, 60, 70])).toBe('10');
});
});
describe('convertedTracesToDownloadData', () => {
test('should convert trace data correctly', () => {
const data = [
{
name: 'op1',
p50: 50000000,
p95: 95000000,
p99: 99000000,
numCalls: 100,
errorCount: 10,
},
];
expect(convertedTracesToDownloadData(data)).toEqual([
{
Name: 'op1',
'P50 (in ms)': '50.00',
'P95 (in ms)': '95.00',
'P99 (in ms)': '99.00',
'Number of calls': '100',
'Error Rate (%)': '10.00',
},
]);
});
});

View File

@@ -5,12 +5,8 @@ import history from 'lib/history';
import { TopOperationList } from './TopOperationsTable';
import { NavigateToTraceProps } from './types';
export const getErrorRate = (list: TopOperationList): number => {
if (list.errorCount === 0 && list.numCalls === 0) {
return 0;
}
return (list.errorCount / list.numCalls) * 100;
};
export const getErrorRate = (list: TopOperationList): number =>
(list.errorCount / list.numCalls) * 100;
export const navigateToTrace = ({
servicename,

View File

@@ -29,7 +29,7 @@ function SettingsDrawer({ drawerTitle }: { drawerTitle: string }): JSX.Element {
<DrawerContainer
title={drawerTitle}
placement="right"
width="60%"
width="50%"
onClose={onClose}
open={visible}
>

View File

@@ -1,5 +0,0 @@
.delete-variable-name {
font-weight: 700;
color: rgb(207, 19, 34);
font-style: italic;
}

View File

@@ -18,10 +18,10 @@ import {
VariableQueryTypeArr,
VariableSortTypeArr,
} from 'types/api/dashboard/getAll';
import { v4 as generateUUID } from 'uuid';
import { v4 } from 'uuid';
import { variablePropsToPayloadVariables } from '../../../utils';
import { TVariableMode } from '../types';
import { TVariableViewMode } from '../types';
import { LabelContainer, VariableItemRow } from './styles';
const { Option } = Select;
@@ -30,9 +30,9 @@ interface VariableItemProps {
variableData: IDashboardVariable;
existingVariables: Record<string, IDashboardVariable>;
onCancel: () => void;
onSave: (mode: TVariableMode, variableData: IDashboardVariable) => void;
onSave: (name: string, arg0: IDashboardVariable, arg1: string) => void;
validateName: (arg0: string) => boolean;
mode: TVariableMode;
variableViewMode: TVariableViewMode;
}
function VariableItem({
variableData,
@@ -40,7 +40,7 @@ function VariableItem({
onCancel,
onSave,
validateName,
mode,
variableViewMode,
}: VariableItemProps): JSX.Element {
const [variableName, setVariableName] = useState<string>(
variableData.name || '',
@@ -97,7 +97,7 @@ function VariableItem({
]);
const handleSave = (): void => {
const variable: IDashboardVariable = {
const newVariableData: IDashboardVariable = {
name: variableName,
description: variableDescription,
type: queryType,
@@ -111,12 +111,16 @@ function VariableItem({
selectedValue: (variableData.selectedValue ||
variableTextboxValue) as never,
}),
modificationUUID: generateUUID(),
id: variableData.id || generateUUID(),
order: variableData.order,
modificationUUID: v4(),
};
onSave(mode, variable);
onSave(
variableName,
newVariableData,
(variableViewMode === 'EDIT' && variableName !== variableData.name
? variableData.name
: '') as string,
);
onCancel();
};
// Fetches the preview values for the SQL variable query
@@ -136,12 +140,11 @@ function VariableItem({
enabled: false,
queryFn: () =>
dashboardVariablesQuery({
query: variableQueryValue || '',
query: variableData.queryValue || '',
variables: variablePropsToPayloadVariables(existingVariables),
}),
refetchOnWindowFocus: false,
onSuccess: (response) => {
setErrorPreview(null);
handleQueryResult(response);
},
onError: (error: {
@@ -171,6 +174,7 @@ function VariableItem({
return (
<div className="variable-item-container">
<div className="variable-item-content">
{/* <Typography.Title level={3}>Add Variable</Typography.Title> */}
<VariableItemRow>
<LabelContainer>
<Typography>Name</Typography>

View File

@@ -1,78 +1,20 @@
import '../DashboardSettings.styles.scss';
import { blue, red } from '@ant-design/colors';
import { MenuOutlined, PlusOutlined } from '@ant-design/icons';
import type { DragEndEvent, UniqueIdentifier } from '@dnd-kit/core';
import {
DndContext,
PointerSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable';
// eslint-disable-next-line import/no-extraneous-dependencies
import { CSS } from '@dnd-kit/utilities';
import { Button, Modal, Row, Space, Table, Typography } from 'antd';
import { RowProps } from 'antd/lib';
import { convertVariablesToDbFormat } from 'container/NewDashboard/DashboardVariablesSelection/util';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Modal, Row, Space, Tag } from 'antd';
import { ResizeTable } from 'components/ResizeTable';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useNotifications } from 'hooks/useNotifications';
import { PencilIcon, TrashIcon } from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import React, { useEffect, useRef, useState } from 'react';
import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
import { TVariableMode } from './types';
import { TVariableViewMode } from './types';
import VariableItem from './VariableItem/VariableItem';
function TableRow({ children, ...props }: RowProps): JSX.Element {
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
} = useSortable({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
id: props['data-row-key'],
});
const style: React.CSSProperties = {
...props.style,
transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }),
transition,
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
return (
// eslint-disable-next-line react/jsx-props-no-spreading
<tr {...props} ref={setNodeRef} style={style} {...attributes}>
{React.Children.map(children, (child) => {
if ((child as React.ReactElement).key === 'sort') {
return React.cloneElement(child as React.ReactElement, {
children: (
<MenuOutlined
ref={setActivatorNodeRef}
style={{ touchAction: 'none', cursor: 'move' }}
// eslint-disable-next-line react/jsx-props-no-spreading
{...listeners}
/>
),
});
}
return child;
})}
</tr>
);
}
function VariablesSetting(): JSX.Element {
const variableToDelete = useRef<IDashboardVariable | null>(null);
const variableToDelete = useRef<string | null>(null);
const [deleteVariableModal, setDeleteVariableModal] = useState(false);
const { t } = useTranslation(['dashboard']);
@@ -83,15 +25,16 @@ function VariablesSetting(): JSX.Element {
const { variables = {} } = selectedDashboard?.data || {};
const [variablesTableData, setVariablesTableData] = useState<any>([]);
const [variblesOrderArr, setVariablesOrderArr] = useState<number[]>([]);
const [existingVariableNamesMap, setExistingVariableNamesMap] = useState<
Record<string, string>
>({});
const variablesTableData = Object.keys(variables).map((variableName) => ({
key: variableName,
name: variableName,
...variables[variableName],
}));
const [variableViewMode, setVariableViewMode] = useState<null | TVariableMode>(
null,
);
const [
variableViewMode,
setVariableViewMode,
] = useState<null | TVariableViewMode>(null);
const [
variableEditData,
@@ -104,7 +47,7 @@ function VariablesSetting(): JSX.Element {
};
const onVariableViewModeEnter = (
viewType: TVariableMode,
viewType: TVariableViewMode,
varData: IDashboardVariable,
): void => {
setVariableEditData(varData);
@@ -113,41 +56,6 @@ function VariablesSetting(): JSX.Element {
const updateMutation = useUpdateDashboard();
useEffect(() => {
const tableRowData = [];
const variableOrderArr = [];
const variableNamesMap = {};
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(variables)) {
const { order, id, name } = value;
tableRowData.push({
key,
name: key,
...variables[key],
id,
});
if (name) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
variableNamesMap[name] = name;
}
if (order) {
variableOrderArr.push(order);
}
}
tableRowData.sort((a, b) => a.order - b.order);
variableOrderArr.sort((a, b) => a - b);
setVariablesTableData(tableRowData);
setVariablesOrderArr(variableOrderArr);
setExistingVariableNamesMap(variableNamesMap);
}, [variables]);
const updateVariables = (
updatedVariablesData: Dashboard['data']['variables'],
): void => {
@@ -181,58 +89,34 @@ function VariablesSetting(): JSX.Element {
);
};
const getVariableOrder = (): number => {
if (variblesOrderArr && variblesOrderArr.length > 0) {
return variblesOrderArr[variblesOrderArr.length - 1] + 1;
}
return 0;
};
const onVariableSaveHandler = (
mode: TVariableMode,
name: string,
variableData: IDashboardVariable,
oldName: string,
): void => {
const updatedVariableData = {
...variableData,
order: variableData?.order >= 0 ? variableData.order : getVariableOrder(),
};
const newVariablesArr = variablesTableData.map(
(variable: IDashboardVariable) => {
if (variable.id === updatedVariableData.id) {
return updatedVariableData;
}
return variable;
},
);
if (mode === 'ADD') {
newVariablesArr.push(updatedVariableData);
if (!variableData.name) {
return;
}
const variables = convertVariablesToDbFormat(newVariablesArr);
const newVariables = { ...variables };
newVariables[name] = variableData;
setVariablesTableData(newVariablesArr);
updateVariables(variables);
if (oldName) {
delete newVariables[oldName];
}
updateVariables(newVariables);
onDoneVariableViewMode();
};
const onVariableDeleteHandler = (variable: IDashboardVariable): void => {
variableToDelete.current = variable;
const onVariableDeleteHandler = (variableName: string): void => {
variableToDelete.current = variableName;
setDeleteVariableModal(true);
};
const handleDeleteConfirm = (): void => {
const newVariablesArr = variablesTableData.filter(
(variable: IDashboardVariable) =>
variable.id !== variableToDelete?.current?.id,
);
const updatedVariables = convertVariablesToDbFormat(newVariablesArr);
updateVariables(updatedVariables);
const newVariables = { ...variables };
if (variableToDelete?.current) delete newVariables[variableToDelete?.current];
updateVariables(newVariables);
variableToDelete.current = null;
setDeleteVariableModal(false);
};
@@ -241,36 +125,31 @@ function VariablesSetting(): JSX.Element {
setDeleteVariableModal(false);
};
const validateVariableName = (name: string): boolean =>
!existingVariableNamesMap[name];
const validateVariableName = (name: string): boolean => !variables[name];
const columns = [
{
key: 'sort',
width: '10%',
},
{
title: 'Variable',
dataIndex: 'name',
width: '40%',
width: 100,
key: 'name',
},
{
title: 'Description',
dataIndex: 'description',
width: '35%',
width: 100,
key: 'description',
},
{
title: 'Actions',
width: '15%',
width: 50,
key: 'action',
render: (variable: IDashboardVariable): JSX.Element => (
render: (_: IDashboardVariable): JSX.Element => (
<Space>
<Button
type="text"
style={{ padding: 8, cursor: 'pointer', color: blue[5] }}
onClick={(): void => onVariableViewModeEnter('EDIT', variable)}
onClick={(): void => onVariableViewModeEnter('EDIT', _)}
>
<PencilIcon size={14} />
</Button>
@@ -278,9 +157,7 @@ function VariablesSetting(): JSX.Element {
type="text"
style={{ padding: 8, color: red[6], cursor: 'pointer' }}
onClick={(): void => {
if (variable) {
onVariableDeleteHandler(variable);
}
if (_.name) onVariableDeleteHandler(_.name);
}}
>
<TrashIcon size={14} />
@@ -290,51 +167,6 @@ function VariablesSetting(): JSX.Element {
},
];
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
// https://docs.dndkit.com/api-documentation/sensors/pointer#activation-constraints
distance: 1,
},
}),
);
const onDragEnd = ({ active, over }: DragEndEvent): void => {
if (active.id !== over?.id) {
const activeIndex = variablesTableData.findIndex(
(i: { key: UniqueIdentifier }) => i.key === active.id,
);
const overIndex = variablesTableData.findIndex(
(i: { key: UniqueIdentifier | undefined }) => i.key === over?.id,
);
const updatedVariables: IDashboardVariable[] = arrayMove(
variablesTableData,
activeIndex,
overIndex,
);
const reArrangedVariables = {};
for (let index = 0; index < updatedVariables.length; index += 1) {
const variableName = updatedVariables[index].name;
if (variableName) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
reArrangedVariables[variableName] = {
...updatedVariables[index],
order: index,
};
}
}
updateVariables(reArrangedVariables);
setVariablesTableData(updatedVariables);
}
};
return (
<>
{variableViewMode ? (
@@ -344,17 +176,11 @@ function VariablesSetting(): JSX.Element {
onSave={onVariableSaveHandler}
onCancel={onDoneVariableViewMode}
validateName={validateVariableName}
mode={variableViewMode}
variableViewMode={variableViewMode}
/>
) : (
<>
<Row
style={{
flexDirection: 'row',
justifyContent: 'flex-end',
padding: '0.5rem 0',
}}
>
<Row style={{ flexDirection: 'row-reverse', padding: '0.5rem 0' }}>
<Button
data-testid="add-new-variable"
type="primary"
@@ -366,28 +192,7 @@ function VariablesSetting(): JSX.Element {
</Button>
</Row>
<DndContext
sensors={sensors}
modifiers={[restrictToVerticalAxis]}
onDragEnd={onDragEnd}
>
<SortableContext
// rowKey array
items={variablesTableData.map((variable: { key: any }) => variable.key)}
>
<Table
components={{
body: {
row: TableRow,
},
}}
rowKey="key"
columns={columns}
pagination={false}
dataSource={variablesTableData}
/>
</SortableContext>
</DndContext>
<ResizeTable columns={columns} dataSource={variablesTableData} />
</>
)}
<Modal
@@ -397,13 +202,8 @@ function VariablesSetting(): JSX.Element {
onOk={handleDeleteConfirm}
onCancel={handleDeleteCancel}
>
<Typography.Text>
Are you sure you want to delete variable{' '}
<span className="delete-variable-name">
{variableToDelete?.current?.name}
</span>
?
</Typography.Text>
Are you sure you want to delete variable{' '}
<Tag>{variableToDelete.current}</Tag>?
</Modal>
</>
);

View File

@@ -1,7 +1 @@
export type TVariableMode = 'VIEW' | 'EDIT' | 'ADD';
export const VariableModes = {
VIEW: 'VIEW',
EDIT: 'EDIT',
ADD: 'ADD',
};
export type TVariableViewMode = 'EDIT' | 'ADD';

View File

@@ -1,14 +1,14 @@
import { Row } from 'antd';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useNotifications } from 'hooks/useNotifications';
import { map, sortBy } from 'lodash-es';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { memo, useEffect, useState } from 'react';
import { memo, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app';
import { convertVariablesToDbFormat } from './util';
import VariableItem from './VariableItem';
function DashboardVariableSelection(): JSX.Element | null {
@@ -21,32 +21,8 @@ function DashboardVariableSelection(): JSX.Element | null {
const [update, setUpdate] = useState<boolean>(false);
const [lastUpdatedVar, setLastUpdatedVar] = useState<string>('');
const [variablesTableData, setVariablesTableData] = useState<any>([]);
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
useEffect(() => {
if (variables) {
const tableRowData = [];
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(variables)) {
const { id } = value;
tableRowData.push({
key,
name: key,
...variables[key],
id,
});
}
tableRowData.sort((a, b) => a.order - b.order);
setVariablesTableData(tableRowData);
}
}, [variables]);
const onVarChanged = (name: string): void => {
setLastUpdatedVar(name);
setUpdate(!update);
@@ -88,56 +64,40 @@ function DashboardVariableSelection(): JSX.Element | null {
const onValueUpdate = (
name: string,
id: string,
value: IDashboardVariable['selectedValue'],
allSelected: boolean,
): void => {
if (id) {
const newVariablesArr = variablesTableData.map(
(variable: IDashboardVariable) => {
const variableCopy = { ...variable };
const updatedVariablesData = { ...variables };
updatedVariablesData[name].selectedValue = value;
updatedVariablesData[name].allSelected = allSelected;
if (variableCopy.id === id) {
variableCopy.selectedValue = value;
variableCopy.allSelected = allSelected;
}
console.log('onValue Update', name);
return variableCopy;
},
);
const variables = convertVariablesToDbFormat(newVariablesArr);
if (role !== 'VIEWER' && selectedDashboard) {
updateVariables(name, variables);
}
onVarChanged(name);
setUpdate(!update);
if (role !== 'VIEWER' && selectedDashboard) {
updateVariables(name, updatedVariablesData);
}
onVarChanged(name);
setUpdate(!update);
};
if (!variables) {
return null;
}
const orderBasedSortedVariables = variablesTableData.sort(
(a: { order: number }, b: { order: number }) => a.order - b.order,
);
const variablesKeys = sortBy(Object.keys(variables));
return (
<Row>
{orderBasedSortedVariables &&
Array.isArray(orderBasedSortedVariables) &&
orderBasedSortedVariables.length > 0 &&
orderBasedSortedVariables.map((variable) => (
{variablesKeys &&
map(variablesKeys, (variableName) => (
<VariableItem
key={`${variable.name}${variable.id}}${variable.order}`}
key={`${variableName}${variables[variableName].modificationUUID}`}
existingVariables={variables}
lastUpdatedVar={lastUpdatedVar}
variableData={{
name: variable.name,
...variable,
name: variableName,
...variables[variableName],
change: update,
}}
onValueUpdate={onValueUpdate}

View File

@@ -14,7 +14,6 @@ import { IDashboardVariable } from 'types/api/dashboard/getAll';
import VariableItem from './VariableItem';
const mockVariableData: IDashboardVariable = {
id: 'test_variable',
description: 'Test Variable',
type: 'TEXTBOX',
textboxValue: 'defaultValue',
@@ -96,7 +95,6 @@ describe('VariableItem', () => {
// expect(mockOnValueUpdate).toHaveBeenCalledTimes(1);
expect(mockOnValueUpdate).toHaveBeenCalledWith(
'testVariable',
'test_variable',
'newValue',
false,
);

View File

@@ -2,14 +2,13 @@ import './DashboardVariableSelection.styles.scss';
import { orange } from '@ant-design/colors';
import { WarningOutlined } from '@ant-design/icons';
import { Input, Popover, Select, Tooltip, Typography } from 'antd';
import { Input, Popover, Select, Typography } from 'antd';
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import useDebounce from 'hooks/useDebounce';
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
import map from 'lodash-es/map';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { memo, useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
@@ -28,7 +27,6 @@ interface VariableItemProps {
existingVariables: Record<string, IDashboardVariable>;
onValueUpdate: (
name: string,
id: string,
arg1: IDashboardVariable['selectedValue'],
allSelected: boolean,
) => void;
@@ -50,7 +48,6 @@ function VariableItem({
onValueUpdate,
lastUpdatedVar,
}: VariableItemProps): JSX.Element {
const { isDashboardLocked } = useDashboard();
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
[],
);
@@ -140,9 +137,8 @@ function VariableItem({
} else {
[value] = newOptionsData;
}
if (variableData && variableData?.name && variableData?.id) {
onValueUpdate(variableData.name, variableData.id, value, allSelected);
if (variableData.name) {
onValueUpdate(variableData.name, value, allSelected);
}
}
@@ -153,13 +149,14 @@ function VariableItem({
console.error(e);
}
} else if (variableData.type === 'CUSTOM') {
const optionsData = sortValues(
commaValuesParser(variableData.customValue || ''),
variableData.sort,
) as never;
setOptionsData(optionsData);
setOptionsData(
sortValues(
commaValuesParser(variableData.customValue || ''),
variableData.sort,
) as never,
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
};
const { isLoading } = useQuery(getQueryKey(variableData), {
@@ -198,9 +195,9 @@ function VariableItem({
(Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) ||
(Array.isArray(value) && value.length === 0)
) {
onValueUpdate(variableData.name, variableData.id, optionsData, true);
onValueUpdate(variableData.name, optionsData, true);
} else {
onValueUpdate(variableData.name, variableData.id, value, false);
onValueUpdate(variableData.name, value, false);
}
};
@@ -227,85 +224,70 @@ function VariableItem({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedVariableValue]);
useEffect(() => {
// Fetch options for CUSTOM Type
if (variableData.type === 'CUSTOM') {
getOptions(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [variableData.type, variableData.customValue]);
return (
<Tooltip
placement="top"
title={isDashboardLocked ? 'Dashboard is locked' : ''}
>
<VariableContainer>
<Typography.Text className="variable-name" ellipsis>
${variableData.name}
</Typography.Text>
<VariableValue>
{variableData.type === 'TEXTBOX' ? (
<Input
placeholder="Enter value"
disabled={isDashboardLocked}
<VariableContainer>
<Typography.Text className="variable-name" ellipsis>
${variableData.name}
</Typography.Text>
<VariableValue>
{variableData.type === 'TEXTBOX' ? (
<Input
placeholder="Enter value"
bordered={false}
value={variableValue}
onChange={(e): void => {
setVaribleValue(e.target.value || '');
}}
style={{
width:
50 + ((variableData.selectedValue?.toString()?.length || 0) * 7 || 50),
}}
/>
) : (
!errorMessage &&
optionsData && (
<Select
value={selectValue}
onChange={handleChange}
bordered={false}
value={variableValue}
onChange={(e): void => {
setVaribleValue(e.target.value || '');
}}
style={{
width:
50 + ((variableData.selectedValue?.toString()?.length || 0) * 7 || 50),
}}
/>
) : (
!errorMessage &&
optionsData && (
<Select
value={selectValue}
onChange={handleChange}
bordered={false}
placeholder="Select value"
mode={mode}
dropdownMatchSelectWidth={false}
style={SelectItemStyle}
loading={isLoading}
showArrow
showSearch
data-testid="variable-select"
disabled={isDashboardLocked}
>
{enableSelectAll && (
<Select.Option data-testid="option-ALL" value={ALL_SELECT_VALUE}>
ALL
</Select.Option>
)}
{map(optionsData, (option) => (
<Select.Option
data-testid={`option-${option}`}
key={option.toString()}
value={option}
>
{option.toString()}
</Select.Option>
))}
</Select>
)
)}
{variableData.type !== 'TEXTBOX' && errorMessage && (
<span style={{ margin: '0 0.5rem' }}>
<Popover
placement="top"
content={<Typography>{errorMessage}</Typography>}
>
<WarningOutlined style={{ color: orange[5] }} />
</Popover>
</span>
)}
</VariableValue>
</VariableContainer>
</Tooltip>
placeholder="Select value"
mode={mode}
dropdownMatchSelectWidth={false}
style={SelectItemStyle}
loading={isLoading}
showArrow
showSearch
data-testid="variable-select"
>
{enableSelectAll && (
<Select.Option data-testid="option-ALL" value={ALL_SELECT_VALUE}>
ALL
</Select.Option>
)}
{map(optionsData, (option) => (
<Select.Option
data-testid={`option-${option}`}
key={option.toString()}
value={option}
>
{option.toString()}
</Select.Option>
))}
</Select>
)
)}
{errorMessage && (
<span style={{ margin: '0 0.5rem' }}>
<Popover
placement="top"
content={<Typography>{errorMessage}</Typography>}
>
<WarningOutlined style={{ color: orange[5] }} />
</Popover>
</span>
)}
</VariableValue>
</VariableContainer>
);
}

View File

@@ -1,5 +1,3 @@
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
export function areArraysEqual(
a: (string | number | boolean)[],
b: (string | number | boolean)[],
@@ -16,16 +14,3 @@ export function areArraysEqual(
return true;
}
export const convertVariablesToDbFormat = (
variblesArr: IDashboardVariable[],
): Dashboard['data']['variables'] =>
variblesArr.reduce((result, obj: IDashboardVariable) => {
const { id } = obj;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line no-param-reassign
result[id] = obj;
return result;
}, {});

View File

@@ -6,10 +6,8 @@ export function variablePropsToPayloadVariables(
): PayloadVariables {
const payloadVariables: PayloadVariables = {};
Object.entries(variables).forEach(([, value]) => {
if (value?.name) {
payloadVariables[value.name] = value?.selectedValue;
}
Object.entries(variables).forEach(([key, value]) => {
payloadVariables[key] = value?.selectedValue;
});
return payloadVariables;

View File

@@ -6,16 +6,13 @@ import { useResizeObserver } from 'hooks/useDimensions';
import useUrlQuery from 'hooks/useUrlQuery';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useMemo, useRef } from 'react';
import { UseQueryResult } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getTimeRange } from 'utils/getTimeRange';
function WidgetGraph({
getWidgetQueryRange,
@@ -26,21 +23,6 @@ function WidgetGraph({
}: WidgetGraphProps): JSX.Element {
const { stagedQuery } = useQueryBuilder();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
useEffect((): void => {
const { startTime, endTime } = getTimeRange(getWidgetQueryRange);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [getWidgetQueryRange, maxTime, minTime, globalSelectedInterval]);
const graphRef = useRef<HTMLDivElement>(null);
const containerDimensions = useResizeObserver(graphRef);
@@ -81,8 +63,6 @@ function WidgetGraph({
onDragSelect,
thresholds,
fillSpans,
minTimeScale,
maxTimeScale,
}),
[
widgetId,
@@ -93,8 +73,6 @@ function WidgetGraph({
onDragSelect,
thresholds,
fillSpans,
minTimeScale,
maxTimeScale,
],
);

View File

@@ -12,7 +12,8 @@ export const Container = styled(Card)<Props>`
}
.ant-card-body {
padding: 8px;
padding: ${({ $panelType }): string =>
$panelType === PANEL_TYPES.TABLE ? '0 0' : '1.5rem 0'};
height: 57vh;
overflow: auto;
display: flex;

View File

@@ -78,7 +78,6 @@ export const alertsCategory = [
name: CategoryNames.Miscellaneous,
formats: [
{ name: 'Percent (0.0-1.0)', id: MiscellaneousFormats.PercentUnit },
{ name: 'Percent (0 - 100)', id: MiscellaneousFormats.Percent },
],
},
{

View File

@@ -1,6 +1,5 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { LockFilled, WarningOutlined } from '@ant-design/icons';
import { Button, Modal, Space, Tooltip, Typography } from 'antd';
import { LockFilled } from '@ant-design/icons';
import { Button, Modal, Tooltip, Typography } from 'antd';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { FeatureKeys } from 'constants/features';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -19,7 +18,6 @@ import {
getSelectedWidgetIndex,
} from 'providers/Dashboard/util';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { generatePath, useLocation, useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
@@ -41,7 +39,6 @@ import {
RightContainerWrapper,
} from './styles';
import { NewWidgetProps } from './types';
import { getIsQueryModified } from './utils';
function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
const {
@@ -50,14 +47,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
setToScrollWidgetId,
} = useDashboard();
const { t } = useTranslation(['dashboard']);
const { currentQuery, stagedQuery } = useQueryBuilder();
const isQueryModified = useMemo(
() => getIsQueryModified(currentQuery, stagedQuery),
[currentQuery, stagedQuery],
);
const { currentQuery } = useQueryBuilder();
const { featureResponse } = useSelector<AppState, AppReducer>(
(state) => state.app,
@@ -102,12 +92,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
selectedWidget?.fillSpans || false,
);
const [saveModal, setSaveModal] = useState(false);
const [discardModal, setDiscardModal] = useState(false);
const closeModal = (): void => {
setSaveModal(false);
setDiscardModal(false);
};
const [graphType, setGraphType] = useState(selectedGraph);
@@ -222,14 +206,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
]);
const onClickDiscardHandler = useCallback(() => {
if (isQueryModified) {
setDiscardModal(true);
return;
}
history.push(generatePath(ROUTES.DASHBOARD, { dashboardId }));
}, [dashboardId, isQueryModified]);
const discardChanges = useCallback(() => {
history.push(generatePath(ROUTES.DASHBOARD, { dashboardId }));
}, [dashboardId]);
@@ -345,54 +321,21 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
</RightContainerWrapper>
</PanelContainer>
<Modal
title={
isQueryModified ? (
<Space>
<WarningOutlined style={{ fontSize: '16px', color: '#fdd600' }} />
Unsaved Changes
</Space>
) : (
'Save Widget'
)
}
title="Save Changes"
focusTriggerAfterClose
forceRender
destroyOnClose
closable
onCancel={closeModal}
onCancel={(): void => setSaveModal(false)}
onOk={onClickSaveHandler}
centered
open={saveModal}
width={600}
>
{!isQueryModified ? (
<Typography>
{t('your_graph_build_with')}{' '}
<QueryTypeTag queryType={currentQuery.queryType} />
{t('dashboar_ok_confirm')}
</Typography>
) : (
<Typography>{t('dashboard_unsave_changes')} </Typography>
)}
</Modal>
<Modal
title={
<Space>
<WarningOutlined style={{ fontSize: '16px', color: '#fdd600' }} />
Unsaved Changes
</Space>
}
focusTriggerAfterClose
forceRender
destroyOnClose
closable
onCancel={closeModal}
onOk={discardChanges}
centered
open={discardModal}
width={600}
>
<Typography>{t('dashboard_unsave_changes')}</Typography>
<Typography>
Your graph built with <QueryTypeTag queryType={currentQuery.queryType} />{' '}
query will be saved. Press OK to confirm.
</Typography>
</Modal>
</Container>
);

View File

@@ -1,15 +0,0 @@
import { omitIdFromQuery } from 'components/ExplorerCard/utils';
import { isEqual } from 'lodash-es';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
export const getIsQueryModified = (
currentQuery: Query,
stagedQuery: Query | null,
): boolean => {
if (!stagedQuery) {
return false;
}
const omitIdFromStageQuery = omitIdFromQuery(stagedQuery);
const omitIdFromCurrentQuery = omitIdFromQuery(currentQuery);
return !isEqual(omitIdFromStageQuery, omitIdFromCurrentQuery);
};

View File

@@ -71,8 +71,8 @@ export default function ModuleStepsContainer({
} = useOnboardingContext();
const [current, setCurrent] = useState(0);
const { trackEvent } = useAnalytics();
const [metaData, setMetaData] = useState<MetaDataProps[]>(defaultMetaData);
const { trackEvent } = useAnalytics();
const lastStepIndex = selectedModuleSteps.length - 1;
const isValidForm = (): boolean => {

View File

@@ -1,25 +0,0 @@
import { Input, InputProps } from 'antd';
import { ChangeEventHandler, useState } from 'react';
function CSVInput({ value, onChange, ...otherProps }: InputProps): JSX.Element {
const [inputValue, setInputValue] = useState(
((value as string[]) || []).join(', '),
);
const onChangeHandler = (onChange as unknown) as (v: string[]) => void;
const onInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const newValue = e.target.value;
setInputValue(newValue);
if (onChangeHandler) {
const splitValues = newValue.split(',').map((v) => v.trim());
onChangeHandler(splitValues);
}
};
// eslint-disable-next-line react/jsx-props-no-spreading
return <Input value={inputValue} onChange={onInputChange} {...otherProps} />;
}
export default CSVInput;

View File

@@ -1,13 +1,15 @@
import './styles.scss';
import { Form, Input, Select } from 'antd';
import { ModalFooterTitle } from 'container/PipelinePage/styles';
import { useTranslation } from 'react-i18next';
import { formValidationRules } from '../config';
import { processorFields, ProcessorFormField } from './config';
import CSVInput from './FormFields/CSVInput';
import { FormWrapper, PipelineIndexIcon, StyledSelect } from './styles';
import {
Container,
FormWrapper,
PipelineIndexIcon,
StyledSelect,
} from './styles';
function ProcessorFieldInput({
fieldData,
@@ -23,63 +25,35 @@ function ProcessorFieldInput({
return null;
}
// Do not render display elements for hidden inputs.
if (fieldData?.hidden) {
return (
<Form.Item
name={fieldData.name}
initialValue={fieldData.initialValue}
dependencies={fieldData.dependencies || []}
style={{ display: 'none' }}
>
<Input type="hidden" />
</Form.Item>
);
}
let inputField;
if (fieldData?.options) {
inputField = (
<StyledSelect>
{fieldData.options.map(({ value, label }) => (
<Select.Option key={value + label} value={value}>
{label}
</Select.Option>
))}
</StyledSelect>
);
} else if (Array.isArray(fieldData?.initialValue)) {
inputField = <CSVInput placeholder={t(fieldData.placeholder)} />;
} else {
inputField = <Input placeholder={t(fieldData.placeholder)} />;
}
return (
<div
className={
fieldData?.compact
? 'compact-processor-field-container'
: 'processor-field-container'
}
>
{!fieldData?.compact && (
<PipelineIndexIcon size="small">
{Number(fieldData.id) + 1}
</PipelineIndexIcon>
)}
<Container>
<PipelineIndexIcon size="small">
{Number(fieldData.id) + 1}
</PipelineIndexIcon>
<FormWrapper>
<Form.Item
required={false}
label={<ModalFooterTitle>{fieldData.fieldName}</ModalFooterTitle>}
key={fieldData.id}
name={fieldData.name}
initialValue={fieldData.initialValue}
rules={fieldData.rules ? fieldData.rules : formValidationRules}
dependencies={fieldData.dependencies || []}
>
{inputField}
{fieldData?.options ? (
<StyledSelect>
{fieldData.options.map(({ value, label }) => (
<Select.Option key={value + label} value={value}>
{label}
</Select.Option>
))}
</StyledSelect>
) : (
<Input placeholder={t(fieldData.placeholder)} />
)}
</Form.Item>
</FormWrapper>
</div>
</Container>
);
}
@@ -89,12 +63,9 @@ interface ProcessorFieldInputProps {
function ProcessorForm({ processorType }: ProcessorFormProps): JSX.Element {
return (
<div className="processor-form-container">
<div>
{processorFields[processorType]?.map((fieldData: ProcessorFormField) => (
<ProcessorFieldInput
key={fieldData.name + String(fieldData.initialValue)}
fieldData={fieldData}
/>
<ProcessorFieldInput key={fieldData.id} fieldData={fieldData} />
))}
</div>
);

View File

@@ -17,7 +17,6 @@ export const processorTypes: Array<ProcessorType> = [
{ key: 'json_parser', value: 'json_parser', label: 'Json Parser' },
{ key: 'trace_parser', value: 'trace_parser', label: 'Trace Parser' },
{ key: 'time_parser', value: 'time_parser', label: 'Timestamp Parser' },
{ key: 'severity_parser', value: 'severity_parser', label: 'Severity Parser' },
{ key: 'add', value: 'add', label: 'Add' },
{ key: 'remove', value: 'remove', label: 'Remove' },
// { key: 'retain', value: 'retain', label: 'Retain' }, @Chintan - Commented as per Nitya's suggestion
@@ -32,15 +31,13 @@ export type ProcessorFieldOption = {
value: string;
};
// TODO(Raj): Refactor Processor Form code after putting e2e UI tests in place.
export type ProcessorFormField = {
id: number;
fieldName: string;
placeholder: string;
name: string | NamePath;
rules?: Array<Rule>;
hidden?: boolean;
initialValue?: boolean | string | Array<string>;
initialValue?: string;
dependencies?: Array<string | NamePath>;
options?: Array<ProcessorFieldOption>;
shouldRender?: (form: FormInstance) => boolean;
@@ -48,10 +45,6 @@ export type ProcessorFormField = {
changedValues: ProcessorData,
form: FormInstance,
) => void;
// Should this field have its own row or should it
// be packed with other compact fields.
compact?: boolean;
};
const traceParserFieldValidator: RuleRender = (form) => ({
@@ -324,85 +317,6 @@ export const processorFields: { [key: string]: Array<ProcessorFormField> } = {
initialValue: '%Y-%m-%dT%H:%M:%S.%f%z',
},
],
severity_parser: [
{
id: 1,
fieldName: 'Name of Severity Parsing Processor',
placeholder: 'processor_name_placeholder',
name: 'name',
},
{
id: 2,
fieldName: 'Parse Severity Value From',
placeholder: 'processor_parsefrom_placeholder',
name: 'parse_from',
initialValue: 'attributes.logLevel',
},
{
id: 3,
fieldName: 'Values for level TRACE',
placeholder: 'Specify comma separated values. Eg: trace, 0',
name: ['mapping', 'trace'],
rules: [],
initialValue: ['trace'],
compact: true,
},
{
id: 4,
fieldName: 'Values for level DEBUG',
placeholder: 'Specify comma separated values. Eg: debug, 2xx',
name: ['mapping', 'debug'],
rules: [],
initialValue: ['debug'],
compact: true,
},
{
id: 5,
fieldName: 'Values for level INFO',
placeholder: 'Specify comma separated values. Eg: info, 3xx',
name: ['mapping', 'info'],
rules: [],
initialValue: ['info'],
compact: true,
},
{
id: 6,
fieldName: 'Values for level WARN',
placeholder: 'Specify comma separated values. Eg: warning, 4xx',
name: ['mapping', 'warn'],
rules: [],
initialValue: ['warn'],
compact: true,
},
{
id: 7,
fieldName: 'Values for level ERROR',
placeholder: 'Specify comma separated values. Eg: error, 5xx',
name: ['mapping', 'error'],
rules: [],
initialValue: ['error'],
compact: true,
},
{
id: 8,
fieldName: 'Values for level FATAL',
placeholder: 'Specify comma separated values. Eg: fatal, panic',
name: ['mapping', 'fatal'],
rules: [],
initialValue: ['fatal'],
compact: true,
},
{
id: 9,
fieldName: 'Override Severity Text',
placeholder:
'Should the parsed severity set both severity and severityText?',
name: ['overwrite_text'],
rules: [],
initialValue: true,
hidden: true,
},
],
retain: [
{
id: 1,

View File

@@ -1,27 +0,0 @@
.processor-form-container {
position: relative;
width: 100%;
display: flex;
flex-wrap: wrap
}
.processor-field-container {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 0rem;
gap: 1rem;
width: 100%;
}
.compact-processor-field-container {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 0rem;
min-width: 40%;
flex-grow: 1;
margin-left: 2.5rem;
}

View File

@@ -53,7 +53,7 @@ const usePipelinePreview = ({
isLoading: isFetching,
outputLogs,
isError,
errorMsg: error?.message || '',
errorMsg: error?.response?.data?.error || '',
};
};

View File

@@ -10,11 +10,10 @@ import { filterOption } from './utils';
function BuilderUnitsFilter({
onChange,
yAxisUnit,
}: IBuilderUnitsFilterProps): JSX.Element {
const { currentQuery, handleOnUnitsChange } = useQueryBuilder();
const selectedValue = yAxisUnit || currentQuery?.unit;
const selectedValue = currentQuery?.unit;
const allOptions = categoryToSupport.map((category) => ({
label: category,

View File

@@ -1,4 +1,3 @@
export interface IBuilderUnitsFilterProps {
onChange?: (value: string) => void;
yAxisUnit?: string;
}

View File

@@ -3,13 +3,9 @@ import Uplot from 'components/Uplot';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { useMemo, useRef } from 'react';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getTimeRange } from 'utils/getTimeRange';
import { Container, ErrorText } from './styles';
@@ -35,21 +31,6 @@ function TimeSeriesView({
? graphRef.current.clientHeight
: 300;
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
useEffect((): void => {
const { startTime, endTime } = getTimeRange();
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [maxTime, minTime, globalSelectedInterval, data]);
const chartOptions = getUPlotChartOptions({
yAxisUnit: yAxisUnit || '',
apiResponse: data?.payload,
@@ -58,8 +39,6 @@ function TimeSeriesView({
height,
},
isDarkMode,
minTimeScale,
maxTimeScale,
});
return (

View File

@@ -3,16 +3,12 @@ import { Button, Select as DefaultSelect } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import dayjs, { Dayjs } from 'dayjs';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
import useUrlQuery from 'hooks/useUrlQuery';
import GetMinMax from 'lib/getMinMax';
import getTimeString from 'lib/getTimeString';
import history from 'lib/history';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { bindActionCreators, Dispatch } from 'redux';
@@ -38,9 +34,9 @@ function DateTimeSelection({
}: Props): JSX.Element {
const [formSelector] = Form.useForm();
const urlQuery = useUrlQuery();
const searchStartTime = urlQuery.get('startTime');
const searchEndTime = urlQuery.get('endTime');
const params = new URLSearchParams(location.search);
const searchStartTime = params.get('startTime');
const searchEndTime = params.get('endTime');
const localstorageStartTime = getLocalStorageKey('startTime');
const localstorageEndTime = getLocalStorageKey('endTime');
@@ -173,11 +169,6 @@ function DateTimeSelection({
return `Last refresh - ${secondsDiff} sec ago`;
}, [maxTime, minTime, selectedTime]);
const isLogsExplorerPage = useMemo(
() => location.pathname === ROUTES.LOGS_EXPLORER,
[location.pathname],
);
const onSelectHandler = (value: Time): void => {
if (value !== 'custom') {
updateTimeInterval(value);
@@ -190,18 +181,12 @@ function DateTimeSelection({
setCustomDTPickerVisible(true);
}
const { maxTime, minTime } = GetMinMax(value, getTime());
if (!isLogsExplorerPage) {
urlQuery.set(QueryParams.startTime, minTime.toString());
urlQuery.set(QueryParams.endTime, maxTime.toString());
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
history.replace(generatedUrl);
}
if (!stagedQuery) {
return;
}
const { maxTime, minTime } = GetMinMax(value, getTime());
initQueryBuilderData(updateStepInterval(stagedQuery, maxTime, minTime));
};
@@ -222,12 +207,6 @@ function DateTimeSelection({
setLocalStorageKey('startTime', startTimeMoment.toString());
setLocalStorageKey('endTime', endTimeMoment.toString());
updateLocalStorageForRoutes('custom');
if (!isLogsExplorerPage) {
urlQuery.set(QueryParams.startTime, startTimeMoment.toString());
urlQuery.set(QueryParams.endTime, endTimeMoment.toString());
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
history.replace(generatedUrl);
}
}
}
};
@@ -255,6 +234,7 @@ function DateTimeSelection({
if (searchEndTime !== null && searchStartTime !== null) {
return 'custom';
}
if (
(localstorageEndTime === null || localstorageStartTime === null) &&
time === 'custom'
@@ -272,8 +252,16 @@ function DateTimeSelection({
setRefreshButtonHidden(updatedTime === 'custom');
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.pathname, updateTimeInterval, globalTimeLoading]);
}, [
location.pathname,
getTime,
localstorageEndTime,
localstorageStartTime,
searchEndTime,
searchStartTime,
updateTimeInterval,
globalTimeLoading,
]);
return (
<>

View File

@@ -1,35 +1,11 @@
/* eslint-disable react/no-unstable-nested-components */
import type { SelectProps } from 'antd';
import { Tag, Tooltip } from 'antd';
import { BaseOptionType } from 'antd/es/select';
import { Dispatch, SetStateAction, useCallback, useMemo, useRef } from 'react';
import { Tag } from 'antd';
import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
import { Alerts } from 'types/api/alerts/getTriggered';
import { Container, Select } from './styles';
function TextOverflowTooltip({
option,
}: {
option: BaseOptionType;
}): JSX.Element {
const contentRef = useRef<HTMLDivElement | null>(null);
const isOverflow = contentRef.current
? contentRef.current?.offsetWidth < contentRef.current?.scrollWidth
: false;
return (
<Tooltip
placement="left"
title={option.value}
// eslint-disable-next-line react/jsx-props-no-spreading
{...(!isOverflow ? { open: false } : {})}
>
<div className="ant-select-item-option-content" ref={contentRef}>
{option.value}
</div>
</Tooltip>
);
}
function Filter({
setSelectedFilter,
setSelectedGroup,
@@ -75,7 +51,6 @@ function Filter({
const options = uniqueLabels.map((e) => ({
value: e,
title: '',
}));
const getTags: SelectProps['tagRender'] = (props): JSX.Element => {
@@ -113,9 +88,6 @@ function Filter({
placeholder="Group by any tag"
tagRender={(props): JSX.Element => getTags(props)}
options={options}
optionRender={(option): JSX.Element => (
<TextOverflowTooltip option={option} />
)}
/>
</Container>
);

View File

@@ -3,7 +3,6 @@ import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
import useUrlQueryData from 'hooks/useUrlQueryData';
import history from 'lib/history';
import {
MouseEventHandler,
useCallback,
@@ -29,27 +28,16 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
(state) => state.globalTime,
);
const { queryData: timeRange } = useUrlQueryData<LogTimeRange | null>(
QueryParams.timeRange,
null,
);
const {
queryData: timeRange,
redirectWithQuery: onTimeRangeChange,
} = useUrlQueryData<LogTimeRange | null>(QueryParams.timeRange, null);
const { queryData: activeLogId } = useUrlQueryData<string | null>(
QueryParams.activeLogId,
null,
);
const onTimeRangeChange = useCallback(
(newTimeRange: LogTimeRange | null): void => {
urlQuery.set(QueryParams.timeRange, JSON.stringify(newTimeRange));
urlQuery.set(QueryParams.startTime, newTimeRange?.start.toString() || '');
urlQuery.set(QueryParams.endTime, newTimeRange?.end.toString() || '');
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
history.replace(generatedUrl);
},
[pathname, urlQuery],
);
const isActiveLog = useMemo(() => activeLogId === logId, [activeLogId, logId]);
const [isHighlighted, setIsHighlighted] = useState<boolean>(isActiveLog);

View File

@@ -15,19 +15,14 @@ type UseGetQueryRange = (
export const useGetQueryRange: UseGetQueryRange = (requestData, options) => {
const queryKey = useMemo(() => {
if (options?.queryKey && Array.isArray(options.queryKey)) {
if (options?.queryKey) {
return [...options.queryKey];
}
if (options?.queryKey && typeof options.queryKey === 'string') {
return options.queryKey;
}
return [REACT_QUERY_KEY.GET_QUERY_RANGE, requestData];
}, [options?.queryKey, requestData]);
return useQuery<SuccessResponse<MetricRangePayloadProps>, Error>({
queryFn: async ({ signal }) => GetMetricQueryRange(requestData, signal),
queryFn: async () => GetMetricQueryRange(requestData),
...options,
queryKey,
});

View File

@@ -1 +0,0 @@
{"/en-GB/alerts":"37ea40b758e14f100b970178809147d7","/en-GB/channels":"b855a58fce92ff62a0ce50cc40d8da0b","/en-GB/common":"d918932fcd1d34b2d84cb463812bd157","/en-GB/dashboard":"9ec66badfc02995263cf108615f6380c","/en-GB/errorDetails":"a1a1ea54ed8adc720e7942c42ce4be0f","/en-GB/explorer":"98106bbc79e701d81f5731dd53a158f0","/en-GB/generalSettings":"65fca62d2f109d73fa4bdc447c353857","/en-GB/licenses":"dc2fea934c67b5b3bf8c940019d820cd","/en-GB/login":"c9d63ef04a9af5ae6aed12b4b725add5","/en-GB/logs":"de363f7feee26d9fc72eccdf69988f09","/en-GB/organizationsettings":"e24624bba7bdd7bf071873940742b1a8","/en-GB/routes":"08585a25257ed898131ba43e4c927d7e","/en-GB/rules":"f134663a0943cdb8cd2a2c169f27ba90","/en-GB/settings":"e2c4003664cc9ba476b658f1e6304fe5","/en-GB/signup":"59c64809b8b4c7b1b8902da5aa2315f0","/en-GB/titles":"9e0515203efab287fdd50afb96bde8c8","/en-GB/trace":"fcf3fda7bee8b609b5a3ab0f749f2594","/en-GB/traceDetails":"f91795f15c286f15bf630a454febb015","/en-GB/translation":"532cce878c691d9ff3c689a73279fd2e","/en/alerts":"37ea40b758e14f100b970178809147d7","/en/channels":"c9bfbd14bb4d3f38e2a669f5fbfadc17","/en/common":"d918932fcd1d34b2d84cb463812bd157","/en/dashboard":"6de8356a6ed53c109746c0f7ef37ffcf","/en/errorDetails":"eb83b35f49830420547f30c08bd88c4e","/en/explorer":"98106bbc79e701d81f5731dd53a158f0","/en/generalSettings":"65fca62d2f109d73fa4bdc447c353857","/en/licenses":"dc2fea934c67b5b3bf8c940019d820cd","/en/login":"c9d63ef04a9af5ae6aed12b4b725add5","/en/logs":"de363f7feee26d9fc72eccdf69988f09","/en/organizationsettings":"e24624bba7bdd7bf071873940742b1a8","/en/pipeline":"9f75c31214b2ae9d362bb6e5985c2e1f","/en/routes":"08585a25257ed898131ba43e4c927d7e","/en/rules":"f134663a0943cdb8cd2a2c169f27ba90","/en/settings":"ffabe7ca89d7992d9639695b4df4d6e9","/en/signup":"59c64809b8b4c7b1b8902da5aa2315f0","/en/titles":"49d542f8f3ca9291777b9042ed8faf1a","/en/trace":"fcf3fda7bee8b609b5a3ab0f749f2594","/en/traceDetails":"f91795f15c286f15bf630a454febb015","/en/translation":"921a0256c8d4d3522754557b41e24362","/en/valueGraph":"cc57d9b83919574016dab2fc9e5adedf"}

View File

@@ -20,13 +20,9 @@ export const getDashboardVariables = (
SIGNOZ_START_TIME: parseInt(start, 10) * 1e3,
SIGNOZ_END_TIME: parseInt(end, 10) * 1e3,
};
Object.entries(variables).forEach(([, value]) => {
if (value?.name) {
variablesTuple[value.name] = value?.selectedValue;
}
Object.keys(variables).forEach((key) => {
variablesTuple[key] = variables[key].selectedValue;
});
return variablesTuple;
} catch (e) {
console.error(e);

View File

@@ -17,11 +17,10 @@ import { prepareQueryRangePayload } from './prepareQueryRangePayload';
export async function GetMetricQueryRange(
props: GetQueryResultsProps,
signal?: AbortSignal,
): Promise<SuccessResponse<MetricRangePayloadProps>> {
const { legendMap, queryPayload } = prepareQueryRangePayload(props);
const response = await getMetricsQueryRange(queryPayload, signal);
const response = await getMetricsQueryRange(queryPayload);
if (response.statusCode >= 400) {
throw new Error(

View File

@@ -232,11 +232,6 @@ const unitsMapping = [
{
label: 'Percent (0.0-1.0)',
value: 'percentunit',
factor: 100,
},
{
label: 'Percent (0 - 100)',
value: 'percent',
factor: 1,
},
],

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
/* eslint-disable sonarjs/cognitive-complexity */
@@ -16,7 +15,6 @@ import onClickPlugin, { OnClickPluginOpts } from './plugins/onClickPlugin';
import tooltipPlugin from './plugins/tooltipPlugin';
import getAxes from './utils/getAxes';
import getSeries from './utils/getSeriesData';
import { getXAxisScale } from './utils/getXAxisScale';
import { getYAxisScale } from './utils/getYAxisScale';
interface GetUPlotChartOptions {
@@ -33,8 +31,6 @@ interface GetUPlotChartOptions {
thresholdValue?: number;
thresholdText?: string;
fillSpans?: boolean;
minTimeScale?: number;
maxTimeScale?: number;
}
export const getUPlotChartOptions = ({
@@ -44,24 +40,21 @@ export const getUPlotChartOptions = ({
apiResponse,
onDragSelect,
yAxisUnit,
minTimeScale,
maxTimeScale,
onClickHandler = _noop,
graphsVisibilityStates,
setGraphsVisibilityStates,
thresholds,
fillSpans,
}: GetUPlotChartOptions): uPlot.Options => {
const timeScaleProps = getXAxisScale(minTimeScale, maxTimeScale);
return {
// eslint-disable-next-line sonarjs/prefer-immediate-return
const chartOptions = {
id,
width: dimensions.width,
height: dimensions.height - 30,
height: dimensions.height - 45,
// tzDate: (ts) => uPlot.tzDate(new Date(ts * 1e3), ''), // Pass timezone for 2nd param
legend: {
show: true,
live: false,
isolate: true,
},
focus: {
alpha: 0.3,
@@ -73,18 +66,18 @@ export const getUPlotChartOptions = ({
bias: 1,
},
points: {
size: (u, seriesIdx): number => u.series[seriesIdx].points.size * 3,
size: (u, seriesIdx): number => u.series[seriesIdx].points.size * 2.5,
width: (u, seriesIdx, size): number => size / 4,
stroke: (u, seriesIdx): string =>
`${u.series[seriesIdx].points.stroke(u, seriesIdx)}90`,
fill: (): string => '#fff',
},
},
padding: [16, 16, 8, 8],
padding: [16, 16, 16, 16],
scales: {
x: {
spanGaps: true,
...timeScaleProps,
time: true,
auto: true, // Automatically adjust scale range
},
y: {
...getYAxisScale(
@@ -165,24 +158,16 @@ export const getUPlotChartOptions = ({
(self): void => {
const legend = self.root.querySelector('.u-legend');
if (legend) {
const seriesEls = legend.querySelectorAll('.u-series');
const seriesEls = legend.querySelectorAll('.u-label');
const seriesArray = Array.from(seriesEls);
seriesArray.forEach((seriesEl, index) => {
seriesEl.addEventListener('click', () => {
if (graphsVisibilityStates) {
setGraphsVisibilityStates?.((prev) => {
const newGraphVisibilityStates = [...prev];
if (
newGraphVisibilityStates[index + 1] &&
newGraphVisibilityStates.every((value, i) =>
i === index + 1 ? value : !value,
)
) {
newGraphVisibilityStates.fill(true);
} else {
newGraphVisibilityStates.fill(false);
newGraphVisibilityStates[index + 1] = true;
}
newGraphVisibilityStates[index + 1] = !newGraphVisibilityStates[
index + 1
];
return newGraphVisibilityStates;
});
}
@@ -200,4 +185,6 @@ export const getUPlotChartOptions = ({
),
axes: getAxes(isDarkMode, yAxisUnit),
};
return chartOptions;
};

View File

@@ -54,7 +54,7 @@ const generateTooltipContent = (
const value = data[index][idx];
const label = getLabelName(metric, queryName || '', legend || '');
if (Number.isFinite(value)) {
if (value) {
const tooltipValue = getToolTipValue(value, yAxisUnit);
const dataObj = {
@@ -191,8 +191,7 @@ const tooltipPlugin = (
if (overlay) {
overlay.textContent = '';
const { left, top, idx } = u.cursor;
if (Number.isInteger(idx)) {
if (idx) {
const anchor = { left: left + bLeft, top: top + bTop };
const content = generateTooltipContent(
apiResult,

View File

@@ -9,7 +9,8 @@ const getAxes = (isDarkMode: boolean, yAxisUnit?: string): any => [
stroke: isDarkMode ? 'white' : 'black', // Color of the axis line
grid: {
stroke: getGridColor(isDarkMode), // Color of the grid lines
width: 0.2, // Width of the grid lines,
dash: [10, 10], // Dash pattern for grid lines,
width: 0.5, // Width of the grid lines,
show: true,
},
ticks: {
@@ -23,7 +24,8 @@ const getAxes = (isDarkMode: boolean, yAxisUnit?: string): any => [
stroke: isDarkMode ? 'white' : 'black', // Color of the axis line
grid: {
stroke: getGridColor(isDarkMode), // Color of the grid lines
width: 0.2, // Width of the grid lines
dash: [10, 10], // Dash pattern for grid lines,
width: 0.3, // Width of the grid lines
},
ticks: {
// stroke: isDarkMode ? 'white' : 'black', // Color of the tick lines

View File

@@ -1,8 +1,8 @@
const getGridColor = (isDarkMode: boolean): string => {
if (isDarkMode) {
return 'rgba(231,233,237,0.3)';
return 'rgba(231,233,237,0.2)';
}
return 'rgba(0,0,0,0.5)';
return 'rgba(231,233,237,0.8)';
};
export default getGridColor;

View File

@@ -46,22 +46,17 @@ const getSeries = (
legend || '',
);
const pointSize = seriesList[i].values.length > 1 ? 5 : 10;
const showPoints = !(seriesList[i].values.length > 1);
const seriesObj: any = {
width: 1.4,
paths,
drawStyle: drawStyles.line,
lineInterpolation: lineInterpolations.spline,
show: newGraphVisibilityStates ? newGraphVisibilityStates[i] : true,
label,
stroke: color,
width: 2,
spanGaps: true,
points: {
size: pointSize,
show: showPoints,
stroke: color,
show: false,
},
};

View File

@@ -1,40 +0,0 @@
function getFallbackMinMaxTimeStamp(): {
fallbackMin: number;
fallbackMax: number;
} {
const currentDate = new Date();
// Get the Unix timestamp (milliseconds since January 1, 1970)
const currentTime = currentDate.getTime();
const currentUnixTimestamp = Math.floor(currentTime / 1000);
// Calculate the date and time one day ago
const oneDayAgoUnixTimestamp = Math.floor(
(currentDate.getTime() - 86400000) / 1000,
); // 86400000 milliseconds in a day
return {
fallbackMin: oneDayAgoUnixTimestamp,
fallbackMax: currentUnixTimestamp,
};
}
export const getXAxisScale = (
minTimeScale: number,
maxTimeScale: number,
): {
time: boolean;
auto: boolean;
range?: [number, number];
} => {
let minTime = minTimeScale;
let maxTime = maxTimeScale;
if (!minTimeScale || !maxTimeScale) {
const { fallbackMin, fallbackMax } = getFallbackMinMaxTimeStamp();
minTime = fallbackMin;
maxTime = fallbackMax;
}
return { time: true, auto: false, range: [minTime, maxTime] };
};

View File

@@ -54,12 +54,7 @@ function getRange(
const [minSeriesValue, maxSeriesValue] = findMinMaxValues(series);
const min = Math.min(minThresholdValue, minSeriesValue);
let max = Math.max(maxThresholdValue, maxSeriesValue);
// this is a temp change, we need to figure out a generic way to better handle ranges based on yAxisUnit
if (yAxisUnit === 'percentunit' && max < 1) {
max = 1;
}
const max = Math.max(maxThresholdValue, maxSeriesValue);
return [min, max];
}

View File

@@ -12,9 +12,7 @@ function DashboardPage(): JSX.Element {
const { isFetching, isError, isLoading } = dashboardResponse;
const errorMessage = isError
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(dashboardResponse?.error as AxiosError)?.response?.data?.errorType
? (dashboardResponse?.error as AxiosError)?.response?.data.errorType
: 'Something went wrong';
if (isError && !isFetching && errorMessage === ErrorType.NotFound) {

View File

@@ -5,9 +5,7 @@ import Spinner from 'components/Spinner';
import ChangeHistory from 'container/PipelinePage/Layouts/ChangeHistory';
import PipelinePage from 'container/PipelinePage/Layouts/Pipeline';
import { useNotifications } from 'hooks/useNotifications';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { useEffect, useMemo } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { SuccessResponse } from 'types/api';
@@ -79,11 +77,7 @@ function Pipelines(): JSX.Element {
return <Spinner height="75vh" tip="Loading Pipelines..." />;
}
return (
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<Tabs defaultActiveKey="pipelines" items={tabItems} />;
</ErrorBoundary>
);
return <Tabs defaultActiveKey="pipelines" items={tabItems} />;
}
export default Pipelines;

View File

@@ -1,7 +1,6 @@
import './Support.styles.scss';
import { Button, Card, Typography } from 'antd';
import useAnalytics from 'hooks/analytics/useAnalytics';
import {
Book,
Cable,
@@ -83,8 +82,6 @@ const supportChannels = [
];
export default function Support(): JSX.Element {
const { trackEvent } = useAnalytics();
const handleChannelWithRedirects = (url: string): void => {
window.open(url, '_blank');
};
@@ -114,8 +111,6 @@ export default function Support(): JSX.Element {
};
const handleChannelClick = (channel: Channel): void => {
trackEvent(`Support : ${channel.name}`);
switch (channel.key) {
case channelsMap.documentation:
case channelsMap.github:

View File

@@ -1,5 +1,5 @@
import { Modal } from 'antd';
import getDashboard from 'api/dashboard/get';
import Modal from 'antd/es/modal';
import get from 'api/dashboard/get';
import lockDashboardApi from 'api/dashboard/lockDashboard';
import unlockDashboardApi from 'api/dashboard/unlockDashboard';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
@@ -30,10 +30,9 @@ import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
import { Dashboard } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app';
import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 as generateUUID } from 'uuid';
import { IDashboardContext } from './types';
@@ -103,75 +102,36 @@ export function DashboardProvider({
const { t } = useTranslation(['dashboard']);
const dashboardRef = useRef<Dashboard>();
// As we do not have order and ID's in the variables object, we have to process variables to add order and ID if they do not exist in the variables object
// eslint-disable-next-line sonarjs/cognitive-complexity
const transformDashboardVariables = (data: Dashboard): Dashboard => {
if (data && data.data && data.data.variables) {
const clonedDashboardData = JSON.parse(JSON.stringify(data));
const { variables } = clonedDashboardData.data;
const existingOrders: Set<number> = new Set();
// eslint-disable-next-line no-restricted-syntax
for (const key in variables) {
// eslint-disable-next-line no-prototype-builtins
if (variables.hasOwnProperty(key)) {
const variable: IDashboardVariable = variables[key];
// Check if 'order' property doesn't exist or is undefined
if (variable.order === undefined) {
// Find a unique order starting from 0
let order = 0;
while (existingOrders.has(order)) {
order += 1;
}
variable.order = order;
existingOrders.add(order);
}
if (variable.id === undefined) {
variable.id = generateUUID();
}
}
}
return clonedDashboardData;
}
return data;
};
const dashboardResponse = useQuery(
[REACT_QUERY_KEY.DASHBOARD_BY_ID, isDashboardPage?.params],
{
enabled: (!!isDashboardPage || !!isDashboardWidgetPage) && isLoggedIn,
queryFn: () =>
getDashboard({
get({
uuid: dashboardId,
}),
refetchOnWindowFocus: false,
onSuccess: (data) => {
const updatedDashboardData = transformDashboardVariables(data);
const updatedDate = dayjs(updatedDashboardData.updated_at);
const updatedDate = dayjs(data.updated_at);
setIsDashboardLocked(updatedDashboardData?.isLocked || false);
setIsDashboardLocked(data?.isLocked || false);
// on first render
if (updatedTimeRef.current === null) {
setSelectedDashboard(updatedDashboardData);
setSelectedDashboard(data);
updatedTimeRef.current = updatedDate;
dashboardRef.current = updatedDashboardData;
dashboardRef.current = data;
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
setLayouts(getUpdatedLayout(data.data.layout));
}
if (
updatedTimeRef.current !== null &&
updatedDate.isAfter(updatedTimeRef.current) &&
isVisible &&
dashboardRef.current?.id === updatedDashboardData.id
dashboardRef.current?.id === data.id
) {
// show modal when state is out of sync
const modal = onModal.confirm({
@@ -179,7 +139,7 @@ export function DashboardProvider({
title: t('dashboard_has_been_updated'),
content: t('do_you_want_to_refresh_the_dashboard'),
onOk() {
setSelectedDashboard(updatedDashboardData);
setSelectedDashboard(data);
const { maxTime, minTime } = getMinMax(
globalTime.selectedTime,
@@ -196,32 +156,32 @@ export function DashboardProvider({
},
});
dashboardRef.current = updatedDashboardData;
dashboardRef.current = data;
updatedTimeRef.current = dayjs(updatedDashboardData.updated_at);
updatedTimeRef.current = dayjs(data.updated_at);
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
setLayouts(getUpdatedLayout(data.data.layout));
},
});
modalRef.current = modal;
} else {
// normal flow
updatedTimeRef.current = dayjs(updatedDashboardData.updated_at);
updatedTimeRef.current = dayjs(data.updated_at);
dashboardRef.current = updatedDashboardData;
dashboardRef.current = data;
if (!isEqual(selectedDashboard, updatedDashboardData)) {
setSelectedDashboard(updatedDashboardData);
if (!isEqual(selectedDashboard, data)) {
setSelectedDashboard(data);
}
if (
!isEqual(
[omitBy(layouts, (value): boolean => isUndefined(value))[0]],
updatedDashboardData.data.layout,
data.data.layout,
)
) {
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
setLayouts(getUpdatedLayout(data.data.layout));
}
}
},

View File

@@ -14,8 +14,6 @@ export const VariableSortTypeArr = ['DISABLED', 'ASC', 'DESC'] as const;
export type TSortVariableValuesType = typeof VariableSortTypeArr[number];
export interface IDashboardVariable {
id: string;
order?: any;
name?: string; // key will be the source of truth
description: string;
type: TVariableQueryType;

View File

@@ -1,36 +0,0 @@
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import { UseQueryResult } from 'react-query';
import store from 'store';
import { SuccessResponse } from 'types/api';
import {
MetricRangePayloadProps,
QueryRangePayload,
} from 'types/api/metrics/getQueryRange';
export const getTimeRange = (
widgetQueryRange?: UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>,
): Record<string, number> => {
const widgetParams =
(widgetQueryRange?.data?.params as QueryRangePayload) || null;
if (widgetParams && widgetParams?.start && widgetParams?.end) {
return {
startTime: widgetParams.start / 1000,
endTime: widgetParams.end / 1000,
};
}
const { globalTime } = store.getState();
const { start: globalStartTime, end: globalEndTime } = getStartEndRangeTime({
type: 'GLOBAL_TIME',
interval: globalTime.selectedTime,
});
return {
startTime: (parseInt(globalStartTime, 10) * 1e3) / 1000,
endTime: (parseInt(globalEndTime, 10) * 1e3) / 1000,
};
};

View File

@@ -2346,45 +2346,6 @@
resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
"@dnd-kit/accessibility@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz#1054e19be276b5f1154ced7947fc0cb5d99192e0"
integrity sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==
dependencies:
tslib "^2.0.0"
"@dnd-kit/core@6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.1.0.tgz#e81a3d10d9eca5d3b01cbf054171273a3fe01def"
integrity sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==
dependencies:
"@dnd-kit/accessibility" "^3.1.0"
"@dnd-kit/utilities" "^3.2.2"
tslib "^2.0.0"
"@dnd-kit/modifiers@7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@dnd-kit/modifiers/-/modifiers-7.0.0.tgz#229666dd4e8b9487f348035117f993af755b3db9"
integrity sha512-BG/ETy3eBjFap7+zIti53f0PCLGDzNXyTmn6fSdrudORf+OH04MxrW4p5+mPu4mgMk9kM41iYONjc3DOUWTcfg==
dependencies:
"@dnd-kit/utilities" "^3.2.2"
tslib "^2.0.0"
"@dnd-kit/sortable@8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-8.0.0.tgz#086b7ac6723d4618a4ccb6f0227406d8a8862a96"
integrity sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==
dependencies:
"@dnd-kit/utilities" "^3.2.2"
tslib "^2.0.0"
"@dnd-kit/utilities@^3.2.2":
version "3.2.2"
resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b"
integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==
dependencies:
tslib "^2.0.0"
"@emotion/hash@^0.8.0":
version "0.8.0"
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz"
@@ -4678,16 +4639,7 @@ axe-core@^4.6.2:
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz"
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
axios@1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2"
integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axios@^0.21.1:
axios@^0.21.0, axios@^0.21.1:
version "0.21.4"
resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
@@ -7758,11 +7710,6 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.0:
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
follow-redirects@^1.15.0:
version "1.15.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a"
integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==
fontfaceobserver@2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz"
@@ -7812,15 +7759,6 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
format@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
@@ -12356,11 +12294,6 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz"
@@ -13029,9 +12962,9 @@ react-markdown@8.0.7, react-markdown@~8.0.0:
unist-util-visit "^4.0.0"
vfile "^5.0.0"
react-query@3.39.3:
react-query@^3.34.19:
version "3.39.3"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.3.tgz#4cea7127c6c26bdea2de5fb63e51044330b03f35"
resolved "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz"
integrity sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==
dependencies:
"@babel/runtime" "^7.5.5"
@@ -14860,11 +14793,6 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"

2
go.mod
View File

@@ -5,7 +5,7 @@ go 1.21
require (
github.com/ClickHouse/clickhouse-go/v2 v2.15.0
github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb
github.com/SigNoz/signoz-otel-collector v0.88.3
github.com/SigNoz/signoz-otel-collector v0.88.1
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974
github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974
github.com/antonmedv/expr v1.15.3

4
go.sum
View File

@@ -98,8 +98,8 @@ github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb h1:bneLSKPf9YUSFm
github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb/go.mod h1:JznGDNg9x1cujDKa22RaQOimOvvEfy3nxzDGd8XDgmA=
github.com/SigNoz/prometheus v1.9.78 h1:bB3yuDrRzi/Mv00kWayR9DZbyjTuGfendSqISyDcXiY=
github.com/SigNoz/prometheus v1.9.78/go.mod h1:MffmFu2qFILQrOHehx3D0XjYtaZMVfI+Ppeiv98x4Ww=
github.com/SigNoz/signoz-otel-collector v0.88.3 h1:30sEJZmCQjfjo8CZGxqXKZkWE7Zij9TeS1uUqNFEZRU=
github.com/SigNoz/signoz-otel-collector v0.88.3/go.mod h1:KyEc6JSFS6f8Nw3UdSm4aGDGucEpQYZUdYwjvY8uMVc=
github.com/SigNoz/signoz-otel-collector v0.88.1 h1:Xeu6Kn8VA0g6it60PMIAclayYSIogBq0rnkodlpxllI=
github.com/SigNoz/signoz-otel-collector v0.88.1/go.mod h1:KyEc6JSFS6f8Nw3UdSm4aGDGucEpQYZUdYwjvY8uMVc=
github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc=
github.com/SigNoz/zap_otlp v0.1.0/go.mod h1:lcHvbDbRgvDnPxo9lDlaL1JK2PyOyouP/C3ynnYIvyo=
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 h1:PKVgdf83Yw+lZJbFtNGBgqXiXNf3+kOXW2qZ7Ms7OaY=

View File

@@ -50,8 +50,8 @@ func (r *Repo) GetConfigHistory(
disabled,
deploy_status,
deploy_result,
coalesce(last_hash, '') as last_hash,
coalesce(last_config, '{}') as last_config
last_hash,
last_config
FROM agent_config_versions AS v
WHERE element_type = $1
ORDER BY created_at desc, version desc
@@ -89,8 +89,8 @@ func (r *Repo) GetConfigVersion(
disabled,
deploy_status,
deploy_result,
coalesce(last_hash, '') as last_hash,
coalesce(last_config, '{}') as last_config
last_hash,
last_config
FROM agent_config_versions v
WHERE element_type = $1
AND version = $2`, typ, v)

View File

@@ -172,6 +172,21 @@ func (m *Manager) ReportConfigDeploymentStatus(
}
}
// Ready indicates if Manager can accept new config update requests
func (mgr *Manager) Ready() bool {
if atomic.LoadUint32(&mgr.lock) != 0 {
return false
}
return opamp.Ready()
}
// Static methods for working with default manager instance in this module.
// Ready indicates if Manager can accept new config update requests
func Ready() bool {
return m.Ready()
}
func GetLatestVersion(
ctx context.Context, elementType ElementTypeDef,
) (*ConfigVersion, *model.ApiError) {
@@ -195,6 +210,11 @@ func StartNewVersion(
ctx context.Context, userId string, eleType ElementTypeDef, elementIds []string,
) (*ConfigVersion, *model.ApiError) {
if !m.Ready() {
// agent is already being updated, ask caller to wait and re-try after sometime
return nil, model.UnavailableError(fmt.Errorf("agent updater is busy"))
}
// create a new version
cfg := NewConfigversion(eleType)

View File

@@ -53,8 +53,6 @@ func NewConfigversion(typeDef ElementTypeDef) *ConfigVersion {
IsValid: false,
Disabled: false,
DeployStatus: PendingDeploy,
LastHash: "",
LastConf: "{}",
// todo: get user id from context?
// CreatedBy
}

View File

@@ -43,7 +43,6 @@ import (
promModel "github.com/prometheus/common/model"
"go.uber.org/zap"
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
"go.signoz.io/signoz/pkg/query-service/app/logs"
"go.signoz.io/signoz/pkg/query-service/app/services"
"go.signoz.io/signoz/pkg/query-service/auth"
@@ -52,7 +51,6 @@ import (
"go.signoz.io/signoz/pkg/query-service/interfaces"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/rules"
"go.signoz.io/signoz/pkg/query-service/telemetry"
"go.signoz.io/signoz/pkg/query-service/utils"
)
@@ -3423,100 +3421,6 @@ func (r *ClickHouseReader) GetTagsInfoInLastHeartBeatInterval(ctx context.Contex
return &tagsInfo, nil
}
// GetDashboardsInfo returns analytics data for dashboards
func (r *ClickHouseReader) GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error) {
dashboardsInfo := model.DashboardsInfo{}
// fetch dashboards from dashboard db
query := "SELECT data FROM dashboards"
var dashboardsData []dashboards.Dashboard
err := r.localDB.Select(&dashboardsData, query)
if err != nil {
zap.S().Debug("Error in processing sql query: ", err)
return &dashboardsInfo, err
}
for _, dashboard := range dashboardsData {
dashboardsInfo = countPanelsInDashboard(dashboard.Data)
}
dashboardsInfo.TotalDashboards = len(dashboardsData)
return &dashboardsInfo, nil
}
func countPanelsInDashboard(data map[string]interface{}) model.DashboardsInfo {
var logsPanelCount, tracesPanelCount, metricsPanelCount int
// totalPanels := 0
if data != nil && data["widgets"] != nil {
widgets, ok := data["widgets"].(interface{})
if ok {
data, ok := widgets.([]interface{})
if ok {
for _, widget := range data {
sData, ok := widget.(map[string]interface{})
if ok && sData["query"] != nil {
// totalPanels++
query, ok := sData["query"].(interface{}).(map[string]interface{})
if ok && query["queryType"] == "builder" && query["builder"] != nil {
builderData, ok := query["builder"].(interface{}).(map[string]interface{})
if ok && builderData["queryData"] != nil {
builderQueryData, ok := builderData["queryData"].([]interface{})
if ok {
for _, queryData := range builderQueryData {
data, ok := queryData.(map[string]interface{})
if ok {
if data["dataSource"] == "traces" {
tracesPanelCount++
} else if data["dataSource"] == "metrics" {
metricsPanelCount++
} else if data["dataSource"] == "logs" {
logsPanelCount++
}
}
}
}
}
}
}
}
}
}
}
return model.DashboardsInfo{
LogsBasedPanels: logsPanelCount,
TracesBasedPanels: tracesPanelCount,
MetricBasedPanels: metricsPanelCount,
}
}
func (r *ClickHouseReader) GetAlertsInfo(ctx context.Context) (*model.AlertsInfo, error) {
alertsInfo := model.AlertsInfo{}
// fetch alerts from rules db
query := "SELECT data FROM rules"
var alertsData []string
err := r.localDB.Select(&alertsData, query)
if err != nil {
zap.S().Debug("Error in processing sql query: ", err)
return &alertsInfo, err
}
for _, alert := range alertsData {
var rule rules.GettableRule
err = json.Unmarshal([]byte(alert), &rule)
if err != nil {
zap.S().Errorf("msg:", "invalid rule data", "\t err:", err)
continue
}
if rule.AlertType == "LOGS_BASED_ALERT" {
alertsInfo.LogsBasedAlerts = alertsInfo.LogsBasedAlerts + 1
} else if rule.AlertType == "METRIC_BASED_ALERT" {
alertsInfo.MetricBasedAlerts = alertsInfo.MetricBasedAlerts + 1
} else if rule.AlertType == "TRACES_BASED_ALERT" {
alertsInfo.TracesBasedAlerts = alertsInfo.TracesBasedAlerts + 1
}
alertsInfo.TotalAlerts = alertsInfo.TotalAlerts + 1
}
return &alertsInfo, nil
}
func (r *ClickHouseReader) GetLogFields(ctx context.Context) (*model.GetFieldsResponse, *model.ApiError) {
// response will contain top level fields from the otel log model
response := model.GetFieldsResponse{

View File

@@ -314,7 +314,6 @@ func (aH *APIHandler) RegisterQueryRangeV3Routes(router *mux.Router, am *AuthMid
subRouter.HandleFunc("/autocomplete/attribute_values", am.ViewAccess(
withCacheControl(AutoCompleteCacheControlAge, aH.autoCompleteAttributeValues))).Methods(http.MethodGet)
subRouter.HandleFunc("/query_range", am.ViewAccess(aH.QueryRangeV3)).Methods(http.MethodPost)
subRouter.HandleFunc("/query_range/format", am.ViewAccess(aH.QueryRangeV3Format)).Methods(http.MethodPost)
// live logs
subRouter.HandleFunc("/logs/livetail", am.ViewAccess(aH.liveTailLogs)).Methods(http.MethodGet)
@@ -3002,18 +3001,6 @@ func (aH *APIHandler) getSpanKeysV3(ctx context.Context, queryRangeParams *v3.Qu
return data, nil
}
func (aH *APIHandler) QueryRangeV3Format(w http.ResponseWriter, r *http.Request) {
queryRangeParams, apiErrorObj := ParseQueryRangeParams(r)
if apiErrorObj != nil {
zap.S().Errorf(apiErrorObj.Err.Error())
RespondError(w, apiErrorObj, nil)
return
}
aH.Respond(w, queryRangeParams)
}
func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.QueryRangeParamsV3, w http.ResponseWriter, r *http.Request) {
var result []*v3.Result

View File

@@ -73,6 +73,12 @@ func (ic *LogParsingPipelineController) ApplyPipelines(
}
if !agentConf.Ready() {
return nil, model.UnavailableError(fmt.Errorf(
"agent updater unavailable at the moment. Please try in sometime",
))
}
// prepare config elements
elements := make([]string, len(pipelines))
for i, p := range pipelines {

View File

@@ -483,10 +483,8 @@ func isOrderByTs(orderBy []v3.OrderBy) bool {
func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.PanelType, mq *v3.BuilderQuery, options Options) (string, error) {
// adjust the start and end time to the step interval
if panelType != v3.PanelTypeList {
start = start - (start % (mq.StepInterval * 1000))
end = end - (end % (mq.StepInterval * 1000))
}
start = start - (start % (mq.StepInterval * 1000))
end = end - (end % (mq.StepInterval * 1000))
if options.IsLivetailQuery {
query, err := buildLogsLiveTailQuery(mq)

View File

@@ -1353,7 +1353,7 @@ var testPrepLogsQueryLimitOffsetData = []struct {
PageSize: 5,
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) order by timestamp desc LIMIT 1",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360000000000 AND timestamp <= 1680066420000000000) order by timestamp desc LIMIT 1",
},
{
Name: "Test limit greater than pageSize - order by ts",
@@ -1374,7 +1374,7 @@ var testPrepLogsQueryLimitOffsetData = []struct {
PageSize: 10,
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by timestamp desc LIMIT 10",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360000000000 AND timestamp <= 1680066420000000000) AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by timestamp desc LIMIT 10",
},
{
Name: "Test limit less than pageSize - order by custom",
@@ -1393,7 +1393,7 @@ var testPrepLogsQueryLimitOffsetData = []struct {
PageSize: 5,
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) order by attributes_string_value[indexOf(attributes_string_key, 'method')] desc LIMIT 1 OFFSET 0",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360000000000 AND timestamp <= 1680066420000000000) order by attributes_string_value[indexOf(attributes_string_key, 'method')] desc LIMIT 1 OFFSET 0",
},
{
Name: "Test limit greater than pageSize - order by custom",
@@ -1414,7 +1414,7 @@ var testPrepLogsQueryLimitOffsetData = []struct {
PageSize: 50,
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by attributes_string_value[indexOf(attributes_string_key, 'method')] desc LIMIT 50 OFFSET 50",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360000000000 AND timestamp <= 1680066420000000000) AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by attributes_string_value[indexOf(attributes_string_key, 'method')] desc LIMIT 50 OFFSET 50",
},
}

View File

@@ -1,57 +0,0 @@
package cumulative
import (
"fmt"
"strings"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
)
// groupingSets returns a string of comma separated tags for group by clause
// `ts` is always added to the group by clause
func groupingSets(tags ...string) string {
withTs := append(tags, "ts")
return fmt.Sprintf(`GROUPING SETS ( (%s), (%s) )`, strings.Join(withTs, ", "), strings.Join(tags, ", "))
}
// groupingSetsByAttributeKeyTags returns a string of comma separated tags for group by clause
func groupingSetsByAttributeKeyTags(tags ...v3.AttributeKey) string {
groupTags := []string{}
for _, tag := range tags {
groupTags = append(groupTags, tag.Key)
}
return groupingSets(groupTags...)
}
// groupBy returns a string of comma separated tags for group by clause
func groupByAttributeKeyTags(tags ...v3.AttributeKey) string {
groupTags := []string{}
for _, tag := range tags {
groupTags = append(groupTags, tag.Key)
}
groupTags = append(groupTags, "ts")
return strings.Join(groupTags, ", ")
}
// orderBy returns a string of comma separated tags for order by clause
// if the order is not specified, it defaults to ASC
func orderByAttributeKeyTags(items []v3.OrderBy, tags []v3.AttributeKey) string {
var orderBy []string
for _, tag := range tags {
found := false
for _, item := range items {
if item.ColumnName == tag.Key {
found = true
orderBy = append(orderBy, fmt.Sprintf("%s %s", item.ColumnName, item.Order))
break
}
}
if !found {
orderBy = append(orderBy, fmt.Sprintf("%s ASC", tag.Key))
}
}
orderBy = append(orderBy, "ts ASC")
return strings.Join(orderBy, ", ")
}

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