Compare commits

..

2 Commits

Author SHA1 Message Date
YounixM
3476f3032d format 2024-06-22 14:45:47 +05:30
YounixM
4b757b382f feat: resizable table 2024-06-22 14:04:55 +05:30
114 changed files with 1062 additions and 2991 deletions

View File

@@ -158,7 +158,6 @@ jobs:
echo 'SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env echo 'SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env
echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env
echo 'POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env
- name: Install dependencies - name: Install dependencies
working-directory: frontend working-directory: frontend
run: yarn install run: yarn install

View File

@@ -347,7 +347,7 @@ curl -sL https://github.com/SigNoz/signoz/raw/develop/sample-apps/hotrod/hotrod-
```bash ```bash
kubectl -n sample-application run strzal --image=djbingham/curl \ kubectl -n sample-application run strzal --image=djbingham/curl \
--restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \ --restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \
'user_count=6' -F 'spawn_rate=2' http://locust-master:8089/swarm 'locust_count=6' -F 'hatch_rate=2' http://locust-master:8089/swarm
``` ```
**5.1.3 To stop the load generation:** **5.1.3 To stop the load generation:**

View File

@@ -188,4 +188,3 @@ test:
go test ./pkg/query-service/tests/integration/... go test ./pkg/query-service/tests/integration/...
go test ./pkg/query-service/rules/... go test ./pkg/query-service/rules/...
go test ./pkg/query-service/collectorsimulator/... go test ./pkg/query-service/collectorsimulator/...
go test ./pkg/query-service/postprocess/...

View File

