mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-08 02:39:55 +00:00
Compare commits
12 Commits
feat/timez
...
v0.58.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d859301d30 | ||
|
|
a11aadb712 | ||
|
|
bc9c7b5f1d | ||
|
|
1bba932d08 | ||
|
|
3f7adeb040 | ||
|
|
eb6670980a | ||
|
|
48f3b9cacb | ||
|
|
eaf8571fe9 | ||
|
|
b10c22223b | ||
|
|
cdde369748 | ||
|
|
523cbcd6fc | ||
|
|
eeadc021e1 |
@@ -146,7 +146,7 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.56.0
|
||||
image: signoz/query-service:0.58.1
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
@@ -186,7 +186,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.56.0
|
||||
image: signoz/frontend:0.58.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@@ -199,7 +199,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.111.5
|
||||
image: signoz/signoz-otel-collector:0.111.8
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@@ -237,7 +237,7 @@ services:
|
||||
- query-service
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:0.111.5
|
||||
image: signoz/signoz-schema-migrator:0.111.8
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -69,7 +69,7 @@ services:
|
||||
- --storage.path=/data
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.8}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@@ -84,7 +84,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
otel-collector:
|
||||
container_name: signoz-otel-collector
|
||||
image: signoz/signoz-otel-collector:0.111.5
|
||||
image: signoz/signoz-otel-collector:0.111.8
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
|
||||
@@ -162,7 +162,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.56.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.58.1}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
[
|
||||
@@ -201,7 +201,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.56.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.58.1}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -213,7 +213,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector-migrator-sync:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.8}
|
||||
container_name: otel-migrator-sync
|
||||
command:
|
||||
- "sync"
|
||||
@@ -228,7 +228,7 @@ services:
|
||||
# condition: service_healthy
|
||||
|
||||
otel-collector-migrator-async:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.8}
|
||||
container_name: otel-migrator-async
|
||||
command:
|
||||
- "async"
|
||||
@@ -245,7 +245,7 @@ services:
|
||||
# condition: service_healthy
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.8}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
|
||||
@@ -167,7 +167,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.56.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.58.1}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
[
|
||||
@@ -208,7 +208,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.56.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.58.1}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -220,7 +220,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.8}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@@ -234,7 +234,7 @@ services:
|
||||
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.8}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# use a minimal alpine image
|
||||
FROM alpine:3.20.3
|
||||
FROM alpine:3.18.6
|
||||
|
||||
# Add Maintainer Info
|
||||
LABEL maintainer="signoz"
|
||||
|
||||
@@ -67,30 +67,6 @@ func StartManager(dbType string, db *sqlx.DB, useLicensesV3 bool, features ...ba
|
||||
repo: &repo,
|
||||
}
|
||||
|
||||
if useLicensesV3 {
|
||||
// get active license from the db
|
||||
active, err := m.repo.GetActiveLicense(context.Background())
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
// if we have an active license then need to fetch the complete details
|
||||
if active != nil {
|
||||
// fetch the new license structure from control plane
|
||||
licenseV3, apiError := validate.ValidateLicenseV3(active.Key)
|
||||
if apiError != nil {
|
||||
return m, apiError
|
||||
}
|
||||
|
||||
// insert the licenseV3 in sqlite db
|
||||
apiError = m.repo.InsertLicenseV3(context.Background(), licenseV3)
|
||||
// if the license already exists move ahead.
|
||||
if apiError != nil && apiError.Typ != model.ErrorConflict {
|
||||
return m, apiError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.start(useLicensesV3, features...); err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
@@ -61,11 +61,6 @@ func NewAnomalyRule(
|
||||
|
||||
zap.L().Info("creating new AnomalyRule", zap.String("id", id), zap.Any("opts", opts))
|
||||
|
||||
if p.RuleCondition.CompareOp == baserules.ValueIsBelow {
|
||||
target := -1 * *p.RuleCondition.Target
|
||||
p.RuleCondition.Target = &target
|
||||
}
|
||||
|
||||
baseRule, err := baserules.NewBaseRule(id, p, reader, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -186,7 +186,6 @@
|
||||
"@types/webpack-dev-server": "^4.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vvo/tzdb": "6.149.0",
|
||||
"autoprefixer": "10.4.19",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"compression-webpack-plugin": "9.0.0",
|
||||
|
||||
@@ -119,42 +119,3 @@
|
||||
color: var(--bg-slate-400) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.date-time-popover-footer {
|
||||
border-top: 1px solid var(--bg-ink-200);
|
||||
padding: 8px 14px;
|
||||
.timezone-container {
|
||||
&,
|
||||
.timezone {
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--bg-vanilla-400);
|
||||
gap: 6px;
|
||||
.timezone {
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
color: var(--bg-vanilla-100);
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.timezone-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
background: rgba(171, 189, 255, 0.04);
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.06px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -15,14 +15,11 @@ import { isValidTimeFormat } from 'lib/getMinMax';
|
||||
import { defaultTo, isFunction, noop } from 'lodash-es';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import { CheckCircle, ChevronDown, Clock } from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import {
|
||||
ChangeEvent,
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
@@ -31,8 +28,6 @@ import { popupContainer } from 'utils/selectPopupContainer';
|
||||
import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent';
|
||||
|
||||
const maxAllowedMinTimeInMonths = 6;
|
||||
type ViewType = 'datetime' | 'timezone';
|
||||
const DEFAULT_VIEW: ViewType = 'datetime';
|
||||
|
||||
interface CustomTimePickerProps {
|
||||
onSelect: (value: string) => void;
|
||||
@@ -86,25 +81,6 @@ function CustomTimePicker({
|
||||
const location = useLocation();
|
||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||
|
||||
const [activeView, setActiveView] = useState<ViewType>(DEFAULT_VIEW);
|
||||
|
||||
const { timezone, browserTimezone } = useTimezone();
|
||||
const activeTimezoneOffset = timezone?.offset;
|
||||
const isTimezoneOverridden = useMemo(
|
||||
() => timezone?.offset !== browserTimezone.offset,
|
||||
[timezone, browserTimezone],
|
||||
);
|
||||
|
||||
const handleViewChange = useCallback(
|
||||
(newView: 'timezone' | 'datetime'): void => {
|
||||
if (activeView !== newView) {
|
||||
setActiveView(newView);
|
||||
}
|
||||
setOpen(!open);
|
||||
},
|
||||
[activeView, open, setOpen],
|
||||
);
|
||||
|
||||
const getSelectedTimeRangeLabel = (
|
||||
selectedTime: string,
|
||||
selectedTimeValue: string,
|
||||
@@ -156,7 +132,6 @@ function CustomTimePicker({
|
||||
setOpen(newOpen);
|
||||
if (!newOpen) {
|
||||
setCustomDTPickerVisible?.(false);
|
||||
setActiveView('datetime');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -306,8 +281,6 @@ function CustomTimePicker({
|
||||
handleGoLive={defaultTo(handleGoLive, noop)}
|
||||
options={items}
|
||||
selectedTime={selectedTime}
|
||||
activeView={activeView}
|
||||
setActiveView={setActiveView}
|
||||
/>
|
||||
) : (
|
||||
content
|
||||
@@ -344,23 +317,12 @@ function CustomTimePicker({
|
||||
)
|
||||
}
|
||||
suffix={
|
||||
<>
|
||||
{!!isTimezoneOverridden && activeTimezoneOffset && (
|
||||
<div
|
||||
className="timezone-badge"
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
handleViewChange('timezone');
|
||||
}}
|
||||
>
|
||||
<span>{activeTimezoneOffset}</span>
|
||||
</div>
|
||||
)}
|
||||
<ChevronDown
|
||||
size={14}
|
||||
onClick={(): void => handleViewChange('datetime')}
|
||||
/>
|
||||
</>
|
||||
<ChevronDown
|
||||
size={14}
|
||||
onClick={(): void => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import './CustomTimePicker.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import ROUTES from 'constants/routes';
|
||||
@@ -10,13 +9,10 @@ import {
|
||||
Option,
|
||||
RelativeDurationSuggestionOptions,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import { Clock } from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { Dispatch, SetStateAction, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import RangePickerModal from './RangePickerModal';
|
||||
import TimezonePicker from './TimezonePicker';
|
||||
|
||||
interface CustomTimePickerPopoverContentProps {
|
||||
options: any[];
|
||||
@@ -30,11 +26,8 @@ interface CustomTimePickerPopoverContentProps {
|
||||
onSelectHandler: (label: string, value: string) => void;
|
||||
handleGoLive: () => void;
|
||||
selectedTime: string;
|
||||
activeView: 'datetime' | 'timezone';
|
||||
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function CustomTimePickerPopoverContent({
|
||||
options,
|
||||
setIsOpen,
|
||||
@@ -44,16 +37,12 @@ function CustomTimePickerPopoverContent({
|
||||
onSelectHandler,
|
||||
handleGoLive,
|
||||
selectedTime,
|
||||
activeView,
|
||||
setActiveView,
|
||||
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
|
||||
pathname,
|
||||
]);
|
||||
const { timezone } = useTimezone();
|
||||
const activeTimezoneOffset = timezone?.offset;
|
||||
|
||||
function getTimeChips(options: Option[]): JSX.Element {
|
||||
return (
|
||||
@@ -74,74 +63,54 @@ function CustomTimePickerPopoverContent({
|
||||
);
|
||||
}
|
||||
|
||||
return activeView === 'datetime' ? (
|
||||
<div>
|
||||
<div className="date-time-popover">
|
||||
<div className="date-time-options">
|
||||
{isLogsExplorerPage && (
|
||||
<Button className="data-time-live" type="text" onClick={handleGoLive}>
|
||||
Live
|
||||
</Button>
|
||||
)}
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
type="text"
|
||||
key={option.label + option.value}
|
||||
onClick={(): void => {
|
||||
onSelectHandler(option.label, option.value);
|
||||
}}
|
||||
className={cx(
|
||||
'date-time-options-btn',
|
||||
customDateTimeVisible
|
||||
? option.value === 'custom' && 'active'
|
||||
: selectedTime === option.value && 'active',
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className={cx(
|
||||
'relative-date-time',
|
||||
selectedTime === 'custom' || customDateTimeVisible
|
||||
? 'date-picker'
|
||||
: 'relative-times',
|
||||
)}
|
||||
>
|
||||
{selectedTime === 'custom' || customDateTimeVisible ? (
|
||||
<RangePickerModal
|
||||
setCustomDTPickerVisible={setCustomDTPickerVisible}
|
||||
setIsOpen={setIsOpen}
|
||||
onCustomDateHandler={onCustomDateHandler}
|
||||
selectedTime={selectedTime}
|
||||
/>
|
||||
) : (
|
||||
<div className="relative-times-container">
|
||||
<div className="time-heading">RELATIVE TIMES</div>
|
||||
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="date-time-popover-footer">
|
||||
<div className="timezone-container">
|
||||
<Clock color={Color.BG_VANILLA_400} height={12} width={12} />
|
||||
<span className="timezone-text">You are at</span>
|
||||
<button
|
||||
type="button"
|
||||
className="timezone"
|
||||
onClick={(): void => setActiveView('timezone')}
|
||||
>
|
||||
{activeTimezoneOffset}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
return (
|
||||
<div className="date-time-popover">
|
||||
<TimezonePicker setActiveView={setActiveView} setIsOpen={setIsOpen} />
|
||||
<div className="date-time-options">
|
||||
{isLogsExplorerPage && (
|
||||
<Button className="data-time-live" type="text" onClick={handleGoLive}>
|
||||
Live
|
||||
</Button>
|
||||
)}
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
type="text"
|
||||
key={option.label + option.value}
|
||||
onClick={(): void => {
|
||||
onSelectHandler(option.label, option.value);
|
||||
}}
|
||||
className={cx(
|
||||
'date-time-options-btn',
|
||||
customDateTimeVisible
|
||||
? option.value === 'custom' && 'active'
|
||||
: selectedTime === option.value && 'active',
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className={cx(
|
||||
'relative-date-time',
|
||||
selectedTime === 'custom' || customDateTimeVisible
|
||||
? 'date-picker'
|
||||
: 'relative-times',
|
||||
)}
|
||||
>
|
||||
{selectedTime === 'custom' || customDateTimeVisible ? (
|
||||
<RangePickerModal
|
||||
setCustomDTPickerVisible={setCustomDTPickerVisible}
|
||||
setIsOpen={setIsOpen}
|
||||
onCustomDateHandler={onCustomDateHandler}
|
||||
selectedTime={selectedTime}
|
||||
/>
|
||||
) : (
|
||||
<div className="relative-times-container">
|
||||
<div className="time-heading">RELATIVE TIMES</div>
|
||||
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { DatePicker } from 'antd';
|
||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -50,8 +49,6 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
||||
}
|
||||
onCustomDateHandler(date_time, LexicalContext.CUSTOM_DATE_PICKER);
|
||||
};
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
return (
|
||||
<div className="custom-date-picker">
|
||||
<RangePicker
|
||||
@@ -61,10 +58,7 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
||||
onOk={onModalOkHandler}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...(selectedTime === 'custom' && {
|
||||
defaultValue: [
|
||||
dayjs(minTime / 1000000).tz(timezone.value),
|
||||
dayjs(maxTime / 1000000).tz(timezone.value),
|
||||
],
|
||||
defaultValue: [dayjs(minTime / 1000000), dayjs(maxTime / 1000000)],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
// Variables
|
||||
$font-family: 'Inter';
|
||||
$border-color: var(--bg-slate-400);
|
||||
$item-spacing: 8px;
|
||||
|
||||
// Mixins
|
||||
@mixin text-style-base {
|
||||
font-family: $font-family;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.timezone-picker {
|
||||
width: 532px;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: $font-family;
|
||||
|
||||
&__search {
|
||||
@include flex-center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
&__input-container {
|
||||
@include flex-center;
|
||||
gap: 6px;
|
||||
width: -webkit-fill-available;
|
||||
}
|
||||
|
||||
&__input {
|
||||
@include text-style-base;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
padding: 0;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
|
||||
&__esc-key {
|
||||
@include text-style-base;
|
||||
font-size: 8px;
|
||||
color: var(--bg-vanilla-400);
|
||||
letter-spacing: -0.04px;
|
||||
border-radius: 2.286px;
|
||||
border: 1.143px solid var(--bg-ink-200);
|
||||
border-bottom-width: 2.286px;
|
||||
background: var(--bg-ink-400);
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
&__list {
|
||||
max-height: 310px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__item {
|
||||
@include flex-center;
|
||||
justify-content: space-between;
|
||||
padding: 7.5px 6px 7.5px $item-spacing;
|
||||
margin: 4px $item-spacing;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
width: -webkit-fill-available;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: $font-family;
|
||||
|
||||
&:hover,
|
||||
&.selected {
|
||||
border-radius: 2px;
|
||||
background: rgba(171, 189, 255, 0.04);
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
&.has-divider {
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: -$item-spacing;
|
||||
right: -$item-spacing;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include text-style-base;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
&__offset {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
}
|
||||
|
||||
.timezone-name-wrapper {
|
||||
@include flex-center;
|
||||
gap: 6px;
|
||||
|
||||
&__selected-icon {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import './TimezonePicker.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import cx from 'classnames';
|
||||
import { TimezonePickerShortcuts } from 'constants/shortcuts/TimezonePickerShortcuts';
|
||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||
import { Check, Search } from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { Timezone, TIMEZONE_DATA } from './timezoneUtils';
|
||||
|
||||
interface SearchBarProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
interface TimezoneItemProps {
|
||||
timezone: Timezone;
|
||||
isSelected?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const ICON_SIZE = 14;
|
||||
|
||||
function SearchBar({ value, onChange }: SearchBarProps): JSX.Element {
|
||||
return (
|
||||
<div className="timezone-picker__search">
|
||||
<div className="timezone-picker__input-container">
|
||||
<Search color={Color.BG_VANILLA_400} height={ICON_SIZE} width={ICON_SIZE} />
|
||||
<input
|
||||
type="text"
|
||||
className="timezone-picker__input"
|
||||
placeholder="Search timezones..."
|
||||
value={value}
|
||||
onChange={(e): void => onChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<kbd className="timezone-picker__esc-key">esc</kbd>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TimezoneItem({
|
||||
timezone,
|
||||
isSelected = false,
|
||||
onClick,
|
||||
}: TimezoneItemProps): JSX.Element {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cx('timezone-picker__item', {
|
||||
selected: isSelected,
|
||||
'has-divider': timezone.hasDivider,
|
||||
})}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="timezone-name-wrapper">
|
||||
<div className="timezone-name-wrapper__selected-icon">
|
||||
{isSelected && (
|
||||
<Check
|
||||
color={Color.BG_VANILLA_100}
|
||||
height={ICON_SIZE}
|
||||
width={ICON_SIZE}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="timezone-picker__name">{timezone.name}</div>
|
||||
</div>
|
||||
<div className="timezone-picker__offset">{timezone.offset}</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
TimezoneItem.defaultProps = {
|
||||
isSelected: false,
|
||||
onClick: undefined,
|
||||
};
|
||||
|
||||
interface TimezonePickerProps {
|
||||
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
function TimezonePicker({
|
||||
setActiveView,
|
||||
setIsOpen,
|
||||
}: TimezonePickerProps): JSX.Element {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const { timezone, updateTimezone } = useTimezone();
|
||||
const [selectedTimezone, setSelectedTimezone] = useState<string>(
|
||||
timezone?.name ?? TIMEZONE_DATA[0].name,
|
||||
);
|
||||
|
||||
const getFilteredTimezones = useCallback((searchTerm: string): Timezone[] => {
|
||||
const normalizedSearch = searchTerm.toLowerCase();
|
||||
return TIMEZONE_DATA.filter(
|
||||
(tz) =>
|
||||
tz.name.toLowerCase().includes(normalizedSearch) ||
|
||||
tz.offset.toLowerCase().includes(normalizedSearch) ||
|
||||
tz.searchIndex.toLowerCase().includes(normalizedSearch),
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleCloseTimezonePicker = useCallback(() => {
|
||||
setActiveView('datetime');
|
||||
}, [setActiveView]);
|
||||
|
||||
const handleTimezoneSelect = useCallback(
|
||||
(timezone: Timezone) => {
|
||||
setSelectedTimezone(timezone.name);
|
||||
updateTimezone(timezone);
|
||||
handleCloseTimezonePicker();
|
||||
setIsOpen(false);
|
||||
},
|
||||
[handleCloseTimezonePicker, setIsOpen, updateTimezone],
|
||||
);
|
||||
|
||||
// Register keyboard shortcuts
|
||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||
|
||||
useEffect(() => {
|
||||
registerShortcut(
|
||||
TimezonePickerShortcuts.CloseTimezonePicker,
|
||||
handleCloseTimezonePicker,
|
||||
);
|
||||
|
||||
return (): void => {
|
||||
deregisterShortcut(TimezonePickerShortcuts.CloseTimezonePicker);
|
||||
};
|
||||
}, [deregisterShortcut, handleCloseTimezonePicker, registerShortcut]);
|
||||
|
||||
return (
|
||||
<div className="timezone-picker">
|
||||
<SearchBar value={searchTerm} onChange={setSearchTerm} />
|
||||
<div className="timezone-picker__list">
|
||||
{getFilteredTimezones(searchTerm).map((timezone) => (
|
||||
<TimezoneItem
|
||||
key={timezone.value}
|
||||
timezone={timezone}
|
||||
isSelected={timezone.name === selectedTimezone}
|
||||
onClick={(): void => handleTimezoneSelect(timezone)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TimezonePicker;
|
||||
@@ -1,142 +0,0 @@
|
||||
import { getTimeZones } from '@vvo/tzdb';
|
||||
import dayjs from 'dayjs';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export interface Timezone {
|
||||
name: string;
|
||||
value: string;
|
||||
offset: string;
|
||||
searchIndex: string;
|
||||
hasDivider?: boolean;
|
||||
}
|
||||
|
||||
// Constants
|
||||
const TIMEZONE_TYPES = {
|
||||
BROWSER: 'BROWSER',
|
||||
UTC: 'UTC',
|
||||
STANDARD: 'STANDARD',
|
||||
} as const;
|
||||
|
||||
type TimezoneType = typeof TIMEZONE_TYPES[keyof typeof TIMEZONE_TYPES];
|
||||
|
||||
const UTC_TIMEZONE: Timezone = {
|
||||
name: 'Coordinated Universal Time — UTC, GMT',
|
||||
value: 'UTC',
|
||||
offset: 'UTC',
|
||||
searchIndex: 'UTC',
|
||||
hasDivider: true,
|
||||
};
|
||||
|
||||
// Helper functions
|
||||
const isValidTimezone = (tzName: string): boolean => {
|
||||
try {
|
||||
dayjs.tz(dayjs(), tzName);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const formatOffset = (offsetMinutes: number): string => {
|
||||
if (offsetMinutes === 0) return 'UTC';
|
||||
|
||||
const hours = Math.floor(Math.abs(offsetMinutes) / 60);
|
||||
const minutes = Math.abs(offsetMinutes) % 60;
|
||||
const sign = offsetMinutes > 0 ? '+' : '-';
|
||||
|
||||
return `UTC ${sign} ${hours}${
|
||||
minutes ? `:${minutes.toString().padStart(2, '0')}` : ':00'
|
||||
}`;
|
||||
};
|
||||
|
||||
const createTimezoneEntry = (
|
||||
name: string,
|
||||
offsetMinutes: number,
|
||||
type: TimezoneType = TIMEZONE_TYPES.STANDARD,
|
||||
hasDivider = false,
|
||||
): Timezone => {
|
||||
const offset = formatOffset(offsetMinutes);
|
||||
let value = name;
|
||||
let displayName = name;
|
||||
|
||||
switch (type) {
|
||||
case TIMEZONE_TYPES.BROWSER:
|
||||
displayName = `Browser time — ${name}`;
|
||||
value = name;
|
||||
break;
|
||||
case TIMEZONE_TYPES.UTC:
|
||||
displayName = 'Coordinated Universal Time — UTC, GMT';
|
||||
value = 'UTC';
|
||||
break;
|
||||
case TIMEZONE_TYPES.STANDARD:
|
||||
displayName = name;
|
||||
value = name;
|
||||
break;
|
||||
default:
|
||||
console.error(`Invalid timezone type: ${type}`);
|
||||
}
|
||||
|
||||
return {
|
||||
name: displayName,
|
||||
value,
|
||||
offset,
|
||||
searchIndex: offset.replace(/ /g, ''),
|
||||
...(hasDivider && { hasDivider }),
|
||||
};
|
||||
};
|
||||
|
||||
const getOffsetByTimezone = (timezone: string): number => {
|
||||
const dayjsTimezone = dayjs().tz(timezone);
|
||||
return dayjsTimezone.utcOffset();
|
||||
};
|
||||
|
||||
export const getBrowserTimezone = (): Timezone => {
|
||||
const browserTz = dayjs.tz.guess();
|
||||
const browserOffset = getOffsetByTimezone(browserTz);
|
||||
return createTimezoneEntry(browserTz, browserOffset, TIMEZONE_TYPES.BROWSER);
|
||||
};
|
||||
|
||||
const filterAndSortTimezones = (
|
||||
allTimezones: ReturnType<typeof getTimeZones>,
|
||||
browserTzName?: string,
|
||||
): Timezone[] =>
|
||||
allTimezones
|
||||
.filter(
|
||||
(tz) =>
|
||||
!tz.name.startsWith('Etc/') &&
|
||||
isValidTimezone(tz.name) &&
|
||||
tz.name !== browserTzName,
|
||||
)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((tz) => createTimezoneEntry(tz.name, tz.rawOffsetInMinutes));
|
||||
|
||||
const generateTimezoneData = (): Timezone[] => {
|
||||
const allTimezones = getTimeZones();
|
||||
const timezones: Timezone[] = [];
|
||||
|
||||
// Add browser timezone
|
||||
const browserTzObject = getBrowserTimezone();
|
||||
timezones.push(browserTzObject);
|
||||
|
||||
// Add UTC timezone with divider
|
||||
timezones.push(UTC_TIMEZONE);
|
||||
|
||||
// Add remaining timezones
|
||||
timezones.push(...filterAndSortTimezones(allTimezones, browserTzObject.value));
|
||||
|
||||
return timezones;
|
||||
};
|
||||
|
||||
export const getTimezoneObjectByTimezoneString = (
|
||||
timezone: string,
|
||||
): Timezone => {
|
||||
const utcOffset = getOffsetByTimezone(timezone);
|
||||
|
||||
return createTimezoneEntry(timezone, utcOffset);
|
||||
};
|
||||
|
||||
export const TIMEZONE_DATA = generateTimezoneData();
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
_adapters,
|
||||
BarController,
|
||||
BarElement,
|
||||
CategoryScale,
|
||||
@@ -19,10 +18,8 @@ import {
|
||||
} from 'chart.js';
|
||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||
import { generateGridTitle } from 'container/GridPanelSwitch/utils';
|
||||
import dayjs from 'dayjs';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import {
|
||||
forwardRef,
|
||||
memo,
|
||||
@@ -65,17 +62,6 @@ Chart.register(
|
||||
|
||||
Tooltip.positioners.custom = TooltipPositionHandler;
|
||||
|
||||
// Map of Chart.js time formats to dayjs format strings
|
||||
const formatMap = {
|
||||
'HH:mm:ss': 'HH:mm:ss',
|
||||
'HH:mm': 'HH:mm',
|
||||
'MM/DD HH:mm': 'MM/DD HH:mm',
|
||||
'MM/dd HH:mm': 'MM/DD HH:mm',
|
||||
'MM/DD': 'MM/DD',
|
||||
'YY-MM': 'YY-MM',
|
||||
YY: 'YY',
|
||||
};
|
||||
|
||||
const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||
(
|
||||
{
|
||||
@@ -94,13 +80,11 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||
dragSelectColor,
|
||||
},
|
||||
ref,
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
): JSX.Element => {
|
||||
const nearestDatasetIndex = useRef<null | number>(null);
|
||||
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const gridTitle = useMemo(() => generateGridTitle(title), [title]);
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const currentTheme = isDarkMode ? 'dark' : 'light';
|
||||
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
|
||||
@@ -128,22 +112,6 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||
return 'rgba(231,233,237,0.8)';
|
||||
}, [currentTheme]);
|
||||
|
||||
// Override Chart.js date adapter to use dayjs with timezone support
|
||||
useEffect(() => {
|
||||
_adapters._date.override({
|
||||
format(time: number | Date, fmt: string) {
|
||||
const dayjsTime = dayjs(time).tz(timezone?.value);
|
||||
const format = formatMap[fmt as keyof typeof formatMap];
|
||||
if (!format) {
|
||||
console.warn(`Missing datetime format for ${fmt}`);
|
||||
return dayjsTime.format('YYYY-MM-DD HH:mm:ss'); // fallback format
|
||||
}
|
||||
|
||||
return dayjsTime.format(format);
|
||||
},
|
||||
});
|
||||
}, [timezone]);
|
||||
|
||||
const buildChart = useCallback(() => {
|
||||
if (lineChartRef.current !== undefined) {
|
||||
lineChartRef.current.destroy();
|
||||
@@ -164,7 +132,6 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||
isStacked,
|
||||
onClickHandler,
|
||||
data,
|
||||
timezone,
|
||||
);
|
||||
|
||||
const chartHasData = hasData(data);
|
||||
@@ -199,7 +166,6 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||
isStacked,
|
||||
onClickHandler,
|
||||
data,
|
||||
timezone,
|
||||
name,
|
||||
type,
|
||||
]);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Chart, ChartConfiguration, ChartData, Color } from 'chart.js';
|
||||
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import dayjs from 'dayjs';
|
||||
import { MutableRefObject } from 'react';
|
||||
|
||||
@@ -51,7 +50,6 @@ export const getGraphOptions = (
|
||||
isStacked: boolean | undefined,
|
||||
onClickHandler: GraphOnClickHandler | undefined,
|
||||
data: ChartData,
|
||||
timezone: Timezone,
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
): CustomChartOptions => ({
|
||||
animation: {
|
||||
@@ -99,7 +97,7 @@ export const getGraphOptions = (
|
||||
callbacks: {
|
||||
title(context): string | string[] {
|
||||
const date = dayjs(context[0].parsed.x);
|
||||
return date.tz(timezone?.value).format('MMM DD, YYYY, HH:mm:ss');
|
||||
return date.format('MMM DD, YYYY, HH:mm:ss');
|
||||
},
|
||||
label(context): string | string[] {
|
||||
let label = context.dataset.label || '';
|
||||
|
||||
@@ -8,13 +8,13 @@ import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import { unescapeString } from 'container/LogDetailedView/utils';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import dayjs from 'dayjs';
|
||||
import dompurify from 'dompurify';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
// utils
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
// interfaces
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
@@ -174,20 +174,12 @@ function ListLogView({
|
||||
[selectedFields],
|
||||
);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const timestampValue = useMemo(
|
||||
() =>
|
||||
typeof flattenLogData.timestamp === 'string'
|
||||
? formatTimezoneAdjustedTimestamp(
|
||||
flattenLogData.timestamp,
|
||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
||||
)
|
||||
: formatTimezoneAdjustedTimestamp(
|
||||
flattenLogData.timestamp / 1e6,
|
||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
||||
),
|
||||
[flattenLogData.timestamp, formatTimezoneAdjustedTimestamp],
|
||||
? dayjs(flattenLogData.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS')
|
||||
: dayjs(flattenLogData.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'),
|
||||
[flattenLogData.timestamp],
|
||||
);
|
||||
|
||||
const logType = getLogIndicatorType(logData);
|
||||
|
||||
@@ -6,6 +6,7 @@ import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
|
||||
import { unescapeString } from 'container/LogDetailedView/utils';
|
||||
import LogsExplorerContext from 'container/LogsExplorerContext';
|
||||
import dayjs from 'dayjs';
|
||||
import dompurify from 'dompurify';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
@@ -13,7 +14,6 @@ import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { isEmpty, isNumber, isUndefined } from 'lodash-es';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import {
|
||||
KeyboardEvent,
|
||||
MouseEvent,
|
||||
@@ -89,24 +89,16 @@ function RawLogView({
|
||||
attributesText += ' | ';
|
||||
}
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const text = useMemo(() => {
|
||||
const date =
|
||||
typeof data.timestamp === 'string'
|
||||
? formatTimezoneAdjustedTimestamp(data.timestamp, 'YYYY-MM-DD HH:mm:ss.SSS')
|
||||
: formatTimezoneAdjustedTimestamp(
|
||||
data.timestamp / 1e6,
|
||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
||||
);
|
||||
? dayjs(data.timestamp)
|
||||
: dayjs(data.timestamp / 1e6);
|
||||
|
||||
return `${date} | ${attributesText} ${data.body}`;
|
||||
}, [
|
||||
data.timestamp,
|
||||
data.body,
|
||||
attributesText,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
]);
|
||||
return `${date.format('YYYY-MM-DD HH:mm:ss.SSS')} | ${attributesText} ${
|
||||
data.body
|
||||
}`;
|
||||
}, [data.timestamp, data.body, attributesText]);
|
||||
|
||||
const handleClickExpand = useCallback(() => {
|
||||
if (activeContextLog || isReadOnly) return;
|
||||
|
||||
@@ -5,10 +5,10 @@ import { Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import cx from 'classnames';
|
||||
import { unescapeString } from 'container/LogDetailedView/utils';
|
||||
import dayjs from 'dayjs';
|
||||
import dompurify from 'dompurify';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useMemo } from 'react';
|
||||
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
|
||||
|
||||
@@ -44,8 +44,6 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
logs,
|
||||
]);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
||||
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
|
||||
.filter((e) => e.name !== 'id')
|
||||
@@ -83,11 +81,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
render: (field, item): ColumnTypeRender<Record<string, unknown>> => {
|
||||
const date =
|
||||
typeof field === 'string'
|
||||
? formatTimezoneAdjustedTimestamp(field, 'YYYY-MM-DD HH:mm:ss.SSS')
|
||||
: formatTimezoneAdjustedTimestamp(
|
||||
field / 1e6,
|
||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
||||
);
|
||||
? dayjs(field).format('YYYY-MM-DD HH:mm:ss.SSS')
|
||||
: dayjs(field / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
|
||||
return {
|
||||
children: (
|
||||
<div className="table-timestamp">
|
||||
@@ -130,15 +125,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
},
|
||||
...(appendTo === 'end' ? fieldColumns : []),
|
||||
];
|
||||
}, [
|
||||
fields,
|
||||
isListViewPanel,
|
||||
appendTo,
|
||||
isDarkMode,
|
||||
linesPerRow,
|
||||
fontSize,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
]);
|
||||
}, [fields, isListViewPanel, appendTo, isDarkMode, linesPerRow, fontSize]);
|
||||
|
||||
return { columns, dataSource: flattenLogData };
|
||||
};
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { Typography } from 'antd';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
|
||||
import getFormattedDate from 'lib/getFormatedDate';
|
||||
|
||||
function Time({ CreatedOrUpdateTime }: DateProps): JSX.Element {
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
const time = new Date(CreatedOrUpdateTime);
|
||||
const timeString = formatTimezoneAdjustedTimestamp(
|
||||
time,
|
||||
'MM/DD/YYYY hh:mm:ss A (UTC Z)',
|
||||
);
|
||||
const date = getFormattedDate(time);
|
||||
const timeString = `${date} ${convertDateToAmAndPm(time)}`;
|
||||
return <Typography>{timeString}</Typography>;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,5 +21,4 @@ export enum LOCALSTORAGE {
|
||||
THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1',
|
||||
LAST_USED_SAVED_VIEWS = 'LAST_USED_SAVED_VIEWS',
|
||||
SHOW_LOGS_QUICK_FILTERS = 'SHOW_LOGS_QUICK_FILTERS',
|
||||
PREFERRED_TIMEZONE = 'PREFERRED_TIMEZONE',
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const TimezonePickerShortcuts = {
|
||||
CloseTimezonePicker: 'escape',
|
||||
};
|
||||
@@ -7,7 +7,6 @@ import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import history from 'lib/history';
|
||||
import heatmapPlugin from 'lib/uPlotLib/plugins/heatmapPlugin';
|
||||
import timelinePlugin from 'lib/uPlotLib/plugins/timelinePlugin';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
@@ -49,7 +48,6 @@ function HorizontalTimelineGraph({
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const dispatch = useDispatch();
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const options: uPlot.Options = useMemo(
|
||||
() => ({
|
||||
@@ -118,18 +116,8 @@ function HorizontalTimelineGraph({
|
||||
}),
|
||||
]
|
||||
: [],
|
||||
|
||||
tzDate: (timestamp: number): Date =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
|
||||
}),
|
||||
[
|
||||
width,
|
||||
isDarkMode,
|
||||
transformedData.length,
|
||||
urlQuery,
|
||||
dispatch,
|
||||
timezone?.value,
|
||||
],
|
||||
[width, isDarkMode, transformedData.length, urlQuery, dispatch],
|
||||
);
|
||||
return <Uplot data={transformedData} options={options} />;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
useGetAlertRuleDetailsTimelineTable,
|
||||
useTimelineTable,
|
||||
} from 'pages/AlertDetails/hooks';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@@ -40,8 +39,6 @@ function TimelineTable(): JSX.Element {
|
||||
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
if (isError || !isValidRuleId || !ruleId) {
|
||||
return <div>{t('something_went_wrong')}</div>;
|
||||
}
|
||||
@@ -54,7 +51,6 @@ function TimelineTable(): JSX.Element {
|
||||
filters,
|
||||
labels: labels ?? {},
|
||||
setFilters,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
})}
|
||||
dataSource={timelineData}
|
||||
pagination={paginationConfig}
|
||||
|
||||
@@ -8,7 +8,6 @@ import ClientSideQBSearch, {
|
||||
import { ConditionalAlertPopover } from 'container/AlertHistory/AlertPopover/AlertPopover';
|
||||
import { transformKeyValuesToAttributeValuesMap } from 'container/QueryBuilder/filters/utils';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
|
||||
import { Search } from 'lucide-react';
|
||||
import AlertLabels, {
|
||||
AlertLabelsProps,
|
||||
@@ -17,6 +16,7 @@ import AlertState from 'pages/AlertDetails/AlertHeader/AlertState/AlertState';
|
||||
import { useMemo } from 'react';
|
||||
import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { formatEpochTimestamp } from 'utils/timeUtils';
|
||||
|
||||
const transformLabelsToQbKeys = (
|
||||
labels: AlertRuleTimelineTableResponse['labels'],
|
||||
@@ -74,15 +74,10 @@ export const timelineTableColumns = ({
|
||||
filters,
|
||||
labels,
|
||||
setFilters,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
}: {
|
||||
filters: TagFilter;
|
||||
labels: AlertLabelsProps['labels'];
|
||||
setFilters: (filters: TagFilter) => void;
|
||||
formatTimezoneAdjustedTimestamp: (
|
||||
input: TimestampInput,
|
||||
format?: string,
|
||||
) => string;
|
||||
}): ColumnsType<AlertRuleTimelineTableResponse> => [
|
||||
{
|
||||
title: 'STATE',
|
||||
@@ -111,9 +106,7 @@ export const timelineTableColumns = ({
|
||||
dataIndex: 'unixMilli',
|
||||
width: 200,
|
||||
render: (value): JSX.Element => (
|
||||
<div className="alert-rule__created-at">
|
||||
{formatTimezoneAdjustedTimestamp(value, 'MMM D, YYYY ⎯ HH:mm:ss')}
|
||||
</div>
|
||||
<div className="alert-rule__created-at">{formatEpochTimestamp(value)}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -17,15 +17,14 @@ import getAll from 'api/errors/getAll';
|
||||
import getErrorCounts from 'api/errors/getErrorCounts';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import ROUTES from 'constants/routes';
|
||||
import dayjs from 'dayjs';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import history from 'lib/history';
|
||||
import { isUndefined } from 'lodash-es';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueries } from 'react-query';
|
||||
@@ -156,16 +155,8 @@ function AllErrors(): JSX.Element {
|
||||
}
|
||||
}, [data?.error, data?.payload, t, notifications]);
|
||||
|
||||
const getDateValue = (
|
||||
value: string,
|
||||
formatTimezoneAdjustedTimestamp: (
|
||||
input: TimestampInput,
|
||||
format?: string,
|
||||
) => string,
|
||||
): JSX.Element => (
|
||||
<Typography>
|
||||
{formatTimezoneAdjustedTimestamp(value, 'DD/MM/YYYY hh:mm:ss A')}
|
||||
</Typography>
|
||||
const getDateValue = (value: string): JSX.Element => (
|
||||
<Typography>{dayjs(value).format('DD/MM/YYYY HH:mm:ss A')}</Typography>
|
||||
);
|
||||
|
||||
const filterIcon = useCallback(() => <SearchOutlined />, []);
|
||||
@@ -292,8 +283,6 @@ function AllErrors(): JSX.Element {
|
||||
[filterIcon, filterDropdownWrapper],
|
||||
);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const columns: ColumnsType<Exception> = [
|
||||
{
|
||||
title: 'Exception Type',
|
||||
@@ -353,8 +342,7 @@ function AllErrors(): JSX.Element {
|
||||
dataIndex: 'lastSeen',
|
||||
width: 80,
|
||||
key: 'lastSeen',
|
||||
render: (value): JSX.Element =>
|
||||
getDateValue(value, formatTimezoneAdjustedTimestamp),
|
||||
render: getDateValue,
|
||||
sorter: true,
|
||||
defaultSortOrder: getDefaultOrder(
|
||||
getUpdatedParams,
|
||||
@@ -367,8 +355,7 @@ function AllErrors(): JSX.Element {
|
||||
dataIndex: 'firstSeen',
|
||||
width: 80,
|
||||
key: 'firstSeen',
|
||||
render: (value): JSX.Element =>
|
||||
getDateValue(value, formatTimezoneAdjustedTimestamp),
|
||||
render: getDateValue,
|
||||
sorter: true,
|
||||
defaultSortOrder: getDefaultOrder(
|
||||
getUpdatedParams,
|
||||
|
||||
@@ -10,7 +10,6 @@ import getAxes from 'lib/uPlotLib/utils/getAxes';
|
||||
import { getUplotChartDataForAnomalyDetection } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { getYAxisScaleForAnomalyDetection } from 'lib/uPlotLib/utils/getYAxisScale';
|
||||
import { LineChart } from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
@@ -149,12 +148,10 @@ function AnomalyAlertEvaluationView({
|
||||
]
|
||||
: [];
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const options = {
|
||||
width: dimensions.width,
|
||||
height: dimensions.height - 36,
|
||||
plugins: [bandsPlugin, tooltipPlugin(isDarkMode, timezone?.value)],
|
||||
plugins: [bandsPlugin, tooltipPlugin(isDarkMode)],
|
||||
focus: {
|
||||
alpha: 0.3,
|
||||
},
|
||||
@@ -259,8 +256,6 @@ function AnomalyAlertEvaluationView({
|
||||
show: true,
|
||||
},
|
||||
axes: getAxes(isDarkMode, yAxisUnit),
|
||||
tzDate: (timestamp: number): Date =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
|
||||
};
|
||||
|
||||
const handleSearch = (searchText: string): void => {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { themeColors } from 'constants/theme';
|
||||
import dayjs from 'dayjs';
|
||||
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
||||
|
||||
const tooltipPlugin = (
|
||||
isDarkMode: boolean,
|
||||
timezone: string,
|
||||
): { hooks: { init: (u: any) => void } } => {
|
||||
let tooltip: HTMLDivElement;
|
||||
const tooltipLeftOffset = 10;
|
||||
@@ -19,7 +17,7 @@ const tooltipPlugin = (
|
||||
return value.toFixed(3);
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return dayjs(value).tz(timezone).format('MM/DD/YYYY, h:mm:ss A');
|
||||
return value.toLocaleString();
|
||||
}
|
||||
if (value == null) {
|
||||
return 'N/A';
|
||||
|
||||
@@ -6,12 +6,12 @@ import getNextPrevId from 'api/errors/getNextPrevId';
|
||||
import Editor from 'components/Editor';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { getNanoSeconds } from 'container/AllError/utils';
|
||||
import dayjs from 'dayjs';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import history from 'lib/history';
|
||||
import { isUndefined } from 'lodash-es';
|
||||
import { urlKey } from 'pages/ErrorDetails/utils';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
@@ -103,6 +103,8 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const timeStamp = dayjs(errorDetail.timestamp);
|
||||
|
||||
const data: { key: string; value: string }[] = Object.keys(errorDetail)
|
||||
.filter((e) => !keyToExclude.includes(e))
|
||||
.map((key) => ({
|
||||
@@ -134,8 +136,6 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data]);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>{errorDetail.exceptionType}</Typography>
|
||||
@@ -145,12 +145,7 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
||||
<EventContainer>
|
||||
<div>
|
||||
<Typography>Event {errorDetail.errorId}</Typography>
|
||||
<Typography>
|
||||
{formatTimezoneAdjustedTimestamp(
|
||||
errorDetail.timestamp,
|
||||
'DD/MM/YYYY hh:mm:ss A (UTC Z)',
|
||||
)}
|
||||
</Typography>
|
||||
<Typography>{timeStamp.format('MMM DD YYYY hh:mm:ss A')}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Space align="end" direction="horizontal">
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -83,22 +83,16 @@ function BasicInfo({
|
||||
window.open(ROUTES.CHANNELS_NEW, '_blank');
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const hasLoggedEvent = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!channels.loading && isNewRule && !hasLoggedEvent.current) {
|
||||
if (!channels.loading && isNewRule) {
|
||||
logEvent('Alert: New alert creation page visited', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
numberOfChannels: channels?.payload?.length,
|
||||
});
|
||||
hasLoggedEvent.current = true;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [channels.loading]);
|
||||
|
||||
const refetchChannels = async (): Promise<void> => {
|
||||
await channels.refetch();
|
||||
};
|
||||
}, [channels.payload, channels.loading]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -203,7 +197,7 @@ function BasicInfo({
|
||||
{!shouldBroadCastToAllChannels && (
|
||||
<Tooltip
|
||||
title={
|
||||
noChannels && !addNewChannelPermission
|
||||
noChannels
|
||||
? 'No channels. Ask an admin to create a notification channel'
|
||||
: undefined
|
||||
}
|
||||
@@ -218,10 +212,10 @@ function BasicInfo({
|
||||
]}
|
||||
>
|
||||
<ChannelSelect
|
||||
onDropdownOpen={refetchChannels}
|
||||
disabled={shouldBroadCastToAllChannels}
|
||||
disabled={
|
||||
shouldBroadCastToAllChannels || noChannels || !!channels.loading
|
||||
}
|
||||
currentValue={alertDef.preferredChannels}
|
||||
handleCreateNewChannels={handleCreateNewChannels}
|
||||
channels={channels}
|
||||
onSelectChannels={(preferredChannels): void => {
|
||||
setAlertDef({
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Select, Spin } from 'antd';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { Select } from 'antd';
|
||||
import { State } from 'hooks/useFetch';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { PayloadProps } from 'types/api/channels/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { StyledCreateChannelOption, StyledSelect } from './styles';
|
||||
import { StyledSelect } from './styles';
|
||||
|
||||
export interface ChannelSelectProps {
|
||||
disabled?: boolean;
|
||||
currentValue?: string[];
|
||||
onSelectChannels: (s: string[]) => void;
|
||||
onDropdownOpen: () => void;
|
||||
channels: State<PayloadProps | undefined>;
|
||||
handleCreateNewChannels: () => void;
|
||||
}
|
||||
|
||||
function ChannelSelect({
|
||||
disabled,
|
||||
currentValue,
|
||||
onSelectChannels,
|
||||
onDropdownOpen,
|
||||
channels,
|
||||
handleCreateNewChannels,
|
||||
}: ChannelSelectProps): JSX.Element | null {
|
||||
// init namespace for translations
|
||||
const { t } = useTranslation('alerts');
|
||||
@@ -35,10 +26,6 @@ function ChannelSelect({
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const handleChange = (value: string[]): void => {
|
||||
if (value.includes('add-new-channel')) {
|
||||
handleCreateNewChannels();
|
||||
return;
|
||||
}
|
||||
onSelectChannels(value);
|
||||
};
|
||||
|
||||
@@ -48,27 +35,9 @@ function ChannelSelect({
|
||||
description: channels.errorMessage,
|
||||
});
|
||||
}
|
||||
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [addNewChannelPermission] = useComponentPermission(
|
||||
['add_new_channel'],
|
||||
role,
|
||||
);
|
||||
|
||||
const renderOptions = (): ReactNode[] => {
|
||||
const children: ReactNode[] = [];
|
||||
|
||||
if (!channels.loading && addNewChannelPermission) {
|
||||
children.push(
|
||||
<Select.Option key="add-new-channel" value="add-new-channel">
|
||||
<StyledCreateChannelOption>
|
||||
<PlusOutlined />
|
||||
Create a new channel
|
||||
</StyledCreateChannelOption>
|
||||
</Select.Option>,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
channels.loading ||
|
||||
channels.payload === undefined ||
|
||||
@@ -87,7 +56,6 @@ function ChannelSelect({
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledSelect
|
||||
disabled={disabled}
|
||||
@@ -97,12 +65,6 @@ function ChannelSelect({
|
||||
placeholder={t('placeholder_channel_select')}
|
||||
data-testid="alert-channel-select"
|
||||
value={currentValue}
|
||||
notFoundContent={channels.loading && <Spin size="small" />}
|
||||
onDropdownVisibleChange={(open): void => {
|
||||
if (open) {
|
||||
onDropdownOpen();
|
||||
}
|
||||
}}
|
||||
onChange={(value): void => {
|
||||
handleChange(value as string[]);
|
||||
}}
|
||||
|
||||
@@ -4,10 +4,3 @@ import styled from 'styled-components';
|
||||
export const StyledSelect = styled(Select)`
|
||||
border-radius: 4px;
|
||||
`;
|
||||
|
||||
export const StyledCreateChannelOption = styled.div`
|
||||
color: var(--bg-robin-500);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
@@ -25,7 +25,6 @@ import getTimeString from 'lib/getTimeString';
|
||||
import history from 'lib/history';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -36,7 +35,6 @@ import { AlertDef } from 'types/api/alerts/def';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import uPlot from 'uplot';
|
||||
import { getGraphType } from 'utils/getGraphType';
|
||||
import { getSortedSeriesData } from 'utils/getSortedSeriesData';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
@@ -203,8 +201,6 @@ function ChartPreview({
|
||||
[dispatch, location.pathname, urlQuery],
|
||||
);
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
getUPlotChartOptions({
|
||||
@@ -240,9 +236,6 @@ function ChartPreview({
|
||||
softMax: null,
|
||||
softMin: null,
|
||||
panelType: graphType,
|
||||
tzDate: (timestamp: number) =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
|
||||
timezone: timezone?.value,
|
||||
}),
|
||||
[
|
||||
yAxisUnit,
|
||||
@@ -257,7 +250,6 @@ function ChartPreview({
|
||||
optionName,
|
||||
alertDef?.condition.targetUnit,
|
||||
graphType,
|
||||
timezone?.value,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -102,9 +102,9 @@ function RuleOptions({
|
||||
<Select.Option value="4">{t('option_notequal')}</Select.Option>
|
||||
</>
|
||||
)}
|
||||
{/* the value 5 and 6 are reserved for above or equal and below or equal */}
|
||||
|
||||
{ruleType === 'anomaly_rule' && (
|
||||
<Select.Option value="7">{t('option_above_below')}</Select.Option>
|
||||
<Select.Option value="5">{t('option_above_below')}</Select.Option>
|
||||
)}
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Popover, Typography } from 'antd';
|
||||
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||
import dayjs from 'dayjs';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useEffect } from 'react';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
|
||||
@@ -33,17 +32,13 @@ function Span(props: SpanLengthProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { time, timeUnitName } = convertTimeToRelevantUnit(inMsCount);
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.scrollTop = document.documentElement.clientHeight;
|
||||
document.documentElement.scrollLeft = document.documentElement.clientWidth;
|
||||
}, []);
|
||||
|
||||
const getContent = (): JSX.Element => {
|
||||
const timeStamp = dayjs(startTime)
|
||||
.tz(timezone.value)
|
||||
.format('h:mm:ss:SSS A (UTC Z)');
|
||||
const timeStamp = dayjs(startTime).format('h:mm:ss:SSS A');
|
||||
const startTimeInMs = startTime - globalStart;
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -31,7 +31,7 @@ import { AxiosError } from 'axios';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import Tags from 'components/Tags/Tags';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import dayjs from 'dayjs';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
@@ -51,7 +51,6 @@ import {
|
||||
Trash2,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { ChangeEvent, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMutation } from 'react-query';
|
||||
@@ -71,10 +70,7 @@ const { Option } = Select;
|
||||
|
||||
const BYTES = 1073741824;
|
||||
|
||||
// Using any type here because antd's DatePicker expects its own internal Dayjs type
|
||||
// which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc).
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
||||
export const disabledDate = (current: any): boolean =>
|
||||
export const disabledDate = (current: Dayjs): boolean =>
|
||||
// Disable all dates before today
|
||||
current && current < dayjs().endOf('day');
|
||||
|
||||
@@ -397,11 +393,8 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
|
||||
const gbToBytes = (gb: number): number => Math.round(gb * 1024 ** 3);
|
||||
|
||||
const getFormattedTime = (
|
||||
date: string,
|
||||
formatTimezoneAdjustedTimestamp: (date: string, format: string) => string,
|
||||
): string =>
|
||||
formatTimezoneAdjustedTimestamp(date, 'MMM DD,YYYY, hh:mm a (UTC Z)');
|
||||
const getFormattedTime = (date: string): string =>
|
||||
dayjs(date).format('MMM DD,YYYY, hh:mm a');
|
||||
|
||||
const showDeleteLimitModal = (
|
||||
APIKey: IngestionKeyProps,
|
||||
@@ -551,27 +544,17 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const columns: AntDTableProps<IngestionKeyProps>['columns'] = [
|
||||
{
|
||||
title: 'Ingestion Key',
|
||||
key: 'ingestion-key',
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
render: (APIKey: IngestionKeyProps): JSX.Element => {
|
||||
const createdOn = getFormattedTime(
|
||||
APIKey.created_at,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
);
|
||||
const createdOn = getFormattedTime(APIKey.created_at);
|
||||
const formattedDateAndTime =
|
||||
APIKey &&
|
||||
APIKey?.expires_at &&
|
||||
getFormattedTime(APIKey?.expires_at, formatTimezoneAdjustedTimestamp);
|
||||
APIKey && APIKey?.expires_at && getFormattedTime(APIKey?.expires_at);
|
||||
|
||||
const updatedOn = getFormattedTime(
|
||||
APIKey?.updated_at,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
);
|
||||
const updatedOn = getFormattedTime(APIKey?.updated_at);
|
||||
|
||||
const limits: { [key: string]: LimitProps } = {};
|
||||
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
import { Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { License } from 'types/api/licenses/def';
|
||||
|
||||
function ValidityColumn({ value }: { value: string }): JSX.Element {
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
return (
|
||||
<Typography>
|
||||
{formatTimezoneAdjustedTimestamp(value, 'YYYY-MM-DD HH:mm:ss (UTC Z)')}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
function ListLicenses({ licenses }: ListLicensesProps): JSX.Element {
|
||||
const { t } = useTranslation(['licenses']);
|
||||
|
||||
@@ -35,14 +23,12 @@ function ListLicenses({ licenses }: ListLicensesProps): JSX.Element {
|
||||
title: t('column_valid_from'),
|
||||
dataIndex: 'ValidFrom',
|
||||
key: 'valid from',
|
||||
render: (value: string): JSX.Element => ValidityColumn({ value }),
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('column_valid_until'),
|
||||
dataIndex: 'ValidUntil',
|
||||
key: 'valid until',
|
||||
render: (value: string): JSX.Element => ValidityColumn({ value }),
|
||||
width: 80,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -57,7 +57,6 @@ import {
|
||||
// see more: https://github.com/lucide-icons/lucide/issues/94
|
||||
import { handleContactSupport } from 'pages/Integrations/utils';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import {
|
||||
ChangeEvent,
|
||||
Key,
|
||||
@@ -344,13 +343,31 @@ function DashboardsList(): JSX.Element {
|
||||
}
|
||||
}, [state.error, state.value, t, notifications]);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
function getFormattedTime(dashboard: Dashboard, option: string): string {
|
||||
return formatTimezoneAdjustedTimestamp(
|
||||
get(dashboard, option, ''),
|
||||
'MMM D, YYYY ⎯ HH:mm:ss',
|
||||
const timeOptions: Intl.DateTimeFormatOptions = {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
};
|
||||
const formattedTime = new Date(get(dashboard, option, '')).toLocaleTimeString(
|
||||
'en-US',
|
||||
timeOptions,
|
||||
);
|
||||
|
||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
};
|
||||
|
||||
const formattedDate = new Date(get(dashboard, option, '')).toLocaleDateString(
|
||||
'en-US',
|
||||
dateOptions,
|
||||
);
|
||||
|
||||
// Combine time and date
|
||||
return `${formattedDate} ⎯ ${formattedTime}`;
|
||||
}
|
||||
|
||||
const onLastUpdated = (time: string): string => {
|
||||
@@ -393,11 +410,31 @@ function DashboardsList(): JSX.Element {
|
||||
title: 'Dashboards',
|
||||
key: 'dashboard',
|
||||
render: (dashboard: Data, _, index): JSX.Element => {
|
||||
const formattedDateAndTime = formatTimezoneAdjustedTimestamp(
|
||||
dashboard.createdAt,
|
||||
'MMM D, YYYY ⎯ HH:mm:ss',
|
||||
const timeOptions: Intl.DateTimeFormatOptions = {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
};
|
||||
const formattedTime = new Date(dashboard.createdAt).toLocaleTimeString(
|
||||
'en-US',
|
||||
timeOptions,
|
||||
);
|
||||
|
||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
};
|
||||
|
||||
const formattedDate = new Date(dashboard.createdAt).toLocaleDateString(
|
||||
'en-US',
|
||||
dateOptions,
|
||||
);
|
||||
|
||||
// Combine time and date
|
||||
const formattedDateAndTime = `${formattedDate} ⎯ ${formattedTime}`;
|
||||
|
||||
const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${dashboard.id}`;
|
||||
|
||||
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
|
||||
|
||||
@@ -8,12 +8,10 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useQueries, UseQueryResult } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import {
|
||||
getHostQueryPayload,
|
||||
@@ -75,8 +73,6 @@ function NodeMetrics({
|
||||
[queries],
|
||||
);
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
queries.map(({ data }, idx) =>
|
||||
@@ -90,9 +86,6 @@ function NodeMetrics({
|
||||
minTimeScale: start,
|
||||
maxTimeScale: end,
|
||||
verticalLineTimestamp,
|
||||
tzDate: (timestamp: number) =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
|
||||
timezone: timezone?.value,
|
||||
}),
|
||||
),
|
||||
[
|
||||
@@ -103,7 +96,6 @@ function NodeMetrics({
|
||||
start,
|
||||
verticalLineTimestamp,
|
||||
end,
|
||||
timezone?.value,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -8,12 +8,10 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useQueries, UseQueryResult } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { getPodQueryPayload, podWidgetInfo } from './constants';
|
||||
|
||||
@@ -62,7 +60,6 @@ function PodMetrics({
|
||||
() => queries.map(({ data }) => getUPlotChartData(data?.payload)),
|
||||
[queries],
|
||||
);
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
@@ -77,20 +74,9 @@ function PodMetrics({
|
||||
minTimeScale: start,
|
||||
maxTimeScale: end,
|
||||
verticalLineTimestamp,
|
||||
tzDate: (timestamp: number) =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
|
||||
timezone: timezone?.value,
|
||||
}),
|
||||
),
|
||||
[
|
||||
queries,
|
||||
isDarkMode,
|
||||
dimensions,
|
||||
start,
|
||||
end,
|
||||
verticalLineTimestamp,
|
||||
timezone?.value,
|
||||
],
|
||||
[queries, isDarkMode, dimensions, start, verticalLineTimestamp, end],
|
||||
);
|
||||
|
||||
const renderCardContent = (
|
||||
|
||||
@@ -11,8 +11,7 @@ import ROUTES from 'constants/routes';
|
||||
import dompurify from 'dompurify';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
|
||||
@@ -69,8 +68,6 @@ export function TableViewActions(
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
if (record.field === 'body') {
|
||||
const parsedBody = recursiveParseJSON(fieldData.value);
|
||||
if (!isEmpty(parsedBody)) {
|
||||
@@ -103,44 +100,33 @@ export function TableViewActions(
|
||||
);
|
||||
}
|
||||
|
||||
let cleanTimestamp: string;
|
||||
if (record.field === 'timestamp') {
|
||||
cleanTimestamp = fieldData.value.replace(/^["']|["']$/g, '');
|
||||
}
|
||||
|
||||
const renderFieldContent = (): JSX.Element => {
|
||||
const commonStyles: React.CSSProperties = {
|
||||
color: Color.BG_SIENNA_400,
|
||||
whiteSpace: 'pre-wrap',
|
||||
tabSize: 4,
|
||||
};
|
||||
|
||||
switch (record.field) {
|
||||
case 'body':
|
||||
return <span style={commonStyles} dangerouslySetInnerHTML={bodyHtml} />;
|
||||
|
||||
case 'timestamp':
|
||||
return (
|
||||
<span style={commonStyles}>
|
||||
{formatTimezoneAdjustedTimestamp(
|
||||
cleanTimestamp,
|
||||
'MM/DD/YYYY, HH:mm:ss.SSS (UTC Z)',
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<span style={commonStyles}>{removeEscapeCharacters(fieldData.value)}</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx('value-field', isOpen ? 'open-popover' : '')}>
|
||||
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
|
||||
{renderFieldContent()}
|
||||
</CopyClipboardHOC>
|
||||
{record.field === 'body' ? (
|
||||
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
|
||||
<span
|
||||
style={{
|
||||
color: Color.BG_SIENNA_400,
|
||||
whiteSpace: 'pre-wrap',
|
||||
tabSize: 4,
|
||||
}}
|
||||
dangerouslySetInnerHTML={bodyHtml}
|
||||
/>
|
||||
</CopyClipboardHOC>
|
||||
) : (
|
||||
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
|
||||
<span
|
||||
style={{
|
||||
color: Color.BG_SIENNA_400,
|
||||
whiteSpace: 'pre-wrap',
|
||||
tabSize: 4,
|
||||
}}
|
||||
>
|
||||
{removeEscapeCharacters(fieldData.value)}
|
||||
</span>
|
||||
</CopyClipboardHOC>
|
||||
)}
|
||||
|
||||
{!isListViewPanel && (
|
||||
<span className="action-btn">
|
||||
<Tooltip title="Filter for value">
|
||||
|
||||
@@ -15,7 +15,6 @@ import { useLogsData } from 'hooks/useLogsData';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import {
|
||||
Dispatch,
|
||||
HTMLAttributes,
|
||||
@@ -77,12 +76,7 @@ function LogsPanelComponent({
|
||||
});
|
||||
};
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const columns = getLogPanelColumnsList(
|
||||
widget.selectedLogFields,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
);
|
||||
const columns = getLogPanelColumnsList(widget.selectedLogFields);
|
||||
|
||||
const dataLength =
|
||||
queryResponse.data?.payload?.data?.newResult?.data?.result[0]?.list?.length;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import { Typography } from 'antd/lib';
|
||||
import { OPERATORS } from 'constants/queryBuilder';
|
||||
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
|
||||
// import Typography from 'antd/es/typography/Typography';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { ReactNode } from 'react';
|
||||
@@ -14,31 +13,18 @@ import { v4 as uuid } from 'uuid';
|
||||
|
||||
export const getLogPanelColumnsList = (
|
||||
selectedLogFields: Widgets['selectedLogFields'],
|
||||
formatTimezoneAdjustedTimestamp: (
|
||||
input: TimestampInput,
|
||||
format?: string,
|
||||
) => string,
|
||||
): ColumnsType<RowData> => {
|
||||
const initialColumns: ColumnsType<RowData> = [];
|
||||
|
||||
const columns: ColumnsType<RowData> =
|
||||
selectedLogFields?.map((field: IField) => {
|
||||
const { name } = field;
|
||||
|
||||
return {
|
||||
title: name,
|
||||
dataIndex: name,
|
||||
key: name,
|
||||
width: name === 'body' ? 350 : 100,
|
||||
render: (value: ReactNode): JSX.Element => {
|
||||
if (name === 'timestamp') {
|
||||
return (
|
||||
<Typography.Text>
|
||||
{formatTimezoneAdjustedTimestamp(value as string)}
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'body') {
|
||||
return (
|
||||
<Typography.Paragraph ellipsis={{ rows: 1 }} data-testid={name}>
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
.timezone-adaption {
|
||||
padding: 16px;
|
||||
background: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-ink-500);
|
||||
border-radius: 4px;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: var(--bg-vanilla-300);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
&__note {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 7.5px 12px;
|
||||
background: rgba(78, 116, 248, 0.1);
|
||||
border: 1px solid rgba(78, 116, 248, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&__bullet {
|
||||
color: var(--bg-robin-400);
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__note-text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__note-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--bg-robin-400);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
&__note-text-overridden {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 2px;
|
||||
background: rgba(171, 189, 255, 0.04);
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
&__clear-override {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: var(--bg-robin-300);
|
||||
font-size: 12px;
|
||||
line-height: 16px; /* 133.333% */
|
||||
letter-spacing: 0.12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import './TimezoneAdaptation.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Switch } from 'antd';
|
||||
import { Delete } from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
function TimezoneAdaptation(): JSX.Element {
|
||||
const { timezone, browserTimezone, updateTimezone } = useTimezone();
|
||||
|
||||
const isTimezoneOverridden = useMemo(
|
||||
() => timezone?.offset !== browserTimezone.offset,
|
||||
[timezone, browserTimezone],
|
||||
);
|
||||
|
||||
const [isAdaptationEnabled, setIsAdaptationEnabled] = useState(true);
|
||||
|
||||
const getSwitchStyles = (): React.CSSProperties => ({
|
||||
backgroundColor:
|
||||
isAdaptationEnabled && isTimezoneOverridden ? Color.BG_AMBER_400 : undefined,
|
||||
});
|
||||
|
||||
const handleOverrideClear = (): void => {
|
||||
updateTimezone(browserTimezone);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="timezone-adaption">
|
||||
<div className="timezone-adaption__header">
|
||||
<h2 className="timezone-adaption__title">Adapt to my timezone</h2>
|
||||
<Switch
|
||||
checked={isAdaptationEnabled}
|
||||
onChange={setIsAdaptationEnabled}
|
||||
style={getSwitchStyles()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="timezone-adaption__description">
|
||||
Adapt the timestamps shown in the SigNoz console to my active timezone.
|
||||
</p>
|
||||
|
||||
<div className="timezone-adaption__note">
|
||||
<div className="timezone-adaption__note-text-container">
|
||||
<span className="timezone-adaption__bullet">•</span>
|
||||
<span className="timezone-adaption__note-text">
|
||||
{isTimezoneOverridden ? (
|
||||
<>
|
||||
Your current timezone is overridden to
|
||||
<span className="timezone-adaption__note-text-overridden">
|
||||
{timezone?.offset}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
You can override the timezone adaption for any view with the time
|
||||
picker.
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{!!isTimezoneOverridden && (
|
||||
<button
|
||||
type="button"
|
||||
className="timezone-adaption__clear-override"
|
||||
onClick={handleOverrideClear}
|
||||
>
|
||||
<Delete height={12} width={12} color={Color.BG_ROBIN_300} />
|
||||
Clear override
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TimezoneAdaptation;
|
||||
@@ -7,7 +7,6 @@ import { LogOut, Moon, Sun } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import Password from './Password';
|
||||
import TimezoneAdaptation from './TimezoneAdaptation/TimezoneAdaptation';
|
||||
import UserInfo from './UserInfo';
|
||||
|
||||
function MySettings(): JSX.Element {
|
||||
@@ -79,8 +78,6 @@ function MySettings(): JSX.Element {
|
||||
<Password />
|
||||
</div>
|
||||
|
||||
<TimezoneAdaptation />
|
||||
|
||||
<Button
|
||||
className="flexBtn"
|
||||
onClick={(): void => Logout()}
|
||||
|
||||
@@ -14,9 +14,7 @@ import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { cloneDeep, isEqual, isUndefined } from 'lodash-es';
|
||||
import _noop from 'lodash-es/noop';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import uPlot from 'uplot';
|
||||
import { getSortedSeriesData } from 'utils/getSortedSeriesData';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
@@ -107,8 +105,6 @@ function UplotPanelWrapper({
|
||||
}
|
||||
}, [graphVisibility, hiddenGraph, widget.panelTypes, widget?.stackedBarChart]);
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
getUPlotChartOptions({
|
||||
@@ -132,9 +128,6 @@ function UplotPanelWrapper({
|
||||
hiddenGraph,
|
||||
setHiddenGraph,
|
||||
customTooltipElement,
|
||||
tzDate: (timestamp: number) =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
|
||||
timezone: timezone?.value,
|
||||
}),
|
||||
[
|
||||
widget?.id,
|
||||
@@ -157,7 +150,6 @@ function UplotPanelWrapper({
|
||||
currentQuery,
|
||||
hiddenGraph,
|
||||
customTooltipElement,
|
||||
timezone?.value,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
function DeploymentTime(deployTime: string): JSX.Element {
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
return (
|
||||
<span>
|
||||
{formatTimezoneAdjustedTimestamp(
|
||||
deployTime,
|
||||
'MMMM DD, YYYY hh:mm A (UTC Z)',
|
||||
)}{' '}
|
||||
</span>
|
||||
<span>{dayjs(deployTime).locale('en').format('MMMM DD, YYYY hh:mm A')}</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import './styles.scss';
|
||||
import { ExpandAltOutlined } from '@ant-design/icons';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import dayjs from 'dayjs';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
function LogsList({ logs }: LogsListProps): JSX.Element {
|
||||
@@ -18,17 +18,12 @@ function LogsList({ logs }: LogsListProps): JSX.Element {
|
||||
|
||||
const makeLogDetailsHandler = (log: ILog) => (): void => onSetActiveLog(log);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
return (
|
||||
<div className="logs-preview-list-container">
|
||||
{logs.map((log) => (
|
||||
<div key={log.id} className="logs-preview-list-item">
|
||||
<div className="logs-preview-list-item-timestamp">
|
||||
{formatTimezoneAdjustedTimestamp(
|
||||
log.timestamp,
|
||||
'MMM DD HH:mm:ss.SSS (UTC Z)',
|
||||
)}
|
||||
{dayjs(log.timestamp).format('MMM DD HH:mm:ss.SSS')}
|
||||
</div>
|
||||
<div className="logs-preview-list-item-body">{log.body}</div>
|
||||
<div
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import dayjs from 'dayjs';
|
||||
import React from 'react';
|
||||
import { PipelineData, ProcessorData } from 'types/api/pipeline/def';
|
||||
|
||||
@@ -6,18 +6,13 @@ import { PipelineIndexIcon } from '../AddNewProcessor/styles';
|
||||
import { ColumnDataStyle, ListDataStyle, ProcessorIndexIcon } from '../styles';
|
||||
import PipelineFilterSummary from './PipelineFilterSummary';
|
||||
|
||||
function CreatedAtComponent({ record }: { record: Record }): JSX.Element {
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
return (
|
||||
<ColumnDataStyle>
|
||||
{formatTimezoneAdjustedTimestamp(record, 'MMMM DD, YYYY hh:mm A (UTC Z)')}
|
||||
</ColumnDataStyle>
|
||||
);
|
||||
}
|
||||
|
||||
const componentMap: ComponentMap = {
|
||||
orderId: ({ record }) => <PipelineIndexIcon>{record}</PipelineIndexIcon>,
|
||||
createdAt: ({ record }) => <CreatedAtComponent record={record} />,
|
||||
createdAt: ({ record }) => (
|
||||
<ColumnDataStyle>
|
||||
{dayjs(record).locale('en').format('MMMM DD, YYYY hh:mm A')}
|
||||
</ColumnDataStyle>
|
||||
),
|
||||
id: ({ record }) => <ProcessorIndexIcon>{record}</ProcessorIndexIcon>,
|
||||
name: ({ record }) => <ListDataStyle>{record}</ListDataStyle>,
|
||||
filter: ({ record }) => <PipelineFilterSummary filter={record} />,
|
||||
|
||||
@@ -17,7 +17,6 @@ import history from 'lib/history';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
@@ -27,7 +26,6 @@ import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import uPlot from 'uplot';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
import { Container } from './styles';
|
||||
@@ -120,8 +118,6 @@ function TimeSeriesView({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const chartOptions = getUPlotChartOptions({
|
||||
onDragSelect,
|
||||
yAxisUnit: yAxisUnit || '',
|
||||
@@ -135,9 +131,6 @@ function TimeSeriesView({
|
||||
maxTimeScale,
|
||||
softMax: null,
|
||||
softMin: null,
|
||||
tzDate: (timestamp: number) =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
|
||||
timezone: timezone?.value,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -28,7 +28,6 @@ import getTimeString from 'lib/getTimeString';
|
||||
import history from 'lib/history';
|
||||
import { isObject } from 'lodash-es';
|
||||
import { Check, Copy, Info, Send, Undo } from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
@@ -614,8 +613,6 @@ function DateTimeSelection({
|
||||
);
|
||||
};
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
return (
|
||||
<div className="date-time-selector">
|
||||
{showResetButton && selectedTime !== defaultRelativeTime && (
|
||||
@@ -667,8 +664,8 @@ function DateTimeSelection({
|
||||
setIsValidteRelativeTime(isValid);
|
||||
}}
|
||||
selectedValue={getInputLabel(
|
||||
dayjs(minTime / 1000000).tz(timezone.value),
|
||||
dayjs(maxTime / 1000000).tz(timezone.value),
|
||||
dayjs(minTime / 1000000),
|
||||
dayjs(maxTime / 1000000),
|
||||
selectedTime,
|
||||
)}
|
||||
data-testid="dropDown"
|
||||
|
||||
@@ -24,7 +24,6 @@ import history from 'lib/history';
|
||||
import { map } from 'lodash-es';
|
||||
import { PanelRight } from 'lucide-react';
|
||||
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ITraceForest, PayloadProps } from 'types/api/trace/getTraceItem';
|
||||
import { getSpanTreeMetadata } from 'utils/getSpanTreeMetadata';
|
||||
@@ -140,8 +139,6 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
return (
|
||||
<StyledRow styledclass={[Flex({ flex: 1 })]}>
|
||||
<StyledCol flex="auto" styledclass={styles.leftContainer}>
|
||||
@@ -198,9 +195,7 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
||||
{isGlobalTimeVisible && (
|
||||
<styles.TimeStampContainer flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`}>
|
||||
<Typography>
|
||||
{dayjs(traceMetaData.globalStart)
|
||||
.tz(timezone.value)
|
||||
.format('hh:mm:ss a (UTC Z) MM/DD')}
|
||||
{dayjs(traceMetaData.globalStart).format('hh:mm:ss a MM/DD')}
|
||||
</Typography>
|
||||
</styles.TimeStampContainer>
|
||||
)}
|
||||
|
||||
@@ -15,7 +15,6 @@ import useDragColumns from 'hooks/useDragColumns';
|
||||
import { getDraggedColumns } from 'hooks/useDragColumns/utils';
|
||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -98,15 +97,10 @@ function ListView({ isFilterApplied }: ListViewProps): JSX.Element {
|
||||
queryTableDataResult,
|
||||
]);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const updatedColumns = getListColumns(
|
||||
options?.selectColumns || [],
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
);
|
||||
const updatedColumns = getListColumns(options?.selectColumns || []);
|
||||
return getDraggedColumns(updatedColumns, draggedColumns);
|
||||
}, [options?.selectColumns, formatTimezoneAdjustedTimestamp, draggedColumns]);
|
||||
}, [options?.selectColumns, draggedColumns]);
|
||||
|
||||
const transformedQueryTableData = useMemo(
|
||||
() => transformDataWithDate(queryTableData) || [],
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ColumnsType } from 'antd/es/table';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util';
|
||||
import { formUrlParams } from 'container/TraceDetail/utils';
|
||||
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
|
||||
import dayjs from 'dayjs';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
@@ -40,10 +40,6 @@ export const getTraceLink = (record: RowData): string =>
|
||||
|
||||
export const getListColumns = (
|
||||
selectedColumns: BaseAutocompleteData[],
|
||||
formatTimezoneAdjustedTimestamp: (
|
||||
input: TimestampInput,
|
||||
format?: string,
|
||||
) => string | number,
|
||||
): ColumnsType<RowData> => {
|
||||
const initialColumns: ColumnsType<RowData> = [
|
||||
{
|
||||
@@ -54,8 +50,8 @@ export const getListColumns = (
|
||||
render: (value, item): JSX.Element => {
|
||||
const date =
|
||||
typeof value === 'string'
|
||||
? formatTimezoneAdjustedTimestamp(value, 'YYYY-MM-DD HH:mm:ss.SSS')
|
||||
: formatTimezoneAdjustedTimestamp(value / 1e6, 'YYYY-MM-DD HH:mm:ss.SSS');
|
||||
? dayjs(value).format('YYYY-MM-DD HH:mm:ss.SSS')
|
||||
: dayjs(value / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
|
||||
return (
|
||||
<BlockLink to={getTraceLink(item)}>
|
||||
<Typography.Text>{date}</Typography.Text>
|
||||
|
||||
@@ -15,7 +15,6 @@ import { Pagination } from 'hooks/queryPagination';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import history from 'lib/history';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import {
|
||||
Dispatch,
|
||||
HTMLAttributes,
|
||||
@@ -50,12 +49,7 @@ function TracesTableComponent({
|
||||
}));
|
||||
}, [pagination, setRequestData]);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const columns = getListColumns(
|
||||
widget.selectedTracesFields || [],
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
);
|
||||
const columns = getListColumns(widget.selectedTracesFields || []);
|
||||
|
||||
const dataLength =
|
||||
queryResponse.data?.payload?.data?.newResult?.data?.result[0]?.list?.length;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Tag, Typography } from 'antd';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
|
||||
import getFormattedDate from 'lib/getFormatedDate';
|
||||
import { Alerts } from 'types/api/alerts/getTriggered';
|
||||
|
||||
import Status from '../TableComponents/AlertStatus';
|
||||
import { TableCell, TableRow } from './styles';
|
||||
|
||||
function ExapandableRow({ allAlerts }: ExapandableRowProps): JSX.Element {
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
return (
|
||||
<>
|
||||
{allAlerts.map((alert) => {
|
||||
@@ -40,9 +40,8 @@ function ExapandableRow({ allAlerts }: ExapandableRowProps): JSX.Element {
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Typography>{`${formatTimezoneAdjustedTimestamp(
|
||||
<Typography>{`${getFormattedDate(formatedDate)} ${convertDateToAmAndPm(
|
||||
formatedDate,
|
||||
'MM/DD/YYYY hh:mm:ss A (UTC Z)',
|
||||
)}`}</Typography>
|
||||
</TableCell>
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import { ColumnsType } from 'antd/lib/table';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import LabelColumn from 'components/TableRenderer/LabelColumn';
|
||||
import AlertStatus from 'container/TriggeredAlerts/TableComponents/AlertStatus';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
|
||||
import getFormattedDate from 'lib/getFormatedDate';
|
||||
import { Alerts } from 'types/api/alerts/getTriggered';
|
||||
|
||||
import { Value } from './Filter';
|
||||
@@ -15,7 +16,6 @@ function NoFilterTable({
|
||||
selectedFilter,
|
||||
}: NoFilterTableProps): JSX.Element {
|
||||
const filteredAlerts = FilterAlerts(allAlerts, selectedFilter);
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
// need to add the filter
|
||||
const columns: ColumnsType<Alerts> = [
|
||||
@@ -83,12 +83,15 @@ function NoFilterTable({
|
||||
width: 100,
|
||||
sorter: (a, b): number =>
|
||||
new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime(),
|
||||
render: (date): JSX.Element => (
|
||||
<Typography>{`${formatTimezoneAdjustedTimestamp(
|
||||
date,
|
||||
'MM/DD/YYYY hh:mm:ss A (UTC Z)',
|
||||
)}`}</Typography>
|
||||
),
|
||||
render: (date): JSX.Element => {
|
||||
const formatedDate = new Date(date);
|
||||
|
||||
return (
|
||||
<Typography>{`${getFormattedDate(formatedDate)} ${convertDateToAmAndPm(
|
||||
formatedDate,
|
||||
)}`}</Typography>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
function useFetch<PayloadProps, FunctionParams>(
|
||||
@@ -10,7 +10,7 @@ function useFetch<PayloadProps, FunctionParams>(
|
||||
(arg0: any): Promise<SuccessResponse<PayloadProps> | ErrorResponse>;
|
||||
},
|
||||
param?: FunctionParams,
|
||||
): State<PayloadProps | undefined> & { refetch: () => Promise<void> } {
|
||||
): State<PayloadProps | undefined> {
|
||||
const [state, setStates] = useState<State<PayloadProps | undefined>>({
|
||||
loading: true,
|
||||
success: null,
|
||||
@@ -19,28 +19,37 @@ function useFetch<PayloadProps, FunctionParams>(
|
||||
payload: undefined,
|
||||
});
|
||||
|
||||
const fetchData = useCallback(async (): Promise<void> => {
|
||||
setStates((prev) => ({ ...prev, loading: true }));
|
||||
try {
|
||||
const response = await functions(param);
|
||||
const loadingRef = useRef(0);
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
setStates({
|
||||
loading: false,
|
||||
error: false,
|
||||
success: true,
|
||||
payload: response.payload,
|
||||
errorMessage: '',
|
||||
});
|
||||
} else {
|
||||
setStates({
|
||||
loading: false,
|
||||
error: true,
|
||||
success: false,
|
||||
payload: undefined,
|
||||
errorMessage: response.error as string,
|
||||
});
|
||||
}
|
||||
useEffect(() => {
|
||||
try {
|
||||
(async (): Promise<void> => {
|
||||
if (state.loading) {
|
||||
const response = await functions(param);
|
||||
|
||||
if (loadingRef.current === 0) {
|
||||
loadingRef.current = 1;
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
setStates({
|
||||
loading: false,
|
||||
error: false,
|
||||
success: true,
|
||||
payload: response.payload,
|
||||
errorMessage: '',
|
||||
});
|
||||
} else {
|
||||
setStates({
|
||||
loading: false,
|
||||
error: true,
|
||||
success: false,
|
||||
payload: undefined,
|
||||
errorMessage: response.error as string,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
} catch (error) {
|
||||
setStates({
|
||||
payload: undefined,
|
||||
@@ -50,16 +59,13 @@ function useFetch<PayloadProps, FunctionParams>(
|
||||
errorMessage: error as string,
|
||||
});
|
||||
}
|
||||
}, [functions, param]);
|
||||
|
||||
// Initial fetch
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
return (): void => {
|
||||
loadingRef.current = 1;
|
||||
};
|
||||
}, [functions, param, state.loading]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
refetch: fetchData,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import dayjs from 'dayjs';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
// Initialize dayjs plugins
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
// Types
|
||||
export type TimestampInput = string | number | Date;
|
||||
interface CacheEntry {
|
||||
value: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// Constants
|
||||
const CACHE_SIZE_LIMIT = 1000;
|
||||
const CACHE_CLEANUP_PERCENTAGE = 0.5; // Remove 50% when limit is reached
|
||||
|
||||
function useTimezoneFormatter({
|
||||
userTimezone,
|
||||
}: {
|
||||
userTimezone: Timezone;
|
||||
}): {
|
||||
formatTimezoneAdjustedTimestamp: (
|
||||
input: TimestampInput,
|
||||
format?: string,
|
||||
) => string;
|
||||
} {
|
||||
// Initialize cache using useMemo to persist between renders
|
||||
const cache = useMemo(() => new Map<string, CacheEntry>(), []);
|
||||
|
||||
// Clear cache when timezone changes
|
||||
useEffect(() => {
|
||||
cache.clear();
|
||||
}, [cache, userTimezone]);
|
||||
|
||||
const clearCacheEntries = useCallback(() => {
|
||||
if (cache.size <= CACHE_SIZE_LIMIT) return;
|
||||
|
||||
// Sort entries by timestamp (oldest first)
|
||||
const sortedEntries = Array.from(cache.entries()).sort(
|
||||
(a, b) => a[1].timestamp - b[1].timestamp,
|
||||
);
|
||||
|
||||
// Calculate how many entries to remove (50% or overflow, whichever is larger)
|
||||
const entriesToRemove = Math.max(
|
||||
Math.floor(cache.size * CACHE_CLEANUP_PERCENTAGE),
|
||||
cache.size - CACHE_SIZE_LIMIT,
|
||||
);
|
||||
|
||||
// Remove oldest entries
|
||||
sortedEntries.slice(0, entriesToRemove).forEach(([key]) => cache.delete(key));
|
||||
}, [cache]);
|
||||
|
||||
/**
|
||||
* Formats a timestamp with the user's timezone and caches the result
|
||||
* @param {TimestampInput} input - The timestamp to format (string, number, or Date)
|
||||
* @param {string} [format='YYYY-MM-DD HH:mm:ss'] - The desired output format
|
||||
* @returns {string} The formatted timestamp string in the user's timezone
|
||||
* @example
|
||||
* // Input: UTC timestamp
|
||||
* // User timezone: 'UTC - 4'
|
||||
* // Returns: "2024-03-14 15:30:00"
|
||||
* formatTimezoneAdjustedTimestamp('2024-03-14T19:30:00Z')
|
||||
*/
|
||||
const formatTimezoneAdjustedTimestamp = useCallback(
|
||||
(input: TimestampInput, format = 'YYYY-MM-DD HH:mm:ss'): string => {
|
||||
const timestamp = dayjs(input).valueOf();
|
||||
const cacheKey = `${timestamp}_${userTimezone?.value}`;
|
||||
|
||||
// Check cache first
|
||||
const cachedValue = cache.get(cacheKey);
|
||||
if (cachedValue) {
|
||||
return cachedValue.value;
|
||||
}
|
||||
// Format timestamp
|
||||
const formattedValue = dayjs(input).tz(userTimezone?.value).format(format);
|
||||
|
||||
// Update cache
|
||||
cache.set(cacheKey, {
|
||||
value: formattedValue,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// Clear expired entries and enforce size limit
|
||||
if (cache.size > CACHE_SIZE_LIMIT) {
|
||||
clearCacheEntries();
|
||||
}
|
||||
|
||||
return formattedValue;
|
||||
},
|
||||
[cache, clearCacheEntries, userTimezone],
|
||||
);
|
||||
|
||||
return { formatTimezoneAdjustedTimestamp };
|
||||
}
|
||||
|
||||
export default useTimezoneFormatter;
|
||||
@@ -7,7 +7,6 @@ import { AxiosError } from 'axios';
|
||||
import { ThemeProvider } from 'hooks/useDarkMode';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import posthog from 'posthog-js';
|
||||
import TimezoneProvider from 'providers/Timezone';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
@@ -70,16 +69,14 @@ if (container) {
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<HelmetProvider>
|
||||
<ThemeProvider>
|
||||
<TimezoneProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<AppRoutes />
|
||||
</Provider>
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
)}
|
||||
</QueryClientProvider>
|
||||
</TimezoneProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<AppRoutes />
|
||||
</Provider>
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
)}
|
||||
</QueryClientProvider>
|
||||
</ThemeProvider>
|
||||
</HelmetProvider>
|
||||
</Sentry.ErrorBoundary>,
|
||||
|
||||
@@ -55,8 +55,6 @@ export interface GetUPlotChartOptions {
|
||||
>;
|
||||
customTooltipElement?: HTMLDivElement;
|
||||
verticalLineTimestamp?: number;
|
||||
tzDate?: (timestamp: number) => Date;
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
/** the function converts series A , series B , series C to
|
||||
@@ -160,8 +158,6 @@ export const getUPlotChartOptions = ({
|
||||
setHiddenGraph,
|
||||
customTooltipElement,
|
||||
verticalLineTimestamp,
|
||||
tzDate,
|
||||
timezone,
|
||||
}: GetUPlotChartOptions): uPlot.Options => {
|
||||
const timeScaleProps = getXAxisScale(minTimeScale, maxTimeScale);
|
||||
|
||||
@@ -200,7 +196,6 @@ export const getUPlotChartOptions = ({
|
||||
fill: (): string => '#fff',
|
||||
},
|
||||
},
|
||||
tzDate,
|
||||
padding: [16, 16, 8, 8],
|
||||
bands,
|
||||
scales: {
|
||||
@@ -227,7 +222,6 @@ export const getUPlotChartOptions = ({
|
||||
stackBarChart,
|
||||
isDarkMode,
|
||||
customTooltipElement,
|
||||
timezone,
|
||||
}),
|
||||
onClickPlugin({
|
||||
onClick: onClickHandler,
|
||||
|
||||
@@ -46,7 +46,6 @@ const generateTooltipContent = (
|
||||
isHistogramGraphs?: boolean,
|
||||
isMergedSeries?: boolean,
|
||||
stackBarChart?: boolean,
|
||||
timezone?: string,
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
): HTMLElement => {
|
||||
const container = document.createElement('div');
|
||||
@@ -70,13 +69,9 @@ const generateTooltipContent = (
|
||||
series.forEach((item, index) => {
|
||||
if (index === 0) {
|
||||
if (isBillingUsageGraphs) {
|
||||
tooltipTitle = dayjs(data[0][idx] * 1000)
|
||||
.tz(timezone)
|
||||
.format('MMM DD YYYY');
|
||||
tooltipTitle = dayjs(data[0][idx] * 1000).format('MMM DD YYYY');
|
||||
} else {
|
||||
tooltipTitle = dayjs(data[0][idx] * 1000)
|
||||
.tz(timezone)
|
||||
.format('MMM DD YYYY h:mm:ss A');
|
||||
tooltipTitle = dayjs(data[0][idx] * 1000).format('MMM DD YYYY HH:mm:ss');
|
||||
}
|
||||
} else if (item.show) {
|
||||
const {
|
||||
@@ -228,7 +223,6 @@ type ToolTipPluginProps = {
|
||||
stackBarChart?: boolean;
|
||||
isDarkMode: boolean;
|
||||
customTooltipElement?: HTMLDivElement;
|
||||
timezone?: string;
|
||||
};
|
||||
|
||||
const tooltipPlugin = ({
|
||||
@@ -240,7 +234,6 @@ const tooltipPlugin = ({
|
||||
stackBarChart,
|
||||
isDarkMode,
|
||||
customTooltipElement,
|
||||
timezone,
|
||||
}: // eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
ToolTipPluginProps): any => {
|
||||
let over: HTMLElement;
|
||||
@@ -307,7 +300,6 @@ ToolTipPluginProps): any => {
|
||||
isHistogramGraphs,
|
||||
isMergedSeries,
|
||||
stackBarChart,
|
||||
timezone,
|
||||
);
|
||||
if (customTooltipElement) {
|
||||
content.appendChild(customTooltipElement);
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
Trash2,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -208,8 +207,6 @@ function SaveView(): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
|
||||
const columns: TableProps<ViewProps>['columns'] = [
|
||||
{
|
||||
title: 'Save View',
|
||||
@@ -221,10 +218,31 @@ function SaveView(): JSX.Element {
|
||||
bgColor = extraData.color;
|
||||
}
|
||||
|
||||
const formattedDateAndTime = formatTimezoneAdjustedTimestamp(
|
||||
view.createdAt,
|
||||
'HH:mm:ss ⎯ MMM D, YYYY (UTC Z)',
|
||||
const timeOptions: Intl.DateTimeFormatOptions = {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
};
|
||||
const formattedTime = new Date(view.createdAt).toLocaleTimeString(
|
||||
'en-US',
|
||||
timeOptions,
|
||||
);
|
||||
|
||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
};
|
||||
|
||||
const formattedDate = new Date(view.createdAt).toLocaleDateString(
|
||||
'en-US',
|
||||
dateOptions,
|
||||
);
|
||||
|
||||
// Combine time and date
|
||||
const formattedDateAndTime = `${formattedTime} ⎯ ${formattedDate}`;
|
||||
|
||||
const isEditDeleteSupported = allowedRoles.includes(role as string);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import {
|
||||
getBrowserTimezone,
|
||||
getTimezoneObjectByTimezoneString,
|
||||
Timezone,
|
||||
} from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import useTimezoneFormatter, {
|
||||
TimestampInput,
|
||||
} from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
interface TimezoneContextType {
|
||||
timezone: Timezone;
|
||||
browserTimezone: Timezone;
|
||||
updateTimezone: (timezone: Timezone) => void;
|
||||
formatTimezoneAdjustedTimestamp: (
|
||||
input: TimestampInput,
|
||||
format?: string,
|
||||
) => string;
|
||||
}
|
||||
|
||||
const TimezoneContext = createContext<TimezoneContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
function TimezoneProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const getStoredTimezoneValue = (): Timezone | null => {
|
||||
try {
|
||||
const timezoneValue = localStorage.getItem(LOCALSTORAGE.PREFERRED_TIMEZONE);
|
||||
if (timezoneValue) {
|
||||
return getTimezoneObjectByTimezoneString(timezoneValue);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error reading timezone from localStorage:', error);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const setStoredTimezoneValue = (value: string): void => {
|
||||
try {
|
||||
localStorage.setItem(LOCALSTORAGE.PREFERRED_TIMEZONE, value);
|
||||
} catch (error) {
|
||||
console.error('Error saving timezone to localStorage:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const browserTimezone = useMemo(() => getBrowserTimezone(), []);
|
||||
|
||||
const [timezone, setTimezone] = useState<Timezone>(
|
||||
getStoredTimezoneValue() ?? browserTimezone,
|
||||
);
|
||||
|
||||
const updateTimezone = useCallback((timezone: Timezone): void => {
|
||||
if (!timezone.value) return;
|
||||
|
||||
// TODO(shaheer): replace this with user preferences API
|
||||
setStoredTimezoneValue(timezone.value);
|
||||
setTimezone(timezone);
|
||||
}, []);
|
||||
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezoneFormatter({
|
||||
userTimezone: timezone,
|
||||
});
|
||||
|
||||
const value = React.useMemo(
|
||||
() => ({
|
||||
timezone,
|
||||
browserTimezone,
|
||||
updateTimezone,
|
||||
formatTimezoneAdjustedTimestamp,
|
||||
}),
|
||||
[timezone, browserTimezone, updateTimezone, formatTimezoneAdjustedTimestamp],
|
||||
);
|
||||
|
||||
return (
|
||||
<TimezoneContext.Provider value={value}>{children}</TimezoneContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useTimezone = (): TimezoneContextType => {
|
||||
const context = useContext(TimezoneContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useTimezone must be used within a TimezoneProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export default TimezoneProvider;
|
||||
@@ -4764,11 +4764,6 @@
|
||||
d3-time-format "4.1.0"
|
||||
internmap "2.0.3"
|
||||
|
||||
"@vvo/tzdb@6.149.0":
|
||||
version "6.149.0"
|
||||
resolved "https://registry.yarnpkg.com/@vvo/tzdb/-/tzdb-6.149.0.tgz#e4fcca3c49b90d5910a8679267540cb532809075"
|
||||
integrity sha512-d68+oW1TE60Ho9FlCDO5Ks4suk6hp5umjNIrtWytVB0B/X0/P1T9yWdnH7EhNb2fx1CQE+MM1qmLUGzT+QAqdw==
|
||||
|
||||
"@webassemblyjs/ast@1.12.1":
|
||||
version "1.12.1"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb"
|
||||
|
||||
2
go.mod
2
go.mod
@@ -8,7 +8,7 @@ require (
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.25.0
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
|
||||
github.com/SigNoz/signoz-otel-collector v0.111.5
|
||||
github.com/SigNoz/signoz-otel-collector v0.111.8
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974
|
||||
github.com/antonmedv/expr v1.15.3
|
||||
|
||||
4
go.sum
4
go.sum
@@ -70,8 +70,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/prometheus v1.12.0 h1:+BXeIHyMOOWWa+xjhJ+x80JFva7r1WzWIfIhQ5PUmIE=
|
||||
github.com/SigNoz/prometheus v1.12.0/go.mod h1:EqNM27OwmPfqMUk+E+XG1L9rfDFcyXnzzDrg0EPOfxA=
|
||||
github.com/SigNoz/signoz-otel-collector v0.111.5 h1:kLpJSv9U46doA+89nfUvTLcNb6WbIxiMAtNlTNL88ZE=
|
||||
github.com/SigNoz/signoz-otel-collector v0.111.5/go.mod h1:/nyVFDiEz/QBfyqekB3zRwstZ/KSIB85qgV9NnzAtig=
|
||||
github.com/SigNoz/signoz-otel-collector v0.111.8 h1:t3V3Ahue2ucryRdHvqz33zRCPGQ86xkAsx9J23ZNPk0=
|
||||
github.com/SigNoz/signoz-otel-collector v0.111.8/go.mod h1:/nyVFDiEz/QBfyqekB3zRwstZ/KSIB85qgV9NnzAtig=
|
||||
github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc=
|
||||
github.com/SigNoz/zap_otlp v0.1.0/go.mod h1:lcHvbDbRgvDnPxo9lDlaL1JK2PyOyouP/C3ynnYIvyo=
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 h1:PKVgdf83Yw+lZJbFtNGBgqXiXNf3+kOXW2qZ7Ms7OaY=
|
||||
|
||||
@@ -766,6 +766,307 @@ func buildFilterArrayQuery(_ context.Context, excludeMap map[string]struct{}, pa
|
||||
return args
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetSpanFilters(ctx context.Context, queryParams *model.SpanFilterParams) (*model.SpanFiltersResponse, *model.ApiError) {
|
||||
|
||||
var query string
|
||||
excludeMap := make(map[string]struct{})
|
||||
for _, e := range queryParams.Exclude {
|
||||
if e == constants.OperationRequest {
|
||||
excludeMap[constants.OperationDB] = struct{}{}
|
||||
continue
|
||||
}
|
||||
excludeMap[e] = struct{}{}
|
||||
}
|
||||
|
||||
args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))}
|
||||
if len(queryParams.TraceID) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args)
|
||||
}
|
||||
if len(queryParams.ServiceName) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpRoute) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpRoute, constants.HttpRoute, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpHost) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpHost, constants.HttpHost, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpMethod) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpMethod, constants.HttpMethod, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpUrl) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpUrl, constants.HttpUrl, &query, args)
|
||||
}
|
||||
if len(queryParams.Operation) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Operation, constants.OperationDB, &query, args)
|
||||
}
|
||||
if len(queryParams.RPCMethod) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.RPCMethod, constants.RPCMethod, &query, args)
|
||||
}
|
||||
if len(queryParams.ResponseStatusCode) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ResponseStatusCode, constants.ResponseStatusCode, &query, args)
|
||||
}
|
||||
|
||||
if len(queryParams.MinDuration) != 0 {
|
||||
query = query + " AND durationNano >= @durationNanoMin"
|
||||
args = append(args, clickhouse.Named("durationNanoMin", queryParams.MinDuration))
|
||||
}
|
||||
if len(queryParams.MaxDuration) != 0 {
|
||||
query = query + " AND durationNano <= @durationNanoMax"
|
||||
args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration))
|
||||
}
|
||||
|
||||
if len(queryParams.SpanKind) != 0 {
|
||||
query = query + " AND kind = @kind"
|
||||
args = append(args, clickhouse.Named("kind", queryParams.SpanKind))
|
||||
}
|
||||
|
||||
query = getStatusFilters(query, queryParams.Status, excludeMap)
|
||||
|
||||
traceFilterReponse := model.SpanFiltersResponse{
|
||||
Status: map[string]uint64{},
|
||||
Duration: map[string]uint64{},
|
||||
ServiceName: map[string]uint64{},
|
||||
Operation: map[string]uint64{},
|
||||
ResponseStatusCode: map[string]uint64{},
|
||||
RPCMethod: map[string]uint64{},
|
||||
HttpMethod: map[string]uint64{},
|
||||
HttpUrl: map[string]uint64{},
|
||||
HttpRoute: map[string]uint64{},
|
||||
HttpHost: map[string]uint64{},
|
||||
}
|
||||
|
||||
for _, e := range queryParams.GetFilters {
|
||||
switch e {
|
||||
case constants.TraceID:
|
||||
continue
|
||||
case constants.ServiceName:
|
||||
finalQuery := fmt.Sprintf("SELECT serviceName, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
finalQuery += " GROUP BY serviceName"
|
||||
var dBResponse []model.DBResponseServiceName
|
||||
err := r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
for _, service := range dBResponse {
|
||||
if service.ServiceName != "" {
|
||||
traceFilterReponse.ServiceName[service.ServiceName] = service.Count
|
||||
}
|
||||
}
|
||||
case constants.HttpRoute:
|
||||
finalQuery := fmt.Sprintf("SELECT httpRoute, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
finalQuery += " GROUP BY httpRoute"
|
||||
var dBResponse []model.DBResponseHttpRoute
|
||||
err := r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
for _, service := range dBResponse {
|
||||
if service.HttpRoute != "" {
|
||||
traceFilterReponse.HttpRoute[service.HttpRoute] = service.Count
|
||||
}
|
||||
}
|
||||
case constants.HttpUrl:
|
||||
finalQuery := fmt.Sprintf("SELECT httpUrl, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
finalQuery += " GROUP BY httpUrl"
|
||||
var dBResponse []model.DBResponseHttpUrl
|
||||
err := r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
for _, service := range dBResponse {
|
||||
if service.HttpUrl != "" {
|
||||
traceFilterReponse.HttpUrl[service.HttpUrl] = service.Count
|
||||
}
|
||||
}
|
||||
case constants.HttpMethod:
|
||||
finalQuery := fmt.Sprintf("SELECT httpMethod, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
finalQuery += " GROUP BY httpMethod"
|
||||
var dBResponse []model.DBResponseHttpMethod
|
||||
err := r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
for _, service := range dBResponse {
|
||||
if service.HttpMethod != "" {
|
||||
traceFilterReponse.HttpMethod[service.HttpMethod] = service.Count
|
||||
}
|
||||
}
|
||||
case constants.HttpHost:
|
||||
finalQuery := fmt.Sprintf("SELECT httpHost, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
finalQuery += " GROUP BY httpHost"
|
||||
var dBResponse []model.DBResponseHttpHost
|
||||
err := r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
for _, service := range dBResponse {
|
||||
if service.HttpHost != "" {
|
||||
traceFilterReponse.HttpHost[service.HttpHost] = service.Count
|
||||
}
|
||||
}
|
||||
case constants.OperationRequest:
|
||||
finalQuery := fmt.Sprintf("SELECT name, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
finalQuery += " GROUP BY name"
|
||||
var dBResponse []model.DBResponseOperation
|
||||
err := r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
for _, service := range dBResponse {
|
||||
if service.Operation != "" {
|
||||
traceFilterReponse.Operation[service.Operation] = service.Count
|
||||
}
|
||||
}
|
||||
case constants.Status:
|
||||
finalQuery := fmt.Sprintf("SELECT COUNT(*) as numTotal FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU AND hasError = true", r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
var dBResponse []model.DBResponseTotal
|
||||
err := r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
|
||||
finalQuery2 := fmt.Sprintf("SELECT COUNT(*) as numTotal FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU AND hasError = false", r.TraceDB, r.indexTable)
|
||||
finalQuery2 += query
|
||||
var dBResponse2 []model.DBResponseTotal
|
||||
err = r.db.Select(ctx, &dBResponse2, finalQuery2, args...)
|
||||
zap.L().Info(finalQuery2)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
if len(dBResponse) > 0 && len(dBResponse2) > 0 {
|
||||
traceFilterReponse.Status = map[string]uint64{"ok": dBResponse2[0].NumTotal, "error": dBResponse[0].NumTotal}
|
||||
} else if len(dBResponse) > 0 {
|
||||
traceFilterReponse.Status = map[string]uint64{"ok": 0, "error": dBResponse[0].NumTotal}
|
||||
} else if len(dBResponse2) > 0 {
|
||||
traceFilterReponse.Status = map[string]uint64{"ok": dBResponse2[0].NumTotal, "error": 0}
|
||||
} else {
|
||||
traceFilterReponse.Status = map[string]uint64{"ok": 0, "error": 0}
|
||||
}
|
||||
case constants.Duration:
|
||||
err := r.featureFlags.CheckFeature(constants.DurationSort)
|
||||
durationSortEnabled := err == nil
|
||||
finalQuery := ""
|
||||
if !durationSortEnabled {
|
||||
// if duration sort is not enabled, we need to get the min and max duration from the index table
|
||||
finalQuery = fmt.Sprintf("SELECT min(durationNano) as min, max(durationNano) as max FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
var dBResponse []model.DBResponseMinMax
|
||||
err = r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
if len(dBResponse) > 0 {
|
||||
traceFilterReponse.Duration = map[string]uint64{"minDuration": dBResponse[0].Min, "maxDuration": dBResponse[0].Max}
|
||||
}
|
||||
} else {
|
||||
// when duration sort is enabled, we need to get the min and max duration from the duration table
|
||||
finalQuery = fmt.Sprintf("SELECT durationNano as numTotal FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.durationTable)
|
||||
finalQuery += query
|
||||
finalQuery += " ORDER BY durationNano LIMIT 1"
|
||||
var dBResponse []model.DBResponseTotal
|
||||
err = r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
|
||||
finalQuery = fmt.Sprintf("SELECT durationNano as numTotal FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.durationTable)
|
||||
finalQuery += query
|
||||
finalQuery += " ORDER BY durationNano DESC LIMIT 1"
|
||||
var dBResponse2 []model.DBResponseTotal
|
||||
err = r.db.Select(ctx, &dBResponse2, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
if len(dBResponse) > 0 {
|
||||
traceFilterReponse.Duration["minDuration"] = dBResponse[0].NumTotal
|
||||
}
|
||||
if len(dBResponse2) > 0 {
|
||||
traceFilterReponse.Duration["maxDuration"] = dBResponse2[0].NumTotal
|
||||
}
|
||||
}
|
||||
case constants.RPCMethod:
|
||||
finalQuery := fmt.Sprintf("SELECT rpcMethod, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
finalQuery += " GROUP BY rpcMethod"
|
||||
var dBResponse []model.DBResponseRPCMethod
|
||||
err := r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
for _, service := range dBResponse {
|
||||
if service.RPCMethod != "" {
|
||||
traceFilterReponse.RPCMethod[service.RPCMethod] = service.Count
|
||||
}
|
||||
}
|
||||
|
||||
case constants.ResponseStatusCode:
|
||||
finalQuery := fmt.Sprintf("SELECT responseStatusCode, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
finalQuery += " GROUP BY responseStatusCode"
|
||||
var dBResponse []model.DBResponseStatusCodeMethod
|
||||
err := r.db.Select(ctx, &dBResponse, finalQuery, args...)
|
||||
zap.L().Info(finalQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query: %s", err)}
|
||||
}
|
||||
for _, service := range dBResponse {
|
||||
if service.ResponseStatusCode != "" {
|
||||
traceFilterReponse.ResponseStatusCode[service.ResponseStatusCode] = service.Count
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("filter type: %s not supported", e)}
|
||||
}
|
||||
}
|
||||
|
||||
return &traceFilterReponse, nil
|
||||
}
|
||||
|
||||
func getStatusFilters(query string, statusParams []string, excludeMap map[string]struct{}) string {
|
||||
|
||||
// status can only be two and if both are selected than they are equivalent to none selected
|
||||
@@ -787,6 +1088,140 @@ func getStatusFilters(query string, statusParams []string, excludeMap map[string
|
||||
return query
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetFilteredSpans(ctx context.Context, queryParams *model.GetFilteredSpansParams) (*model.GetFilterSpansResponse, *model.ApiError) {
|
||||
|
||||
queryTable := fmt.Sprintf("%s.%s", r.TraceDB, r.indexTable)
|
||||
|
||||
excludeMap := make(map[string]struct{})
|
||||
for _, e := range queryParams.Exclude {
|
||||
if e == constants.OperationRequest {
|
||||
excludeMap[constants.OperationDB] = struct{}{}
|
||||
continue
|
||||
}
|
||||
excludeMap[e] = struct{}{}
|
||||
}
|
||||
|
||||
var query string
|
||||
args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))}
|
||||
if len(queryParams.TraceID) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args)
|
||||
}
|
||||
if len(queryParams.ServiceName) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpRoute) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpRoute, constants.HttpRoute, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpHost) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpHost, constants.HttpHost, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpMethod) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpMethod, constants.HttpMethod, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpUrl) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpUrl, constants.HttpUrl, &query, args)
|
||||
}
|
||||
if len(queryParams.Operation) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Operation, constants.OperationDB, &query, args)
|
||||
}
|
||||
if len(queryParams.RPCMethod) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.RPCMethod, constants.RPCMethod, &query, args)
|
||||
}
|
||||
|
||||
if len(queryParams.ResponseStatusCode) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ResponseStatusCode, constants.ResponseStatusCode, &query, args)
|
||||
}
|
||||
|
||||
if len(queryParams.MinDuration) != 0 {
|
||||
query = query + " AND durationNano >= @durationNanoMin"
|
||||
args = append(args, clickhouse.Named("durationNanoMin", queryParams.MinDuration))
|
||||
}
|
||||
if len(queryParams.MaxDuration) != 0 {
|
||||
query = query + " AND durationNano <= @durationNanoMax"
|
||||
args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration))
|
||||
}
|
||||
query = getStatusFilters(query, queryParams.Status, excludeMap)
|
||||
|
||||
if len(queryParams.SpanKind) != 0 {
|
||||
query = query + " AND kind = @kind"
|
||||
args = append(args, clickhouse.Named("kind", queryParams.SpanKind))
|
||||
}
|
||||
|
||||
// create TagQuery from TagQueryParams
|
||||
tags := createTagQueryFromTagQueryParams(queryParams.Tags)
|
||||
subQuery, argsSubQuery, errStatus := buildQueryWithTagParams(ctx, tags)
|
||||
query += subQuery
|
||||
args = append(args, argsSubQuery...)
|
||||
if errStatus != nil {
|
||||
return nil, errStatus
|
||||
}
|
||||
|
||||
if len(queryParams.OrderParam) != 0 {
|
||||
if queryParams.OrderParam == constants.Duration {
|
||||
queryTable = fmt.Sprintf("%s.%s", r.TraceDB, r.durationTable)
|
||||
if queryParams.Order == constants.Descending {
|
||||
query = query + " ORDER BY durationNano DESC"
|
||||
}
|
||||
if queryParams.Order == constants.Ascending {
|
||||
query = query + " ORDER BY durationNano ASC"
|
||||
}
|
||||
} else if queryParams.OrderParam == constants.Timestamp {
|
||||
projectionOptQuery := "SET allow_experimental_projection_optimization = 1"
|
||||
err := r.db.Exec(ctx, projectionOptQuery)
|
||||
|
||||
zap.L().Info(projectionOptQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query")}
|
||||
}
|
||||
if queryParams.Order == constants.Descending {
|
||||
query = query + " ORDER BY timestamp DESC"
|
||||
}
|
||||
if queryParams.Order == constants.Ascending {
|
||||
query = query + " ORDER BY timestamp ASC"
|
||||
}
|
||||
}
|
||||
}
|
||||
if queryParams.Limit > 0 {
|
||||
query = query + " LIMIT @limit"
|
||||
args = append(args, clickhouse.Named("limit", queryParams.Limit))
|
||||
}
|
||||
|
||||
if queryParams.Offset > 0 {
|
||||
query = query + " OFFSET @offset"
|
||||
args = append(args, clickhouse.Named("offset", queryParams.Offset))
|
||||
}
|
||||
|
||||
var getFilterSpansResponseItems []model.GetFilterSpansResponseItem
|
||||
|
||||
baseQuery := fmt.Sprintf("SELECT timestamp, spanID, traceID, serviceName, name, durationNano, httpMethod, rpcMethod, responseStatusCode FROM %s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryTable)
|
||||
baseQuery += query
|
||||
err := r.db.Select(ctx, &getFilterSpansResponseItems, baseQuery, args...)
|
||||
// Fill status and method
|
||||
for i, e := range getFilterSpansResponseItems {
|
||||
if e.RPCMethod != "" {
|
||||
getFilterSpansResponseItems[i].Method = e.RPCMethod
|
||||
} else {
|
||||
getFilterSpansResponseItems[i].Method = e.HttpMethod
|
||||
}
|
||||
}
|
||||
|
||||
zap.L().Info(baseQuery)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query")}
|
||||
}
|
||||
|
||||
getFilterSpansResponse := model.GetFilterSpansResponse{
|
||||
Spans: getFilterSpansResponseItems,
|
||||
TotalSpans: 1000,
|
||||
}
|
||||
|
||||
return &getFilterSpansResponse, nil
|
||||
}
|
||||
|
||||
func createTagQueryFromTagQueryParams(queryParams []model.TagQueryParam) []model.TagQuery {
|
||||
tags := []model.TagQuery{}
|
||||
for _, tag := range queryParams {
|
||||
@@ -944,6 +1379,87 @@ func addExistsOperator(item model.TagQuery, tagMapType string, not bool) (string
|
||||
return fmt.Sprintf(" AND %s (%s)", notStr, strings.Join(tagOperatorPair, " OR ")), args
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetTagFilters(ctx context.Context, queryParams *model.TagFilterParams) (*model.TagFilters, *model.ApiError) {
|
||||
|
||||
excludeMap := make(map[string]struct{})
|
||||
for _, e := range queryParams.Exclude {
|
||||
if e == constants.OperationRequest {
|
||||
excludeMap[constants.OperationDB] = struct{}{}
|
||||
continue
|
||||
}
|
||||
excludeMap[e] = struct{}{}
|
||||
}
|
||||
|
||||
var query string
|
||||
args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))}
|
||||
if len(queryParams.TraceID) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args)
|
||||
}
|
||||
if len(queryParams.ServiceName) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpRoute) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpRoute, constants.HttpRoute, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpHost) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpHost, constants.HttpHost, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpMethod) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpMethod, constants.HttpMethod, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpUrl) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpUrl, constants.HttpUrl, &query, args)
|
||||
}
|
||||
if len(queryParams.Operation) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Operation, constants.OperationDB, &query, args)
|
||||
}
|
||||
if len(queryParams.RPCMethod) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.RPCMethod, constants.RPCMethod, &query, args)
|
||||
}
|
||||
if len(queryParams.ResponseStatusCode) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ResponseStatusCode, constants.ResponseStatusCode, &query, args)
|
||||
}
|
||||
if len(queryParams.MinDuration) != 0 {
|
||||
query = query + " AND durationNano >= @durationNanoMin"
|
||||
args = append(args, clickhouse.Named("durationNanoMin", queryParams.MinDuration))
|
||||
}
|
||||
if len(queryParams.MaxDuration) != 0 {
|
||||
query = query + " AND durationNano <= @durationNanoMax"
|
||||
args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration))
|
||||
}
|
||||
if len(queryParams.SpanKind) != 0 {
|
||||
query = query + " AND kind = @kind"
|
||||
args = append(args, clickhouse.Named("kind", queryParams.SpanKind))
|
||||
}
|
||||
|
||||
query = getStatusFilters(query, queryParams.Status, excludeMap)
|
||||
|
||||
tagFilters := []model.TagFilters{}
|
||||
|
||||
// Alternative finalQuery := fmt.Sprintf(`SELECT DISTINCT arrayJoin(tagMap.keys) as tagKeys FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU`, r.TraceDB, r.indexTable)
|
||||
finalQuery := fmt.Sprintf(`SELECT groupUniqArrayArray(mapKeys(stringTagMap)) as stringTagKeys, groupUniqArrayArray(mapKeys(numberTagMap)) as numberTagKeys, groupUniqArrayArray(mapKeys(boolTagMap)) as boolTagKeys FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU`, r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
err := r.db.Select(ctx, &tagFilters, finalQuery, args...)
|
||||
|
||||
zap.L().Info(query)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query")}
|
||||
}
|
||||
tagFiltersResult := model.TagFilters{
|
||||
StringTagKeys: make([]string, 0),
|
||||
NumberTagKeys: make([]string, 0),
|
||||
BoolTagKeys: make([]string, 0),
|
||||
}
|
||||
if len(tagFilters) != 0 {
|
||||
tagFiltersResult.StringTagKeys = excludeTags(ctx, tagFilters[0].StringTagKeys)
|
||||
tagFiltersResult.NumberTagKeys = excludeTags(ctx, tagFilters[0].NumberTagKeys)
|
||||
tagFiltersResult.BoolTagKeys = excludeTags(ctx, tagFilters[0].BoolTagKeys)
|
||||
}
|
||||
return &tagFiltersResult, nil
|
||||
}
|
||||
|
||||
func excludeTags(_ context.Context, tags []string) []string {
|
||||
excludedTagsMap := map[string]bool{
|
||||
"http.code": true,
|
||||
@@ -967,6 +1483,102 @@ func excludeTags(_ context.Context, tags []string) []string {
|
||||
return newTags
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetTagValues(ctx context.Context, queryParams *model.TagFilterParams) (*model.TagValues, *model.ApiError) {
|
||||
|
||||
if queryParams.TagKey.Type == model.TagTypeNumber {
|
||||
return &model.TagValues{
|
||||
NumberTagValues: make([]float64, 0),
|
||||
StringTagValues: make([]string, 0),
|
||||
BoolTagValues: make([]bool, 0),
|
||||
}, nil
|
||||
} else if queryParams.TagKey.Type == model.TagTypeBool {
|
||||
return &model.TagValues{
|
||||
NumberTagValues: make([]float64, 0),
|
||||
StringTagValues: make([]string, 0),
|
||||
BoolTagValues: []bool{true, false},
|
||||
}, nil
|
||||
}
|
||||
|
||||
excludeMap := make(map[string]struct{})
|
||||
for _, e := range queryParams.Exclude {
|
||||
if e == constants.OperationRequest {
|
||||
excludeMap[constants.OperationDB] = struct{}{}
|
||||
continue
|
||||
}
|
||||
excludeMap[e] = struct{}{}
|
||||
}
|
||||
|
||||
var query string
|
||||
args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))}
|
||||
if len(queryParams.TraceID) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args)
|
||||
}
|
||||
if len(queryParams.ServiceName) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpRoute) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpRoute, constants.HttpRoute, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpHost) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpHost, constants.HttpHost, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpMethod) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpMethod, constants.HttpMethod, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpUrl) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpUrl, constants.HttpUrl, &query, args)
|
||||
}
|
||||
if len(queryParams.Operation) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Operation, constants.OperationDB, &query, args)
|
||||
}
|
||||
if len(queryParams.MinDuration) != 0 {
|
||||
query = query + " AND durationNano >= @durationNanoMin"
|
||||
args = append(args, clickhouse.Named("durationNanoMin", queryParams.MinDuration))
|
||||
}
|
||||
if len(queryParams.MaxDuration) != 0 {
|
||||
query = query + " AND durationNano <= @durationNanoMax"
|
||||
args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration))
|
||||
}
|
||||
if len(queryParams.SpanKind) != 0 {
|
||||
query = query + " AND kind = @kind"
|
||||
args = append(args, clickhouse.Named("kind", queryParams.SpanKind))
|
||||
}
|
||||
|
||||
query = getStatusFilters(query, queryParams.Status, excludeMap)
|
||||
|
||||
tagValues := []model.TagValues{}
|
||||
|
||||
finalQuery := fmt.Sprintf(`SELECT groupArray(DISTINCT stringTagMap[@key]) as stringTagValues FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU`, r.TraceDB, r.indexTable)
|
||||
finalQuery += query
|
||||
finalQuery += " LIMIT @limit"
|
||||
|
||||
args = append(args, clickhouse.Named("key", queryParams.TagKey.Key))
|
||||
args = append(args, clickhouse.Named("limit", queryParams.Limit))
|
||||
err := r.db.Select(ctx, &tagValues, finalQuery, args...)
|
||||
|
||||
zap.L().Info(query)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query")}
|
||||
}
|
||||
|
||||
cleanedTagValues := model.TagValues{
|
||||
StringTagValues: []string{},
|
||||
NumberTagValues: []float64{},
|
||||
BoolTagValues: []bool{},
|
||||
}
|
||||
if len(tagValues) == 0 {
|
||||
return &cleanedTagValues, nil
|
||||
}
|
||||
for _, e := range tagValues[0].StringTagValues {
|
||||
if e != "" {
|
||||
cleanedTagValues.StringTagValues = append(cleanedTagValues.StringTagValues, e)
|
||||
}
|
||||
}
|
||||
return &cleanedTagValues, nil
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetTopOperations(ctx context.Context, queryParams *model.GetTopOperationsParams) (*[]model.TopOperationsItem, *model.ApiError) {
|
||||
|
||||
namedArgs := []interface{}{
|
||||
@@ -1211,6 +1823,185 @@ func (r *ClickHouseReader) GetDependencyGraph(ctx context.Context, queryParams *
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetFilteredSpansAggregates(ctx context.Context, queryParams *model.GetFilteredSpanAggregatesParams) (*model.GetFilteredSpansAggregatesResponse, *model.ApiError) {
|
||||
|
||||
excludeMap := make(map[string]struct{})
|
||||
for _, e := range queryParams.Exclude {
|
||||
if e == constants.OperationRequest {
|
||||
excludeMap[constants.OperationDB] = struct{}{}
|
||||
continue
|
||||
}
|
||||
excludeMap[e] = struct{}{}
|
||||
}
|
||||
|
||||
SpanAggregatesDBResponseItems := []model.SpanAggregatesDBResponseItem{}
|
||||
|
||||
aggregation_query := ""
|
||||
if queryParams.Dimension == "duration" {
|
||||
switch queryParams.AggregationOption {
|
||||
case "p50":
|
||||
aggregation_query = " quantile(0.50)(durationNano) as float64Value "
|
||||
case "p95":
|
||||
aggregation_query = " quantile(0.95)(durationNano) as float64Value "
|
||||
case "p90":
|
||||
aggregation_query = " quantile(0.90)(durationNano) as float64Value "
|
||||
case "p99":
|
||||
aggregation_query = " quantile(0.99)(durationNano) as float64Value "
|
||||
case "max":
|
||||
aggregation_query = " max(durationNano) as value "
|
||||
case "min":
|
||||
aggregation_query = " min(durationNano) as value "
|
||||
case "avg":
|
||||
aggregation_query = " avg(durationNano) as float64Value "
|
||||
case "sum":
|
||||
aggregation_query = " sum(durationNano) as value "
|
||||
default:
|
||||
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("aggregate type: %s not supported", queryParams.AggregationOption)}
|
||||
}
|
||||
} else if queryParams.Dimension == "calls" {
|
||||
aggregation_query = " count(*) as value "
|
||||
}
|
||||
|
||||
args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))}
|
||||
|
||||
var query string
|
||||
var customStr []string
|
||||
_, columnExists := constants.GroupByColMap[queryParams.GroupBy]
|
||||
// Using %s for groupBy params as it can be a custom column and custom columns are not supported by clickhouse-go yet:
|
||||
// issue link: https://github.com/ClickHouse/clickhouse-go/issues/870
|
||||
if queryParams.GroupBy != "" && columnExists {
|
||||
query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, %s as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, queryParams.GroupBy, aggregation_query, r.TraceDB, r.indexTable)
|
||||
args = append(args, clickhouse.Named("groupByVar", queryParams.GroupBy))
|
||||
} else if queryParams.GroupBy != "" {
|
||||
customStr = strings.Split(queryParams.GroupBy, ".(")
|
||||
if len(customStr) < 2 {
|
||||
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("GroupBy: %s not supported", queryParams.GroupBy)}
|
||||
}
|
||||
if customStr[1] == string(model.TagTypeString)+")" {
|
||||
query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, stringTagMap['%s'] as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, customStr[0], aggregation_query, r.TraceDB, r.indexTable)
|
||||
} else if customStr[1] == string(model.TagTypeNumber)+")" {
|
||||
query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, toString(numberTagMap['%s']) as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, customStr[0], aggregation_query, r.TraceDB, r.indexTable)
|
||||
} else if customStr[1] == string(model.TagTypeBool)+")" {
|
||||
query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, toString(boolTagMap['%s']) as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, customStr[0], aggregation_query, r.TraceDB, r.indexTable)
|
||||
} else {
|
||||
// return error for unsupported group by
|
||||
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("GroupBy: %s not supported", queryParams.GroupBy)}
|
||||
}
|
||||
} else {
|
||||
query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.TraceDB, r.indexTable)
|
||||
}
|
||||
|
||||
if len(queryParams.TraceID) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args)
|
||||
}
|
||||
if len(queryParams.ServiceName) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpRoute) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpRoute, constants.HttpRoute, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpHost) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpHost, constants.HttpHost, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpMethod) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpMethod, constants.HttpMethod, &query, args)
|
||||
}
|
||||
if len(queryParams.HttpUrl) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpUrl, constants.HttpUrl, &query, args)
|
||||
}
|
||||
if len(queryParams.Operation) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Operation, constants.OperationDB, &query, args)
|
||||
}
|
||||
if len(queryParams.RPCMethod) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.RPCMethod, constants.RPCMethod, &query, args)
|
||||
}
|
||||
if len(queryParams.ResponseStatusCode) > 0 {
|
||||
args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ResponseStatusCode, constants.ResponseStatusCode, &query, args)
|
||||
}
|
||||
if len(queryParams.MinDuration) != 0 {
|
||||
query = query + " AND durationNano >= @durationNanoMin"
|
||||
args = append(args, clickhouse.Named("durationNanoMin", queryParams.MinDuration))
|
||||
}
|
||||
if len(queryParams.MaxDuration) != 0 {
|
||||
query = query + " AND durationNano <= @durationNanoMax"
|
||||
args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration))
|
||||
}
|
||||
query = getStatusFilters(query, queryParams.Status, excludeMap)
|
||||
|
||||
if len(queryParams.SpanKind) != 0 {
|
||||
query = query + " AND kind = @kind"
|
||||
args = append(args, clickhouse.Named("kind", queryParams.SpanKind))
|
||||
}
|
||||
// create TagQuery from TagQueryParams
|
||||
tags := createTagQueryFromTagQueryParams(queryParams.Tags)
|
||||
subQuery, argsSubQuery, errStatus := buildQueryWithTagParams(ctx, tags)
|
||||
query += subQuery
|
||||
args = append(args, argsSubQuery...)
|
||||
|
||||
if errStatus != nil {
|
||||
return nil, errStatus
|
||||
}
|
||||
|
||||
if queryParams.GroupBy != "" && columnExists {
|
||||
query = query + fmt.Sprintf(" GROUP BY time, %s as groupBy ORDER BY time", queryParams.GroupBy)
|
||||
} else if queryParams.GroupBy != "" {
|
||||
if customStr[1] == string(model.TagTypeString)+")" {
|
||||
query = query + fmt.Sprintf(" GROUP BY time, stringTagMap['%s'] as groupBy ORDER BY time", customStr[0])
|
||||
} else if customStr[1] == string(model.TagTypeNumber)+")" {
|
||||
query = query + fmt.Sprintf(" GROUP BY time, toString(numberTagMap['%s']) as groupBy ORDER BY time", customStr[0])
|
||||
} else if customStr[1] == string(model.TagTypeBool)+")" {
|
||||
query = query + fmt.Sprintf(" GROUP BY time, toString(boolTagMap['%s']) as groupBy ORDER BY time", customStr[0])
|
||||
}
|
||||
} else {
|
||||
query = query + " GROUP BY time ORDER BY time"
|
||||
}
|
||||
|
||||
err := r.db.Select(ctx, &SpanAggregatesDBResponseItems, query, args...)
|
||||
|
||||
zap.L().Info(query)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing sql query")}
|
||||
}
|
||||
|
||||
GetFilteredSpansAggregatesResponse := model.GetFilteredSpansAggregatesResponse{
|
||||
Items: map[int64]model.SpanAggregatesResponseItem{},
|
||||
}
|
||||
|
||||
for i := range SpanAggregatesDBResponseItems {
|
||||
if SpanAggregatesDBResponseItems[i].Value == 0 {
|
||||
SpanAggregatesDBResponseItems[i].Value = uint64(SpanAggregatesDBResponseItems[i].Float64Value)
|
||||
}
|
||||
SpanAggregatesDBResponseItems[i].Timestamp = int64(SpanAggregatesDBResponseItems[i].Time.UnixNano())
|
||||
SpanAggregatesDBResponseItems[i].FloatValue = float32(SpanAggregatesDBResponseItems[i].Value)
|
||||
if queryParams.AggregationOption == "rate_per_sec" {
|
||||
SpanAggregatesDBResponseItems[i].FloatValue = float32(SpanAggregatesDBResponseItems[i].Value) / float32(queryParams.StepSeconds)
|
||||
}
|
||||
if responseElement, ok := GetFilteredSpansAggregatesResponse.Items[SpanAggregatesDBResponseItems[i].Timestamp]; !ok {
|
||||
if queryParams.GroupBy != "" && SpanAggregatesDBResponseItems[i].GroupBy != "" {
|
||||
GetFilteredSpansAggregatesResponse.Items[SpanAggregatesDBResponseItems[i].Timestamp] = model.SpanAggregatesResponseItem{
|
||||
Timestamp: SpanAggregatesDBResponseItems[i].Timestamp,
|
||||
GroupBy: map[string]float32{SpanAggregatesDBResponseItems[i].GroupBy: SpanAggregatesDBResponseItems[i].FloatValue},
|
||||
}
|
||||
} else if queryParams.GroupBy == "" {
|
||||
GetFilteredSpansAggregatesResponse.Items[SpanAggregatesDBResponseItems[i].Timestamp] = model.SpanAggregatesResponseItem{
|
||||
Timestamp: SpanAggregatesDBResponseItems[i].Timestamp,
|
||||
Value: SpanAggregatesDBResponseItems[i].FloatValue,
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if queryParams.GroupBy != "" && SpanAggregatesDBResponseItems[i].GroupBy != "" {
|
||||
responseElement.GroupBy[SpanAggregatesDBResponseItems[i].GroupBy] = SpanAggregatesDBResponseItems[i].FloatValue
|
||||
}
|
||||
GetFilteredSpansAggregatesResponse.Items[SpanAggregatesDBResponseItems[i].Timestamp] = responseElement
|
||||
}
|
||||
}
|
||||
|
||||
return &GetFilteredSpansAggregatesResponse, nil
|
||||
}
|
||||
|
||||
func getLocalTableName(tableName string) string {
|
||||
|
||||
tableNameSplit := strings.Split(tableName, ".")
|
||||
|
||||
@@ -526,6 +526,12 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *AuthMiddleware) {
|
||||
router.HandleFunc("/api/v1/configs", am.OpenAccess(aH.getConfigs)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/health", am.OpenAccess(aH.getHealth)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/getSpanFilters", am.ViewAccess(aH.getSpanFilters)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/getTagFilters", am.ViewAccess(aH.getTagFilters)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/getFilteredSpans", am.ViewAccess(aH.getFilteredSpans)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/getFilteredSpans/aggregates", am.ViewAccess(aH.getFilteredSpanAggregates)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/getTagValues", am.ViewAccess(aH.getTagValues)).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/api/v1/listErrors", am.ViewAccess(aH.listErrors)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/countErrors", am.ViewAccess(aH.countErrors)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/errorFromErrorID", am.ViewAccess(aH.getErrorFromErrorID)).Methods(http.MethodGet)
|
||||
@@ -1841,6 +1847,86 @@ func (aH *APIHandler) getErrorFromGroupID(w http.ResponseWriter, r *http.Request
|
||||
aH.WriteJSON(w, r, result)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getSpanFilters(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
query, err := parseSpanFilterRequestBody(r)
|
||||
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
result, apiErr := aH.reader.GetSpanFilters(r.Context(), query)
|
||||
|
||||
if apiErr != nil && aH.HandleError(w, apiErr.Err, http.StatusInternalServerError) {
|
||||
return
|
||||
}
|
||||
|
||||
aH.WriteJSON(w, r, result)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getFilteredSpans(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
query, err := parseFilteredSpansRequest(r, aH)
|
||||
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
result, apiErr := aH.reader.GetFilteredSpans(r.Context(), query)
|
||||
|
||||
if apiErr != nil && aH.HandleError(w, apiErr.Err, http.StatusInternalServerError) {
|
||||
return
|
||||
}
|
||||
|
||||
aH.WriteJSON(w, r, result)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getFilteredSpanAggregates(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
query, err := parseFilteredSpanAggregatesRequest(r)
|
||||
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
result, apiErr := aH.reader.GetFilteredSpansAggregates(r.Context(), query)
|
||||
|
||||
if apiErr != nil && aH.HandleError(w, apiErr.Err, http.StatusInternalServerError) {
|
||||
return
|
||||
}
|
||||
|
||||
aH.WriteJSON(w, r, result)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getTagFilters(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
query, err := parseTagFilterRequest(r)
|
||||
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
result, apiErr := aH.reader.GetTagFilters(r.Context(), query)
|
||||
|
||||
if apiErr != nil && aH.HandleError(w, apiErr.Err, http.StatusInternalServerError) {
|
||||
return
|
||||
}
|
||||
|
||||
aH.WriteJSON(w, r, result)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getTagValues(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
query, err := parseTagValueRequest(r)
|
||||
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
result, apiErr := aH.reader.GetTagValues(r.Context(), query)
|
||||
|
||||
if apiErr != nil && aH.HandleError(w, apiErr.Err, http.StatusInternalServerError) {
|
||||
return
|
||||
}
|
||||
|
||||
aH.WriteJSON(w, r, result)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) setTTL(w http.ResponseWriter, r *http.Request) {
|
||||
ttlParams, err := parseTTLParams(r)
|
||||
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||
|
||||
@@ -142,7 +142,7 @@ func enrichFieldWithMetadata(field v3.AttributeKey, fields map[string]v3.Attribu
|
||||
}
|
||||
|
||||
// check if the field is present in the fields map
|
||||
for _, key := range utils.GenerateEnrichmentKeys(field) {
|
||||
for _, key := range utils.GenerateLogEnrichmentKeys(field) {
|
||||
if val, ok := fields[key]; ok {
|
||||
return val
|
||||
}
|
||||
|
||||
@@ -384,11 +384,6 @@ func LogCommentEnricher(next http.Handler) http.Handler {
|
||||
client = "api"
|
||||
}
|
||||
|
||||
email, err := auth.GetEmailFromJwt(r.Context())
|
||||
if err != nil {
|
||||
zap.S().Errorf("error while getting email from jwt: %v", err)
|
||||
}
|
||||
|
||||
kvs := map[string]string{
|
||||
"path": path,
|
||||
"dashboardID": dashboardID,
|
||||
@@ -397,7 +392,6 @@ func LogCommentEnricher(next http.Handler) http.Handler {
|
||||
"client": client,
|
||||
"viewName": viewName,
|
||||
"servicesTab": tab,
|
||||
"email": email,
|
||||
}
|
||||
|
||||
r = r.WithContext(context.WithValue(r.Context(), common.LogCommentKey, kvs))
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||
)
|
||||
|
||||
// if the field is timestamp/id/value we don't need to enrich
|
||||
// if the field is static we don't need to enrich
|
||||
// for all others we need to enrich
|
||||
// an attribute/resource can be materialized/dematerialized
|
||||
// but the query should work regardless and shouldn't fail
|
||||
func isEnriched(field v3.AttributeKey) bool {
|
||||
// if it is timestamp/id dont check
|
||||
if field.Key == "timestamp" || field.Key == constants.SigNozOrderByValue {
|
||||
return true
|
||||
}
|
||||
|
||||
// we need to check if the field is static and return false if isColumn is not set
|
||||
if _, ok := constants.StaticFieldsTraces[field.Key]; ok && field.IsColumn {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func enrichKeyWithMetadata(key v3.AttributeKey, keys map[string]v3.AttributeKey) v3.AttributeKey {
|
||||
if isEnriched(key) {
|
||||
return key
|
||||
}
|
||||
|
||||
if v, ok := constants.StaticFieldsTraces[key.Key]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
for _, key := range utils.GenerateEnrichmentKeys(key) {
|
||||
if val, ok := keys[key]; ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// enrich with default values if metadata is not found
|
||||
if key.Type == "" {
|
||||
key.Type = v3.AttributeKeyTypeTag
|
||||
}
|
||||
if key.DataType == "" {
|
||||
key.DataType = v3.AttributeKeyDataTypeString
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func Enrich(params *v3.QueryRangeParamsV3, keys map[string]v3.AttributeKey) {
|
||||
if params.CompositeQuery.QueryType != v3.QueryTypeBuilder {
|
||||
return
|
||||
}
|
||||
|
||||
for _, query := range params.CompositeQuery.BuilderQueries {
|
||||
if query.DataSource == v3.DataSourceTraces {
|
||||
EnrichTracesQuery(query, keys)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func EnrichTracesQuery(query *v3.BuilderQuery, keys map[string]v3.AttributeKey) {
|
||||
// enrich aggregate attribute
|
||||
query.AggregateAttribute = enrichKeyWithMetadata(query.AggregateAttribute, keys)
|
||||
|
||||
// enrich filter items
|
||||
if query.Filters != nil && len(query.Filters.Items) > 0 {
|
||||
for idx, filter := range query.Filters.Items {
|
||||
query.Filters.Items[idx].Key = enrichKeyWithMetadata(filter.Key, keys)
|
||||
// if the serviceName column is used, use the corresponding resource attribute as well during filtering
|
||||
// since there is only one of these resource attributes we are adding it here directly.
|
||||
// move it somewhere else if this list is big
|
||||
if filter.Key.Key == "serviceName" {
|
||||
query.Filters.Items[idx].Key = v3.AttributeKey{
|
||||
Key: "service.name",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeResource,
|
||||
IsColumn: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enrich group by
|
||||
for idx, groupBy := range query.GroupBy {
|
||||
query.GroupBy[idx] = enrichKeyWithMetadata(groupBy, keys)
|
||||
}
|
||||
|
||||
// enrich order by
|
||||
query.OrderBy = enrichOrderBy(query.OrderBy, keys)
|
||||
|
||||
// enrich select columns
|
||||
for idx, selectColumn := range query.SelectColumns {
|
||||
query.SelectColumns[idx] = enrichKeyWithMetadata(selectColumn, keys)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func enrichOrderBy(items []v3.OrderBy, keys map[string]v3.AttributeKey) []v3.OrderBy {
|
||||
enrichedItems := []v3.OrderBy{}
|
||||
for i := 0; i < len(items); i++ {
|
||||
attributeKey := enrichKeyWithMetadata(v3.AttributeKey{
|
||||
Key: items[i].ColumnName,
|
||||
}, keys)
|
||||
enrichedItems = append(enrichedItems, v3.OrderBy{
|
||||
ColumnName: items[i].ColumnName,
|
||||
Order: items[i].Order,
|
||||
Key: attributeKey.Key,
|
||||
DataType: attributeKey.DataType,
|
||||
Type: attributeKey.Type,
|
||||
IsColumn: attributeKey.IsColumn,
|
||||
})
|
||||
}
|
||||
return enrichedItems
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
)
|
||||
|
||||
func TestEnrichTracesQuery(t *testing.T) {
|
||||
type args struct {
|
||||
query *v3.BuilderQuery
|
||||
keys map[string]v3.AttributeKey
|
||||
want *v3.BuilderQuery
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "test 1",
|
||||
args: args{
|
||||
query: &v3.BuilderQuery{
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "bytes", Type: v3.AttributeKeyTypeTag}, Value: 100, Operator: ">"},
|
||||
},
|
||||
},
|
||||
OrderBy: []v3.OrderBy{},
|
||||
},
|
||||
keys: map[string]v3.AttributeKey{
|
||||
"bytes##tag##int64": {Key: "bytes", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag},
|
||||
},
|
||||
want: &v3.BuilderQuery{
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "bytes", Type: v3.AttributeKeyTypeTag, DataType: v3.AttributeKeyDataTypeInt64}, Value: 100, Operator: ">"},
|
||||
},
|
||||
},
|
||||
OrderBy: []v3.OrderBy{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test service name",
|
||||
args: args{
|
||||
query: &v3.BuilderQuery{
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Value: "myservice", Operator: "="},
|
||||
{Key: v3.AttributeKey{Key: "serviceName"}, Value: "myservice", Operator: "="},
|
||||
},
|
||||
},
|
||||
OrderBy: []v3.OrderBy{},
|
||||
},
|
||||
keys: map[string]v3.AttributeKey{},
|
||||
want: &v3.BuilderQuery{
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "service.name", Type: v3.AttributeKeyTypeResource, DataType: v3.AttributeKeyDataTypeString}, Value: "myservice", Operator: "="},
|
||||
{Key: v3.AttributeKey{Key: "service.name", Type: v3.AttributeKeyTypeResource, DataType: v3.AttributeKeyDataTypeString}, Value: "myservice", Operator: "="},
|
||||
},
|
||||
},
|
||||
OrderBy: []v3.OrderBy{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test mat attrs",
|
||||
args: args{
|
||||
query: &v3.BuilderQuery{
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "http.route", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Value: "/api", Operator: "="},
|
||||
{Key: v3.AttributeKey{Key: "msgSystem"}, Value: "name", Operator: "="},
|
||||
{Key: v3.AttributeKey{Key: "external_http_url"}, Value: "name", Operator: "="},
|
||||
},
|
||||
},
|
||||
OrderBy: []v3.OrderBy{},
|
||||
},
|
||||
keys: map[string]v3.AttributeKey{},
|
||||
want: &v3.BuilderQuery{
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "http.route", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Value: "/api", Operator: "="},
|
||||
{Key: v3.AttributeKey{Key: "msgSystem", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Value: "name", Operator: "="},
|
||||
{Key: v3.AttributeKey{Key: "external_http_url", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Value: "name", Operator: "="},
|
||||
},
|
||||
},
|
||||
OrderBy: []v3.OrderBy{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test aggregateattr, filter, groupby, order by",
|
||||
args: args{
|
||||
query: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorCount,
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: "http.route",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
},
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "http.route", DataType: v3.AttributeKeyDataTypeString}, Value: "/api", Operator: "="},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{Key: "http.route", DataType: v3.AttributeKeyDataTypeString},
|
||||
{Key: "msgSystem", DataType: v3.AttributeKeyDataTypeString},
|
||||
},
|
||||
OrderBy: []v3.OrderBy{
|
||||
{ColumnName: "httpRoute", Order: v3.DirectionAsc},
|
||||
},
|
||||
},
|
||||
keys: map[string]v3.AttributeKey{
|
||||
"http.route##tag##string": {Key: "http.route", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
|
||||
},
|
||||
want: &v3.BuilderQuery{
|
||||
AggregateAttribute: v3.AttributeKey{
|
||||
Key: "http.route",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
IsColumn: true,
|
||||
},
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "http.route", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Value: "/api", Operator: "="},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{Key: "http.route", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
|
||||
{Key: "msgSystem", DataType: v3.AttributeKeyDataTypeString, IsJSON: false, IsColumn: true},
|
||||
},
|
||||
OrderBy: []v3.OrderBy{
|
||||
{Key: "httpRoute", Order: v3.DirectionAsc, ColumnName: "httpRoute", DataType: v3.AttributeKeyDataTypeString, IsColumn: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enrich default values",
|
||||
args: args{
|
||||
query: &v3.BuilderQuery{
|
||||
Filters: &v3.FilterSet{
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "testattr"}},
|
||||
},
|
||||
},
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: v3.DirectionAsc}},
|
||||
},
|
||||
keys: map[string]v3.AttributeKey{},
|
||||
want: &v3.BuilderQuery{
|
||||
Filters: &v3.FilterSet{
|
||||
Items: []v3.FilterItem{{Key: v3.AttributeKey{Key: "testattr", Type: v3.AttributeKeyTypeTag, DataType: v3.AttributeKeyDataTypeString}}},
|
||||
},
|
||||
// isColumn won't matter in timestamp as it will always be a column
|
||||
OrderBy: []v3.OrderBy{{Key: "timestamp", Order: v3.DirectionAsc, ColumnName: "timestamp"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
EnrichTracesQuery(tt.args.query, tt.args.keys)
|
||||
// Check AggregateAttribute
|
||||
if tt.args.query.AggregateAttribute.Key != "" && !reflect.DeepEqual(tt.args.query.AggregateAttribute, tt.args.want.AggregateAttribute) {
|
||||
t.Errorf("EnrichTracesQuery() AggregateAttribute = %v, want %v", tt.args.query.AggregateAttribute, tt.args.want.AggregateAttribute)
|
||||
}
|
||||
|
||||
// Check Filters
|
||||
if tt.args.query.Filters != nil && !reflect.DeepEqual(tt.args.query.Filters, tt.args.want.Filters) {
|
||||
t.Errorf("EnrichTracesQuery() Filters = %v, want %v", tt.args.query.Filters, tt.args.want.Filters)
|
||||
}
|
||||
|
||||
// Check GroupBy
|
||||
if tt.args.query.GroupBy != nil && !reflect.DeepEqual(tt.args.query.GroupBy, tt.args.want.GroupBy) {
|
||||
t.Errorf("EnrichTracesQuery() GroupBy = %v, want %v", tt.args.query.GroupBy, tt.args.want.GroupBy)
|
||||
}
|
||||
|
||||
// Check OrderBy
|
||||
if tt.args.query.OrderBy != nil && !reflect.DeepEqual(tt.args.query.OrderBy, tt.args.want.OrderBy) {
|
||||
t.Errorf("EnrichTracesQuery() OrderBy = %v, want %v", tt.args.query.OrderBy, tt.args.want.OrderBy)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -124,27 +124,6 @@ func Test_getColumnName(t *testing.T) {
|
||||
},
|
||||
want: "attributes_string['xyz']",
|
||||
},
|
||||
{
|
||||
name: "new composite column",
|
||||
args: args{
|
||||
key: v3.AttributeKey{Key: "response_status_code"},
|
||||
},
|
||||
want: "response_status_code",
|
||||
},
|
||||
{
|
||||
name: "new composite column with metadata",
|
||||
args: args{
|
||||
key: v3.AttributeKey{Key: "response_status_code", DataType: v3.AttributeKeyDataTypeString, IsColumn: true},
|
||||
},
|
||||
want: "response_status_code",
|
||||
},
|
||||
{
|
||||
name: "new normal column with metadata",
|
||||
args: args{
|
||||
key: v3.AttributeKey{Key: "http.route", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
|
||||
},
|
||||
want: "`attribute_string_http$$route`",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -589,60 +589,6 @@ var StaticFieldsTraces = map[string]v3.AttributeKey{
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
|
||||
// new support
|
||||
"response_status_code": {
|
||||
Key: "response_status_code",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"external_http_url": {
|
||||
Key: "external_http_url",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"http_url": {
|
||||
Key: "http_url",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"external_http_method": {
|
||||
Key: "external_http_method",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"http_method": {
|
||||
Key: "http_method",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"http_host": {
|
||||
Key: "http_host",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"db_name": {
|
||||
Key: "db_name",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"db_operation": {
|
||||
Key: "db_operation",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"has_error": {
|
||||
Key: "has_error",
|
||||
DataType: v3.AttributeKeyDataTypeBool,
|
||||
IsColumn: true,
|
||||
},
|
||||
"is_remote": {
|
||||
Key: "is_remote",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
// the simple attributes are not present here as
|
||||
// they are taken care by new format <attribute_type>_<attribute_datatype>_'<attribute_key>'
|
||||
}
|
||||
|
||||
const TRACE_V4_MAX_PAGINATION_LIMIT = 10000
|
||||
|
||||
@@ -183,7 +183,7 @@ func PrepareFilters(labels map[string]string, whereClauseItems []v3.FilterItem,
|
||||
var attrFound bool
|
||||
|
||||
// as of now this logic will only apply for logs
|
||||
for _, tKey := range utils.GenerateEnrichmentKeys(v3.AttributeKey{Key: key}) {
|
||||
for _, tKey := range utils.GenerateLogEnrichmentKeys(v3.AttributeKey{Key: key}) {
|
||||
if val, ok := keys[tKey]; ok {
|
||||
attributeKey = val
|
||||
attrFound = true
|
||||
|
||||
@@ -29,10 +29,15 @@ type Reader interface {
|
||||
// GetDisks returns a list of disks configured in the underlying DB. It is supported by
|
||||
// clickhouse only.
|
||||
GetDisks(ctx context.Context) (*[]model.DiskItem, *model.ApiError)
|
||||
GetSpanFilters(ctx context.Context, query *model.SpanFilterParams) (*model.SpanFiltersResponse, *model.ApiError)
|
||||
GetTraceAggregateAttributes(ctx context.Context, req *v3.AggregateAttributeRequest) (*v3.AggregateAttributeResponse, error)
|
||||
GetTraceAttributeKeys(ctx context.Context, req *v3.FilterAttributeKeyRequest) (*v3.FilterAttributeKeyResponse, error)
|
||||
GetTraceAttributeValues(ctx context.Context, req *v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error)
|
||||
GetSpanAttributeKeys(ctx context.Context) (map[string]v3.AttributeKey, error)
|
||||
GetTagFilters(ctx context.Context, query *model.TagFilterParams) (*model.TagFilters, *model.ApiError)
|
||||
GetTagValues(ctx context.Context, query *model.TagFilterParams) (*model.TagValues, *model.ApiError)
|
||||
GetFilteredSpans(ctx context.Context, query *model.GetFilteredSpansParams) (*model.GetFilterSpansResponse, *model.ApiError)
|
||||
GetFilteredSpansAggregates(ctx context.Context, query *model.GetFilteredSpanAggregatesParams) (*model.GetFilteredSpansAggregatesResponse, *model.ApiError)
|
||||
|
||||
ListErrors(ctx context.Context, params *model.ListErrorsParams) (*[]model.Error, *model.ApiError)
|
||||
CountErrors(ctx context.Context, params *model.CountErrorsParams) (uint64, *model.ApiError)
|
||||
|
||||
@@ -463,9 +463,9 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
|
||||
}
|
||||
} else if r.compareOp() == ValueOutsideBounds {
|
||||
for _, smpl := range series.Points {
|
||||
if math.Abs(smpl.Value) < r.targetVal() {
|
||||
if math.Abs(smpl.Value) >= r.targetVal() {
|
||||
alertSmpl = Sample{Point: Point{V: smpl.Value}, Metric: lbls}
|
||||
shouldAlert = false
|
||||
shouldAlert = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +347,6 @@ func createTelemetry() {
|
||||
"alertsWithTSV2": alertsInfo.AlertsWithTSV2,
|
||||
"logsBasedAlerts": alertsInfo.LogsBasedAlerts,
|
||||
"metricBasedAlerts": alertsInfo.MetricBasedAlerts,
|
||||
"anomalyBasedAlerts": alertsInfo.AnomalyBasedAlerts,
|
||||
"tracesBasedAlerts": alertsInfo.TracesBasedAlerts,
|
||||
"totalChannels": alertsInfo.TotalChannels,
|
||||
"totalSavedViews": savedViewsInfo.TotalSavedViews,
|
||||
|
||||
@@ -40,8 +40,8 @@ func GetListTsRanges(start, end int64) []LogsListTsRange {
|
||||
}
|
||||
|
||||
// This tries to see all possible fields that it can fall back to if some meta is missing
|
||||
// check Test_GenerateEnrichmentKeys for example
|
||||
func GenerateEnrichmentKeys(field v3.AttributeKey) []string {
|
||||
// check Test_GenerateLogEnrichmentKeys for example
|
||||
func GenerateLogEnrichmentKeys(field v3.AttributeKey) []string {
|
||||
names := []string{}
|
||||
if field.Type != v3.AttributeKeyTypeUnspecified && field.DataType != v3.AttributeKeyDataTypeUnspecified {
|
||||
names = append(names, field.Key+"##"+field.Type.String()+"##"+field.DataType.String())
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestListTsRange(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GenerateEnrichmentKeys(t *testing.T) {
|
||||
func Test_GenerateLogEnrichmentKeys(t *testing.T) {
|
||||
type args struct {
|
||||
field v3.AttributeKey
|
||||
}
|
||||
@@ -96,8 +96,8 @@ func Test_GenerateEnrichmentKeys(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := GenerateEnrichmentKeys(tt.args.field); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GenerateEnrichmentKeys() = %v, want %v", got, tt.want)
|
||||
if got := GenerateLogEnrichmentKeys(tt.args.field); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("generateLogEnrichmentKeys() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user