@@ -146,7 +146,7 @@ services:
condition: on-failure condition: on-failure
query-service: query-service:
image: signoz/query-service:0.49.1 image: signoz/query-service:0.46.0
command: command:
[ [
"-config=/root/config/prometheus.yml", "-config=/root/config/prometheus.yml",
@@ -186,7 +186,7 @@ services:
<<: *db-depend <<: *db-depend
frontend: frontend:
image: signoz/frontend:0.48.0 image: signoz/frontend:0.46.0
deploy: deploy:
restart_policy: restart_policy:
condition: on-failure condition: on-failure
@@ -199,7 +199,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:0.102.2 image: signoz/signoz-otel-collector:0.88.24
command: command:
[ [
"--config=/etc/otel-collector-config.yaml", "--config=/etc/otel-collector-config.yaml",
@@ -237,7 +237,7 @@ services:
- query-service - query-service
otel-collector-migrator: otel-collector-migrator:
image: signoz/signoz-schema-migrator:0.102.2 image: signoz/signoz-schema-migrator:0.88.24
deploy: deploy:
restart_policy: restart_policy:
condition: on-failure condition: on-failure

View File

@@ -66,7 +66,7 @@ services:
- --storage.path=/data - --storage.path=/data
otel-collector-migrator: otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
container_name: otel-migrator container_name: otel-migrator
command: command:
- "--dsn=tcp://clickhouse:9000" - "--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` # 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: otel-collector:
container_name: signoz-otel-collector container_name: signoz-otel-collector
image: signoz/signoz-otel-collector:0.102.2 image: signoz/signoz-otel-collector:0.88.24
command: command:
[ [
"--config=/etc/otel-collector-config.yaml", "--config=/etc/otel-collector-config.yaml",

View File

@@ -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` # 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: query-service:
image: signoz/query-service:${DOCKER_TAG:-0.49.1} image: signoz/query-service:${DOCKER_TAG:-0.46.0}
container_name: signoz-query-service container_name: signoz-query-service
command: command:
[ [
@@ -204,7 +204,7 @@ services:
<<: *db-depend <<: *db-depend
frontend: frontend:
image: signoz/frontend:${DOCKER_TAG:-0.49.1} image: signoz/frontend:${DOCKER_TAG:-0.46.0}
container_name: signoz-frontend container_name: signoz-frontend
restart: on-failure restart: on-failure
depends_on: depends_on:
@@ -216,7 +216,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator: otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
container_name: otel-migrator container_name: otel-migrator
command: command:
- "--dsn=tcp://clickhouse:9000" - "--dsn=tcp://clickhouse:9000"
@@ -230,7 +230,7 @@ services:
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.2} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.24}
container_name: signoz-otel-collector container_name: signoz-otel-collector
command: command:
[ [

View File

@@ -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` # 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: query-service:
image: signoz/query-service:${DOCKER_TAG:-0.49.1} image: signoz/query-service:${DOCKER_TAG:-0.46.0}
container_name: signoz-query-service container_name: signoz-query-service
command: command:
[ [
@@ -203,7 +203,7 @@ services:
<<: *db-depend <<: *db-depend
frontend: frontend:
image: signoz/frontend:${DOCKER_TAG:-0.49.1} image: signoz/frontend:${DOCKER_TAG:-0.46.0}
container_name: signoz-frontend container_name: signoz-frontend
restart: on-failure restart: on-failure
depends_on: depends_on:
@@ -215,7 +215,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator: otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
container_name: otel-migrator container_name: otel-migrator
command: command:
- "--dsn=tcp://clickhouse:9000" - "--dsn=tcp://clickhouse:9000"
@@ -229,7 +229,7 @@ services:
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.2} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.24}
container_name: signoz-otel-collector container_name: signoz-otel-collector
command: command:
[ [

View File

@@ -389,7 +389,7 @@ trap bye EXIT
URL="https://api.segment.io/v1/track" URL="https://api.segment.io/v1/track"
HEADER_1="Content-Type: application/json" HEADER_1="Content-Type: application/json"
HEADER_2="Authorization: Basic OWtScko3b1BDR1BFSkxGNlFqTVBMdDVibGpGaFJRQnI=" HEADER_2="Authorization: Basic NEdtb2E0aXhKQVVIeDJCcEp4c2p3QTFiRWZud0VlUno6"
send_event() { send_event() {
error="" error=""

View File

@@ -24,6 +24,7 @@ import (
type APIHandlerOptions struct { type APIHandlerOptions struct {
DataConnector interfaces.DataConnector DataConnector interfaces.DataConnector
SkipConfig *basemodel.SkipConfig SkipConfig *basemodel.SkipConfig
PreferDelta bool
PreferSpanMetrics bool PreferSpanMetrics bool
MaxIdleConns int MaxIdleConns int
MaxOpenConns int MaxOpenConns int
@@ -52,6 +53,7 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{ baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector, Reader: opts.DataConnector,
SkipConfig: opts.SkipConfig, SkipConfig: opts.SkipConfig,
PerferDelta: opts.PreferDelta,
PreferSpanMetrics: opts.PreferSpanMetrics, PreferSpanMetrics: opts.PreferSpanMetrics,
MaxIdleConns: opts.MaxIdleConns, MaxIdleConns: opts.MaxIdleConns,
MaxOpenConns: opts.MaxOpenConns, MaxOpenConns: opts.MaxOpenConns,

View File

@@ -64,6 +64,7 @@ type ServerOptions struct {
// alert specific params // alert specific params
DisableRules bool DisableRules bool
RuleRepoURL string RuleRepoURL string
PreferDelta bool
PreferSpanMetrics bool PreferSpanMetrics bool
MaxIdleConns int MaxIdleConns int
MaxOpenConns int MaxOpenConns int
@@ -255,6 +256,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
apiOpts := api.APIHandlerOptions{ apiOpts := api.APIHandlerOptions{
DataConnector: reader, DataConnector: reader,
SkipConfig: skipConfig, SkipConfig: skipConfig,
PreferDelta: serverOptions.PreferDelta,
PreferSpanMetrics: serverOptions.PreferSpanMetrics, PreferSpanMetrics: serverOptions.PreferSpanMetrics,
MaxIdleConns: serverOptions.MaxIdleConns, MaxIdleConns: serverOptions.MaxIdleConns,
MaxOpenConns: serverOptions.MaxOpenConns, MaxOpenConns: serverOptions.MaxOpenConns,

View File

@@ -89,6 +89,7 @@ func main() {
var cacheConfigPath, fluxInterval string var cacheConfigPath, fluxInterval string
var enableQueryServiceLogOTLPExport bool var enableQueryServiceLogOTLPExport bool
var preferDelta bool
var preferSpanMetrics bool var preferSpanMetrics bool
var maxIdleConns int var maxIdleConns int
@@ -99,13 +100,14 @@ func main() {
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)") flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)") flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)") flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
flag.BoolVar(&preferDelta, "prefer-delta", false, "(prefer delta over cumulative metrics)")
flag.BoolVar(&preferSpanMetrics, "prefer-span-metrics", false, "(prefer span metrics for service level metrics)") flag.BoolVar(&preferSpanMetrics, "prefer-span-metrics", false, "(prefer span metrics for service level metrics)")
flag.IntVar(&maxIdleConns, "max-idle-conns", 50, "(number of connections to maintain in the pool.)") flag.IntVar(&maxIdleConns, "max-idle-conns", 50, "(number of connections to maintain in the pool.)")
flag.IntVar(&maxOpenConns, "max-open-conns", 100, "(max connections for use at any time.)") flag.IntVar(&maxOpenConns, "max-open-conns", 100, "(max connections for use at any time.)")
flag.DurationVar(&dialTimeout, "dial-timeout", 5*time.Second, "(the maximum time to establish a connection.)") flag.DurationVar(&dialTimeout, "dial-timeout", 5*time.Second, "(the maximum time to establish a connection.)")
flag.StringVar(&ruleRepoURL, "rules.repo-url", baseconst.AlertHelpPage, "(host address used to build rule link in alert messages)") flag.StringVar(&ruleRepoURL, "rules.repo-url", baseconst.AlertHelpPage, "(host address used to build rule link in alert messages)")
flag.StringVar(&cacheConfigPath, "experimental.cache-config", "", "(cache config to use)") flag.StringVar(&cacheConfigPath, "experimental.cache-config", "", "(cache config to use)")
flag.StringVar(&fluxInterval, "flux-interval", "5m", "(the interval to exclude data from being cached to avoid incorrect cache for data in motion)") flag.StringVar(&fluxInterval, "flux-interval", "5m", "(cache config to use)")
flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)") flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)")
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')") flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)") flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
@@ -123,6 +125,7 @@ func main() {
HTTPHostPort: baseconst.HTTPHostPort, HTTPHostPort: baseconst.HTTPHostPort,
PromConfigPath: promConfigPath, PromConfigPath: promConfigPath,
SkipTopLvlOpsPath: skipTopLvlOpsPath, SkipTopLvlOpsPath: skipTopLvlOpsPath,
PreferDelta: preferDelta,
PreferSpanMetrics: preferSpanMetrics, PreferSpanMetrics: preferSpanMetrics,
PrivateHostPort: baseconst.PrivateHostPort, PrivateHostPort: baseconst.PrivateHostPort,
DisableRules: disableRules, DisableRules: disableRules,

View File

@@ -34,6 +34,8 @@
"@dnd-kit/core": "6.1.0", "@dnd-kit/core": "6.1.0",
"@dnd-kit/modifiers": "7.0.0", "@dnd-kit/modifiers": "7.0.0",
"@dnd-kit/sortable": "8.0.0", "@dnd-kit/sortable": "8.0.0",
"@dnd-kit/utilities": "3.2.2",
"@faker-js/faker": "8.4.1",
"@grafana/data": "^9.5.2", "@grafana/data": "^9.5.2",
"@mdx-js/loader": "2.3.0", "@mdx-js/loader": "2.3.0",
"@mdx-js/react": "2.3.0", "@mdx-js/react": "2.3.0",
@@ -43,6 +45,7 @@
"@sentry/react": "7.102.1", "@sentry/react": "7.102.1",
"@sentry/webpack-plugin": "2.16.0", "@sentry/webpack-plugin": "2.16.0",
"@signozhq/design-tokens": "0.0.8", "@signozhq/design-tokens": "0.0.8",
"@tanstack/react-table": "8.17.3",
"@uiw/react-md-editor": "3.23.5", "@uiw/react-md-editor": "3.23.5",
"@visx/group": "3.3.0", "@visx/group": "3.3.0",
"@visx/shape": "3.5.0", "@visx/shape": "3.5.0",
@@ -88,7 +91,6 @@
"lucide-react": "0.379.0", "lucide-react": "0.379.0",
"mini-css-extract-plugin": "2.4.5", "mini-css-extract-plugin": "2.4.5",
"papaparse": "5.4.1", "papaparse": "5.4.1",
"posthog-js": "1.142.1",
"rc-tween-one": "3.0.6", "rc-tween-one": "3.0.6",
"react": "18.2.0", "react": "18.2.0",
"react-addons-update": "15.6.3", "react-addons-update": "15.6.3",

View File

@@ -17,7 +17,6 @@ import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute'; import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history'; import history from 'lib/history';
import { identity, pick, pickBy } from 'lodash-es'; import { identity, pick, pickBy } from 'lodash-es';
import posthog from 'posthog-js';
import { DashboardProvider } from 'providers/Dashboard/Dashboard'; import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { QueryBuilderProvider } from 'providers/QueryBuilder'; import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useEffect, useState } from 'react'; import { Suspense, useEffect, useState } from 'react';
@@ -39,7 +38,7 @@ import defaultRoutes, {
function App(): JSX.Element { function App(): JSX.Element {
const themeConfig = useThemeConfig(); const themeConfig = useThemeConfig();
const { data: licenseData } = useLicense(); const { data } = useLicense();
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes); const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
const { role, isLoggedIn: isLoggedInState, user, org } = useSelector< const { role, isLoggedIn: isLoggedInState, user, org } = useSelector<
AppState, AppState,
@@ -93,10 +92,10 @@ function App(): JSX.Element {
}); });
const isOnBasicPlan = const isOnBasicPlan =
licenseData?.payload?.licenses?.some( data?.payload?.licenses?.some(
(license) => (license) =>
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN, license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN,
) || licenseData?.payload?.licenses === null; ) || data?.payload?.licenses === null;
const enableAnalytics = (user: User): void => { const enableAnalytics = (user: User): void => {
const orgName = const orgName =
@@ -113,7 +112,9 @@ function App(): JSX.Element {
}; };
const sanitizedIdentifyPayload = pickBy(identifyPayload, identity); const sanitizedIdentifyPayload = pickBy(identifyPayload, identity);
const domain = extractDomain(email); const domain = extractDomain(email);
const hostNameParts = hostname.split('.'); const hostNameParts = hostname.split('.');
const groupTraits = { const groupTraits = {
@@ -126,30 +127,10 @@ function App(): JSX.Element {
}; };
window.analytics.identify(email, sanitizedIdentifyPayload); window.analytics.identify(email, sanitizedIdentifyPayload);
window.analytics.group(domain, groupTraits); window.analytics.group(domain, groupTraits);
window.clarity('identify', email, name); window.clarity('identify', email, name);
posthog?.identify(email, {
email,
name,
orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
tenant_url: hostname,
company_domain: domain,
source: 'signoz-ui',
isPaidUser: !!licenseData?.payload?.trialConvertedToSubscription,
});
posthog?.group('company', domain, {
name: orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
tenant_url: hostname,
company_domain: domain,
source: 'signoz-ui',
isPaidUser: !!licenseData?.payload?.trialConvertedToSubscription,
});
}; };
useEffect(() => { useEffect(() => {
@@ -163,6 +144,10 @@ function App(): JSX.Element {
!isIdentifiedUser !isIdentifiedUser
) { ) {
setLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true'); setLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true');
if (isCloudUserVal) {
enableAnalytics(user);
}
} }
if ( if (
@@ -210,11 +195,6 @@ function App(): JSX.Element {
console.error('Failed to parse local storage theme analytics event'); console.error('Failed to parse local storage theme analytics event');
} }
} }
if (isCloudUserVal && user && user.email) {
enableAnalytics(user);
}
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [user]); }, [user]);

View File

@@ -5,13 +5,7 @@ import { Button, Dropdown, MenuProps } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { useState } from 'react'; import { useState } from 'react';
function DropDown({ function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
element,
onDropDownItemClick,
}: {
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
}): JSX.Element {
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const items: MenuProps['items'] = element.map( const items: MenuProps['items'] = element.map(
@@ -29,7 +23,6 @@ function DropDown({
items, items,
onMouseEnter: (): void => setDdOpen(true), onMouseEnter: (): void => setDdOpen(true),
onMouseLeave: (): void => setDdOpen(false), onMouseLeave: (): void => setDdOpen(false),
onClick: (item): void => onDropDownItemClick?.(item),
}} }}
open={isDdOpen} open={isDdOpen}
> >
@@ -47,8 +40,4 @@ function DropDown({
); );
} }
DropDown.defaultProps = {
onDropDownItemClick: (): void => {},
};
export default DropDown; export default DropDown;

View File

@@ -62,6 +62,8 @@ function RawLogView({
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const isReadOnlyLog = !isLogsExplorerPage || isReadOnly; const isReadOnlyLog = !isLogsExplorerPage || isReadOnly;
const severityText = data.severity_text ? `${data.severity_text} |` : '';
const logType = getLogIndicatorType(data); const logType = getLogIndicatorType(data);
const updatedSelecedFields = useMemo( const updatedSelecedFields = useMemo(
@@ -86,16 +88,17 @@ function RawLogView({
attributesText += ' | '; attributesText += ' | ';
} }
const text = useMemo(() => { const text = useMemo(
const date = () =>
typeof data.timestamp === 'string' typeof data.timestamp === 'string'
? dayjs(data.timestamp) ? `${dayjs(data.timestamp).format(
: dayjs(data.timestamp / 1e6); 'YYYY-MM-DD HH:mm:ss.SSS',
)} | ${attributesText} ${severityText} ${data.body}`
return `${date.format('YYYY-MM-DD HH:mm:ss.SSS')} | ${attributesText} ${ : `${dayjs(data.timestamp / 1e6).format(
data.body 'YYYY-MM-DD HH:mm:ss.SSS',
}`; )} | ${attributesText} ${severityText} ${data.body}`,
}, [data.timestamp, data.body, attributesText]); [data.timestamp, data.body, severityText, attributesText],
);
const handleClickExpand = useCallback(() => { const handleClickExpand = useCallback(() => {
if (activeContextLog || isReadOnly) return; if (activeContextLog || isReadOnly) return;

View File

@@ -2,9 +2,7 @@
import './DynamicColumnTable.syles.scss'; import './DynamicColumnTable.syles.scss';
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd'; import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
import { ColumnGroupType, ColumnType } from 'antd/es/table';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import logEvent from 'api/common/logEvent';
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn'; import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { SlidersHorizontal } from 'lucide-react'; import { SlidersHorizontal } from 'lucide-react';
import { memo, useEffect, useState } from 'react'; import { memo, useEffect, useState } from 'react';
@@ -24,7 +22,6 @@ function DynamicColumnTable({
dynamicColumns, dynamicColumns,
onDragColumn, onDragColumn,
facingIssueBtn, facingIssueBtn,
shouldSendAlertsLogEvent,
...restProps ...restProps
}: DynamicColumnTableProps): JSX.Element { }: DynamicColumnTableProps): JSX.Element {
const [columnsData, setColumnsData] = useState<ColumnsType | undefined>( const [columnsData, setColumnsData] = useState<ColumnsType | undefined>(
@@ -50,18 +47,11 @@ function DynamicColumnTable({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [columns, dynamicColumns]); }, [columns, dynamicColumns]);
const onToggleHandler = ( const onToggleHandler = (index: number) => (
index: number, checked: boolean,
column: ColumnGroupType<any> | ColumnType<any>, event: React.MouseEvent<HTMLButtonElement>,
) => (checked: boolean, event: React.MouseEvent<HTMLButtonElement>): void => { ): void => {
event.stopPropagation(); event.stopPropagation();
if (shouldSendAlertsLogEvent) {
logEvent('Alert: Column toggled', {
column: column?.title,
action: checked ? 'Enable' : 'Disable',
});
}
setVisibleColumns({ setVisibleColumns({
tablesource, tablesource,
dynamicColumns, dynamicColumns,
@@ -85,7 +75,7 @@ function DynamicColumnTable({
<div>{column.title?.toString()}</div> <div>{column.title?.toString()}</div>
<Switch <Switch
checked={columnsData?.findIndex((c) => c.key === column.key) !== -1} checked={columnsData?.findIndex((c) => c.key === column.key) !== -1}
onChange={onToggleHandler(index, column)} onChange={onToggleHandler(index)}
/> />
</div> </div>
), ),

View File

@@ -3,7 +3,6 @@
import { Table } from 'antd'; import { Table } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import { dragColumnParams } from 'hooks/useDragColumns/configs'; import { dragColumnParams } from 'hooks/useDragColumns/configs';
import { set } from 'lodash-es';
import { import {
SyntheticEvent, SyntheticEvent,
useCallback, useCallback,
@@ -21,7 +20,6 @@ import { ResizeTableProps } from './types';
function ResizeTable({ function ResizeTable({
columns, columns,
onDragColumn, onDragColumn,
pagination,
...restProps ...restProps
}: ResizeTableProps): JSX.Element { }: ResizeTableProps): JSX.Element {
const [columnsData, setColumns] = useState<ColumnsType>([]); const [columnsData, setColumns] = useState<ColumnsType>([]);
@@ -60,21 +58,14 @@ function ResizeTable({
[columnsData, onDragColumn, handleResize], [columnsData, onDragColumn, handleResize],
); );
const tableParams = useMemo(() => { const tableParams = useMemo(
const props = { () => ({
...restProps, ...restProps,
components: { header: { cell: ResizableHeader } }, components: { header: { cell: ResizableHeader } },
columns: mergedColumns, columns: mergedColumns,
}; }),
[mergedColumns, restProps],
set( );
props,
'pagination',
pagination ? { ...pagination, hideOnSinglePage: true } : false,
);
return props;
}, [mergedColumns, pagination, restProps]);
useEffect(() => { useEffect(() => {
if (columns) { if (columns) {

View File

@@ -14,7 +14,6 @@ export interface DynamicColumnTableProps extends TableProps<any> {
dynamicColumns: TableProps<any>['columns']; dynamicColumns: TableProps<any>['columns'];
onDragColumn?: (fromIndex: number, toIndex: number) => void; onDragColumn?: (fromIndex: number, toIndex: number) => void;
facingIssueBtn?: FacingIssueBtnProps; facingIssueBtn?: FacingIssueBtnProps;
shouldSendAlertsLogEvent?: boolean;
} }
export type GetVisibleColumnsFunction = ( export type GetVisibleColumnsFunction = (

View File

@@ -9,6 +9,7 @@ import { Tooltip } from 'antd';
import { themeColors } from 'constants/theme'; import { themeColors } from 'constants/theme';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { popupContainer } from 'utils/selectPopupContainer';
import { style } from './constant'; import { style } from './constant';
@@ -63,7 +64,7 @@ function TextToolTip({
); );
return ( return (
<Tooltip overlay={overlay}> <Tooltip getTooltipContainer={popupContainer} overlay={overlay}>
{useFilledIcon ? ( {useFilledIcon ? (
<QuestionCircleFilled style={iconStyle} /> <QuestionCircleFilled style={iconStyle} />
) : ( ) : (

View File

@@ -1,15 +1,13 @@
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { Tooltip, Typography } from 'antd'; import { Tooltip, Typography } from 'antd';
import getAll from 'api/channels/getAll'; import getAll from 'api/channels/getAll';
import logEvent from 'api/common/logEvent';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import TextToolTip from 'components/TextToolTip'; import TextToolTip from 'components/TextToolTip';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission'; import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch'; import useFetch from 'hooks/useFetch';
import history from 'lib/history'; import history from 'lib/history';
import { isUndefined } from 'lodash-es'; import { useCallback } from 'react';
import { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@@ -33,14 +31,6 @@ function AlertChannels(): JSX.Element {
const { loading, payload, error, errorMessage } = useFetch(getAll); const { loading, payload, error, errorMessage } = useFetch(getAll);
useEffect(() => {
if (!isUndefined(payload)) {
logEvent('Alert Channel: Channel list page visited', {
number: payload?.length,
});
}
}, [payload]);
if (error) { if (error) {
return <Typography>{errorMessage}</Typography>; return <Typography>{errorMessage}</Typography>;
} }

View File

@@ -6,6 +6,7 @@ import './AppLayout.styles.scss';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { Flex } from 'antd'; import { Flex } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get'; import getLocalStorageKey from 'api/browser/localstorage/get';
import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
import getUserLatestVersion from 'api/user/getLatestVersion'; import getUserLatestVersion from 'api/user/getLatestVersion';
import getUserVersion from 'api/user/getVersion'; import getUserVersion from 'api/user/getVersion';
import cx from 'classnames'; import cx from 'classnames';
@@ -37,6 +38,7 @@ import { sideBarCollapse } from 'store/actions';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { import {
UPDATE_CONFIGS,
UPDATE_CURRENT_ERROR, UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION, UPDATE_CURRENT_VERSION,
UPDATE_LATEST_VERSION, UPDATE_LATEST_VERSION,
@@ -64,7 +66,11 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const { pathname } = useLocation(); const { pathname } = useLocation();
const { t } = useTranslation(['titles']); const { t } = useTranslation(['titles']);
const [getUserVersionResponse, getUserLatestVersionResponse] = useQueries([ const [
getUserVersionResponse,
getUserLatestVersionResponse,
getDynamicConfigsResponse,
] = useQueries([
{ {
queryFn: getUserVersion, queryFn: getUserVersion,
queryKey: ['getUserVersion', user?.accessJwt], queryKey: ['getUserVersion', user?.accessJwt],
@@ -75,6 +81,10 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
queryKey: ['getUserLatestVersion', user?.accessJwt], queryKey: ['getUserLatestVersion', user?.accessJwt],
enabled: isLoggedIn, enabled: isLoggedIn,
}, },
{
queryFn: getDynamicConfigs,
queryKey: ['getDynamicConfigs', user?.accessJwt],
},
]); ]);
useEffect(() => { useEffect(() => {
@@ -85,7 +95,15 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
if (getUserVersionResponse.status === 'idle' && isLoggedIn) { if (getUserVersionResponse.status === 'idle' && isLoggedIn) {
getUserVersionResponse.refetch(); getUserVersionResponse.refetch();
} }
}, [getUserLatestVersionResponse, getUserVersionResponse, isLoggedIn]); if (getDynamicConfigsResponse.status === 'idle') {
getDynamicConfigsResponse.refetch();
}
}, [
getUserLatestVersionResponse,
getUserVersionResponse,
isLoggedIn,
getDynamicConfigsResponse,
]);
const { children } = props; const { children } = props;
@@ -93,6 +111,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const latestCurrentCounter = useRef(0); const latestCurrentCounter = useRef(0);
const latestVersionCounter = useRef(0); const latestVersionCounter = useRef(0);
const latestConfigCounter = useRef(0);
const { notifications } = useNotifications(); const { notifications } = useNotifications();
@@ -170,6 +189,23 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
}, },
}); });
} }
if (
getDynamicConfigsResponse.isFetched &&
getDynamicConfigsResponse.isSuccess &&
getDynamicConfigsResponse.data &&
getDynamicConfigsResponse.data.payload &&
latestConfigCounter.current === 0
) {
latestConfigCounter.current = 1;
dispatch({
type: UPDATE_CONFIGS,
payload: {
configs: getDynamicConfigsResponse.data.payload,
},
});
}
}, [ }, [
dispatch, dispatch,
isLoggedIn, isLoggedIn,
@@ -184,6 +220,9 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
getUserLatestVersionResponse.isFetched, getUserLatestVersionResponse.isFetched,
getUserVersionResponse.isFetched, getUserVersionResponse.isFetched,
getUserLatestVersionResponse.isSuccess, getUserLatestVersionResponse.isSuccess,
getDynamicConfigsResponse.data,
getDynamicConfigsResponse.isFetched,
getDynamicConfigsResponse.isSuccess,
notifications, notifications,
]); ]);

View File

@@ -11,12 +11,11 @@ import testOpsGenie from 'api/channels/testOpsgenie';
import testPagerApi from 'api/channels/testPager'; import testPagerApi from 'api/channels/testPager';
import testSlackApi from 'api/channels/testSlack'; import testSlackApi from 'api/channels/testSlack';
import testWebhookApi from 'api/channels/testWebhook'; import testWebhookApi from 'api/channels/testWebhook';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import FormAlertChannels from 'container/FormAlertChannels'; import FormAlertChannels from 'container/FormAlertChannels';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history'; import history from 'lib/history';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -44,10 +43,6 @@ function CreateAlertChannels({
const [formInstance] = Form.useForm(); const [formInstance] = Form.useForm();
useEffect(() => {
logEvent('Alert Channel: Create channel page visited', {});
}, []);
const [selectedConfig, setSelectedConfig] = useState< const [selectedConfig, setSelectedConfig] = useState<
Partial< Partial<
SlackChannel & SlackChannel &
@@ -144,25 +139,19 @@ function CreateAlertChannels({
description: t('channel_creation_done'), description: t('channel_creation_done'),
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
} }
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) { } catch (error) {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: t('channel_creation_failed'), description: t('channel_creation_failed'),
}); });
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
} }
setSavingState(false);
}, [prepareSlackRequest, t, notifications]); }, [prepareSlackRequest, t, notifications]);
const prepareWebhookRequest = useCallback(() => { const prepareWebhookRequest = useCallback(() => {
@@ -211,25 +200,19 @@ function CreateAlertChannels({
description: t('channel_creation_done'), description: t('channel_creation_done'),
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
} }
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) { } catch (error) {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: t('channel_creation_failed'), description: t('channel_creation_failed'),
}); });
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
} }
setSavingState(false);
}, [prepareWebhookRequest, t, notifications]); }, [prepareWebhookRequest, t, notifications]);
const preparePagerRequest = useCallback(() => { const preparePagerRequest = useCallback(() => {
@@ -262,8 +245,8 @@ function CreateAlertChannels({
setSavingState(true); setSavingState(true);
const request = preparePagerRequest(); const request = preparePagerRequest();
try { if (request) {
if (request) { try {
const response = await createPagerApi(request); const response = await createPagerApi(request);
if (response.statusCode === 200) { if (response.statusCode === 200) {
@@ -272,31 +255,20 @@ function CreateAlertChannels({
description: t('channel_creation_done'), description: t('channel_creation_done'),
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
} }
} catch (e) {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: response.error || t('channel_creation_failed'), description: t('channel_creation_failed'),
}); });
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} }
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
} }
setSavingState(false);
}, [t, notifications, preparePagerRequest]); }, [t, notifications, preparePagerRequest]);
const prepareOpsgenieRequest = useCallback( const prepareOpsgenieRequest = useCallback(
@@ -323,25 +295,19 @@ function CreateAlertChannels({
description: t('channel_creation_done'), description: t('channel_creation_done'),
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
} }
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) { } catch (error) {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: t('channel_creation_failed'), description: t('channel_creation_failed'),
}); });
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
} }
setSavingState(false);
}, [prepareOpsgenieRequest, t, notifications]); }, [prepareOpsgenieRequest, t, notifications]);
const prepareEmailRequest = useCallback( const prepareEmailRequest = useCallback(
@@ -366,25 +332,19 @@ function CreateAlertChannels({
description: t('channel_creation_done'), description: t('channel_creation_done'),
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
} }
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) { } catch (error) {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: t('channel_creation_failed'), description: t('channel_creation_failed'),
}); });
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
} }
setSavingState(false);
}, [prepareEmailRequest, t, notifications]); }, [prepareEmailRequest, t, notifications]);
const prepareMsTeamsRequest = useCallback( const prepareMsTeamsRequest = useCallback(
@@ -410,25 +370,19 @@ function CreateAlertChannels({
description: t('channel_creation_done'), description: t('channel_creation_done'),
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
} }
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) { } catch (error) {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: t('channel_creation_failed'), description: t('channel_creation_failed'),
}); });
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
} }
setSavingState(false);
}, [prepareMsTeamsRequest, t, notifications]); }, [prepareMsTeamsRequest, t, notifications]);
const onSaveHandler = useCallback( const onSaveHandler = useCallback(
@@ -446,15 +400,7 @@ function CreateAlertChannels({
const functionToCall = functionMapper[value as keyof typeof functionMapper]; const functionToCall = functionMapper[value as keyof typeof functionMapper];
if (functionToCall) { if (functionToCall) {
const result = await functionToCall(); functionToCall();
logEvent('Alert Channel: Save channel', {
type: value,
sendResolvedAlert: selectedConfig.send_resolved,
name: selectedConfig.name,
new: 'true',
status: result?.status,
statusMessage: result?.statusMessage,
});
} else { } else {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
@@ -463,7 +409,6 @@ function CreateAlertChannels({
} }
} }
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps
[ [
onSlackHandler, onSlackHandler,
onWebhookHandler, onWebhookHandler,
@@ -527,25 +472,14 @@ function CreateAlertChannels({
description: t('channel_test_failed'), description: t('channel_test_failed'),
}); });
} }
logEvent('Alert Channel: Test notification', {
type: channelType,
sendResolvedAlert: selectedConfig.send_resolved,
name: selectedConfig.name,
new: 'true',
status:
response && response.statusCode === 200 ? 'Test success' : 'Test failed',
});
} catch (error) { } catch (error) {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: t('channel_test_unexpected'), description: t('channel_test_unexpected'),
}); });
} }
setTestingState(false); setTestingState(false);
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps
[ [
prepareWebhookRequest, prepareWebhookRequest,
t, t,

View File

@@ -1,6 +1,4 @@
import { Row, Typography } from 'antd'; import { Row, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { AlertTypes } from 'types/api/alerts/alertTypes'; import { AlertTypes } from 'types/api/alerts/alertTypes';
@@ -36,13 +34,6 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
default: default:
break; break;
} }
logEvent('Alert: Sample alert link clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[option],
link: url,
page: 'New alert data source selection page',
});
window.open(url, '_blank'); window.open(url, '_blank');
} }
const renderOptions = useMemo( const renderOptions = useMemo(

View File

@@ -1,5 +1,4 @@
import { Form, Row } from 'antd'; import { Form, Row } from 'antd';
import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V4 } from 'constants/app'; import { ENTITY_VERSION_V4 } from 'constants/app';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import FormAlertRules from 'container/FormAlertRules'; import FormAlertRules from 'container/FormAlertRules';
@@ -69,8 +68,6 @@ function CreateRules(): JSX.Element {
useEffect(() => { useEffect(() => {
if (alertType) { if (alertType) {
onSelectType(alertType); onSelectType(alertType);
} else {
logEvent('Alert: New alert data source selection page visited', {});
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [alertType]); }, [alertType]);

View File

@@ -11,7 +11,6 @@ import testOpsgenie from 'api/channels/testOpsgenie';
import testPagerApi from 'api/channels/testPager'; import testPagerApi from 'api/channels/testPager';
import testSlackApi from 'api/channels/testSlack'; import testSlackApi from 'api/channels/testSlack';
import testWebhookApi from 'api/channels/testWebhook'; import testWebhookApi from 'api/channels/testWebhook';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import { import {
ChannelType, ChannelType,
@@ -90,7 +89,7 @@ function EditAlertChannels({
description: t('webhook_url_required'), description: t('webhook_url_required'),
}); });
setSavingState(false); setSavingState(false);
return { status: 'failed', statusMessage: t('webhook_url_required') }; return;
} }
const response = await editSlackApi(prepareSlackRequest()); const response = await editSlackApi(prepareSlackRequest());
@@ -102,17 +101,13 @@ function EditAlertChannels({
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
} }
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false); setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareSlackRequest, t, notifications, selectedConfig]); }, [prepareSlackRequest, t, notifications, selectedConfig]);
const prepareWebhookRequest = useCallback(() => { const prepareWebhookRequest = useCallback(() => {
@@ -141,13 +136,13 @@ function EditAlertChannels({
if (selectedConfig?.api_url === '') { if (selectedConfig?.api_url === '') {
showError(t('webhook_url_required')); showError(t('webhook_url_required'));
setSavingState(false); setSavingState(false);
return { status: 'failed', statusMessage: t('webhook_url_required') }; return;
} }
if (username && (!password || password === '')) { if (username && (!password || password === '')) {
showError(t('username_no_password')); showError(t('username_no_password'));
setSavingState(false); setSavingState(false);
return { status: 'failed', statusMessage: t('username_no_password') }; return;
} }
const response = await editWebhookApi(prepareWebhookRequest()); const response = await editWebhookApi(prepareWebhookRequest());
@@ -159,15 +154,10 @@ function EditAlertChannels({
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') }; } else {
showError(response.error || t('channel_edit_failed'));
} }
showError(response.error || t('channel_edit_failed'));
setSavingState(false); setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareWebhookRequest, t, notifications, selectedConfig]); }, [prepareWebhookRequest, t, notifications, selectedConfig]);
const prepareEmailRequest = useCallback( const prepareEmailRequest = useCallback(
@@ -191,18 +181,13 @@ function EditAlertChannels({
description: t('channel_edit_done'), description: t('channel_edit_done'),
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
} }
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false); setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareEmailRequest, t, notifications]); }, [prepareEmailRequest, t, notifications]);
const preparePagerRequest = useCallback( const preparePagerRequest = useCallback(
@@ -233,7 +218,7 @@ function EditAlertChannels({
description: validationError, description: validationError,
}); });
setSavingState(false); setSavingState(false);
return { status: 'failed', statusMessage: validationError }; return;
} }
const response = await editPagerApi(preparePagerRequest()); const response = await editPagerApi(preparePagerRequest());
@@ -244,18 +229,13 @@ function EditAlertChannels({
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
} }
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false); setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [preparePagerRequest, notifications, selectedConfig, t]); }, [preparePagerRequest, notifications, selectedConfig, t]);
const prepareOpsgenieRequest = useCallback( const prepareOpsgenieRequest = useCallback(
@@ -279,7 +259,7 @@ function EditAlertChannels({
description: t('api_key_required'), description: t('api_key_required'),
}); });
setSavingState(false); setSavingState(false);
return { status: 'failed', statusMessage: t('api_key_required') }; return;
} }
const response = await editOpsgenie(prepareOpsgenieRequest()); const response = await editOpsgenie(prepareOpsgenieRequest());
@@ -291,18 +271,13 @@ function EditAlertChannels({
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
} }
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false); setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareOpsgenieRequest, t, notifications, selectedConfig]); }, [prepareOpsgenieRequest, t, notifications, selectedConfig]);
const prepareMsTeamsRequest = useCallback( const prepareMsTeamsRequest = useCallback(
@@ -326,7 +301,7 @@ function EditAlertChannels({
description: t('webhook_url_required'), description: t('webhook_url_required'),
}); });
setSavingState(false); setSavingState(false);
return { status: 'failed', statusMessage: t('webhook_url_required') }; return;
} }
const response = await editMsTeamsApi(prepareMsTeamsRequest()); const response = await editMsTeamsApi(prepareMsTeamsRequest());
@@ -338,46 +313,31 @@ function EditAlertChannels({
}); });
history.replace(ROUTES.ALL_CHANNELS); history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') }; } else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
} }
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false); setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareMsTeamsRequest, t, notifications, selectedConfig]); }, [prepareMsTeamsRequest, t, notifications, selectedConfig]);
const onSaveHandler = useCallback( const onSaveHandler = useCallback(
async (value: ChannelType) => { (value: ChannelType) => {
let result;
if (value === ChannelType.Slack) { if (value === ChannelType.Slack) {
result = await onSlackEditHandler(); onSlackEditHandler();
} else if (value === ChannelType.Webhook) { } else if (value === ChannelType.Webhook) {
result = await onWebhookEditHandler(); onWebhookEditHandler();
} else if (value === ChannelType.Pagerduty) { } else if (value === ChannelType.Pagerduty) {
result = await onPagerEditHandler(); onPagerEditHandler();
} else if (value === ChannelType.MsTeams) { } else if (value === ChannelType.MsTeams) {
result = await onMsTeamsEditHandler(); onMsTeamsEditHandler();
} else if (value === ChannelType.Opsgenie) { } else if (value === ChannelType.Opsgenie) {
result = await onOpsgenieEditHandler(); onOpsgenieEditHandler();
} else if (value === ChannelType.Email) { } else if (value === ChannelType.Email) {
result = await onEmailEditHandler(); onEmailEditHandler();
} }
logEvent('Alert Channel: Save channel', {
type: value,
sendResolvedAlert: selectedConfig.send_resolved,
name: selectedConfig.name,
new: 'false',
status: result?.status,
statusMessage: result?.statusMessage,
});
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps
[ [
onSlackEditHandler, onSlackEditHandler,
onWebhookEditHandler, onWebhookEditHandler,
@@ -439,14 +399,6 @@ function EditAlertChannels({
description: t('channel_test_failed'), description: t('channel_test_failed'),
}); });
} }
logEvent('Alert Channel: Test notification', {
type: channelType,
sendResolvedAlert: selectedConfig.send_resolved,
name: selectedConfig.name,
new: 'false',
status:
response && response.statusCode === 200 ? 'Test success' : 'Test failed',
});
} catch (error) { } catch (error) {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
@@ -455,7 +407,6 @@ function EditAlertChannels({
} }
setTestingState(false); setTestingState(false);
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps
[ [
t, t,
prepareWebhookRequest, prepareWebhookRequest,

View File

@@ -3,8 +3,6 @@ import './FormAlertRules.styles.scss';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { Button, Form, Select, Switch, Tooltip } from 'antd'; import { Button, Form, Select, Switch, Tooltip } from 'antd';
import getChannels from 'api/channels/getAll'; import getChannels from 'api/channels/getAll';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission'; import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch'; import useFetch from 'hooks/useFetch';
@@ -12,7 +10,6 @@ import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef, Labels } from 'types/api/alerts/def'; import { AlertDef, Labels } from 'types/api/alerts/def';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import { requireErrorMessage } from 'utils/form/requireErrorMessage'; import { requireErrorMessage } from 'utils/form/requireErrorMessage';
@@ -76,24 +73,9 @@ function BasicInfo({
const noChannels = channels.payload?.length === 0; const noChannels = channels.payload?.length === 0;
const handleCreateNewChannels = useCallback(() => { const handleCreateNewChannels = useCallback(() => {
logEvent('Alert: Create notification channel button clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
ruleId: isNewRule ? 0 : alertDef?.id,
});
window.open(ROUTES.CHANNELS_NEW, '_blank'); window.open(ROUTES.CHANNELS_NEW, '_blank');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
useEffect(() => {
if (!channels.loading && isNewRule) {
logEvent('Alert: New alert creation page visited', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
numberOfChannels: channels.payload?.length,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [channels.payload, channels.loading]);
return ( return (
<> <>
<StepHeading> {t('alert_form_step3')} </StepHeading> <StepHeading> {t('alert_form_step3')} </StepHeading>

View File

@@ -2,7 +2,6 @@ import './QuerySection.styles.scss';
import { Color } from '@signozhq/design-tokens'; import { Color } from '@signozhq/design-tokens';
import { Button, Tabs, Tooltip } from 'antd'; import { Button, Tabs, Tooltip } from 'antd';
import logEvent from 'api/common/logEvent';
import PromQLIcon from 'assets/Dashboard/PromQl'; import PromQLIcon from 'assets/Dashboard/PromQl';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts'; import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { ENTITY_VERSION_V4 } from 'constants/app'; import { ENTITY_VERSION_V4 } from 'constants/app';
@@ -32,7 +31,6 @@ function QuerySection({
runQuery, runQuery,
alertDef, alertDef,
panelType, panelType,
ruleId,
}: QuerySectionProps): JSX.Element { }: QuerySectionProps): JSX.Element {
// init namespace for translations // init namespace for translations
const { t } = useTranslation('alerts'); const { t } = useTranslation('alerts');
@@ -160,15 +158,7 @@ function QuerySection({
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}> <span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<Button <Button
type="primary" type="primary"
onClick={(): void => { onClick={runQuery}
runQuery();
logEvent('Alert: Stage and run query', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertType],
isNewRule: !ruleId || ruleId === 0,
ruleId,
queryType: queryCategory,
});
}}
className="stage-run-query" className="stage-run-query"
icon={<Play size={14} />} icon={<Play size={14} />}
> >
@@ -238,7 +228,6 @@ interface QuerySectionProps {
runQuery: VoidFunction; runQuery: VoidFunction;
alertDef: AlertDef; alertDef: AlertDef;
panelType: PANEL_TYPES; panelType: PANEL_TYPES;
ruleId: number;
} }
export default QuerySection; export default QuerySection;

View File

@@ -12,10 +12,8 @@ import {
} from 'antd'; } from 'antd';
import saveAlertApi from 'api/alerts/save'; import saveAlertApi from 'api/alerts/save';
import testAlertApi from 'api/alerts/testAlert'; import testAlertApi from 'api/alerts/testAlert';
import logEvent from 'api/common/logEvent';
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn'; import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { alertHelpMessage } from 'components/facingIssueBtn/util'; import { alertHelpMessage } from 'components/facingIssueBtn/util';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { FeatureKeys } from 'constants/features'; import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -340,13 +338,8 @@ function FormAlertRules({
return; return;
} }
const postableAlert = memoizedPreparePostData(); const postableAlert = memoizedPreparePostData();
setLoading(true); setLoading(true);
let logData = {
status: 'error',
statusMessage: t('unexpected_error'),
};
try { try {
const apiReq = const apiReq =
ruleId && ruleId > 0 ruleId && ruleId > 0
@@ -356,15 +349,10 @@ function FormAlertRules({
const response = await saveAlertApi(apiReq); const response = await saveAlertApi(apiReq);
if (response.statusCode === 200) { if (response.statusCode === 200) {
logData = {
status: 'success',
statusMessage:
!ruleId || ruleId === 0 ? t('rule_created') : t('rule_edited'),
};
notifications.success({ notifications.success({
message: 'Success', message: 'Success',
description: logData.statusMessage, description:
!ruleId || ruleId === 0 ? t('rule_created') : t('rule_edited'),
}); });
// invalidate rule in cache // invalidate rule in cache
@@ -379,42 +367,18 @@ function FormAlertRules({
history.replace(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`); history.replace(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
}, 2000); }, 2000);
} else { } else {
logData = {
status: 'error',
statusMessage: response.error || t('unexpected_error'),
};
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: logData.statusMessage, description: response.error || t('unexpected_error'),
}); });
} }
} catch (e) { } catch (e) {
logData = {
status: 'error',
statusMessage: t('unexpected_error'),
};
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: logData.statusMessage, description: t('unexpected_error'),
}); });
} }
setLoading(false); setLoading(false);
logEvent('Alert: Save alert', {
...logData,
dataSource: ALERTS_DATA_SOURCE_MAP[postableAlert?.alertType as AlertTypes],
channelNames: postableAlert?.preferredChannels,
broadcastToAll: postableAlert?.broadcastToAll,
isNewRule: !ruleId || ruleId === 0,
ruleId,
queryType: currentQuery.queryType,
alertId: postableAlert?.id,
alertName: postableAlert?.alert,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
isFormValid, isFormValid,
memoizedPreparePostData, memoizedPreparePostData,
@@ -450,7 +414,6 @@ function FormAlertRules({
} }
const postableAlert = memoizedPreparePostData(); const postableAlert = memoizedPreparePostData();
let statusResponse = { status: 'failed', message: '' };
setLoading(true); setLoading(true);
try { try {
const response = await testAlertApi({ data: postableAlert }); const response = await testAlertApi({ data: postableAlert });
@@ -462,43 +425,25 @@ function FormAlertRules({
message: 'Error', message: 'Error',
description: t('no_alerts_found'), description: t('no_alerts_found'),
}); });
statusResponse = { status: 'failed', message: t('no_alerts_found') };
} else { } else {
notifications.success({ notifications.success({
message: 'Success', message: 'Success',
description: t('rule_test_fired'), description: t('rule_test_fired'),
}); });
statusResponse = { status: 'success', message: t('rule_test_fired') };
} }
} else { } else {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: response.error || t('unexpected_error'), description: response.error || t('unexpected_error'),
}); });
statusResponse = {
status: 'failed',
message: response.error || t('unexpected_error'),
};
} }
} catch (e) { } catch (e) {
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
description: t('unexpected_error'), description: t('unexpected_error'),
}); });
statusResponse = { status: 'failed', message: t('unexpected_error') };
} }
setLoading(false); setLoading(false);
logEvent('Alert: Test notification', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
channelNames: postableAlert?.preferredChannels,
broadcastToAll: postableAlert?.broadcastToAll,
isNewRule: !ruleId || ruleId === 0,
ruleId,
queryType: currentQuery.queryType,
status: statusResponse.status,
statusMessage: statusResponse.message,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [t, isFormValid, memoizedPreparePostData, notifications]); }, [t, isFormValid, memoizedPreparePostData, notifications]);
const renderBasicInfo = (): JSX.Element => ( const renderBasicInfo = (): JSX.Element => (
@@ -568,16 +513,6 @@ function FormAlertRules({
const isRuleCreated = !ruleId || ruleId === 0; const isRuleCreated = !ruleId || ruleId === 0;
useEffect(() => {
if (!isRuleCreated) {
logEvent('Alert: Edit page visited', {
ruleId,
dataSource: ALERTS_DATA_SOURCE_MAP[alertType as AlertTypes],
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function handleRedirection(option: AlertTypes): void { function handleRedirection(option: AlertTypes): void {
let url = ''; let url = '';
switch (option) { switch (option) {
@@ -600,13 +535,6 @@ function FormAlertRules({
default: default:
break; break;
} }
logEvent('Alert: Check example alert clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
isNewRule: !ruleId || ruleId === 0,
ruleId,
queryType: currentQuery.queryType,
link: url,
});
window.open(url, '_blank'); window.open(url, '_blank');
} }
@@ -644,7 +572,6 @@ function FormAlertRules({
alertDef={alertDef} alertDef={alertDef}
panelType={panelType || PANEL_TYPES.TIME_SERIES} panelType={panelType || PANEL_TYPES.TIME_SERIES}
key={currentQuery.queryType} key={currentQuery.queryType}
ruleId={ruleId}
/> />
<RuleOptions <RuleOptions

View File

@@ -80,8 +80,6 @@ function FullView({
query: updatedQuery, query: updatedQuery,
globalSelectedInterval: globalSelectedTime, globalSelectedInterval: globalSelectedTime,
variables: getDashboardVariables(selectedDashboard?.data.variables), variables: getDashboardVariables(selectedDashboard?.data.variables),
fillGaps: widget.fillSpans,
formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE,
}; };
} }
updatedQuery.builder.queryData[0].pageSize = 10; updatedQuery.builder.queryData[0].pageSize = 10;

View File

@@ -109,7 +109,6 @@ function GridCardGraph({
globalSelectedInterval, globalSelectedInterval,
variables: getDashboardVariables(variables), variables: getDashboardVariables(variables),
fillGaps: widget.fillSpans, fillGaps: widget.fillSpans,
formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE,
}; };
} }
updatedQuery.builder.queryData[0].pageSize = 10; updatedQuery.builder.queryData[0].pageSize = 10;

View File

@@ -1,215 +0,0 @@
export const tableDataMultipleQueriesSuccessResponse = {
columns: [
{
name: 'service_name',
queryName: '',
isValueColumn: false,
},
{
name: 'A',
queryName: 'A',
isValueColumn: true,
},
{
name: 'B',
queryName: 'B',
isValueColumn: true,
},
],
rows: [
{
data: {
A: 4196.71,
B: 'n/a',
service_name: 'demo-app',
},
},
{
data: {
A: 500.83,
B: 'n/a',
service_name: 'customer',
},
},
{
data: {
A: 499.5,
B: 'n/a',
service_name: 'mysql',
},
},
{
data: {
A: 293.22,
B: 'n/a',
service_name: 'frontend',
},
},
{
data: {
A: 230.03,
B: 'n/a',
service_name: 'driver',
},
},
{
data: {
A: 67.09,
B: 'n/a',
service_name: 'route',
},
},
{
data: {
A: 30.96,
B: 'n/a',
service_name: 'redis',
},
},
{
data: {
A: 'n/a',
B: 112.27,
service_name: 'n/a',
},
},
],
};
export const widgetQueryWithLegend = {
clickhouse_sql: [
{
name: 'A',
legend: '',
disabled: false,
query: '',
},
],
promql: [
{
name: 'A',
query: '',
legend: '',
disabled: false,
},
],
builder: {
queryData: [
{
dataSource: 'metrics',
queryName: 'A',
aggregateOperator: 'count',
aggregateAttribute: {
dataType: 'float64',
id: 'signoz_latency--float64--ExponentialHistogram--true',
isColumn: true,
isJSON: false,
key: 'signoz_latency',
type: 'ExponentialHistogram',
},
timeAggregation: '',
spaceAggregation: 'p90',
functions: [],
filters: {
items: [],
op: 'AND',
},
expression: 'A',
disabled: false,
stepInterval: 60,
having: [],
limit: null,
orderBy: [],
groupBy: [
{
dataType: 'string',
isColumn: false,
isJSON: false,
key: 'service_name',
type: 'tag',
id: 'service_name--string--tag--false',
},
],
legend: 'p99',
reduceTo: 'avg',
},
{
dataSource: 'metrics',
queryName: 'B',
aggregateOperator: 'rate',
aggregateAttribute: {
dataType: 'float64',
id: 'system_disk_operations--float64--Sum--true',
isColumn: true,
isJSON: false,
key: 'system_disk_operations',
type: 'Sum',
},
timeAggregation: 'rate',
spaceAggregation: 'sum',
functions: [],
filters: {
items: [],
op: 'AND',
},
expression: 'B',
disabled: false,
stepInterval: 60,
having: [],
limit: null,
orderBy: [],
groupBy: [],
legend: '',
reduceTo: 'avg',
},
],
queryFormulas: [],
},
id: '48ad5a67-9a3c-49d4-a886-d7a34f8b875d',
queryType: 'builder',
};
export const expectedOutputWithLegends = {
dataSource: [
{
A: 4196.71,
B: 'n/a',
service_name: 'demo-app',
},
{
A: 500.83,
B: 'n/a',
service_name: 'customer',
},
{
A: 499.5,
B: 'n/a',
service_name: 'mysql',
},
{
A: 293.22,
B: 'n/a',
service_name: 'frontend',
},
{
A: 230.03,
B: 'n/a',
service_name: 'driver',
},
{
A: 67.09,
B: 'n/a',
service_name: 'route',
},
{
A: 30.96,
B: 'n/a',
service_name: 'redis',
},
{
A: 'n/a',
B: 112.27,
service_name: 'n/a',
},
],
};

View File

@@ -1,42 +0,0 @@
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { createColumnsAndDataSource, getQueryLegend } from '../utils';
import {
expectedOutputWithLegends,
tableDataMultipleQueriesSuccessResponse,
widgetQueryWithLegend,
} from './response';
describe('Table Panel utils', () => {
it('createColumnsAndDataSource function', () => {
const data = tableDataMultipleQueriesSuccessResponse;
const query = widgetQueryWithLegend as Query;
const { columns, dataSource } = createColumnsAndDataSource(data, query);
expect(dataSource).toStrictEqual(expectedOutputWithLegends.dataSource);
// this makes sure that the columns are rendered in the same order as response
expect(columns[0].title).toBe('service_name');
// the next specifically makes sure that the legends are properly applied in multiple queries
expect(columns[1].title).toBe('p99');
// this makes sure that the query without a legend takes the title from the query response
expect(columns[2].title).toBe('B');
// this is to ensure that the rows properly map to the column data indexes as the dataIndex should be equal to name of the columns
// returned in the response as the rows will be mapped with them
expect((columns[0] as any).dataIndex).toBe('service_name');
expect((columns[1] as any).dataIndex).toBe('A');
expect((columns[2] as any).dataIndex).toBe('B');
});
it('getQueryLegend function', () => {
const query = widgetQueryWithLegend as Query;
// query A has a legend of p99
expect(getQueryLegend(query, 'A')).toBe('p99');
// should return undefined when legend not present
expect(getQueryLegend(query, 'B')).toBe(undefined);
});
});

View File

@@ -3,7 +3,10 @@ import { Space, Tooltip } from 'antd';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig'; import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import { Events } from 'constants/events'; import { Events } from 'constants/events';
import { QueryTable } from 'container/QueryTable'; import { QueryTable } from 'container/QueryTable';
import { RowData } from 'lib/query/createTableColumnsFromQuery'; import {
createTableColumnsFromQuery,
RowData,
} from 'lib/query/createTableColumnsFromQuery';
import { cloneDeep, get, isEmpty, set } from 'lodash-es'; import { cloneDeep, get, isEmpty, set } from 'lodash-es';
import { memo, ReactNode, useCallback, useEffect, useMemo } from 'react'; import { memo, ReactNode, useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -11,11 +14,7 @@ import { eventEmitter } from 'utils/getEventEmitter';
import { WrapperStyled } from './styles'; import { WrapperStyled } from './styles';
import { GridTableComponentProps } from './types'; import { GridTableComponentProps } from './types';
import { import { findMatchingThreshold } from './utils';
createColumnsAndDataSource,
findMatchingThreshold,
TableData,
} from './utils';
function GridTableComponent({ function GridTableComponent({
data, data,
@@ -26,26 +25,28 @@ function GridTableComponent({
...props ...props
}: GridTableComponentProps): JSX.Element { }: GridTableComponentProps): JSX.Element {
const { t } = useTranslation(['valueGraph']); const { t } = useTranslation(['valueGraph']);
// create columns and dataSource in the ui friendly structure
// use the query from the widget here to extract the legend information
const { columns, dataSource: originalDataSource } = useMemo( const { columns, dataSource: originalDataSource } = useMemo(
() => createColumnsAndDataSource((data as unknown) as TableData, query), () =>
[query, data], createTableColumnsFromQuery({
query,
queryTableData: data,
}),
[data, query],
); );
const createDataInCorrectFormat = useCallback( const createDataInCorrectFormat = useCallback(
(dataSource: RowData[]): RowData[] => (dataSource: RowData[]): RowData[] =>
dataSource.map((d) => { dataSource.map((d) => {
const finalObject = {}; const finalObject = {};
const keys = Object.keys(d);
// we use the order of the columns here to have similar download as the user view keys.forEach((k) => {
columns.forEach((k) => { const label = get(
set( columns.find((c) => get(c, 'dataIndex', '') === k) || {},
finalObject, 'title',
get(k, 'title', '') as string, '',
get(d, get(k, 'dataIndex', ''), 'n/a'),
); );
if (label) {
set(finalObject, label as string, d[k]);
}
}); });
return finalObject as RowData; return finalObject as RowData;
}), }),
@@ -64,11 +65,7 @@ function GridTableComponent({
const newValue = { ...val }; const newValue = { ...val };
Object.keys(val).forEach((k) => { Object.keys(val).forEach((k) => {
if (columnUnits[k]) { if (columnUnits[k]) {
// the check below takes care of not adding units for rows that have n/a values newValue[k] = getYAxisFormattedValue(String(val[k]), columnUnits[k]);
newValue[k] =
val[k] !== 'n/a'
? getYAxisFormattedValue(String(val[k]), columnUnits[k])
: val[k];
newValue[`${k}_without_unit`] = val[k]; newValue[`${k}_without_unit`] = val[k];
} }
}); });

View File

@@ -1,11 +1,4 @@
import { ColumnsType, ColumnType } from 'antd/es/table';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types'; import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config';
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { isEmpty, isNaN } from 'lodash-es';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
// Helper function to evaluate the condition based on the operator // Helper function to evaluate the condition based on the operator
function evaluateCondition( function evaluateCondition(
@@ -63,85 +56,3 @@ export function findMatchingThreshold(
hasMultipleMatches, hasMultipleMatches,
}; };
} }
export interface TableData {
columns: { name: string; queryName: string; isValueColumn: boolean }[];
rows: { data: any }[];
}
export function getQueryLegend(
currentQuery: Query,
queryName: string,
): string | undefined {
let legend: string | undefined;
switch (currentQuery.queryType) {
case EQueryType.QUERY_BUILDER:
// check if the value is present in the queries
legend = currentQuery.builder.queryData.find(
(query) => query.queryName === queryName,
)?.legend;
if (!legend) {
// check if the value is present in the formula
legend = currentQuery.builder.queryFormulas.find(
(query) => query.queryName === queryName,
)?.legend;
}
break;
case EQueryType.CLICKHOUSE:
legend = currentQuery.clickhouse_sql.find(
(query) => query.name === queryName,
)?.legend;
break;
case EQueryType.PROM:
legend = currentQuery.promql.find((query) => query.name === queryName)
?.legend;
break;
default:
legend = undefined;
break;
}
return legend;
}
export function createColumnsAndDataSource(
data: TableData,
currentQuery: Query,
renderColumnCell?: QueryTableProps['renderColumnCell'],
): { columns: ColumnsType<RowData>; dataSource: RowData[] } {
const columns: ColumnsType<RowData> =
data.columns?.reduce<ColumnsType<RowData>>((acc, item) => {
// is the column is the value column then we need to check for the available legend
const legend = item.isValueColumn
? getQueryLegend(currentQuery, item.queryName)
: undefined;
const column: ColumnType<RowData> = {
dataIndex: item.name,
// if no legend present then rely on the column name value
title: !isEmpty(legend) ? legend : item.name,
width: QUERY_TABLE_CONFIG.width,
render: renderColumnCell && renderColumnCell[item.name],
sorter: (a: RowData, b: RowData): number => {
const valueA = Number(a[`${item.name}_without_unit`] ?? a[item.name]);
const valueB = Number(b[`${item.name}_without_unit`] ?? b[item.name]);
if (!isNaN(valueA) && !isNaN(valueB)) {
return valueA - valueB;
}
return ((a[item.name] as string) || '').localeCompare(
(b[item.name] as string) || '',
);
},
};
return [...acc, column];
}, []) || [];
// the rows returned have data encapsulation hence removing the same here
const dataSource = data.rows?.map((d) => d.data) || [];
return { columns, dataSource };
}

View File

@@ -27,7 +27,6 @@ import {
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { License } from 'types/api/licenses/def';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils'; import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
@@ -110,13 +109,9 @@ function HeaderContainer(): JSX.Element {
const { data: licenseData, isFetching, status: licenseStatus } = useLicense(); const { data: licenseData, isFetching, status: licenseStatus } = useLicense();
const licensesStatus: string =
licenseData?.payload?.licenses?.find((e: License) => e.isCurrent)?.status ||
'';
const isLicenseActive = const isLicenseActive =
licensesStatus?.toLocaleLowerCase() === licenseData?.payload?.licenses?.find((e) => e.isCurrent)?.status ===
LICENSE_PLAN_STATUS.VALID.toLocaleLowerCase(); LICENSE_PLAN_STATUS.VALID;
useEffect(() => { useEffect(() => {
if ( if (

View File

@@ -7,20 +7,17 @@ interface AlertInfoCardProps {
header: string; header: string;
subheader: string; subheader: string;
link: string; link: string;
onClick: () => void;
} }
function AlertInfoCard({ function AlertInfoCard({
header, header,
subheader, subheader,
link, link,
onClick,
}: AlertInfoCardProps): JSX.Element { }: AlertInfoCardProps): JSX.Element {
return ( return (
<div <div
className="alert-info-card" className="alert-info-card"
onClick={(): void => { onClick={(): void => {
onClick();
window.open(link, '_blank'); window.open(link, '_blank');
}} }}
> >

View File

@@ -2,7 +2,6 @@ import './AlertsEmptyState.styles.scss';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { Button, Divider, Typography } from 'antd'; import { Button, Divider, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission'; import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
@@ -11,26 +10,12 @@ import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { DataSource } from 'types/common/queryBuilder';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import AlertInfoCard from './AlertInfoCard'; import AlertInfoCard from './AlertInfoCard';
import { ALERT_CARDS, ALERT_INFO_LINKS } from './alertLinks'; import { ALERT_CARDS, ALERT_INFO_LINKS } from './alertLinks';
import InfoLinkText from './InfoLinkText'; import InfoLinkText from './InfoLinkText';
const alertLogEvents = (
title: string,
link: string,
dataSource?: DataSource,
): void => {
const attributes = {
link,
page: 'Alert empty state page',
};
logEvent(title, dataSource ? { ...attributes, dataSource } : attributes);
};
export function AlertsEmptyState(): JSX.Element { export function AlertsEmptyState(): JSX.Element {
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const { role, featureResponse } = useSelector<AppState, AppReducer>( const { role, featureResponse } = useSelector<AppState, AppReducer>(
@@ -106,33 +91,18 @@ export function AlertsEmptyState(): JSX.Element {
link="https://youtu.be/xjxNIqiv4_M" link="https://youtu.be/xjxNIqiv4_M"
leftIconVisible leftIconVisible
rightIconVisible rightIconVisible
onClick={(): void =>
alertLogEvents(
'Alert: Video tutorial link clicked',
'https://youtu.be/xjxNIqiv4_M',
)
}
/> />
</div> </div>
{ALERT_INFO_LINKS.map((info) => { {ALERT_INFO_LINKS.map((info) => (
const logEventTriggered = (): void => <InfoLinkText
alertLogEvents( key={info.link}
'Alert: Tutorial doc link clicked', infoText={info.infoText}
info.link, link={info.link}
info.dataSource, leftIconVisible={info.leftIconVisible}
); rightIconVisible={info.rightIconVisible}
return ( />
<InfoLinkText ))}
key={info.link}
infoText={info.infoText}
link={info.link}
leftIconVisible={info.leftIconVisible}
rightIconVisible={info.rightIconVisible}
onClick={logEventTriggered}
/>
);
})}
</div> </div>
</section> </section>
<div className="get-started-text"> <div className="get-started-text">
@@ -143,23 +113,14 @@ export function AlertsEmptyState(): JSX.Element {
</Divider> </Divider>
</div> </div>
{ALERT_CARDS.map((card) => { {ALERT_CARDS.map((card) => (
const logEventTriggered = (): void => <AlertInfoCard
alertLogEvents( key={card.link}
'Alert: Sample alert link clicked', header={card.header}
card.link, subheader={card.subheader}
card.dataSource, link={card.link}
); />
return ( ))}
<AlertInfoCard
key={card.link}
header={card.header}
subheader={card.subheader}
link={card.link}
onClick={logEventTriggered}
/>
);
})}
</div> </div>
</div> </div>
); );

View File

@@ -6,7 +6,6 @@ interface InfoLinkTextProps {
link: string; link: string;
leftIconVisible: boolean; leftIconVisible: boolean;
rightIconVisible: boolean; rightIconVisible: boolean;
onClick: () => void;
} }
function InfoLinkText({ function InfoLinkText({
@@ -14,12 +13,10 @@ function InfoLinkText({
link, link,
leftIconVisible, leftIconVisible,
rightIconVisible, rightIconVisible,
onClick,
}: InfoLinkTextProps): JSX.Element { }: InfoLinkTextProps): JSX.Element {
return ( return (
<Flex <Flex
onClick={(): void => { onClick={(): void => {
onClick();
window.open(link, '_blank'); window.open(link, '_blank');
}} }}
className="info-link-container" className="info-link-container"

View File

@@ -1,5 +1,3 @@
import { DataSource } from 'types/common/queryBuilder';
export const ALERT_INFO_LINKS = [ export const ALERT_INFO_LINKS = [
{ {
infoText: 'How to create Metrics-based alerts', infoText: 'How to create Metrics-based alerts',
@@ -7,7 +5,6 @@ export const ALERT_INFO_LINKS = [
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page', 'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
leftIconVisible: false, leftIconVisible: false,
rightIconVisible: true, rightIconVisible: true,
dataSource: DataSource.METRICS,
}, },
{ {
infoText: 'How to create Log-based alerts', infoText: 'How to create Log-based alerts',
@@ -15,7 +12,6 @@ export const ALERT_INFO_LINKS = [
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page', 'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
leftIconVisible: false, leftIconVisible: false,
rightIconVisible: true, rightIconVisible: true,
dataSource: DataSource.LOGS,
}, },
{ {
infoText: 'How to create Trace-based alerts', infoText: 'How to create Trace-based alerts',
@@ -23,7 +19,6 @@ export const ALERT_INFO_LINKS = [
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page', 'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
leftIconVisible: false, leftIconVisible: false,
rightIconVisible: true, rightIconVisible: true,
dataSource: DataSource.TRACES,
}, },
]; ];
@@ -31,28 +26,24 @@ export const ALERT_CARDS = [
{ {
header: 'Alert on high memory usage', header: 'Alert on high memory usage',
subheader: "Monitor your host's memory usage", subheader: "Monitor your host's memory usage",
dataSource: DataSource.METRICS,
link: link:
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-memory-usage-for-host-goes-above-400-mb-or-any-fixed-memory', 'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-memory-usage-for-host-goes-above-400-mb-or-any-fixed-memory',
}, },
{ {
header: 'Alert on slow external API calls', header: 'Alert on slow external API calls',
subheader: 'Monitor your external API calls', subheader: 'Monitor your external API calls',
dataSource: DataSource.TRACES,
link: link:
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-external-api-latency-p90-is-over-1-second-for-last-5-mins', 'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-external-api-latency-p90-is-over-1-second-for-last-5-mins',
}, },
{ {
header: 'Alert on high percentage of timeout errors in logs', header: 'Alert on high percentage of timeout errors in logs',
subheader: 'Monitor your logs for errors', subheader: 'Monitor your logs for errors',
dataSource: DataSource.LOGS,
link: link:
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-percentage-of-redis-timeout-error-logs-greater-than-7-in-last-5-mins', 'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-percentage-of-redis-timeout-error-logs-greater-than-7-in-last-5-mins',
}, },
{ {
header: 'Alert on high error percentage of an endpoint', header: 'Alert on high error percentage of an endpoint',
subheader: 'Monitor your API endpoint', subheader: 'Monitor your API endpoint',
dataSource: DataSource.METRICS,
link: link:
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#3-alert-when-the-error-percentage-for-an-endpoint-exceeds-5', 'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#3-alert-when-the-error-percentage-for-an-endpoint-exceeds-5',
}, },

View File

@@ -3,7 +3,6 @@ import { PlusOutlined } from '@ant-design/icons';
import { Input, Typography } from 'antd'; import { Input, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table/interface'; import type { ColumnsType } from 'antd/es/table/interface';
import saveAlertApi from 'api/alerts/save'; import saveAlertApi from 'api/alerts/save';
import logEvent from 'api/common/logEvent';
import DropDown from 'components/DropDown/DropDown'; import DropDown from 'components/DropDown/DropDown';
import { listAlertMessage } from 'components/facingIssueBtn/util'; import { listAlertMessage } from 'components/facingIssueBtn/util';
import { import {
@@ -42,7 +41,7 @@ import {
} from './styles'; } from './styles';
import Status from './TableComponents/Status'; import Status from './TableComponents/Status';
import ToggleAlertState from './ToggleAlertState'; import ToggleAlertState from './ToggleAlertState';
import { alertActionLogEvent, filterAlerts } from './utils'; import { filterAlerts } from './utils';
const { Search } = Input; const { Search } = Input;
@@ -108,16 +107,12 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
}, [notificationsApi, t]); }, [notificationsApi, t]);
const onClickNewAlertHandler = useCallback(() => { const onClickNewAlertHandler = useCallback(() => {
logEvent('Alert: New alert button clicked', {
number: allAlertRules?.length,
});
featureResponse featureResponse
.refetch() .refetch()
.then(() => { .then(() => {
history.push(ROUTES.ALERTS_NEW); history.push(ROUTES.ALERTS_NEW);
}) })
.catch(handleError); .catch(handleError);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [featureResponse, handleError]); }, [featureResponse, handleError]);
const onEditHandler = (record: GettableAlert) => (): void => { const onEditHandler = (record: GettableAlert) => (): void => {
@@ -326,7 +321,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
width: 10, width: 10,
render: (id: GettableAlert['id'], record): JSX.Element => ( render: (id: GettableAlert['id'], record): JSX.Element => (
<DropDown <DropDown
onDropDownItemClick={(item): void => alertActionLogEvent(item.key, record)}
element={[ element={[
<ToggleAlertState <ToggleAlertState
key="1" key="1"
@@ -362,9 +356,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
}); });
} }
const paginationConfig = {
defaultCurrent: Number(paginationParam) || 1,
};
return ( return (
<> <>
<SearchContainer> <SearchContainer>
@@ -394,10 +385,11 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={data} dataSource={data}
shouldSendAlertsLogEvent
dynamicColumns={dynamicColumns} dynamicColumns={dynamicColumns}
onChange={handleChange} onChange={handleChange}
pagination={paginationConfig} pagination={{
defaultCurrent: Number(paginationParam) || 1,
}}
facingIssueBtn={{ facingIssueBtn={{
attributes: { attributes: {
screen: 'Alert list page', screen: 'Alert list page',

View File

@@ -1,11 +1,9 @@
import { Space } from 'antd'; import { Space } from 'antd';
import getAll from 'api/alerts/getAll'; import getAll from 'api/alerts/getAll';
import logEvent from 'api/common/logEvent';
import ReleaseNote from 'components/ReleaseNote'; import ReleaseNote from 'components/ReleaseNote';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import { isUndefined } from 'lodash-es'; import { useEffect } from 'react';
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
@@ -21,19 +19,8 @@ function ListAlertRules(): JSX.Element {
cacheTime: 0, cacheTime: 0,
}); });
const logEventCalledRef = useRef(false);
const { notifications } = useNotifications(); const { notifications } = useNotifications();
useEffect(() => {
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
logEvent('Alert: List page visited', {
number: data?.payload?.length,
});
logEventCalledRef.current = true;
}
}, [data?.payload]);
useEffect(() => { useEffect(() => {
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) { if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
notifications.error({ notifications.error({

View File

@@ -1,6 +1,3 @@
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { GettableAlert } from 'types/api/alerts/get'; import { GettableAlert } from 'types/api/alerts/get';
export const filterAlerts = ( export const filterAlerts = (
@@ -26,32 +23,3 @@ export const filterAlerts = (
); );
}); });
}; };
export const alertActionLogEvent = (
action: string,
record: GettableAlert,
): void => {
let actionValue = '';
switch (action) {
case '0':
actionValue = 'Enable/Disable';
break;
case '1':
actionValue = 'Edit';
break;
case '2':
actionValue = 'Clone';
break;
case '3':
actionValue = 'Delete';
break;
default:
break;
}
logEvent('Alert: Action', {
ruleId: record.id,
dataSource: ALERTS_DATA_SOURCE_MAP[record.alertType as AlertTypes],
name: record.alert,
action: actionValue,
});
};

View File

@@ -609,16 +609,6 @@ function DashboardsList(): JSX.Element {
</> </>
); );
const paginationConfig = data.length > 20 && {
pageSize: 20,
showTotal: showPaginationItem,
showSizeChanger: false,
onChange: (page: any): void => handlePageSizeUpdate(page),
current: Number(sortOrder.pagination),
defaultCurrent: Number(sortOrder.pagination) || 1,
hideOnSinglePage: true,
};
return ( return (
<div className="dashboards-list-container"> <div className="dashboards-list-container">
<div className="dashboards-list-view-content"> <div className="dashboards-list-view-content">
@@ -832,7 +822,16 @@ function DashboardsList(): JSX.Element {
showSorterTooltip showSorterTooltip
loading={isDashboardListLoading || isFilteringDashboards} loading={isDashboardListLoading || isFilteringDashboards}
showHeader={false} showHeader={false}
pagination={paginationConfig} pagination={
data.length > 20 && {
pageSize: 20,
showTotal: showPaginationItem,
showSizeChanger: false,
onChange: (page): void => handlePageSizeUpdate(page),
current: Number(sortOrder.pagination),
defaultCurrent: Number(sortOrder.pagination) || 1,
}
}
/> />
</> </>
)} )}

View File

@@ -16,6 +16,7 @@ import { useOptionsMenu } from 'container/OptionsMenu';
import { useActiveLog } from 'hooks/logs/useActiveLog'; import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink'; import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import PeriscopeTable from 'periscope/components/Table/Table';
import { memo, useCallback, useMemo, useRef } from 'react'; import { memo, useCallback, useMemo, useRef } from 'react';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'; import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
// interfaces // interfaces
@@ -157,6 +158,7 @@ function LogsExplorerList({
return ( return (
<div className="logs-list-view-container"> <div className="logs-list-view-container">
<PeriscopeTable />
{(isLoading || (isFetching && logs.length === 0)) && <LogsLoading />} {(isLoading || (isFetching && logs.length === 0)) && <LogsLoading />}
{!isLoading && {!isLoading &&

View File

@@ -15,7 +15,6 @@ import {
} from 'hooks/useResourceAttribute/utils'; } from 'hooks/useResourceAttribute/utils';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
@@ -94,26 +93,6 @@ function External(): JSX.Element {
[servicename, tagFilterItems], [servicename, tagFilterItems],
); );
const errorApmToTraceQuery = useGetAPMToTracesQueries({
servicename,
isExternalCall: true,
filters: [
{
id: uuid().slice(0, 8),
key: {
key: 'hasError',
dataType: DataTypes.bool,
type: 'tag',
isColumn: true,
isJSON: false,
id: 'hasError--bool--tag--true',
},
op: 'in',
value: ['true'],
},
],
});
const externalCallRPSWidget = useMemo( const externalCallRPSWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder({
@@ -177,7 +156,7 @@ function External(): JSX.Element {
servicename, servicename,
selectedTraceTags, selectedTraceTags,
timestamp: selectedTimeStamp, timestamp: selectedTimeStamp,
apmToTraceQuery: errorApmToTraceQuery, apmToTraceQuery,
})} })}
> >
View Traces View Traces

View File

@@ -2,6 +2,8 @@ import { Card, Typography } from 'antd';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { WidgetGraphContainerProps } from 'container/NewWidget/types'; import { WidgetGraphContainerProps } from 'container/NewWidget/types';
// import useUrlQuery from 'hooks/useUrlQuery';
// import { useDashboard } from 'providers/Dashboard/Dashboard';
import { getSortedSeriesData } from 'utils/getSortedSeriesData'; import { getSortedSeriesData } from 'utils/getSortedSeriesData';
import { NotFoundContainer } from './styles'; import { NotFoundContainer } from './styles';
@@ -12,7 +14,6 @@ function WidgetGraphContainer({
queryResponse, queryResponse,
setRequestData, setRequestData,
selectedWidget, selectedWidget,
isLoadingPanelData,
}: WidgetGraphContainerProps): JSX.Element { }: WidgetGraphContainerProps): JSX.Element {
if (queryResponse.data && selectedGraph === PANEL_TYPES.BAR) { if (queryResponse.data && selectedGraph === PANEL_TYPES.BAR) {
const sortedSeriesData = getSortedSeriesData( const sortedSeriesData = getSortedSeriesData(
@@ -37,10 +38,6 @@ function WidgetGraphContainer({
return <Spinner size="large" tip="Loading..." />; return <Spinner size="large" tip="Loading..." />;
} }
if (isLoadingPanelData) {
return <Spinner size="large" tip="Loading..." />;
}
if ( if (
selectedGraph !== PANEL_TYPES.LIST && selectedGraph !== PANEL_TYPES.LIST &&
queryResponse.data?.payload.data?.result?.length === 0 queryResponse.data?.payload.data?.result?.length === 0
@@ -62,14 +59,6 @@ function WidgetGraphContainer({
); );
} }
if (queryResponse.isIdle) {
return (
<NotFoundContainer>
<Typography>No Data</Typography>
</NotFoundContainer>
);
}
return ( return (
<WidgetGraph <WidgetGraph
selectedWidget={selectedWidget} selectedWidget={selectedWidget}

View File

@@ -17,7 +17,6 @@ function WidgetGraph({
queryResponse, queryResponse,
setRequestData, setRequestData,
selectedWidget, selectedWidget,
isLoadingPanelData,
}: WidgetGraphContainerProps): JSX.Element { }: WidgetGraphContainerProps): JSX.Element {
const { currentQuery } = useQueryBuilder(); const { currentQuery } = useQueryBuilder();
@@ -44,7 +43,6 @@ function WidgetGraph({
)} )}
<WidgetGraphComponent <WidgetGraphComponent
isLoadingPanelData={isLoadingPanelData}
selectedGraph={selectedGraph} selectedGraph={selectedGraph}
queryResponse={queryResponse} queryResponse={queryResponse}
setRequestData={setRequestData} setRequestData={setRequestData}

View File

@@ -1,15 +1,18 @@
import './LeftContainer.styles.scss'; import './LeftContainer.styles.scss';
import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { memo } from 'react'; import { memo, useEffect, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { getGraphType } from 'utils/getGraphType';
import { WidgetGraphProps } from '../types'; import { WidgetGraphProps } from '../types';
import ExplorerColumnsRenderer from './ExplorerColumnsRenderer'; import ExplorerColumnsRenderer from './ExplorerColumnsRenderer';
@@ -24,17 +27,62 @@ function LeftContainer({
selectedTracesFields, selectedTracesFields,
setSelectedTracesFields, setSelectedTracesFields,
selectedWidget, selectedWidget,
requestData, selectedTime,
setRequestData,
isLoadingPanelData,
}: WidgetGraphProps): JSX.Element { }: WidgetGraphProps): JSX.Element {
const { stagedQuery } = useQueryBuilder(); const { stagedQuery, redirectWithQueryBuilderData } = useQueryBuilder();
const { selectedDashboard } = useDashboard(); const { selectedDashboard } = useDashboard();
const { selectedTime: globalSelectedInterval } = useSelector< const { selectedTime: globalSelectedInterval } = useSelector<
AppState, AppState,
GlobalReducer GlobalReducer
>((state) => state.globalTime); >((state) => state.globalTime);
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
if (selectedWidget && selectedGraph !== PANEL_TYPES.LIST) {
return {
selectedTime: selectedWidget?.timePreferance,
graphType: getGraphType(selectedGraph || selectedWidget.panelTypes),
query: stagedQuery || initialQueriesMap.metrics,
globalSelectedInterval,
variables: getDashboardVariables(selectedDashboard?.data.variables),
};
}
const updatedQuery = { ...(stagedQuery || initialQueriesMap.metrics) };
updatedQuery.builder.queryData[0].pageSize = 10;
redirectWithQueryBuilderData(updatedQuery);
return {
query: updatedQuery,
graphType: PANEL_TYPES.LIST,
selectedTime: selectedTime.enum || 'GLOBAL_TIME',
globalSelectedInterval,
tableParams: {
pagination: {
offset: 0,
limit: updatedQuery.builder.queryData[0].limit || 0,
},
},
};
});
useEffect(() => {
if (stagedQuery) {
setRequestData((prev) => ({
...prev,
selectedTime: selectedTime.enum || prev.selectedTime,
globalSelectedInterval,
graphType: getGraphType(selectedGraph || selectedWidget.panelTypes),
query: stagedQuery,
fillGaps: selectedWidget.fillSpans || false,
}));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
stagedQuery,
selectedTime,
selectedWidget.fillSpans,
globalSelectedInterval,
]);
const queryResponse = useGetQueryRange( const queryResponse = useGetQueryRange(
requestData, requestData,
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION, selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
@@ -56,7 +104,6 @@ function LeftContainer({
queryResponse={queryResponse} queryResponse={queryResponse}
setRequestData={setRequestData} setRequestData={setRequestData}
selectedWidget={selectedWidget} selectedWidget={selectedWidget}
isLoadingPanelData={isLoadingPanelData}
/> />
<QueryContainer className="query-section-left-container"> <QueryContainer className="query-section-left-container">
<QuerySection selectedGraph={selectedGraph} queryResponse={queryResponse} /> <QuerySection selectedGraph={selectedGraph} queryResponse={queryResponse} />

View File

@@ -7,7 +7,7 @@ import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { chartHelpMessage } from 'components/facingIssueBtn/util'; import { chartHelpMessage } from 'components/facingIssueBtn/util';
import { FeatureKeys } from 'constants/features'; import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import { DashboardShortcuts } from 'constants/shortcuts/DashboardShortcuts'; import { DashboardShortcuts } from 'constants/shortcuts/DashboardShortcuts';
import { DEFAULT_BUCKET_COUNT } from 'container/PanelWrapper/constants'; import { DEFAULT_BUCKET_COUNT } from 'container/PanelWrapper/constants';
@@ -18,8 +18,6 @@ import useAxiosError from 'hooks/useAxiosError';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag'; import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import history from 'lib/history'; import history from 'lib/history';
import { defaultTo, isUndefined } from 'lodash-es'; import { defaultTo, isUndefined } from 'lodash-es';
import { Check, X } from 'lucide-react'; import { Check, X } from 'lucide-react';
@@ -40,8 +38,6 @@ import { IField } from 'types/api/logs/fields';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getGraphType, getGraphTypeForFormat } from 'utils/getGraphType';
import LeftContainer from './LeftContainer'; import LeftContainer from './LeftContainer';
import QueryTypeTag from './LeftContainer/QueryTypeTag'; import QueryTypeTag from './LeftContainer/QueryTypeTag';
@@ -87,10 +83,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
const { featureResponse } = useSelector<AppState, AppReducer>( const { featureResponse } = useSelector<AppState, AppReducer>(
(state) => state.app, (state) => state.app,
); );
const { selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const { widgets = [] } = selectedDashboard?.data || {}; const { widgets = [] } = selectedDashboard?.data || {};
@@ -286,65 +278,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
const handleError = useAxiosError(); const handleError = useAxiosError();
// this loading state is to take care of mismatch in the responses for table and other panels
// hence while changing the query contains the older value and the processing logic fails
const [isLoadingPanelData, setIsLoadingPanelData] = useState<boolean>(false);
// request data should be handled by the parent and the child components should consume the same
// this has been moved here from the left container
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
if (selectedWidget && selectedGraph !== PANEL_TYPES.LIST) {
return {
selectedTime: selectedWidget?.timePreferance,
graphType: getGraphType(selectedGraph || selectedWidget.panelTypes),
query: stagedQuery || initialQueriesMap.metrics,
globalSelectedInterval,
formatForWeb:
getGraphTypeForFormat(selectedGraph || selectedWidget.panelTypes) ===
PANEL_TYPES.TABLE,
variables: getDashboardVariables(selectedDashboard?.data.variables),
};
}
const updatedQuery = { ...(stagedQuery || initialQueriesMap.metrics) };
updatedQuery.builder.queryData[0].pageSize = 10;
redirectWithQueryBuilderData(updatedQuery);
return {
query: updatedQuery,
graphType: PANEL_TYPES.LIST,
selectedTime: selectedTime.enum || 'GLOBAL_TIME',
globalSelectedInterval,
tableParams: {
pagination: {
offset: 0,
limit: updatedQuery.builder.queryData[0].limit || 0,
},
},
};
});
useEffect(() => {
if (stagedQuery) {
setIsLoadingPanelData(false);
setRequestData((prev) => ({
...prev,
selectedTime: selectedTime.enum || prev.selectedTime,
globalSelectedInterval,
graphType: getGraphType(selectedGraph || selectedWidget.panelTypes),
query: stagedQuery,
fillGaps: selectedWidget.fillSpans || false,
formatForWeb:
getGraphTypeForFormat(selectedGraph || selectedWidget.panelTypes) ===
PANEL_TYPES.TABLE,
}));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
stagedQuery,
selectedTime,
selectedWidget.fillSpans,
globalSelectedInterval,
]);
const onClickSaveHandler = useCallback(() => { const onClickSaveHandler = useCallback(() => {
if (!selectedDashboard) { if (!selectedDashboard) {
return; return;
@@ -469,7 +402,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
}, [dashboardId]); }, [dashboardId]);
const setGraphHandler = (type: PANEL_TYPES): void => { const setGraphHandler = (type: PANEL_TYPES): void => {
setIsLoadingPanelData(true);
const updatedQuery = handleQueryChange(type as any, supersetQuery); const updatedQuery = handleQueryChange(type as any, supersetQuery);
setGraphType(type); setGraphType(type);
redirectWithQueryBuilderData( redirectWithQueryBuilderData(
@@ -595,9 +527,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
setSelectedTracesFields={setSelectedTracesFields} setSelectedTracesFields={setSelectedTracesFields}
selectedWidget={selectedWidget} selectedWidget={selectedWidget}
selectedTime={selectedTime} selectedTime={selectedTime}
requestData={requestData}
setRequestData={setRequestData}
isLoadingPanelData={isLoadingPanelData}
/> />
)} )}
</LeftContainerWrapper> </LeftContainerWrapper>

View File

@@ -24,9 +24,6 @@ export interface WidgetGraphProps {
selectedWidget: Widgets; selectedWidget: Widgets;
selectedGraph: PANEL_TYPES; selectedGraph: PANEL_TYPES;
selectedTime: timePreferance; selectedTime: timePreferance;
requestData: GetQueryResultsProps;
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
isLoadingPanelData: boolean;
} }
export type WidgetGraphContainerProps = { export type WidgetGraphContainerProps = {
@@ -37,5 +34,4 @@ export type WidgetGraphContainerProps = {
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>; setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
selectedGraph: PANEL_TYPES; selectedGraph: PANEL_TYPES;
selectedWidget: Widgets; selectedWidget: Widgets;
isLoadingPanelData: boolean;
}; };

View File

@@ -4,45 +4,50 @@
Prior to installation, you must ensure your Kubernetes cluster is ready and that you have the necessary permissions to deploy applications. Follow these steps to use Helm for setting up the Collector: Prior to installation, you must ensure your Kubernetes cluster is ready and that you have the necessary permissions to deploy applications. Follow these steps to use Helm for setting up the Collector:
&nbsp;
1. **Add the OpenTelemetry Helm repository:** 1. **Add the OpenTelemetry Helm repository:**
```bash ```bash
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
``` ```
&nbsp;
2. **Prepare the `otel-collector-values.yaml` Configuration** 2. **Prepare the `otel-collector-values.yaml` Configuration**
&nbsp; #### Azure Event Hub Receiver Configuration
If you haven't created the logs Event Hub, you can create one by following the steps in the [Azure Event Hubs documentation](../../bootstrapping/data-ingestion).
#### Azure Event Hub Receiver Configuration and replace the placeholders `<Primary Connection String>` with the primary connection string for your Event Hub, it should look something like this:
Replace the placeholders `<Primary Connection String>` with the primary connection string for your Event Hub, it should look something like this: ```yaml
connection: Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234=;EntityPath=hubName
```
The Event Hub docs have a step to create a SAS policy for the event hub and copy the connection string.
```yaml #### Azure Monitor Receiver Configuration
connection: Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234=;EntityPath=hubName
```
The Event Hub setup have a step to create a SAS policy for the event hub and copy the connection string.
&nbsp; You will need to set up a [service principal](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal) with Read permissions to receive data from Azure Monitor.
#### Azure Monitor Receiver Configuration 1. Follow the steps in the [Create a service principal Azure Doc](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal#register-an-application-with-microsoft-entra-id-and-create-a-service-principal) documentation to create a service principal.
You can name it `signoz-central-collector-app` the redirect URI can be empty.
2. To add read permissions to Azure Monitor, Follow the [Assign Role](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal#assign-a-role-to-the-application) documentation. The read acess can be given to the full subscription.
3. There are multiple ways to authenticate the service principal, we will use the client secret option, follow [Creating a client secret](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal#option-3-create-a-new-client-secret) and don't forget to copy the client secret. The secret is used in the configuration file as `client_secret`.
You will need to set up a [service principal](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal) with Read permissions to receive data from Azure Monitor. 4. To find `client_id` and `tenant_id`, go to the [Azure Portal](https://portal.azure.com/) and search for the `Application` you created. You would see the `Application (client) ID` and `Directory (tenant) ID` in the Overview section.
1. Follow the steps in the [Create a service principal Azure Doc](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal#register-an-application-with-microsoft-entra-id-and-create-a-service-principal) documentation to create a service principal. <figure data-zoomable align="center">
You can name it `signoz-central-collector-app` the redirect URI can be empty. <img
2. To add read permissions to Azure Monitor, Follow the [Assign Role](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal#assign-a-role-to-the-application) documentation. The read acess can be given to the full subscription. src="/img/docs/azure-monitoring/service-principal-app-overview.webp"
3. There are multiple ways to authenticate the service principal, we will use the client secret option, follow [Creating a client secret](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-service-principal-portal#option-3-create-a-new-client-secret) and don't forget to copy the client secret. The secret is used in the configuration file as `client_secret`. alt="Application Overview"
/>
<figcaption>
<i>
Application Overview
</i>
</figcaption>
</figure>
4. To find `client_id` and `tenant_id`, go to the [Azure Portal](https://portal.azure.com/) and search for the `Application` you created. You would see the `Application (client) ID` and `Directory (tenant) ID` in the Overview section. 5. To find `subscription_id`, follow steps in [Find Your Subscription](https://learn.microsoft.com/en-us/azure/azure-portal/get-subscription-tenant-id#find-your-azure-subscription) and populate them in the configuration file.
5. To find `subscription_id`, follow steps in [Find Your Subscription](https://learn.microsoft.com/en-us/azure/azure-portal/get-subscription-tenant-id#find-your-azure-subscription) and populate them in the configuration file. 6. Ensure you replace the placeholders `<region>` and `<ingestion-key>` with the appropriate values for your signoz cloud instance.
6. Ensure you replace the placeholders `<region>` and `<ingestion-key>` with the appropriate values for your signoz cloud instance.
@@ -87,15 +92,13 @@ processors:
batch: {} batch: {}
exporters: exporters:
otlp: otlp:
endpoint: "ingest.{{REGION}}.signoz.cloud:443" endpoint: "ingest.<region>.signoz.cloud:443"
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-access-token": "<ingestion-key>"
``` ```
&nbsp;
3. **Deploy the OpenTelemetry Collector to your Kubernetes cluster:** 3. **Deploy the OpenTelemetry Collector to your Kubernetes cluster:**
You'll need to prepare a custom configuration file, say `otel-collector-values.yaml`, that matches your environment's specific needs. Replace `<namespace>` with the Kubernetes namespace where you wish to install the Collector. You'll need to prepare a custom configuration file, say `otel-collector-values.yaml`, that matches your environment's specific needs. Replace `<namespace>` with the Kubernetes namespace where you wish to install the Collector.

View File

@@ -1,37 +0,0 @@
import { act, render, screen, waitFor } from 'tests/test-utils';
import Members from '../Members';
describe('Organization Settings Page', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('render list of members', async () => {
act(() => {
render(<Members />);
});
const title = await screen.findByText(/Members/i);
expect(title).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('firstUser@test.io')).toBeInTheDocument(); // first item
expect(screen.getByText('lastUser@test.io')).toBeInTheDocument(); // last item
});
});
// this is required as our edit/delete logic is dependent on the index and it will break with pagination enabled
it('render list of members without pagination', async () => {
render(<Members />);
await waitFor(() => {
expect(screen.getByText('firstUser@test.io')).toBeInTheDocument(); // first item
expect(screen.getByText('lastUser@test.io')).toBeInTheDocument(); // last item
expect(
document.querySelector('.ant-table-pagination'),
).not.toBeInTheDocument();
});
});
});

View File

@@ -9,7 +9,7 @@ function TablePanelWrapper({
tableProcessedDataRef, tableProcessedDataRef,
}: PanelWrapperProps): JSX.Element { }: PanelWrapperProps): JSX.Element {
const panelData = const panelData =
(queryResponse.data?.payload?.data?.result?.[0] as any)?.table || []; queryResponse.data?.payload?.data?.newResult?.data?.result || [];
const { thresholds } = widget; const { thresholds } = widget;
return ( return (
<GridTableComponent <GridTableComponent

View File

@@ -1,4 +1,3 @@
export const historyPagination = { export const historyPagination = {
defaultPageSize: 5, defaultPageSize: 5,
hideOnSinglePage: true,
}; };

View File

@@ -334,11 +334,6 @@ export function PlannedDowntimeList({
} }
}, [downtimeSchedules.error, downtimeSchedules.isError, notifications]); }, [downtimeSchedules.error, downtimeSchedules.isError, notifications]);
const paginationConfig = {
pageSize: 5,
showSizeChanger: false,
hideOnSinglePage: true,
};
return ( return (
<Table<DowntimeSchedulesTableData> <Table<DowntimeSchedulesTableData>
columns={columns} columns={columns}
@@ -347,7 +342,7 @@ export function PlannedDowntimeList({
dataSource={tableData || []} dataSource={tableData || []}
loading={downtimeSchedules.isLoading || downtimeSchedules.isFetching} loading={downtimeSchedules.isLoading || downtimeSchedules.isFetching}
showHeader={false} showHeader={false}
pagination={paginationConfig} pagination={{ pageSize: 5, showSizeChanger: false }}
/> />
); );
} }

View File

@@ -33,12 +33,10 @@ export const getColumnSearchProps = (
record: ServicesList, record: ServicesList,
): boolean => { ): boolean => {
if (record[dataIndex]) { if (record[dataIndex]) {
return ( record[dataIndex]
record[dataIndex] ?.toString()
?.toString() .toLowerCase()
.toLowerCase() .includes(value.toString().toLowerCase());
.includes(value.toString().toLowerCase()) || false
);
} }
return false; return false;

View File

@@ -79,11 +79,6 @@ function ServiceMetricTable({
} }
}, [services, licenseData, isFetching, isCloudUserVal]); }, [services, licenseData, isFetching, isCloudUserVal]);
const paginationConfig = {
defaultPageSize: 10,
showTotal: (total: number, range: number[]): string =>
`${range[0]}-${range[1]} of ${total} items`,
};
return ( return (
<> <>
{RPS > MAX_RPS_LIMIT && ( {RPS > MAX_RPS_LIMIT && (
@@ -97,7 +92,11 @@ function ServiceMetricTable({
<ResourceAttributesFilter /> <ResourceAttributesFilter />
<ResizeTable <ResizeTable
pagination={paginationConfig} pagination={{
defaultPageSize: 10,
showTotal: (total: number, range: number[]): string =>
`${range[0]}-${range[1]} of ${total} items`,
}}
columns={tableColumns} columns={tableColumns}
loading={isLoading} loading={isLoading}
dataSource={services} dataSource={services}

View File

@@ -36,11 +36,6 @@ function ServiceTraceTable({
} }
}, [services, licenseData, isFetching, isCloudUserVal]); }, [services, licenseData, isFetching, isCloudUserVal]);
const paginationConfig = {
defaultPageSize: 10,
showTotal: (total: number, range: number[]): string =>
`${range[0]}-${range[1]} of ${total} items`,
};
return ( return (
<> <>
{RPS > MAX_RPS_LIMIT && ( {RPS > MAX_RPS_LIMIT && (
@@ -54,7 +49,11 @@ function ServiceTraceTable({
<ResourceAttributesFilter /> <ResourceAttributesFilter />
<ResizeTable <ResizeTable
pagination={paginationConfig} pagination={{
defaultPageSize: 10,
showTotal: (total: number, range: number[]): string =>
`${range[0]}-${range[1]} of ${total} items`,
}}
columns={tableColumns} columns={tableColumns}
loading={loading} loading={loading}
dataSource={services} dataSource={services}

View File

@@ -152,13 +152,9 @@ function SideNav({
const { t } = useTranslation(''); const { t } = useTranslation('');
const licenseStatus: string =
licenseData?.payload?.licenses?.find((e: License) => e.isCurrent)?.status ||
'';
const isLicenseActive = const isLicenseActive =
licenseStatus?.toLocaleLowerCase() === licenseData?.payload?.licenses?.find((e: License) => e.isCurrent)?.status ===
LICENSE_PLAN_STATUS.VALID.toLocaleLowerCase(); LICENSE_PLAN_STATUS.VALID;
const isEnterprise = licenseData?.payload?.licenses?.some( const isEnterprise = licenseData?.payload?.licenses?.some(
(license: License) => (license: License) =>

View File

@@ -1,5 +1,3 @@
import './Tags.styles.scss';
import { Tooltip } from 'antd'; import { Tooltip } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { Fragment, useMemo } from 'react'; import { Fragment, useMemo } from 'react';
@@ -28,12 +26,7 @@ function Tag({ tags, onToggleHandler, setText }: TagProps): JSX.Element {
<Container> <Container>
<CustomSubTitle>{tags.key}</CustomSubTitle> <CustomSubTitle>{tags.key}</CustomSubTitle>
<SubTextContainer isDarkMode={isDarkMode}> <SubTextContainer isDarkMode={isDarkMode}>
<Tooltip <Tooltip overlay={(): string => value}>
overlayClassName="tagTooltip"
placement="left"
autoAdjustOverflow
title={value}
>
<CustomSubText <CustomSubText
ellipsis={{ ellipsis={{
rows: isEllipsed ? 2 : 0, rows: isEllipsed ? 2 : 0,

View File

@@ -1,37 +0,0 @@
.tagTooltip {
.ant-tooltip-content {
max-height: 300px;
overflow-x: hidden;
overflow-y: auto;
padding: 8px;
background-color: var(--bg-slate-400);
margin-bottom: 8px;
}
.ant-tooltip-inner {
box-shadow: none;
}
}
.lightMode {
.tagTooltip {
.ant-tooltip-content {
background-color: var(--bg-vanilla-300);
}
.ant-tooltip-inner {
box-shadow: none;
background-color: var(--bg-vanilla-300);
color: var(--bg-ink-200);
}
.ant-tooltip-arrow {
border-top-color: var(--bg-vanilla-300) !important;
}
&.ant-tooltip {
--antd-arrow-background-color: var(--bg-vanilla-300) !important;
}
}
}

View File

@@ -19,17 +19,12 @@ import { CardContainer, CustomSubText, styles } from './styles';
import Tags from './Tags'; import Tags from './Tags';
function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element { function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
const { tree, firstSpanStartTime } = props;
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
const {
tree,
firstSpanStartTime,
traceStartTime = minTime,
traceEndTime = maxTime,
} = props;
const { id: traceId } = useParams<Params>(); const { id: traceId } = useParams<Params>();
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
@@ -79,7 +74,7 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
]; ];
const onLogsHandler = (): void => { const onLogsHandler = (): void => {
const query = getTraceToLogsQuery(traceId, traceStartTime, traceEndTime); const query = getTraceToLogsQuery(traceId, minTime, maxTime);
history.push( history.push(
`${ROUTES.LOGS_EXPLORER}?${createQueryParams({ `${ROUTES.LOGS_EXPLORER}?${createQueryParams({
@@ -145,14 +140,10 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
interface SelectedSpanDetailsProps { interface SelectedSpanDetailsProps {
tree?: ITraceTree; tree?: ITraceTree;
firstSpanStartTime: number; firstSpanStartTime: number;
traceStartTime?: number;
traceEndTime?: number;
} }
SelectedSpanDetails.defaultProps = { SelectedSpanDetails.defaultProps = {
tree: undefined, tree: undefined,
traceStartTime: undefined,
traceEndTime: undefined,
}; };
export interface ModalText { export interface ModalText {

View File

@@ -48,12 +48,6 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
[response], [response],
); );
const traceStartTime = useMemo(() => response[0].startTimestampMillis, [
response,
]);
const traceEndTime = useMemo(() => response[0].endTimestampMillis, [response]);
const urlQuery = useUrlQuery(); const urlQuery = useUrlQuery();
const [spanId] = useState<string | null>(urlQuery.get('spanId')); const [spanId] = useState<string | null>(urlQuery.get('spanId'));
@@ -266,8 +260,6 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
<StyledCol styledclass={[styles.selectedSpanDetailContainer]}> <StyledCol styledclass={[styles.selectedSpanDetailContainer]}>
<SelectedSpanDetails <SelectedSpanDetails
firstSpanStartTime={firstSpanStartTime} firstSpanStartTime={firstSpanStartTime}
traceStartTime={traceStartTime}
traceEndTime={traceEndTime}
tree={[ tree={[
...(getSelectedNode.spanTree ? getSelectedNode.spanTree : []), ...(getSelectedNode.spanTree ? getSelectedNode.spanTree : []),
...(getSelectedNode.missingSpanTree ...(getSelectedNode.missingSpanTree

View File

@@ -1,10 +1,7 @@
import getTriggeredApi from 'api/alerts/getTriggered'; import getTriggeredApi from 'api/alerts/getTriggered';
import logEvent from 'api/common/logEvent';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import useAxiosError from 'hooks/useAxiosError'; import useAxiosError from 'hooks/useAxiosError';
import { isUndefined } from 'lodash-es';
import { useEffect, useRef } from 'react';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@@ -16,8 +13,6 @@ function TriggeredAlerts(): JSX.Element {
(state) => state.app.user?.userId, (state) => state.app.user?.userId,
); );
const hasLoggedEvent = useRef(false); // Track if logEvent has been called
const handleError = useAxiosError(); const handleError = useAxiosError();
const alertsResponse = useQuery( const alertsResponse = useQuery(
@@ -34,15 +29,6 @@ function TriggeredAlerts(): JSX.Element {
}, },
); );
useEffect(() => {
if (!hasLoggedEvent.current && !isUndefined(alertsResponse.data?.payload)) {
logEvent('Alert: Triggered alert list page visited', {
number: alertsResponse.data?.payload?.length,
});
hasLoggedEvent.current = true;
}
}, [alertsResponse.data?.payload]);
if (alertsResponse.error) { if (alertsResponse.error) {
return <TriggerComponent allAlerts={[]} />; return <TriggerComponent allAlerts={[]} />;
} }

View File

@@ -6,7 +6,6 @@ import AppRoutes from 'AppRoutes';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { ThemeProvider } from 'hooks/useDarkMode'; import { ThemeProvider } from 'hooks/useDarkMode';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import posthog from 'posthog-js';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { HelmetProvider } from 'react-helmet-async'; import { HelmetProvider } from 'react-helmet-async';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClient, QueryClientProvider } from 'react-query';
@@ -34,13 +33,6 @@ const queryClient = new QueryClient({
const container = document.getElementById('root'); const container = document.getElementById('root');
if (process.env.POSTHOG_KEY) {
posthog.init(process.env.POSTHOG_KEY, {
api_host: 'https://us.i.posthog.com',
person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well
});
}
Sentry.init({ Sentry.init({
dsn: process.env.SENTRY_DSN, dsn: process.env.SENTRY_DSN,
tunnel: process.env.TUNNEL_URL, tunnel: process.env.TUNNEL_URL,

View File

@@ -12,7 +12,7 @@ import {
} from 'container/TopNav/DateTimeSelectionV2/config'; } from 'container/TopNav/DateTimeSelectionV2/config';
import { Pagination } from 'hooks/queryPagination'; import { Pagination } from 'hooks/queryPagination';
import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld'; import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld';
import { isEmpty, cloneDeep } from 'lodash-es'; import { isEmpty } from 'lodash-es';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
@@ -40,10 +40,6 @@ export async function GetMetricQueryRange(
throw new Error(error); throw new Error(error);
} }
if (props.formatForWeb) {
return response;
}
if (response.payload?.data?.result) { if (response.payload?.data?.result) {
const v2Range = convertNewDataToOld(response.payload); const v2Range = convertNewDataToOld(response.payload);
@@ -80,7 +76,6 @@ export interface GetQueryResultsProps {
variables?: Record<string, unknown>; variables?: Record<string, unknown>;
params?: Record<string, unknown>; params?: Record<string, unknown>;
fillGaps?: boolean; fillGaps?: boolean;
formatForWeb?: boolean;
tableParams?: { tableParams?: {
pagination?: Pagination; pagination?: Pagination;
selectColumns?: any; selectColumns?: any;

View File

@@ -16,7 +16,6 @@ export const prepareQueryRangePayload = ({
query, query,
globalSelectedInterval, globalSelectedInterval,
graphType, graphType,
formatForWeb,
selectedTime, selectedTime,
tableParams, tableParams,
variables = {}, variables = {},
@@ -103,7 +102,6 @@ export const prepareQueryRangePayload = ({
inputFormat: 'ns', inputFormat: 'ns',
}), }),
variables, variables,
formatForWeb,
compositeQuery, compositeQuery,
...restParams, ...restParams,
}; };

View File

@@ -583,11 +583,11 @@ export const createTableColumnsFromQuery: CreateTableDataFromQuery = ({
q.series?.sort((a, b) => { q.series?.sort((a, b) => {
let labelA = ''; let labelA = '';
let labelB = ''; let labelB = '';
a.labelsArray?.forEach((lab) => { a.labelsArray.forEach((lab) => {
labelA += Object.values(lab)[0]; labelA += Object.values(lab)[0];
}); });
b.labelsArray?.forEach((lab) => { b.labelsArray.forEach((lab) => {
labelB += Object.values(lab)[0]; labelB += Object.values(lab)[0];
}); });

View File

@@ -64,10 +64,6 @@ export interface GetUPlotChartOptions {
function getStackedSeries(apiResponse: QueryData[]): QueryData[] { function getStackedSeries(apiResponse: QueryData[]): QueryData[] {
const series = cloneDeep(apiResponse); const series = cloneDeep(apiResponse);
if (!series) {
return series;
}
for (let i = series.length - 2; i >= 0; i--) { for (let i = series.length - 2; i >= 0; i--) {
const { values } = series[i]; const { values } = series[i];
for (let j = 0; j < values.length; j++) { for (let j = 0; j < values.length; j++) {
@@ -88,9 +84,6 @@ function getStackedSeries(apiResponse: QueryData[]): QueryData[] {
*/ */
function getStackedSeriesQueryFormat(apiResponse: QueryData[]): QueryData[] { function getStackedSeriesQueryFormat(apiResponse: QueryData[]): QueryData[] {
const series = cloneDeep(apiResponse); const series = cloneDeep(apiResponse);
if (!series) {
return apiResponse;
}
for (let i = series.length - 2; i >= 0; i--) { for (let i = series.length - 2; i >= 0; i--) {
const { values } = series[i]; const { values } = series[i];
@@ -109,12 +102,9 @@ function getStackedSeriesQueryFormat(apiResponse: QueryData[]): QueryData[] {
function getStackedSeriesYAxis(apiResponse: QueryDataV3[]): QueryDataV3[] { function getStackedSeriesYAxis(apiResponse: QueryDataV3[]): QueryDataV3[] {
const series = cloneDeep(apiResponse); const series = cloneDeep(apiResponse);
if (!series) {
return apiResponse;
}
for (let i = 0; i < series.length; i++) { for (let i = 0; i < series.length; i++) {
series[i].series = getStackedSeriesQueryFormat(series[i].series || []); series[i].series = getStackedSeriesQueryFormat(series[i].series);
} }
return series; return series;

View File

@@ -1,220 +0,0 @@
/* eslint-disable sonarjs/no-duplicate-string */
export const membersResponse = [
{
id: '3223a874-5678458745786',
name: 'John Doe',
email: 'firstUser@test.io',
createdAt: 1666357530,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '5e9681b1-5678458745786',
name: 'Jane Doe',
email: 'johndoe2@test.io',
createdAt: 1666365394,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '11e8c55d-5678458745786',
name: 'Alex',
email: 'blah@test.io',
createdAt: 1666366317,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: 'd878012367813286731aab62',
role: 'VIEWER',
organization: 'Test Inc',
flags: null,
},
{
id: '2ad2e404-5678458745786',
name: 'Tom',
email: 'johndoe4@test.io',
createdAt: 1673441483,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '6f532456-5678458745786',
name: 'Harry',
email: 'harry@test.io',
createdAt: 1691551672,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: 'ae22fa73-5678458745786',
name: 'Ron',
email: 'ron@test.io',
createdAt: 1691668239,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '3223a874-5678458745786',
name: 'John Doe',
email: 'johndoe@test.io',
createdAt: 1666357530,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '5e9681b1-5678458745786',
name: 'Jane Doe',
email: 'johndoe2@test.io',
createdAt: 1666365394,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '11e8c55d-5678458745786',
name: 'Alex',
email: 'blah@test.io',
createdAt: 1666366317,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: 'd878012367813286731aab62',
role: 'VIEWER',
organization: 'Test Inc',
flags: null,
},
{
id: '2ad2e404-5678458745786',
name: 'Tom',
email: 'johndoe4@test.io',
createdAt: 1673441483,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '6f532456-5678458745786',
name: 'Harry',
email: 'harry@test.io',
createdAt: 1691551672,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: 'ae22fa73-5678458745786',
name: 'Ron',
email: 'ron@test.io',
createdAt: 1691668239,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '3223a874-5678458745786',
name: 'John Doe',
email: 'johndoe@test.io',
createdAt: 1666357530,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '5e9681b1-5678458745786',
name: 'Jane Doe',
email: 'johndoe2@test.io',
createdAt: 1666365394,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '11e8c55d-5678458745786',
name: 'Alex',
email: 'blah@test.io',
createdAt: 1666366317,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: 'd878012367813286731aab62',
role: 'VIEWER',
organization: 'Test Inc',
flags: null,
},
{
id: '2ad2e404-5678458745786',
name: 'Tom',
email: 'johndoe4@test.io',
createdAt: 1673441483,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: '6f532456-5678458745786',
name: 'Harry',
email: 'harry@test.io',
createdAt: 1691551672,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
{
id: 'ae22fa73-5678458745786',
name: 'Ron',
email: 'lastUser@test.io',
createdAt: 1691668239,
profilePictureURL: '',
orgId: '1287612376312867312867',
groupId: '5678458745786',
role: 'ADMIN',
organization: 'Test Inc',
flags: null,
},
];

View File

@@ -2,7 +2,6 @@ import { rest } from 'msw';
import { billingSuccessResponse } from './__mockdata__/billing'; import { billingSuccessResponse } from './__mockdata__/billing';
import { licensesSuccessResponse } from './__mockdata__/licenses'; import { licensesSuccessResponse } from './__mockdata__/licenses';
import { membersResponse } from './__mockdata__/members';
import { queryRangeSuccessResponse } from './__mockdata__/query_range'; import { queryRangeSuccessResponse } from './__mockdata__/query_range';
import { serviceSuccessResponse } from './__mockdata__/services'; import { serviceSuccessResponse } from './__mockdata__/services';
import { topLevelOperationSuccessResponse } from './__mockdata__/top_level_operations'; import { topLevelOperationSuccessResponse } from './__mockdata__/top_level_operations';
@@ -26,9 +25,6 @@ export const handlers = [
res(ctx.status(200), ctx.json(topLevelOperationSuccessResponse)), res(ctx.status(200), ctx.json(topLevelOperationSuccessResponse)),
), ),
rest.get('http://localhost/api/v1/orgUsers/*', (req, res, ctx) =>
res(ctx.status(200), ctx.json(membersResponse)),
),
rest.get( rest.get(
'http://localhost/api/v3/autocomplete/attribute_keys', 'http://localhost/api/v3/autocomplete/attribute_keys',
(req, res, ctx) => { (req, res, ctx) => {

View File

@@ -46,8 +46,6 @@ function DataCollected(props: DataCollectedProps): JSX.Element {
}, },
]; ];
const paginationConfig = { pageSize: 20, hideOnSinglePage: true };
return ( return (
<div className="integration-data-collected"> <div className="integration-data-collected">
<div className="logs-section"> <div className="logs-section">
@@ -61,7 +59,7 @@ function DataCollected(props: DataCollectedProps): JSX.Element {
index % 2 === 0 ? 'table-row-dark' : '' index % 2 === 0 ? 'table-row-dark' : ''
} }
dataSource={logsData} dataSource={logsData}
pagination={paginationConfig} pagination={{ pageSize: 20 }}
className="logs-section-table" className="logs-section-table"
/> />
</div> </div>
@@ -76,7 +74,7 @@ function DataCollected(props: DataCollectedProps): JSX.Element {
index % 2 === 0 ? 'table-row-dark' : '' index % 2 === 0 ? 'table-row-dark' : ''
} }
dataSource={metricsData} dataSource={metricsData}
pagination={paginationConfig} pagination={{ pageSize: 20 }}
className="metrics-section-table" className="metrics-section-table"
/> />
</div> </div>

View File

@@ -277,8 +277,6 @@ function SaveView(): JSX.Element {
}, },
]; ];
const paginationConfig = { pageSize: 5, hideOnSinglePage: true };
return ( return (
<div className="save-view-container"> <div className="save-view-container">
<div className="save-view-content"> <div className="save-view-content">
@@ -305,7 +303,7 @@ function SaveView(): JSX.Element {
dataSource={dataSource} dataSource={dataSource}
loading={isLoading || isRefetching} loading={isLoading || isRefetching}
showHeader={false} showHeader={false}
pagination={paginationConfig} pagination={{ pageSize: 5 }}
/> />
</div> </div>

View File

@@ -0,0 +1,69 @@
table,
.divTable {
border: 1px solid lightgray;
width: fit-content;
}
.tr {
display: flex;
}
tr,
.tr {
width: fit-content;
height: 30px;
}
th,
.th,
td,
.td {
box-shadow: inset 0 0 0 1px lightgray;
padding: 0.25rem;
}
th,
.th {
padding: 2px 4px;
position: relative;
font-weight: bold;
text-align: center;
height: 30px;
}
td,
.td {
height: 30px;
}
.resizer {
position: absolute;
top: 0;
height: 100%;
right: 0;
width: 5px;
background: rgba(0, 0, 0, 0.5);
cursor: col-resize;
user-select: none;
touch-action: none;
}
.resizer.isResizing {
background: blue;
opacity: 1;
}
@media (hover: hover) {
.resizer {
opacity: 0;
}
*:hover > .resizer {
opacity: 1;
}
}
.container {
border: 1px solid lightgray;
margin: 1rem auto;
}

View File

@@ -0,0 +1,295 @@
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable react/no-unstable-nested-components */
import './Table.styles.scss';
// needed for table body level scope DnD setup
import {
closestCenter,
DndContext,
DragEndEvent,
KeyboardSensor,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { restrictToHorizontalAxis } from '@dnd-kit/modifiers';
import {
arrayMove,
horizontalListSortingStrategy,
SortableContext,
useSortable,
} from '@dnd-kit/sortable';
// needed for row & cell level scope DnD setup
import { CSS } from '@dnd-kit/utilities';
import {
Cell,
ColumnDef,
flexRender,
getCoreRowModel,
Header,
Table,
useReactTable,
} from '@tanstack/react-table';
import React, { CSSProperties } from 'react';
import { makeData, Person } from './makeData';
function DraggableTableHeader({
header,
}: {
header: Header<Person, unknown>;
}): JSX.Element {
const {
attributes,
isDragging,
listeners,
setNodeRef,
transform,
} = useSortable({
id: header.column.id,
});
const style: CSSProperties = {
opacity: isDragging ? 0.8 : 1,
position: 'relative',
transform: CSS.Translate.toString(transform), // translate instead of transform to avoid squishing
transition: 'width transform 0.2s ease-in-out',
whiteSpace: 'nowrap',
width: header.column.getSize(),
zIndex: isDragging ? 1 : 0,
};
return (
<th colSpan={header.colSpan} ref={setNodeRef} style={style}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
<button type="button" {...attributes} {...listeners}>
🟰
</button>
<div
{...{
onDoubleClick: (): void => header.column.resetSize(),
onMouseDown: header.getResizeHandler(),
onTouchStart: header.getResizeHandler(),
className: `resizer ${header.column.getIsResizing() ? 'isResizing' : ''}`,
}}
/>
</th>
);
}
function DragAlongCell({ cell }: { cell: Cell<Person, unknown> }): JSX.Element {
const { isDragging, setNodeRef, transform } = useSortable({
id: cell.column.id,
});
const style: CSSProperties = {
opacity: isDragging ? 0.8 : 1,
position: 'relative',
transform: CSS.Translate.toString(transform), // translate instead of transform to avoid squishing
transition: 'width transform 0.2s ease-in-out',
width: cell.column.getSize(),
zIndex: isDragging ? 1 : 0,
};
return (
<td style={style} ref={setNodeRef}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
);
}
// un-memoized normal table body component - see memoized version below
function TableBody({ table }: { table: Table<Person> }): JSX.Element {
const { columnOrder } = table.getState();
console.log('columnOrder', columnOrder);
return (
<div
{...{
className: 'tbody',
}}
>
{table.getRowModel().rows.map((row) => (
<div key={row.id} className="tr">
{row.getVisibleCells().map((cell) => (
<SortableContext
key={cell.id}
items={columnOrder}
strategy={horizontalListSortingStrategy}
>
<DragAlongCell key={cell.id} cell={cell} />
</SortableContext>
))}
</div>
))}
</div>
);
}
// special memoized wrapper for our table body that we will use during column resizing
export const MemoizedTableBody = React.memo(
TableBody,
(prev, next) => prev.table.options.data === next.table.options.data,
) as typeof TableBody;
function PeriscopeTable(): JSX.Element {
const columns = React.useMemo<ColumnDef<Person>[]>(
() => [
{
accessorKey: 'firstName',
cell: (info): any => info.getValue(),
id: 'firstName',
size: 150,
},
{
accessorFn: (row): any => row.lastName,
cell: (info): any => info.getValue(),
header: (): JSX.Element => <span>Last Name</span>,
id: 'lastName',
size: 150,
},
{
accessorKey: 'age',
header: (): any => 'Age',
id: 'age',
size: 120,
},
{
accessorKey: 'visits',
header: (): JSX.Element => <span>Visits</span>,
id: 'visits',
size: 120,
},
{
accessorKey: 'status',
header: 'Status',
id: 'status',
size: 150,
},
{
accessorKey: 'progress',
header: 'Profile Progress',
id: 'progress',
size: 180,
},
],
[],
);
const [data, setData] = React.useState(() => makeData(20));
const [columnOrder, setColumnOrder] = React.useState<string[]>(() =>
columns.map((c) => c.id!),
);
const [columnVisibility, setColumnVisibility] = React.useState({});
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
state: {
columnOrder,
columnVisibility,
},
defaultColumn: {
minSize: 60,
maxSize: 800,
},
columnResizeMode: 'onChange',
onColumnOrderChange: setColumnOrder,
onColumnVisibilityChange: setColumnVisibility,
debugTable: true,
debugHeaders: true,
debugColumns: true,
});
/**
* Instead of calling `column.getSize()` on every render for every header
* and especially every data cell (very expensive),
* we will calculate all column sizes at once at the root table level in a useMemo
* and pass the column sizes down as CSS variables to the <table> element.
*/
const columnSizeVars = React.useMemo(() => {
const headers = table.getFlatHeaders();
const colSizes: { [key: string]: number } = {};
for (let i = 0; i < headers.length; i++) {
const header = headers[i]!;
colSizes[`--header-${header.id}-size`] = header.getSize();
colSizes[`--col-${header.column.id}-size`] = header.column.getSize();
}
return colSizes;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [table.getState().columnSizingInfo, table.getState().columnSizing]);
// reorder columns after drag & drop
function handleDragEnd(event: DragEndEvent): void {
const { active, over } = event;
console.log('active', active, over);
if (active && over && active.id !== over.id) {
setColumnOrder((columnOrder) => {
const oldIndex = columnOrder.indexOf(active.id as string);
const newIndex = columnOrder.indexOf(over.id as string);
return arrayMove(columnOrder, oldIndex, newIndex); // this is just a splice util
});
}
}
const sensors = useSensors(
useSensor(MouseSensor, {}),
useSensor(TouchSensor, {}),
useSensor(KeyboardSensor, {}),
);
return (
<DndContext
collisionDetection={closestCenter}
modifiers={[restrictToHorizontalAxis]}
// eslint-disable-next-line react/jsx-no-bind
onDragEnd={handleDragEnd}
sensors={sensors}
>
<div className="p-2">
<div className="overflow-x-auto">
<div
className="divTable"
style={{
...columnSizeVars, // Define column sizes on the <table> element
width: table.getTotalSize(),
}}
>
<div className="thead">
{table.getHeaderGroups().map((headerGroup) => (
<div key={headerGroup.id} className="tr">
<SortableContext
items={columnOrder}
strategy={horizontalListSortingStrategy}
>
{headerGroup.headers.map((header) => (
<div
key={header.id}
className="th"
style={{
width: `calc(var(--header-${header?.id}-size) * 1px)`,
}}
>
<DraggableTableHeader key={header.id} header={header} />
</div>
))}
</SortableContext>
</div>
))}
</div>
<TableBody table={table} />
</div>
</div>
</div>
</DndContext>
);
}
export default PeriscopeTable;

View File

@@ -0,0 +1,46 @@
import { faker } from '@faker-js/faker';
export type Person = {
firstName: string;
lastName: string;
age: number;
visits: number;
progress: number;
status: 'relationship' | 'complicated' | 'single';
subRows?: Person[];
};
const range = (len: number) => {
const arr: number[] = [];
for (let i = 0; i < len; i++) {
arr.push(i);
}
return arr;
};
const newPerson = (): Person => ({
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
age: faker.number.int(40),
visits: faker.number.int(1000),
progress: faker.number.int(100),
status: faker.helpers.shuffle<Person['status']>([
'relationship',
'complicated',
'single',
])[0]!,
});
export function makeData(...lens: number[]): any {
const makeDataLevel = (depth = 0): Person[] => {
const len = lens[depth]!;
return range(len).map(
(d): Person => ({
...newPerson(),
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
}),
);
};
return makeDataLevel();
}

View File

@@ -42,15 +42,6 @@ const mockStored = (role?: string): any =>
accessJwt: '', accessJwt: '',
refreshJwt: '', refreshJwt: '',
}, },
org: [
{
createdAt: 0,
hasOptedUpdates: false,
id: 'xyz',
isAnonymous: false,
name: 'Test Inc. - India',
},
],
}, },
}); });

View File

@@ -24,7 +24,6 @@ export type QueryRangePayload = {
start: number; start: number;
step: number; step: number;
variables?: Record<string, unknown>; variables?: Record<string, unknown>;
formatForWeb?: boolean;
[param: string]: unknown; [param: string]: unknown;
}; };
export interface MetricRangePayloadProps { export interface MetricRangePayloadProps {

View File

@@ -15,8 +15,6 @@ export interface PayloadProps {
segmentID: string; segmentID: string;
columns: string[]; columns: string[];
isSubTree: boolean; isSubTree: boolean;
startTimestampMillis: number;
endTimestampMillis: number;
}; };
} }

View File

@@ -10,6 +10,3 @@ export const getGraphType = (panelType: PANEL_TYPES): PANEL_TYPES => {
} }
return panelType; return panelType;
}; };
export const getGraphTypeForFormat = (panelType: PANEL_TYPES): PANEL_TYPES =>
panelType;

View File

@@ -22,7 +22,6 @@ const plugins = [
template: 'src/index.html.ejs', template: 'src/index.html.ejs',
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID, INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
SEGMENT_ID: process.env.SEGMENT_ID, SEGMENT_ID: process.env.SEGMENT_ID,
POSTHOG_KEY: process.env.POSTHOG_KEY,
CLARITY_PROJECT_ID: process.env.CLARITY_PROJECT_ID, CLARITY_PROJECT_ID: process.env.CLARITY_PROJECT_ID,
SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN, SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN,
SENTRY_ORG: process.env.SENTRY_ORG, SENTRY_ORG: process.env.SENTRY_ORG,
@@ -40,7 +39,6 @@ const plugins = [
FRONTEND_API_ENDPOINT: process.env.FRONTEND_API_ENDPOINT, FRONTEND_API_ENDPOINT: process.env.FRONTEND_API_ENDPOINT,
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID, INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
SEGMENT_ID: process.env.SEGMENT_ID, SEGMENT_ID: process.env.SEGMENT_ID,
POSTHOG_KEY: process.env.POSTHOG_KEY,
CLARITY_PROJECT_ID: process.env.CLARITY_PROJECT_ID, CLARITY_PROJECT_ID: process.env.CLARITY_PROJECT_ID,
SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN, SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN,
SENTRY_ORG: process.env.SENTRY_ORG, SENTRY_ORG: process.env.SENTRY_ORG,

View File

@@ -27,7 +27,6 @@ const plugins = [
template: 'src/index.html.ejs', template: 'src/index.html.ejs',
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID, INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
SEGMENT_ID: process.env.SEGMENT_ID, SEGMENT_ID: process.env.SEGMENT_ID,
POSTHOG_KEY: process.env.POSTHOG_KEY,
CLARITY_PROJECT_ID: process.env.CLARITY_PROJECT_ID, CLARITY_PROJECT_ID: process.env.CLARITY_PROJECT_ID,
SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN, SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN,
SENTRY_ORG: process.env.SENTRY_ORG, SENTRY_ORG: process.env.SENTRY_ORG,
@@ -50,7 +49,6 @@ const plugins = [
FRONTEND_API_ENDPOINT: process.env.FRONTEND_API_ENDPOINT, FRONTEND_API_ENDPOINT: process.env.FRONTEND_API_ENDPOINT,
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID, INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
SEGMENT_ID: process.env.SEGMENT_ID, SEGMENT_ID: process.env.SEGMENT_ID,
POSTHOG_KEY: process.env.POSTHOG_KEY,
CLARITY_PROJECT_ID: process.env.CLARITY_PROJECT_ID, CLARITY_PROJECT_ID: process.env.CLARITY_PROJECT_ID,
SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN, SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN,
SENTRY_ORG: process.env.SENTRY_ORG, SENTRY_ORG: process.env.SENTRY_ORG,

View File

@@ -2541,7 +2541,7 @@
"@dnd-kit/utilities" "^3.2.2" "@dnd-kit/utilities" "^3.2.2"
tslib "^2.0.0" tslib "^2.0.0"
"@dnd-kit/utilities@^3.2.2": "@dnd-kit/utilities@3.2.2", "@dnd-kit/utilities@^3.2.2":
version "3.2.2" version "3.2.2"
resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b" resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b"
integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg== integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==
@@ -2597,6 +2597,11 @@
minimatch "^3.0.4" minimatch "^3.0.4"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@faker-js/faker@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.4.1.tgz#5d5e8aee8fce48f5e189bf730ebd1f758f491451"
integrity sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==
"@floating-ui/core@^1.4.2": "@floating-ui/core@^1.4.2":
version "1.5.2" version "1.5.2"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.2.tgz#53a0f7a98c550e63134d504f26804f6b83dbc071" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.2.tgz#53a0f7a98c550e63134d504f26804f6b83dbc071"
@@ -3800,6 +3805,18 @@
dependencies: dependencies:
"@sinonjs/commons" "^1.7.0" "@sinonjs/commons" "^1.7.0"
"@tanstack/react-table@8.17.3":
version "8.17.3"
resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.17.3.tgz#4e10b4cf5355a40d6d72a83d3f4b3ecd32f56bf4"
integrity sha512-5gwg5SvPD3lNAXPuJJz1fOCEZYk9/GeBFH3w/hCgnfyszOIzwkwgp5I7Q4MJtn0WECp84b5STQUDdmvGi8m3nA==
dependencies:
"@tanstack/table-core" "8.17.3"
"@tanstack/table-core@8.17.3":
version "8.17.3"
resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.17.3.tgz#d7a9830abb29cd369b52b2a7159dc0360af646fd"
integrity sha512-mPBodDGVL+fl6d90wUREepHa/7lhsghg2A3vFpakEhrhtbIlgNAZiMr7ccTgak5qbHqF14Fwy+W1yFWQt+WmYQ==
"@testing-library/dom@^8.5.0": "@testing-library/dom@^8.5.0":
version "8.20.0" version "8.20.0"
resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz" resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz"
@@ -6195,11 +6212,11 @@ brace-expansion@^2.0.1:
balanced-match "^1.0.0" balanced-match "^1.0.0"
braces@^3.0.2, braces@~3.0.2: braces@^3.0.2, braces@~3.0.2:
version "3.0.3" version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies: dependencies:
fill-range "^7.1.1" fill-range "^7.0.1"
broadcast-channel@^3.4.1: broadcast-channel@^3.4.1:
version "3.7.0" version "3.7.0"
@@ -8776,11 +8793,6 @@ fb-watchman@^2.0.0:
dependencies: dependencies:
bser "2.1.1" bser "2.1.1"
fflate@^0.4.8:
version "0.4.8"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
figures@^3.0.0: figures@^3.0.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -8808,10 +8820,10 @@ file-saver@^2.0.2:
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
fill-range@^7.1.1: fill-range@^7.0.1:
version "7.1.1" version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies: dependencies:
to-regex-range "^5.0.1" to-regex-range "^5.0.1"
@@ -13705,20 +13717,6 @@ postcss@8.4.38, postcss@^8.0.0, postcss@^8.1.1, postcss@^8.3.7, postcss@^8.4.21,
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^1.2.0" source-map-js "^1.2.0"
posthog-js@1.142.1:
version "1.142.1"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.142.1.tgz#3b91229732938c5c76b5ee6d410698a267e073e9"
integrity sha512-yqeWTWitlb0sCaH5v6s7UJ+pPspzf/lkzPaSE5pMMXRM2i2KNsMoZEAZqbPCW8fQ8QL6lHs6d8PLjHrvbR288w==
dependencies:
fflate "^0.4.8"
preact "^10.19.3"
web-vitals "^4.0.1"
preact@^10.19.3:
version "10.22.0"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.22.0.tgz#a50f38006ae438d255e2631cbdaf7488e6dd4e16"
integrity sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==
prelude-ls@^1.2.1: prelude-ls@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
@@ -17219,11 +17217,6 @@ web-vitals@^0.2.4:
resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-0.2.4.tgz" resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-0.2.4.tgz"
integrity sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg== integrity sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==
web-vitals@^4.0.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.0.tgz#008949ab79717a68ccaaa3c4371cbc7bbbd78a92"
integrity sha512-ohj72kbtVWCpKYMxcbJ+xaOBV3En76hW47j52dG+tEGG36LZQgfFw5yHl9xyjmosy3XUMn8d/GBUAy4YPM839w==
web-worker@^1.2.0: web-worker@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz" resolved "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz"
@@ -17638,14 +17631,14 @@ write-file-atomic@^4.0.2:
signal-exit "^3.0.7" signal-exit "^3.0.7"
ws@^7.3.1, ws@^7.4.6: ws@^7.3.1, ws@^7.4.6:
version "7.5.10" version "7.5.9"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
ws@^8.13.0: ws@^8.13.0:
version "8.17.1" version "8.13.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" resolved "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz"
integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
xhr-request@^1.0.1: xhr-request@^1.0.1:
version "1.1.0" version "1.1.0"

3
go.mod
View File

@@ -6,7 +6,7 @@ require (
github.com/ClickHouse/clickhouse-go/v2 v2.20.0 github.com/ClickHouse/clickhouse-go/v2 v2.20.0
github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
github.com/SigNoz/signoz-otel-collector v0.102.2 github.com/SigNoz/signoz-otel-collector v0.102.0
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 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/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974
github.com/antonmedv/expr v1.15.3 github.com/antonmedv/expr v1.15.3
@@ -37,6 +37,7 @@ require (
github.com/opentracing/opentracing-go v1.2.0 github.com/opentracing/opentracing-go v1.2.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f
github.com/prometheus/common v0.54.0 github.com/prometheus/common v0.54.0
github.com/prometheus/prometheus v2.5.0+incompatible github.com/prometheus/prometheus v2.5.0+incompatible
github.com/rs/cors v1.11.0 github.com/rs/cors v1.11.0

14
go.sum
View File

@@ -64,8 +64,8 @@ github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd h1:Bk43AsDYe0fhkb
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc= github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc=
github.com/SigNoz/prometheus v1.11.1 h1:roM8ugYf4UxaeKKujEeBvoX7ybq3IrS+TB26KiRtIJg= github.com/SigNoz/prometheus v1.11.1 h1:roM8ugYf4UxaeKKujEeBvoX7ybq3IrS+TB26KiRtIJg=
github.com/SigNoz/prometheus v1.11.1/go.mod h1:uv4mQwZQtx7y4GQ6EdHOi8Wsk07uHNn2XHd1zM85m6I= github.com/SigNoz/prometheus v1.11.1/go.mod h1:uv4mQwZQtx7y4GQ6EdHOi8Wsk07uHNn2XHd1zM85m6I=
github.com/SigNoz/signoz-otel-collector v0.102.2 h1:SmjsBZjMjTVVpuOlfJXlsDJQbdefQP/9Wz3CyzSuZuU= github.com/SigNoz/signoz-otel-collector v0.102.0 h1:v6ap+gdvrKklMwU+M9FJgrn28vN0YxrINl3kvdcLonA=
github.com/SigNoz/signoz-otel-collector v0.102.2/go.mod h1:ISAXYhZenojCWg6CdDJtPMpfS6Zwc08+uoxH25tc6Y0= github.com/SigNoz/signoz-otel-collector v0.102.0/go.mod h1:kCx5BfzDujq6C0+kotiqLp5COG2ut4Cb039+55rbWE0=
github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc= 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 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= github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 h1:PKVgdf83Yw+lZJbFtNGBgqXiXNf3+kOXW2qZ7Ms7OaY=
@@ -137,6 +137,7 @@ github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoE
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac= github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -378,8 +379,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
@@ -624,6 +625,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f h1:h0p1aZ9F5d6IXOygysob3g4B07b+HuVUQC0VJKD8wA4=
github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI= github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI=
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
@@ -670,6 +673,7 @@ github.com/russellhaering/gosaml2 v0.9.0 h1:CNMnH42z/GirrKjdmNrSS6bAAs47F9bPdl4P
github.com/russellhaering/gosaml2 v0.9.0/go.mod h1:byViER/1YPUa0Puj9ROZblpoq2jsE7h/CJmitzX0geU= github.com/russellhaering/gosaml2 v0.9.0/go.mod h1:byViER/1YPUa0Puj9ROZblpoq2jsE7h/CJmitzX0geU=
github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg= github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg=
github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@@ -693,6 +697,7 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
@@ -742,6 +747,7 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=

View File

@@ -53,6 +53,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/interfaces" "go.signoz.io/signoz/pkg/query-service/interfaces"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" 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/telemetry"
"go.signoz.io/signoz/pkg/query-service/utils" "go.signoz.io/signoz/pkg/query-service/utils"
) )
@@ -1923,7 +1924,6 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, params *model.Searc
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_TRACE_DETAIL_API, data, userEmail, true, false) telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_TRACE_DETAIL_API, data, userEmail, true, false)
} }
var startTime, endTime, durationNano uint64
var searchScanResponses []model.SearchSpanDBResponseItem var searchScanResponses []model.SearchSpanDBResponseItem
query := fmt.Sprintf("SELECT timestamp, traceID, model FROM %s.%s WHERE traceID=$1", r.TraceDB, r.SpansTable) query := fmt.Sprintf("SELECT timestamp, traceID, model FROM %s.%s WHERE traceID=$1", r.TraceDB, r.SpansTable)
@@ -1941,7 +1941,7 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, params *model.Searc
end := time.Now() end := time.Now()
zap.L().Debug("getTraceSQLQuery took: ", zap.Duration("duration", end.Sub(start))) zap.L().Debug("getTraceSQLQuery took: ", zap.Duration("duration", end.Sub(start)))
searchSpansResult := []model.SearchSpansResult{{ searchSpansResult := []model.SearchSpansResult{{
Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues", "References", "Events", "HasError", "StatusMessage", "StatusCodeString", "SpanKind"}, Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues", "References", "Events", "HasError"},
Events: make([][]interface{}, len(searchScanResponses)), Events: make([][]interface{}, len(searchScanResponses)),
IsSubTree: false, IsSubTree: false,
}, },
@@ -1954,15 +1954,6 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, params *model.Searc
easyjson.Unmarshal([]byte(item.Model), &jsonItem) easyjson.Unmarshal([]byte(item.Model), &jsonItem)
jsonItem.TimeUnixNano = uint64(item.Timestamp.UnixNano() / 1000000) jsonItem.TimeUnixNano = uint64(item.Timestamp.UnixNano() / 1000000)
searchSpanResponses = append(searchSpanResponses, jsonItem) searchSpanResponses = append(searchSpanResponses, jsonItem)
if startTime == 0 || jsonItem.TimeUnixNano < startTime {
startTime = jsonItem.TimeUnixNano
}
if endTime == 0 || jsonItem.TimeUnixNano > endTime {
endTime = jsonItem.TimeUnixNano
}
if durationNano == 0 || uint64(jsonItem.DurationNano) > durationNano {
durationNano = uint64(jsonItem.DurationNano)
}
} }
end = time.Now() end = time.Now()
zap.L().Debug("getTraceSQLQuery unmarshal took: ", zap.Duration("duration", end.Sub(start))) zap.L().Debug("getTraceSQLQuery unmarshal took: ", zap.Duration("duration", end.Sub(start)))
@@ -1992,9 +1983,6 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, params *model.Searc
} }
} }
searchSpansResult[0].StartTimestampMillis = startTime - (durationNano / 1000000)
searchSpansResult[0].EndTimestampMillis = endTime + (durationNano / 1000000)
return &searchSpansResult, nil return &searchSpansResult, nil
} }
@@ -3231,7 +3219,7 @@ func (r *ClickHouseReader) GetSamplesInfoInLastHeartBeatInterval(ctx context.Con
var totalSamples uint64 var totalSamples uint64
queryStr := fmt.Sprintf("select count() from %s.%s where metric_name not like 'signoz_%%' and unix_milli > toUnixTimestamp(now()-toIntervalMinute(%d))*1000;", signozMetricDBName, signozSampleTableName, int(interval.Minutes())) queryStr := fmt.Sprintf("select count() from %s.%s where metric_name not like 'signoz_%%' and timestamp_ms > toUnixTimestamp(now()-toIntervalMinute(%d))*1000;", signozMetricDBName, signozSampleTableName, int(interval.Minutes()))
r.db.QueryRow(ctx, queryStr).Scan(&totalSamples) r.db.QueryRow(ctx, queryStr).Scan(&totalSamples)
@@ -3419,6 +3407,36 @@ func countPanelsInDashboard(data map[string]interface{}) model.DashboardsInfo {
} }
} }
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.L().Error("Error in processing sql query", zap.Error(err))
return &alertsInfo, err
}
for _, alert := range alertsData {
var rule rules.GettableRule
err = json.Unmarshal([]byte(alert), &rule)
if err != nil {
zap.L().Error("invalid rule data", zap.Error(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) GetSavedViewsInfo(ctx context.Context) (*model.SavedViewsInfo, error) { func (r *ClickHouseReader) GetSavedViewsInfo(ctx context.Context) (*model.SavedViewsInfo, error) {
savedViewsInfo := model.SavedViewsInfo{} savedViewsInfo := model.SavedViewsInfo{}
savedViews, err := explorer.GetViews() savedViews, err := explorer.GetViews()
@@ -4403,8 +4421,8 @@ func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([
case *time.Time: case *time.Time:
point.Timestamp = v.UnixMilli() point.Timestamp = v.UnixMilli()
case *float64, *float32: case *float64, *float32:
isValidPoint = true
if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 { if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 {
isValidPoint = true
point.Value = float64(reflect.ValueOf(v).Elem().Float()) point.Value = float64(reflect.ValueOf(v).Elem().Float())
} else { } else {
groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Float())) groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Float()))
@@ -4413,24 +4431,9 @@ func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([
} }
groupAttributes[colName] = fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Float()) groupAttributes[colName] = fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Float())
} }
case **float64, **float32:
val := reflect.ValueOf(v)
if val.IsValid() && !val.IsNil() && !val.Elem().IsNil() {
value := reflect.ValueOf(v).Elem().Elem().Float()
if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 {
isValidPoint = true
point.Value = value
} else {
groupBy = append(groupBy, fmt.Sprintf("%v", value))
if _, ok := groupAttributes[colName]; !ok {
groupAttributesArray = append(groupAttributesArray, map[string]string{colName: fmt.Sprintf("%v", value)})
}
groupAttributes[colName] = fmt.Sprintf("%v", value)
}
}
case *uint, *uint8, *uint64, *uint16, *uint32: case *uint, *uint8, *uint64, *uint16, *uint32:
isValidPoint = true
if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 { if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 {
isValidPoint = true
point.Value = float64(reflect.ValueOf(v).Elem().Uint()) point.Value = float64(reflect.ValueOf(v).Elem().Uint())
} else { } else {
groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Uint())) groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Uint()))
@@ -4439,24 +4442,9 @@ func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([
} }
groupAttributes[colName] = fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Uint()) groupAttributes[colName] = fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Uint())
} }
case **uint, **uint8, **uint64, **uint16, **uint32:
val := reflect.ValueOf(v)
if val.IsValid() && !val.IsNil() && !val.Elem().IsNil() {
value := reflect.ValueOf(v).Elem().Elem().Uint()
if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 {
isValidPoint = true
point.Value = float64(value)
} else {
groupBy = append(groupBy, fmt.Sprintf("%v", value))
if _, ok := groupAttributes[colName]; !ok {
groupAttributesArray = append(groupAttributesArray, map[string]string{colName: fmt.Sprintf("%v", value)})
}
groupAttributes[colName] = fmt.Sprintf("%v", value)
}
}
case *int, *int8, *int16, *int32, *int64: case *int, *int8, *int16, *int32, *int64:
isValidPoint = true
if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 { if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 {
isValidPoint = true
point.Value = float64(reflect.ValueOf(v).Elem().Int()) point.Value = float64(reflect.ValueOf(v).Elem().Int())
} else { } else {
groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Int())) groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Int()))
@@ -4465,21 +4453,6 @@ func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([
} }
groupAttributes[colName] = fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Int()) groupAttributes[colName] = fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Int())
} }
case **int, **int8, **int16, **int32, **int64:
val := reflect.ValueOf(v)
if val.IsValid() && !val.IsNil() && !val.Elem().IsNil() {
value := reflect.ValueOf(v).Elem().Elem().Int()
if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 {
isValidPoint = true
point.Value = float64(value)
} else {
groupBy = append(groupBy, fmt.Sprintf("%v", value))
if _, ok := groupAttributes[colName]; !ok {
groupAttributesArray = append(groupAttributesArray, map[string]string{colName: fmt.Sprintf("%v", value)})
}
groupAttributes[colName] = fmt.Sprintf("%v", value)
}
}
case *bool: case *bool:
groupBy = append(groupBy, fmt.Sprintf("%v", *v)) groupBy = append(groupBy, fmt.Sprintf("%v", *v))
if _, ok := groupAttributes[colName]; !ok { if _, ok := groupAttributes[colName]; !ok {

View File

@@ -76,6 +76,7 @@ type APIHandler struct {
querier interfaces.Querier querier interfaces.Querier
querierV2 interfaces.Querier querierV2 interfaces.Querier
queryBuilder *queryBuilder.QueryBuilder queryBuilder *queryBuilder.QueryBuilder
preferDelta bool
preferSpanMetrics bool preferSpanMetrics bool
// temporalityMap is a map of metric name to temporality // temporalityMap is a map of metric name to temporality
@@ -105,6 +106,7 @@ type APIHandlerOpts struct {
SkipConfig *model.SkipConfig SkipConfig *model.SkipConfig
PerferDelta bool
PreferSpanMetrics bool PreferSpanMetrics bool
MaxIdleConns int MaxIdleConns int
@@ -164,6 +166,7 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
reader: opts.Reader, reader: opts.Reader,
appDao: opts.AppDao, appDao: opts.AppDao,
skipConfig: opts.SkipConfig, skipConfig: opts.SkipConfig,
preferDelta: opts.PerferDelta,
preferSpanMetrics: opts.PreferSpanMetrics, preferSpanMetrics: opts.PreferSpanMetrics,
temporalityMap: make(map[string]map[v3.Temporality]bool), temporalityMap: make(map[string]map[v3.Temporality]bool),
maxIdleConns: opts.MaxIdleConns, maxIdleConns: opts.MaxIdleConns,
@@ -3013,7 +3016,6 @@ func (aH *APIHandler) QueryRangeV3Format(w http.ResponseWriter, r *http.Request)
RespondError(w, apiErrorObj, nil) RespondError(w, apiErrorObj, nil)
return return
} }
queryRangeParams.Version = "v3"
aH.Respond(w, queryRangeParams) aH.Respond(w, queryRangeParams)
} }
@@ -3068,14 +3070,6 @@ func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.Que
postprocess.FillGaps(result, queryRangeParams) postprocess.FillGaps(result, queryRangeParams)
} }
if queryRangeParams.CompositeQuery.PanelType == v3.PanelTypeTable && queryRangeParams.FormatForWeb {
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
result = postprocess.TransformToTableForClickHouseQueries(result)
} else if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder {
result = postprocess.TransformToTableForBuilderQueries(result, queryRangeParams)
}
}
resp := v3.QueryRangeResponse{ resp := v3.QueryRangeResponse{
Result: result, Result: result,
} }
@@ -3324,10 +3318,8 @@ func (aH *APIHandler) queryRangeV4(ctx context.Context, queryRangeParams *v3.Que
} }
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder { if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder {
result, err = postprocess.PostProcessResult(result, queryRangeParams) result, err = postprocess.PostProcessResult(result, queryRangeParams)
} else if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL &&
queryRangeParams.CompositeQuery.PanelType == v3.PanelTypeTable && queryRangeParams.FormatForWeb {
result = postprocess.TransformToTableForClickHouseQueries(result)
} }
if err != nil { if err != nil {
@@ -3351,7 +3343,6 @@ func (aH *APIHandler) QueryRangeV4(w http.ResponseWriter, r *http.Request) {
RespondError(w, apiErrorObj, nil) RespondError(w, apiErrorObj, nil)
return return
} }
queryRangeParams.Version = "v4"
// add temporality for each metric // add temporality for each metric
temporalityErr := aH.populateTemporality(r.Context(), queryRangeParams) temporalityErr := aH.populateTemporality(r.Context(), queryRangeParams)

View File

@@ -142,11 +142,6 @@ func checkDuplicateString(pipeline []string) bool {
for _, processor := range pipeline { for _, processor := range pipeline {
name := processor name := processor
if _, ok := exists[name]; ok { if _, ok := exists[name]; ok {
zap.L().Error(
"duplicate processor name detected in generated collector config for log pipelines",
zap.String("processor", processor),
zap.Any("pipeline", pipeline),
)
return true return true
} }

View File

@@ -5,10 +5,7 @@ import (
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/constants"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"gopkg.in/yaml.v3"
) )
var buildProcessorTestData = []struct { var buildProcessorTestData = []struct {
@@ -207,89 +204,3 @@ func TestBuildLogsPipeline(t *testing.T) {
}) })
} }
} }
func TestPipelineAliasCollisionsDontResultInDuplicateCollectorProcessors(t *testing.T) {
require := require.New(t)
baseConf := []byte(`
receivers:
memory:
id: in-memory-receiver
exporters:
memory:
id: in-memory-exporter
service:
pipelines:
logs:
receivers:
- memory
processors: []
exporters:
- memory
`)
makeTestPipeline := func(name string, alias string) Pipeline {
return Pipeline{
OrderId: 1,
Name: name,
Alias: alias,
Enabled: true,
Filter: &v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: "method",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
Operator: "=",
Value: "GET",
},
},
},
Config: []PipelineOperator{
{
ID: "regex",
Type: "regex_parser",
Enabled: true,
Name: "regex parser",
ParseFrom: "attributes.test_regex_target",
ParseTo: "attributes",
Regex: `^\s*(?P<json_data>{.*})\s*$`,
},
},
}
}
testPipelines := []Pipeline{
makeTestPipeline("test pipeline 1", "pipeline-alias"),
makeTestPipeline("test pipeline 2", "pipeline-alias"),
}
recommendedConfYaml, apiErr := GenerateCollectorConfigWithPipelines(
baseConf, testPipelines,
)
require.Nil(apiErr, fmt.Sprintf("couldn't generate config recommendation: %v", apiErr))
var recommendedConf map[string]interface{}
err := yaml.Unmarshal(recommendedConfYaml, &recommendedConf)
require.Nil(err, "couldn't unmarshal recommended config")
logsProcessors := recommendedConf["service"].(map[string]any)["pipelines"].(map[string]any)["logs"].(map[string]any)["processors"].([]any)
require.Equal(
len(logsProcessors), len(testPipelines),
"test pipelines not included in recommended config as expected",
)
recommendedConfYaml2, apiErr := GenerateCollectorConfigWithPipelines(
baseConf, testPipelines,
)
require.Nil(apiErr, fmt.Sprintf("couldn't generate config recommendation again: %v", apiErr))
require.Equal(
string(recommendedConfYaml), string(recommendedConfYaml2),
"collector config should not change across recommendations for same set of pipelines",
)
}

View File

@@ -24,7 +24,7 @@ func CollectorConfProcessorName(p Pipeline) string {
func PreparePipelineProcessor(pipelines []Pipeline) (map[string]interface{}, []string, error) { func PreparePipelineProcessor(pipelines []Pipeline) (map[string]interface{}, []string, error) {
processors := map[string]interface{}{} processors := map[string]interface{}{}
names := []string{} names := []string{}
for pipelineIdx, v := range pipelines { for _, v := range pipelines {
if !v.Enabled { if !v.Enabled {
continue continue
} }
@@ -70,12 +70,6 @@ func PreparePipelineProcessor(pipelines []Pipeline) (map[string]interface{}, []s
Operators: v.Config, Operators: v.Config,
} }
name := CollectorConfProcessorName(v) name := CollectorConfProcessorName(v)
// Ensure name is unique
if _, nameExists := processors[name]; nameExists {
name = fmt.Sprintf("%s-%d", name, pipelineIdx)
}
processors[name] = processor processors[name] = processor
names = append(names, name) names = append(names, name)
} }

View File

@@ -803,3 +803,76 @@ func TestContainsFilterIsCaseInsensitive(t *testing.T) {
_, test2Exists := result[0].Attributes_string["test2"] _, test2Exists := result[0].Attributes_string["test2"]
require.False(test2Exists) require.False(test2Exists)
} }
func TestTemporaryWorkaroundForSupportingAttribsContainingDots(t *testing.T) {
// TODO(Raj): Remove this after dots are supported
require := require.New(t)
testPipeline := Pipeline{
OrderId: 1,
Name: "pipeline1",
Alias: "pipeline1",
Enabled: true,
Filter: &v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{
{
Key: v3.AttributeKey{
Key: "k8s_deployment_name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeResource,
},
Operator: "=",
Value: "ingress",
},
},
},
Config: []PipelineOperator{
{
ID: "add",
Type: "add",
Enabled: true,
Name: "add",
Field: "attributes.test",
Value: "test-value",
},
},
}
testLogs := []model.SignozLog{{
Timestamp: uint64(time.Now().UnixNano()),
Body: "test log",
Attributes_string: map[string]string{},
Resources_string: map[string]string{
"k8s_deployment_name": "ingress",
},
SeverityText: entry.Info.String(),
SeverityNumber: uint8(entry.Info),
SpanID: "",
TraceID: "",
}, {
Timestamp: uint64(time.Now().UnixNano()),
Body: "test log",
Attributes_string: map[string]string{},
Resources_string: map[string]string{
"k8s.deployment.name": "ingress",
},
SeverityText: entry.Info.String(),
SeverityNumber: uint8(entry.Info),
SpanID: "",
TraceID: "",
}}
result, collectorWarnAndErrorLogs, err := SimulatePipelinesProcessing(
context.Background(),
[]Pipeline{testPipeline},
testLogs,
)
require.Nil(err)
require.Equal(0, len(collectorWarnAndErrorLogs), strings.Join(collectorWarnAndErrorLogs, "\n"))
require.Equal(2, len(result))
for _, processedLog := range result {
require.Equal(processedLog.Attributes_string["test"], "test-value")
}
}

View File

@@ -58,9 +58,7 @@ func ParseLogFilterParams(r *http.Request) (*model.LogsFilterParams, error) {
res.OrderBy = val[0] res.OrderBy = val[0]
} }
if val, ok := params[ORDER]; ok { if val, ok := params[ORDER]; ok {
if val[0] == ASC || val[0] == DESC { res.Order = val[0]
res.Order = val[0]
}
} }
if val, ok := params["q"]; ok { if val, ok := params["q"]; ok {
res.Query = val[0] res.Query = val[0]

View File

@@ -1,8 +1,6 @@
package logs package logs
import ( import (
"net/http"
"net/http/httptest"
"testing" "testing"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@@ -434,51 +432,3 @@ func TestGenerateSQLQuery(t *testing.T) {
}) })
} }
} }
var parseLogFilterParams = []struct {
Name string
ReqParams string
ExpectedLogFilterParams *model.LogsFilterParams
}{
{
Name: "test with proper order by",
ReqParams: "order=desc&q=service.name='myservice'&limit=10",
ExpectedLogFilterParams: &model.LogsFilterParams{
Limit: 10,
OrderBy: "timestamp",
Order: DESC,
Query: "service.name='myservice'",
},
},
{
Name: "test with proper order by asc",
ReqParams: "order=asc&q=service.name='myservice'&limit=10",
ExpectedLogFilterParams: &model.LogsFilterParams{
Limit: 10,
OrderBy: "timestamp",
Order: ASC,
Query: "service.name='myservice'",
},
},
{
Name: "test with incorrect order by",
ReqParams: "order=undefined&q=service.name='myservice'&limit=10",
ExpectedLogFilterParams: &model.LogsFilterParams{
Limit: 10,
OrderBy: "timestamp",
Order: DESC,
Query: "service.name='myservice'",
},
},
}
func TestParseLogFilterParams(t *testing.T) {
for _, test := range parseLogFilterParams {
Convey(test.Name, t, func() {
req := httptest.NewRequest(http.MethodGet, "/logs?"+test.ReqParams, nil)
params, err := ParseLogFilterParams(req)
So(err, ShouldBeNil)
So(params, ShouldEqual, test.ExpectedLogFilterParams)
})
}
}

View File

@@ -55,6 +55,7 @@ type ServerOptions struct {
// alert specific params // alert specific params
DisableRules bool DisableRules bool
RuleRepoURL string RuleRepoURL string
PreferDelta bool
PreferSpanMetrics bool PreferSpanMetrics bool
MaxIdleConns int MaxIdleConns int
MaxOpenConns int MaxOpenConns int
@@ -171,6 +172,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
apiHandler, err := NewAPIHandler(APIHandlerOpts{ apiHandler, err := NewAPIHandler(APIHandlerOpts{
Reader: reader, Reader: reader,
SkipConfig: skipConfig, SkipConfig: skipConfig,
PerferDelta: serverOptions.PreferDelta,
PreferSpanMetrics: serverOptions.PreferSpanMetrics, PreferSpanMetrics: serverOptions.PreferSpanMetrics,
MaxIdleConns: serverOptions.MaxIdleConns, MaxIdleConns: serverOptions.MaxIdleConns,
MaxOpenConns: serverOptions.MaxOpenConns, MaxOpenConns: serverOptions.MaxOpenConns,

View File

@@ -73,6 +73,7 @@ type Reader interface {
LiveTailLogsV3(ctx context.Context, query string, timestampStart uint64, idStart string, client *v3.LogsLiveTailClient) LiveTailLogsV3(ctx context.Context, query string, timestampStart uint64, idStart string, client *v3.LogsLiveTailClient)
GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error) GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error)
GetAlertsInfo(ctx context.Context) (*model.AlertsInfo, error)
GetSavedViewsInfo(ctx context.Context) (*model.SavedViewsInfo, error) GetSavedViewsInfo(ctx context.Context) (*model.SavedViewsInfo, error)
GetTotalSpans(ctx context.Context) (uint64, error) GetTotalSpans(ctx context.Context) (uint64, error)
GetTotalLogs(ctx context.Context) (uint64, error) GetTotalLogs(ctx context.Context) (uint64, error)

View File

@@ -37,6 +37,7 @@ func main() {
var ruleRepoURL, cacheConfigPath, fluxInterval string var ruleRepoURL, cacheConfigPath, fluxInterval string
var cluster string var cluster string
var preferDelta bool
var preferSpanMetrics bool var preferSpanMetrics bool
var maxIdleConns int var maxIdleConns int
@@ -46,10 +47,11 @@ func main() {
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)") flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)") flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)") flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
flag.BoolVar(&preferDelta, "prefer-delta", false, "(prefer delta over cumulative metrics)")
flag.BoolVar(&preferSpanMetrics, "prefer-span-metrics", false, "(prefer span metrics for service level metrics)") flag.BoolVar(&preferSpanMetrics, "prefer-span-metrics", false, "(prefer span metrics for service level metrics)")
flag.StringVar(&ruleRepoURL, "rules.repo-url", constants.AlertHelpPage, "(host address used to build rule link in alert messages)") flag.StringVar(&ruleRepoURL, "rules.repo-url", constants.AlertHelpPage, "(host address used to build rule link in alert messages)")
flag.StringVar(&cacheConfigPath, "experimental.cache-config", "", "(cache config to use)") flag.StringVar(&cacheConfigPath, "experimental.cache-config", "", "(cache config to use)")
flag.StringVar(&fluxInterval, "flux-interval", "5m", "(the interval to exclude data from being cached to avoid incorrect cache for data in motion)") flag.StringVar(&fluxInterval, "flux-interval", "5m", "(cache config to use)")
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')") flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
// Allow using the consistent naming with the signoz collector // Allow using the consistent naming with the signoz collector
flag.StringVar(&cluster, "cluster-name", "cluster", "(cluster name - defaults to 'cluster')") flag.StringVar(&cluster, "cluster-name", "cluster", "(cluster name - defaults to 'cluster')")
@@ -69,6 +71,7 @@ func main() {
HTTPHostPort: constants.HTTPHostPort, HTTPHostPort: constants.HTTPHostPort,
PromConfigPath: promConfigPath, PromConfigPath: promConfigPath,
SkipTopLvlOpsPath: skipTopLvlOpsPath, SkipTopLvlOpsPath: skipTopLvlOpsPath,
PreferDelta: preferDelta,
PreferSpanMetrics: preferSpanMetrics, PreferSpanMetrics: preferSpanMetrics,
PrivateHostPort: constants.PrivateHostPort, PrivateHostPort: constants.PrivateHostPort,
DisableRules: disableRules, DisableRules: disableRules,

View File

@@ -212,11 +212,9 @@ type ServiceOverviewItem struct {
} }
type SearchSpansResult struct { type SearchSpansResult struct {
StartTimestampMillis uint64 `json:"startTimestampMillis"` Columns []string `json:"columns"`
EndTimestampMillis uint64 `json:"endTimestampMillis"` Events [][]interface{} `json:"events"`
Columns []string `json:"columns"` IsSubTree bool `json:"isSubTree"`
Events [][]interface{} `json:"events"`
IsSubTree bool `json:"isSubTree"`
} }
type GetFilterSpansResponseItem struct { type GetFilterSpansResponseItem struct {
@@ -252,22 +250,19 @@ type Event struct {
//easyjson:json //easyjson:json
type SearchSpanResponseItem struct { type SearchSpanResponseItem struct {
TimeUnixNano uint64 `json:"timestamp"` TimeUnixNano uint64 `json:"timestamp"`
DurationNano int64 `json:"durationNano"` DurationNano int64 `json:"durationNano"`
SpanID string `json:"spanId"` SpanID string `json:"spanId"`
RootSpanID string `json:"rootSpanId"` RootSpanID string `json:"rootSpanId"`
TraceID string `json:"traceId"` TraceID string `json:"traceId"`
HasError bool `json:"hasError"` HasError bool `json:"hasError"`
Kind int32 `json:"kind"` Kind int32 `json:"kind"`
ServiceName string `json:"serviceName"` ServiceName string `json:"serviceName"`
Name string `json:"name"` Name string `json:"name"`
References []OtelSpanRef `json:"references,omitempty"` References []OtelSpanRef `json:"references,omitempty"`
TagMap map[string]string `json:"tagMap"` TagMap map[string]string `json:"tagMap"`
Events []string `json:"event"` Events []string `json:"event"`
RootName string `json:"rootName"` RootName string `json:"rootName"`
StatusMessage string `json:"statusMessage"`
StatusCodeString string `json:"statusCodeString"`
SpanKind string `json:"spanKind"`
} }
type OtelSpanRef struct { type OtelSpanRef struct {
@@ -304,7 +299,7 @@ func (item *SearchSpanResponseItem) GetValues() []interface{} {
keys = append(keys, k) keys = append(keys, k)
values = append(values, v) values = append(values, v)
} }
returnArray := []interface{}{item.TimeUnixNano, item.SpanID, item.TraceID, item.ServiceName, item.Name, strconv.Itoa(int(item.Kind)), strconv.FormatInt(item.DurationNano, 10), keys, values, referencesStringArray, item.Events, item.HasError, item.StatusMessage, item.StatusCodeString, item.SpanKind} returnArray := []interface{}{item.TimeUnixNano, item.SpanID, item.TraceID, item.ServiceName, item.Name, strconv.Itoa(int(item.Kind)), strconv.FormatInt(item.DurationNano, 10), keys, values, referencesStringArray, item.Events, item.HasError}
return returnArray return returnArray
} }

View File

@@ -118,12 +118,6 @@ func easyjson6ff3ac1dDecodeGoSignozIoSignozPkgQueryServiceModel(in *jlexer.Lexer
} }
case "rootName": case "rootName":
out.RootName = string(in.String()) out.RootName = string(in.String())
case "statusMessage":
out.StatusMessage = string(in.String())
case "statusCodeString":
out.StatusCodeString = string(in.String())
case "spanKind":
out.SpanKind = string(in.String())
default: default:
in.SkipRecursive() in.SkipRecursive()
} }
@@ -239,21 +233,6 @@ func easyjson6ff3ac1dEncodeGoSignozIoSignozPkgQueryServiceModel(out *jwriter.Wri
out.RawString(prefix) out.RawString(prefix)
out.String(string(in.RootName)) out.String(string(in.RootName))
} }
{
const prefix string = ",\"statusMessage\":"
out.RawString(prefix)
out.String(string(in.StatusMessage))
}
{
const prefix string = ",\"statusCodeString\":"
out.RawString(prefix)
out.String(string(in.StatusCodeString))
}
{
const prefix string = ",\"spanKind\":"
out.RawString(prefix)
out.String(string(in.SpanKind))
}
out.RawByte('}') out.RawByte('}')
} }

View File

@@ -354,8 +354,6 @@ type QueryRangeParamsV3 struct {
CompositeQuery *CompositeQuery `json:"compositeQuery"` CompositeQuery *CompositeQuery `json:"compositeQuery"`
Variables map[string]interface{} `json:"variables,omitempty"` Variables map[string]interface{} `json:"variables,omitempty"`
NoCache bool `json:"noCache"` NoCache bool `json:"noCache"`
Version string `json:"-"`
FormatForWeb bool `json:"formatForWeb,omitempty"`
} }
type PromQuery struct { type PromQuery struct {
@@ -401,11 +399,8 @@ type CompositeQuery struct {
PromQueries map[string]*PromQuery `json:"promQueries,omitempty"` PromQueries map[string]*PromQuery `json:"promQueries,omitempty"`
PanelType PanelType `json:"panelType"` PanelType PanelType `json:"panelType"`
QueryType QueryType `json:"queryType"` QueryType QueryType `json:"queryType"`
// Unit for the time series data shown in the graph Unit string `json:"unit,omitempty"`
// This is used in alerts to format the value and threshold FillGaps bool `json:"fillGaps,omitempty"`
Unit string `json:"unit,omitempty"`
// FillGaps is used to fill the gaps in the time series data
FillGaps bool `json:"fillGaps,omitempty"`
} }
func (c *CompositeQuery) EnabledQueries() int { func (c *CompositeQuery) EnabledQueries() int {
@@ -991,30 +986,10 @@ type QueryRangeResponse struct {
Result []*Result `json:"result"` Result []*Result `json:"result"`
} }
type TableColumn struct {
Name string `json:"name"`
// QueryName is the name of the query that this column belongs to
QueryName string `json:"queryName"`
// IsValueColumn is true if this column is a value column
// i.e it is the column that contains the actual value that is being plotted
IsValueColumn bool `json:"isValueColumn"`
}
type TableRow struct {
Data map[string]interface{} `json:"data"`
QueryName string `json:"-"`
}
type Table struct {
Columns []*TableColumn `json:"columns"`
Rows []*TableRow `json:"rows"`
}
type Result struct { type Result struct {
QueryName string `json:"queryName,omitempty"` QueryName string `json:"queryName"`
Series []*Series `json:"series,omitempty"` Series []*Series `json:"series"`
List []*Row `json:"list,omitempty"` List []*Row `json:"list"`
Table *Table `json:"table,omitempty"`
} }
type LogsLiveTailClient struct { type LogsLiveTailClient struct {

View File

@@ -46,9 +46,6 @@ func fillGap(series *v3.Series, start, end, step int64) *v3.Series {
// TODO(srikanthccv): can WITH FILL be perfect substitute for all cases https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier // TODO(srikanthccv): can WITH FILL be perfect substitute for all cases https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier
func FillGaps(results []*v3.Result, params *v3.QueryRangeParamsV3) { func FillGaps(results []*v3.Result, params *v3.QueryRangeParamsV3) {
if params.CompositeQuery.PanelType != v3.PanelTypeGraph {
return
}
for _, result := range results { for _, result := range results {
// A `result` item in `results` contains the query result for individual query. // A `result` item in `results` contains the query result for individual query.
// If there are no series in the result, we add empty series and `fillGap` adds all zeros // If there are no series in the result, we add empty series and `fillGap` adds all zeros

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