Compare commits

..

20 Commits

Author SHA1 Message Date
Karan Balani
0cf1384892 chore: rename db migration file 2026-02-11 16:47:40 +05:30
Karan Balani
5aa655f875 chore: fix message 2026-02-11 16:27:22 +05:30
Karan Balani
ffde89d905 chore: use authz instead of granter 2026-02-11 16:17:14 +05:30
Karan Balani
ff732c6d6c feat: add reconciliation logic for fresh signoz instance 2026-02-11 16:14:30 +05:30
Karan Balani
30d902f92d fix: xor condition for user config validation 2026-02-11 16:11:57 +05:30
Karan Balani
6a9a8b14b3 fix: make reconciler flow idempotent 2026-02-11 16:11:57 +05:30
Karan Balani
4e7d2f8fb8 fix: improve validation for root user config 2026-02-11 16:11:57 +05:30
Karan Balani
bbc7f7bb3d chore: move to new migration format and minor other changes 2026-02-11 16:11:54 +05:30
Karan Balani
c3294cf704 fix: comments 2026-02-11 16:11:22 +05:30
Karan Balani
e94b2a66d7 chore: minor changes 2026-02-11 16:11:22 +05:30
Karan Balani
ceca9fbd42 fix: reconciler logic for finding root users if email is changed in config 2026-02-11 16:11:22 +05:30
Karan Balani
14cec7b465 chore: various cursor bug bot comments addressed 2026-02-11 16:11:22 +05:30
Karan Balani
04753e2d57 chore: better response for bulk invite failed cases 2026-02-11 16:11:22 +05:30
Karan Balani
85ba9a6840 chore: handle root user invite in bulk invite api 2026-02-11 16:11:22 +05:30
Karan Balani
230b5ab7b7 chore: add check if the user is trying to delete their own user 2026-02-11 16:11:22 +05:30
Karan Balani
93c7c7fc93 chore: dummy push 2026-02-11 16:11:22 +05:30
Karan Balani
3011c24662 chore: handle various edge cases for root user login and operations 2026-02-11 16:11:22 +05:30
Karan Balani
559631f217 fix: go lint 2026-02-11 16:11:22 +05:30
Karan Balani
80bcc8971f chore: minor fixes, still stuck on reconciler 2026-02-11 16:11:22 +05:30
Karan Balani
d8e3134729 feat(authn): root user 2026-02-11 16:11:19 +05:30
91 changed files with 1350 additions and 5064 deletions

View File

@@ -4,6 +4,7 @@ services:
container_name: signoz-otel-collector-dev
command:
- --config=/etc/otel-collector-config.yaml
- --feature-gates=-pkg.translator.prometheus.NormalizeName
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
environment:

View File

@@ -238,4 +238,4 @@ py-clean: ## Clear all pycache and pytest cache from tests directory recursively
.PHONY: gen-mocks
gen-mocks:
@echo ">> Generating mocks"
@mockery --config .mockery.yml
@mockery --config .mockery.yml

View File

@@ -300,3 +300,8 @@ user:
allow_self: true
# The duration within which a user can reset their password.
max_token_lifetime: 6h
root:
# The email of the root user.
email: root@example.com
# The password of the root user.
password: Str0ngP@ssw0rd!

View File

@@ -214,6 +214,7 @@ services:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
- --copy-path=/var/tmp/collector-config.yaml
- --feature-gates=-pkg.translator.prometheus.NormalizeName
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ../common/signoz/otel-collector-opamp-config.yaml:/etc/manager-config.yaml

View File

@@ -155,6 +155,7 @@ services:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
- --copy-path=/var/tmp/collector-config.yaml
- --feature-gates=-pkg.translator.prometheus.NormalizeName
configs:
- source: otel-collector-config
target: /etc/otel-collector-config.yaml

View File

@@ -219,6 +219,7 @@ services:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
- --copy-path=/var/tmp/collector-config.yaml
- --feature-gates=-pkg.translator.prometheus.NormalizeName
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ../common/signoz/otel-collector-opamp-config.yaml:/etc/manager-config.yaml

View File

@@ -150,6 +150,7 @@ services:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
- --copy-path=/var/tmp/collector-config.yaml
- --feature-gates=-pkg.translator.prometheus.NormalizeName
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ../common/signoz/otel-collector-opamp-config.yaml:/etc/manager-config.yaml

View File

@@ -17,8 +17,6 @@ const config: Config.InitialOptions = {
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^@signozhq/icons$':
'<rootDir>/node_modules/@signozhq/icons/dist/index.esm.js',
},
globals: {
extensionsToTreatAsEsm: ['.ts'],

View File

@@ -52,7 +52,6 @@
"@signozhq/combobox": "0.0.2",
"@signozhq/command": "0.0.0",
"@signozhq/design-tokens": "2.1.1",
"@signozhq/icons": "0.1.0",
"@signozhq/input": "0.0.2",
"@signozhq/popover": "0.0.0",
"@signozhq/resizable": "0.0.0",

View File

@@ -1,7 +1,6 @@
{
"SIGN_UP": "SigNoz | Sign Up",
"LOGIN": "SigNoz | Login",
"FORGOT_PASSWORD": "SigNoz | Forgot Password",
"HOME": "SigNoz | Home",
"SERVICE_METRICS": "SigNoz | Service Metrics",
"SERVICE_MAP": "SigNoz | Service Map",

View File

@@ -194,10 +194,6 @@ export const Login = Loadable(
() => import(/* webpackChunkName: "Login" */ 'pages/Login'),
);
export const ForgotPassword = Loadable(
() => import(/* webpackChunkName: "ForgotPassword" */ 'pages/ForgotPassword'),
);
export const UnAuthorized = Loadable(
() => import(/* webpackChunkName: "UnAuthorized" */ 'pages/UnAuthorized'),
);

View File

@@ -17,7 +17,6 @@ import {
DashboardWidget,
EditRulesPage,
ErrorDetails,
ForgotPassword,
Home,
InfrastructureMonitoring,
InstalledIntegrations,
@@ -340,13 +339,6 @@ const routes: AppRoutes[] = [
isPrivate: false,
key: 'LOGIN',
},
{
path: ROUTES.FORGOT_PASSWORD,
exact: true,
component: ForgotPassword,
isPrivate: false,
key: 'FORGOT_PASSWORD',
},
{
path: ROUTES.UN_AUTHORIZED,
exact: true,

View File

@@ -18,7 +18,6 @@ import '@signozhq/checkbox';
import '@signozhq/combobox';
import '@signozhq/command';
import '@signozhq/design-tokens';
import '@signozhq/icons';
import '@signozhq/input';
import '@signozhq/popover';
import '@signozhq/resizable';

View File

@@ -648,13 +648,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
) : (
<Typography.Text
className="value-string"
ellipsis={{
tooltip: {
placement: 'top',
mouseEnterDelay: 0.2,
mouseLeaveDelay: 0,
},
}}
ellipsis={{ tooltip: { placement: 'top' } }}
>
{String(value)}
</Typography.Text>

View File

@@ -1,7 +1,6 @@
const ROUTES = {
SIGN_UP: '/signup',
LOGIN: '/login',
FORGOT_PASSWORD: '/forgot-password',
HOME: '/home',
SERVICE_METRICS: '/services/:servicename',
SERVICE_TOP_LEVEL_OPERATIONS: '/services/:servicename/top-level-operations',

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { LoadingOutlined } from '@ant-design/icons';
import { Spin, Table } from 'antd';
import { Spin, Table, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
@@ -14,13 +14,11 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import { useListOverview } from 'hooks/thirdPartyApis/useListOverview';
import { get } from 'lodash-es';
import { MoveUpRight } from 'lucide-react';
import { AppState } from 'store/reducers';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { HandleChangeQueryDataV5 } from 'types/common/operations.types';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import DOCLINKS from 'utils/docLinks';
import { ApiMonitoringHardcodedAttributeKeys } from '../../constants';
import { DEFAULT_PARAMS, useApiMonitoringParams } from '../../queryParams';
@@ -127,67 +125,51 @@ function DomainList(): JSX.Element {
hardcodedAttributeKeys={ApiMonitoringHardcodedAttributeKeys}
/>
</div>
{!isFetching && !isLoading && formattedDataForTable.length === 0 && (
<div className="no-filtered-domains-message-container">
<div className="no-filtered-domains-message-content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Table
className={cx('api-monitoring-domain-list-table')}
dataSource={isFetching || isLoading ? [] : formattedDataForTable}
columns={columnsConfig}
loading={{
spinning: isFetching || isLoading,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
locale={{
emptyText:
isFetching || isLoading ? null : (
<div className="no-filtered-domains-message-container">
<div className="no-filtered-domains-message-content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<div className="no-filtered-domains-message">
<div className="no-domain-title">
No External API calls detected with applied filters.
<Typography.Text className="no-filtered-domains-message">
This query had no results. Edit your query and try again!
</Typography.Text>
</div>
</div>
<div className="no-domain-subtitle">
Ensure all HTTP client spans are being sent with kind as{' '}
<span className="attribute">Client</span> and url set in{' '}
<span className="attribute">url.full</span> or{' '}
<span className="attribute">http.url</span> attribute.
</div>
<a
href={DOCLINKS.EXTERNAL_API_MONITORING}
target="_blank"
rel="noreferrer"
className="external-api-doc-link"
>
Learn how External API monitoring works in SigNoz{' '}
<MoveUpRight size={14} />
</a>
</div>
</div>
</div>
)}
{(isFetching || isLoading || formattedDataForTable.length > 0) && (
<Table
className="api-monitoring-domain-list-table"
dataSource={isFetching || isLoading ? [] : formattedDataForTable}
columns={columnsConfig}
loading={{
spinning: isFetching || isLoading,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
scroll={{ x: true }}
tableLayout="fixed"
onRow={(record, index): { onClick: () => void; className: string } => ({
onClick: (): void => {
if (index !== undefined) {
const dataIndex = formattedDataForTable.findIndex(
(item) => item.key === record.key,
);
setSelectedDomainIndex(dataIndex);
setParams({ selectedDomain: record.domainName });
logEvent('API Monitoring: Domain name row clicked', {});
}
},
className: 'expanded-clickable-row',
})}
rowClassName={(_, index): string =>
index % 2 === 0 ? 'table-row-dark' : 'table-row-light'
}
/>
)}
),
}}
scroll={{ x: true }}
tableLayout="fixed"
onRow={(record, index): { onClick: () => void; className: string } => ({
onClick: (): void => {
if (index !== undefined) {
const dataIndex = formattedDataForTable.findIndex(
(item) => item.key === record.key,
);
setSelectedDomainIndex(dataIndex);
setParams({ selectedDomain: record.domainName });
logEvent('API Monitoring: Domain name row clicked', {});
}
},
className: 'expanded-clickable-row',
})}
rowClassName={(_, index): string =>
index % 2 === 0 ? 'table-row-dark' : 'table-row-light'
}
/>
{selectedDomainIndex !== -1 && (
<DomainDetails
domainData={formattedDataForTable[selectedDomainIndex]}

View File

@@ -180,59 +180,10 @@
.no-filtered-domains-message {
margin-top: 8px;
display: flex;
gap: 8px;
flex-direction: column;
.no-domain-title {
color: var(--bg-vanilla-100, #fff);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
}
.no-domain-subtitle {
color: var(--bg-vanilla-400, #c0c1c3);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
.attribute {
font-family: 'Space Mono';
}
}
.external-api-doc-link {
display: flex;
flex-direction: row;
gap: 4px;
align-items: center;
}
}
}
.lightMode {
.no-filtered-domains-message-container {
.no-filtered-domains-message-content {
.no-filtered-domains-message {
.no-domain-title {
color: var(--text-ink-500);
}
.no-domain-subtitle {
color: var(--text-ink-400);
.attribute {
font-family: 'Space Mono';
}
}
}
}
}
.api-monitoring-domain-list-table {
.ant-table {
.ant-table-thead > tr > th {

View File

@@ -1,117 +0,0 @@
import { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
import ChartWrapper from 'container/DashboardContainer/visualization/charts/ChartWrapper/ChartWrapper';
import BarChartTooltip from 'lib/uPlotV2/components/Tooltip/BarChartTooltip';
import {
BarTooltipProps,
TooltipRenderArgs,
} from 'lib/uPlotV2/components/types';
import { has } from 'lodash-es';
import uPlot from 'uplot';
import { BarChartProps } from '../types';
import { stack } from './stackUtils';
/** Returns true if the series at the given index is hidden (e.g. via legend toggle) */
function isSeriesHidden(plot: uPlot, seriesIndex: number): boolean {
return !plot.series[seriesIndex]?.show;
}
export default function BarChart(props: BarChartProps): JSX.Element {
const { children, isStackedBarChart, config, data, ...rest } = props;
// Store unstacked source data so uPlot hooks can access it (hooks run outside React's render cycle)
const unstackedDataRef = useRef<uPlot.AlignedData | null>(null);
unstackedDataRef.current = isStackedBarChart ? data : null;
// Prevents re-entrant calls when we update chart data (avoids infinite loop in setData hook)
const isUpdatingChartRef = useRef(false);
const chartData = useMemo((): uPlot.AlignedData => {
if (!isStackedBarChart || !data || data.length < 2) {
return data as uPlot.AlignedData;
}
const noSeriesHidden = (): boolean => false; // include all series in initial stack
const { data: stacked } = stack(data as uPlot.AlignedData, noSeriesHidden);
return stacked;
}, [data, isStackedBarChart]);
const applyStackingToChart = useCallback((plot: uPlot): void => {
const unstacked = unstackedDataRef.current;
const hasValidData =
unstacked && plot.data && unstacked[0]?.length === plot.data[0]?.length;
if (!hasValidData || isUpdatingChartRef.current) {
return;
}
const shouldExcludeSeries = (idx: number): boolean =>
isSeriesHidden(plot, idx);
const { data: stacked, bands } = stack(unstacked, shouldExcludeSeries);
plot.delBand(null);
bands.forEach((band: uPlot.Band) => plot.addBand(band));
isUpdatingChartRef.current = true;
plot.setData(stacked);
isUpdatingChartRef.current = false;
}, []);
useLayoutEffect(() => {
if (!isStackedBarChart || !config) {
return undefined;
}
const onDataChange = (plot: uPlot): void => {
if (!isUpdatingChartRef.current) {
applyStackingToChart(plot);
}
};
const onSeriesVisibilityChange = (
plot: uPlot,
_seriesIdx: number | null,
opts: uPlot.Series,
): void => {
if (has(opts, 'focus')) {
return;
}
applyStackingToChart(plot);
};
const removeSetDataHook = config.addHook('setData', onDataChange);
const removeSetSeriesHook = config.addHook(
'setSeries',
onSeriesVisibilityChange,
);
return (): void => {
removeSetDataHook?.();
removeSetSeriesHook?.();
};
}, [isStackedBarChart, config, applyStackingToChart]);
const renderTooltip = useCallback(
(props: TooltipRenderArgs): React.ReactNode => {
const tooltipProps: BarTooltipProps = {
...props,
timezone: rest.timezone,
yAxisUnit: rest.yAxisUnit,
decimalPrecision: rest.decimalPrecision,
isStackedBarChart: isStackedBarChart,
};
return <BarChartTooltip {...tooltipProps} />;
},
[rest.timezone, rest.yAxisUnit, rest.decimalPrecision, isStackedBarChart],
);
return (
<ChartWrapper
{...rest}
config={config}
data={chartData}
renderTooltip={renderTooltip}
>
{children}
</ChartWrapper>
);
}

View File

@@ -1,57 +0,0 @@
import uPlot, { AlignedData } from 'uplot';
/**
* Stack data cumulatively (top-down: first series = top, last = bottom).
* When omit(i) returns true for a series index, that series is excluded from stacking.
*/
export function stack(
data: AlignedData,
omit: (seriesIndex: number) => boolean,
): { data: AlignedData; bands: uPlot.Band[] } {
const d0Len = data[0].length;
const n = data.length - 1;
const data2: (number | null)[][] = Array(n);
const accum = Array(d0Len).fill(0) as number[];
// Accumulate from last series upward: last = raw, first = total
for (let i = n; i >= 1; i--) {
const seriesData = data[i] as (number | null)[];
if (omit(i)) {
data2[i - 1] = seriesData;
} else {
data2[i - 1] = seriesData.map((v, idx) => {
const val = v == null ? 0 : Number(v);
return (accum[idx] += val);
});
}
}
const bands: uPlot.Band[] = [];
// Bands: [upper, lower] - fill between consecutive visible series
for (let i = 1; i < data.length; i++) {
if (!omit(i)) {
const nextIdx = data.findIndex((_, j) => j > i && !omit(j));
if (nextIdx >= 1) {
bands.push({ series: [i, nextIdx] });
}
}
}
return {
data: [data[0], ...data2] as AlignedData,
bands,
};
}
/**
* Returns band indices for initial stacked state (no series omitted).
* Top-down: first series at top, band fills between consecutive series.
* uPlot band format: [upperSeriesIdx, lowerSeriesIdx].
*/
export function getInitialStackedBands(seriesCount: number): uPlot.Band[] {
const bands: uPlot.Band[] = [];
for (let i = 1; i < seriesCount; i++) {
bands.push({ series: [i, i + 1] });
}
return bands;
}

View File

@@ -23,7 +23,6 @@ export default function ChartWrapper({
width: containerWidth,
height: containerHeight,
showTooltip = true,
showLegend = true,
canPinTooltip = false,
syncMode,
syncKey,
@@ -37,9 +36,6 @@ export default function ChartWrapper({
const legendComponent = useCallback(
(averageLegendWidth: number): React.ReactNode => {
if (!showLegend) {
return null;
}
return (
<Legend
config={config}
@@ -48,7 +44,7 @@ export default function ChartWrapper({
/>
);
},
[config, legendConfig.position, showLegend],
[config, legendConfig.position],
);
const renderTooltipCallback = useCallback(
@@ -64,7 +60,6 @@ export default function ChartWrapper({
return (
<PlotContextProvider>
<ChartLayout
showLegend={showLegend}
config={config}
containerWidth={containerWidth}
containerHeight={containerHeight}

View File

@@ -1,55 +0,0 @@
import { useCallback } from 'react';
import ChartWrapper from 'container/DashboardContainer/visualization/charts/ChartWrapper/ChartWrapper';
import HistogramTooltip from 'lib/uPlotV2/components/Tooltip/HistogramTooltip';
import { buildTooltipContent } from 'lib/uPlotV2/components/Tooltip/utils';
import {
HistogramTooltipProps,
TooltipRenderArgs,
} from 'lib/uPlotV2/components/types';
import { HistogramChartProps } from '../types';
export default function Histogram(props: HistogramChartProps): JSX.Element {
const {
children,
renderTooltip: customRenderTooltip,
isQueriesMerged,
...rest
} = props;
const renderTooltip = useCallback(
(props: TooltipRenderArgs): React.ReactNode => {
if (customRenderTooltip) {
return customRenderTooltip(props);
}
const content = buildTooltipContent({
data: props.uPlotInstance.data,
series: props.uPlotInstance.series,
dataIndexes: props.dataIndexes,
activeSeriesIndex: props.seriesIndex,
uPlotInstance: props.uPlotInstance,
yAxisUnit: rest.yAxisUnit ?? '',
decimalPrecision: rest.decimalPrecision,
});
const tooltipProps: HistogramTooltipProps = {
...props,
timezone: rest.timezone,
yAxisUnit: rest.yAxisUnit,
decimalPrecision: rest.decimalPrecision,
content,
};
return <HistogramTooltip {...tooltipProps} />;
},
[customRenderTooltip, rest.timezone, rest.yAxisUnit, rest.decimalPrecision],
);
return (
<ChartWrapper
showLegend={!isQueriesMerged}
{...rest}
renderTooltip={renderTooltip}
>
{children}
</ChartWrapper>
);
}

View File

@@ -7,7 +7,6 @@ interface BaseChartProps {
width: number;
height: number;
showTooltip?: boolean;
showLegend?: boolean;
timezone: string;
canPinTooltip?: boolean;
yAxisUnit?: string;
@@ -18,7 +17,6 @@ interface BaseChartProps {
interface UPlotBasedChartProps {
config: UPlotConfigBuilder;
data: uPlot.AlignedData;
legendConfig: LegendConfig;
syncMode?: DashboardCursorSync;
syncKey?: string;
plotRef?: (plot: uPlot | null) => void;
@@ -28,20 +26,14 @@ interface UPlotBasedChartProps {
}
export interface TimeSeriesChartProps
extends BaseChartProps,
UPlotBasedChartProps {}
export interface HistogramChartProps
extends BaseChartProps,
UPlotBasedChartProps {
isQueriesMerged?: boolean;
legendConfig: LegendConfig;
}
export interface BarChartProps extends BaseChartProps, UPlotBasedChartProps {
legendConfig: LegendConfig;
isStackedBarChart?: boolean;
}
export type ChartProps =
| TimeSeriesChartProps
| BarChartProps
| HistogramChartProps;
export type ChartProps = TimeSeriesChartProps | BarChartProps;

View File

@@ -1,14 +1,12 @@
import { useMemo } from 'react';
import cx from 'classnames';
import { calculateChartDimensions } from 'container/DashboardContainer/visualization/charts/utils';
import { MAX_LEGEND_WIDTH } from 'lib/uPlotV2/components/Legend/Legend';
import { LegendConfig, LegendPosition } from 'lib/uPlotV2/components/types';
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
import './ChartLayout.styles.scss';
export interface ChartLayoutProps {
showLegend?: boolean;
legendComponent: (legendPerSet: number) => React.ReactNode;
children: (props: {
chartWidth: number;
@@ -22,7 +20,6 @@ export interface ChartLayoutProps {
config: UPlotConfigBuilder;
}
export default function ChartLayout({
showLegend = true,
legendComponent,
children,
layoutChildren,
@@ -33,15 +30,6 @@ export default function ChartLayout({
}: ChartLayoutProps): JSX.Element {
const chartDimensions = useMemo(
() => {
if (!showLegend) {
return {
width: containerWidth,
height: containerHeight,
legendWidth: 0,
legendHeight: 0,
averageLegendWidth: MAX_LEGEND_WIDTH,
};
}
const legendItemsMap = config.getLegendItems();
const seriesLabels = Object.values(legendItemsMap)
.map((item) => item.label)
@@ -54,7 +42,7 @@ export default function ChartLayout({
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[containerWidth, containerHeight, legendConfig, showLegend],
[containerWidth, containerHeight, legendConfig],
);
return (
@@ -72,17 +60,15 @@ export default function ChartLayout({
averageLegendWidth: chartDimensions.averageLegendWidth,
})}
</div>
{showLegend && (
<div
className="chart-layout__legend-wrapper"
style={{
height: chartDimensions.legendHeight,
width: chartDimensions.legendWidth,
}}
>
{legendComponent(chartDimensions.averageLegendWidth)}
</div>
)}
<div
className="chart-layout__legend-wrapper"
style={{
height: chartDimensions.legendHeight,
width: chartDimensions.legendWidth,
}}
>
{legendComponent(chartDimensions.averageLegendWidth)}
</div>
</div>
{layoutChildren}
</div>

View File

@@ -1,154 +0,0 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { LegendPosition } from 'lib/uPlotV2/components/types';
import ContextMenu from 'periscope/components/ContextMenu';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useTimezone } from 'providers/Timezone';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import { getTimeRange } from 'utils/getTimeRange';
import BarChart from '../../charts/BarChart/BarChart';
import ChartManager from '../../components/ChartManager/ChartManager';
import { usePanelContextMenu } from '../../hooks/usePanelContextMenu';
import { prepareBarPanelConfig, prepareBarPanelData } from './utils';
function BarPanel(props: PanelWrapperProps): JSX.Element {
const {
panelMode,
queryResponse,
widget,
onDragSelect,
isFullViewMode,
onToggleModelHandler,
} = props;
const uPlotRef = useRef<uPlot | null>(null);
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
const graphRef = useRef<HTMLDivElement>(null);
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const containerDimensions = useResizeObserver(graphRef);
const isDarkMode = useIsDarkMode();
const { timezone } = useTimezone();
useEffect(() => {
if (toScrollWidgetId === widget.id) {
graphRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
graphRef.current?.focus();
setToScrollWidgetId('');
}
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
useEffect((): void => {
const { startTime, endTime } = getTimeRange(queryResponse);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [queryResponse]);
const {
coordinates,
popoverPosition,
onClose,
menuItemsConfig,
clickHandlerWithContextMenu,
} = usePanelContextMenu({
widget,
queryResponse,
});
const config = useMemo(() => {
return prepareBarPanelConfig({
widget,
isDarkMode,
currentQuery: widget.query,
onClick: clickHandlerWithContextMenu,
onDragSelect,
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
timezone,
panelMode,
minTimeScale: minTimeScale,
maxTimeScale: maxTimeScale,
});
}, [
widget,
isDarkMode,
queryResponse?.data?.payload,
clickHandlerWithContextMenu,
onDragSelect,
minTimeScale,
maxTimeScale,
timezone,
panelMode,
]);
const chartData = useMemo(() => {
if (!queryResponse?.data?.payload) {
return [];
}
return prepareBarPanelData(queryResponse?.data?.payload);
}, [queryResponse?.data?.payload]);
const layoutChildren = useMemo(() => {
if (!isFullViewMode) {
return null;
}
return (
<ChartManager
config={config}
alignedData={chartData}
yAxisUnit={widget.yAxisUnit}
onCancel={onToggleModelHandler}
/>
);
}, [
isFullViewMode,
config,
chartData,
widget.yAxisUnit,
onToggleModelHandler,
]);
return (
<div style={{ height: '100%', width: '100%' }} ref={graphRef}>
{containerDimensions.width > 0 && containerDimensions.height > 0 && (
<BarChart
config={config}
legendConfig={{
position: widget?.legendPosition ?? LegendPosition.BOTTOM,
}}
plotRef={(plot: uPlot | null): void => {
uPlotRef.current = plot;
}}
onDestroy={(): void => {
uPlotRef.current = null;
}}
yAxisUnit={widget.yAxisUnit}
decimalPrecision={widget.decimalPrecision}
timezone={timezone.value}
data={chartData as uPlot.AlignedData}
width={containerDimensions.width}
height={containerDimensions.height}
layoutChildren={layoutChildren}
isStackedBarChart={widget.stackedBarChart ?? false}
>
<ContextMenu
coordinates={coordinates}
popoverPosition={popoverPosition}
title={menuItemsConfig.header as string}
items={menuItemsConfig.items}
onClose={onClose}
/>
</BarChart>
)}
</div>
);
}
export default BarPanel;

View File

@@ -1,108 +0,0 @@
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { getInitialStackedBands } from 'container/DashboardContainer/visualization/charts/BarChart/stackUtils';
import { getLegend } from 'lib/dashboard/getQueryResults';
import getLabelName from 'lib/getLabelName';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import {
DrawStyle,
LineInterpolation,
LineStyle,
VisibilityMode,
} from 'lib/uPlotV2/config/types';
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { QueryData } from 'types/api/widgets/getQuery';
import { AlignedData } from 'uplot';
import { PanelMode } from '../types';
import { fillMissingXAxisTimestamps, getXAxisTimestamps } from '../utils';
import { buildBaseConfig } from '../utils/baseConfigBuilder';
export function prepareBarPanelData(
apiResponse: MetricRangePayloadProps,
): AlignedData {
const seriesList = apiResponse?.data?.result || [];
const timestampArr = getXAxisTimestamps(seriesList);
const yAxisValuesArr = fillMissingXAxisTimestamps(timestampArr, seriesList);
return [timestampArr, ...yAxisValuesArr];
}
export function prepareBarPanelConfig({
widget,
isDarkMode,
currentQuery,
onClick,
onDragSelect,
apiResponse,
timezone,
panelMode,
minTimeScale,
maxTimeScale,
}: {
widget: Widgets;
isDarkMode: boolean;
currentQuery: Query;
onClick: OnClickPluginOpts['onClick'];
onDragSelect: (startTime: number, endTime: number) => void;
apiResponse: MetricRangePayloadProps;
timezone: Timezone;
panelMode: PanelMode;
minTimeScale?: number;
maxTimeScale?: number;
}): UPlotConfigBuilder {
const builder = buildBaseConfig({
widget,
isDarkMode,
onClick,
onDragSelect,
apiResponse,
timezone,
panelMode,
panelType: PANEL_TYPES.BAR,
minTimeScale,
maxTimeScale,
});
builder.setCursor({
focus: {
prox: 1e3,
},
});
if (widget.stackedBarChart) {
const seriesCount = (apiResponse?.data?.result?.length ?? 0) + 1; // +1 for 1-based uPlot series indices
builder.setBands(getInitialStackedBands(seriesCount));
}
const seriesList: QueryData[] = apiResponse?.data?.result || [];
seriesList.forEach((series) => {
const baseLabelName = getLabelName(
series.metric,
series.queryName || '', // query
series.legend || '',
);
const label = currentQuery
? getLegend(series, currentQuery, baseLabelName)
: baseLabelName;
builder.addSeries({
scaleKey: 'y',
drawStyle: DrawStyle.Bar,
panelType: PANEL_TYPES.BAR,
label: label,
colorMapping: widget.customLegendColors ?? {},
spanGaps: false,
lineStyle: LineStyle.Solid,
lineInterpolation: LineInterpolation.Spline,
showPoints: VisibilityMode.Never,
pointSize: 5,
isDarkMode,
});
});
return builder;
}

View File

@@ -1,121 +0,0 @@
import { useEffect, useMemo, useRef } from 'react';
import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { LegendPosition } from 'lib/uPlotV2/components/types';
import { DashboardCursorSync } from 'lib/uPlotV2/plugins/TooltipPlugin/types';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useTimezone } from 'providers/Timezone';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import Histogram from '../../charts/Histogram/Histogram';
import ChartManager from '../../components/ChartManager/ChartManager';
import {
prepareHistogramPanelConfig,
prepareHistogramPanelData,
} from './utils';
function HistogramPanel(props: PanelWrapperProps): JSX.Element {
const {
panelMode,
queryResponse,
widget,
isFullViewMode,
onToggleModelHandler,
} = props;
const uPlotRef = useRef<uPlot | null>(null);
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
const graphRef = useRef<HTMLDivElement>(null);
const containerDimensions = useResizeObserver(graphRef);
const isDarkMode = useIsDarkMode();
const { timezone } = useTimezone();
useEffect(() => {
if (toScrollWidgetId === widget.id) {
graphRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
graphRef.current?.focus();
setToScrollWidgetId('');
}
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
const config = useMemo(() => {
return prepareHistogramPanelConfig({
widget,
isDarkMode,
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
panelMode,
});
}, [widget, isDarkMode, queryResponse?.data?.payload, panelMode]);
const chartData = useMemo(() => {
if (!queryResponse?.data?.payload) {
return [];
}
return prepareHistogramPanelData({
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
bucketWidth: widget?.bucketWidth,
bucketCount: widget?.bucketCount,
mergeAllActiveQueries: widget?.mergeAllActiveQueries,
});
}, [
queryResponse?.data?.payload,
widget?.bucketWidth,
widget?.bucketCount,
widget?.mergeAllActiveQueries,
]);
const layoutChildren = useMemo(() => {
if (!isFullViewMode) {
return null;
}
return (
<ChartManager
config={config}
alignedData={chartData}
yAxisUnit={widget.yAxisUnit}
onCancel={onToggleModelHandler}
/>
);
}, [
isFullViewMode,
config,
chartData,
widget.yAxisUnit,
onToggleModelHandler,
]);
return (
<div style={{ height: '100%', width: '100%' }} ref={graphRef}>
{containerDimensions.width > 0 && containerDimensions.height > 0 && (
<Histogram
config={config}
legendConfig={{
position: widget?.legendPosition ?? LegendPosition.BOTTOM,
}}
plotRef={(plot: uPlot | null): void => {
uPlotRef.current = plot;
}}
onDestroy={(): void => {
uPlotRef.current = null;
}}
isQueriesMerged={widget.mergeAllActiveQueries}
yAxisUnit={widget.yAxisUnit}
decimalPrecision={widget.decimalPrecision}
syncMode={DashboardCursorSync.Crosshair}
timezone={timezone.value}
data={chartData as uPlot.AlignedData}
width={containerDimensions.width}
height={containerDimensions.height}
layoutChildren={layoutChildren}
/>
)}
</div>
);
}
export default HistogramPanel;

View File

@@ -1,214 +0,0 @@
import { histogramBucketSizes } from '@grafana/data';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { DEFAULT_BUCKET_COUNT } from 'container/PanelWrapper/constants';
import { getLegend } from 'lib/dashboard/getQueryResults';
import getLabelName from 'lib/getLabelName';
import { DrawStyle, VisibilityMode } from 'lib/uPlotV2/config/types';
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
import { incrRoundDn } from 'lib/uPlotV2/utils/scale';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { AlignedData } from 'uplot';
import { PanelMode } from '../types';
import { buildBaseConfig } from '../utils/baseConfigBuilder';
import {
addNullToFirstHistogram,
histogram,
join,
replaceUndefinedWithNull,
roundDecimals,
} from '../utils/histogram';
export interface PrepareHistogramPanelDataParams {
apiResponse: MetricRangePayloadProps;
bucketWidth?: number;
bucketCount?: number;
mergeAllActiveQueries?: boolean;
}
const BUCKET_OFFSET = 0;
const HIST_SORT = (a: number, b: number): number => a - b;
function extractNumericValues(
result: MetricRangePayloadProps['data']['result'],
): number[] {
const values: number[] = [];
for (const item of result) {
for (const [, valueStr] of item.values) {
values.push(Number.parseFloat(valueStr) || 0);
}
}
return values;
}
function computeSmallestDelta(sortedValues: number[]): number {
if (sortedValues.length <= 1) {
return 0;
}
let smallest = Infinity;
for (let i = 1; i < sortedValues.length; i++) {
const delta = sortedValues[i] - sortedValues[i - 1];
if (delta > 0) {
smallest = Math.min(smallest, delta);
}
}
return smallest === Infinity ? 0 : smallest;
}
function selectBucketSize({
range,
bucketCount,
smallestDelta,
bucketWidthOverride,
}: {
range: number;
bucketCount: number;
smallestDelta: number;
bucketWidthOverride?: number;
}): number {
if (bucketWidthOverride != null && bucketWidthOverride > 0) {
return bucketWidthOverride;
}
const targetSize = range / bucketCount;
for (const candidate of histogramBucketSizes) {
if (targetSize < candidate && candidate >= smallestDelta) {
return candidate;
}
}
return 0;
}
function buildFrames(
result: MetricRangePayloadProps['data']['result'],
mergeAllActiveQueries: boolean,
): number[][] {
const frames: number[][] = result.map((item) =>
item.values.map(([, valueStr]) => Number.parseFloat(valueStr) || 0),
);
if (mergeAllActiveQueries && frames.length > 1) {
const first = frames[0];
for (let i = 1; i < frames.length; i++) {
first.push(...frames[i]);
frames[i] = [];
}
}
return frames;
}
export function prepareHistogramPanelData({
apiResponse,
bucketWidth,
bucketCount: bucketCountProp = DEFAULT_BUCKET_COUNT,
mergeAllActiveQueries = false,
}: PrepareHistogramPanelDataParams): AlignedData {
const bucketCount = bucketCountProp ?? DEFAULT_BUCKET_COUNT;
const result = apiResponse.data.result;
const seriesValues = extractNumericValues(result);
if (seriesValues.length === 0) {
return [[]];
}
const sorted = [...seriesValues].sort((a, b) => a - b);
const min = sorted[0];
const max = sorted[sorted.length - 1];
const range = max - min;
const smallestDelta = computeSmallestDelta(sorted);
let bucketSize = selectBucketSize({
range,
bucketCount,
smallestDelta,
bucketWidthOverride: bucketWidth,
});
if (bucketSize <= 0) {
bucketSize = range > 0 ? range / bucketCount : 1;
}
const getBucket = (v: number): number =>
roundDecimals(incrRoundDn(v - BUCKET_OFFSET, bucketSize) + BUCKET_OFFSET, 9);
const frames = buildFrames(result, mergeAllActiveQueries);
const histograms: AlignedData[] = frames
.filter((frame) => frame.length > 0)
.map((frame) => histogram(frame, getBucket, HIST_SORT));
if (histograms.length === 0) {
return [[]];
}
const joined = join(histograms);
replaceUndefinedWithNull(joined);
addNullToFirstHistogram(joined, bucketSize);
return joined;
}
export function prepareHistogramPanelConfig({
widget,
apiResponse,
panelMode,
isDarkMode,
}: {
widget: Widgets;
apiResponse: MetricRangePayloadProps;
panelMode: PanelMode;
isDarkMode: boolean;
}): UPlotConfigBuilder {
const builder = buildBaseConfig({
widget,
isDarkMode,
apiResponse,
panelMode,
panelType: PANEL_TYPES.HISTOGRAM,
});
builder.setCursor({
drag: {
x: false,
y: false,
setScale: true,
},
focus: {
prox: 1e3,
},
});
builder.addScale({
scaleKey: 'x',
time: false,
auto: true,
});
builder.addScale({
scaleKey: 'y',
time: false,
auto: true,
});
const currentQuery = widget.query;
apiResponse.data.result.forEach((series) => {
const baseLabelName = getLabelName(
series.metric,
series.queryName || '', // query
series.legend || '',
);
const label = currentQuery
? getLegend(series, currentQuery, baseLabelName)
: baseLabelName;
builder.addSeries({
label: label,
scaleKey: 'y',
drawStyle: DrawStyle.Bar,
panelType: PANEL_TYPES.HISTOGRAM,
colorMapping: widget.customLegendColors ?? {},
spanGaps: false,
barWidthFactor: 1,
showPoints: VisibilityMode.Never,
pointSize: 5,
isDarkMode,
});
});
return builder;
}

View File

@@ -19,9 +19,9 @@ export interface BaseConfigBuilderProps {
widget: Widgets;
apiResponse: MetricRangePayloadProps;
isDarkMode: boolean;
onClick?: OnClickPluginOpts['onClick'];
onDragSelect?: (startTime: number, endTime: number) => void;
timezone?: Timezone;
onClick: OnClickPluginOpts['onClick'];
onDragSelect: (startTime: number, endTime: number) => void;
timezone: Timezone;
panelMode: PanelMode;
panelType: PANEL_TYPES;
minTimeScale?: number;
@@ -40,10 +40,8 @@ export function buildBaseConfig({
minTimeScale,
maxTimeScale,
}: BaseConfigBuilderProps): UPlotConfigBuilder {
const tzDate = timezone
? (timestamp: number): Date =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value)
: undefined;
const tzDate = (timestamp: number): Date =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value);
const builder = new UPlotConfigBuilder({
onDragSelect,

View File

@@ -1,181 +0,0 @@
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-param-reassign */
import {
NULL_EXPAND,
NULL_REMOVE,
NULL_RETAIN,
} from 'container/PanelWrapper/constants';
import { AlignedData } from 'uplot';
export function incrRoundDn(num: number, incr: number): number {
return Math.floor(num / incr) * incr;
}
export function roundDecimals(val: number, dec = 0): number {
if (Number.isInteger(val)) {
return val;
}
const p = 10 ** dec;
const n = val * p * (1 + Number.EPSILON);
return Math.round(n) / p;
}
function nullExpand(
yVals: Array<number | null>,
nullIdxs: number[],
alignedLen: number,
): void {
for (let i = 0, xi, lastNullIdx = -1; i < nullIdxs.length; i++) {
const nullIdx = nullIdxs[i];
if (nullIdx > lastNullIdx) {
xi = nullIdx - 1;
while (xi >= 0 && yVals[xi] == null) {
yVals[xi--] = null;
}
xi = nullIdx + 1;
while (xi < alignedLen && yVals[xi] == null) {
yVals[(lastNullIdx = xi++)] = null;
}
}
}
}
export function join(
tables: AlignedData[],
nullModes?: number[][],
): AlignedData {
let xVals: Set<number>;
// eslint-disable-next-line prefer-const
xVals = new Set();
for (let ti = 0; ti < tables.length; ti++) {
const t = tables[ti];
const xs = t[0];
const len = xs.length;
for (let i = 0; i < len; i++) {
xVals.add(xs[i]);
}
}
const data = [Array.from(xVals).sort((a, b) => a - b)];
const alignedLen = data[0].length;
const xIdxs = new Map();
for (let i = 0; i < alignedLen; i++) {
xIdxs.set(data[0][i], i);
}
for (let ti = 0; ti < tables.length; ti++) {
const t = tables[ti];
const xs = t[0];
for (let si = 1; si < t.length; si++) {
const ys = t[si];
const yVals = Array(alignedLen).fill(undefined);
const nullMode = nullModes ? nullModes[ti][si] : NULL_RETAIN;
const nullIdxs = [];
for (let i = 0; i < ys.length; i++) {
const yVal = ys[i];
const alignedIdx = xIdxs.get(xs[i]);
if (yVal === null) {
if (nullMode !== NULL_REMOVE) {
yVals[alignedIdx] = yVal;
if (nullMode === NULL_EXPAND) {
nullIdxs.push(alignedIdx);
}
}
} else {
yVals[alignedIdx] = yVal;
}
}
nullExpand(yVals, nullIdxs, alignedLen);
data.push(yVals);
}
}
return data as AlignedData;
}
export function histogram(
vals: number[],
getBucket: (v: number) => number,
sort?: ((a: number, b: number) => number) | null,
): AlignedData {
const hist = new Map();
for (let i = 0; i < vals.length; i++) {
let v = vals[i];
if (v != null) {
v = getBucket(v);
}
const entry = hist.get(v);
if (entry) {
entry.count++;
} else {
hist.set(v, { value: v, count: 1 });
}
}
const bins = [...hist.values()];
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
sort && bins.sort((a, b) => sort(a.value, b.value));
const values = Array(bins.length);
const counts = Array(bins.length);
for (let i = 0; i < bins.length; i++) {
values[i] = bins[i].value;
counts[i] = bins[i].count;
}
return [values, counts];
}
export function replaceUndefinedWithNull(data: AlignedData): AlignedData {
const arrays = data as (number | null | undefined)[][];
for (let i = 0; i < arrays.length; i++) {
for (let j = 0; j < arrays[i].length; j++) {
if (arrays[i][j] === undefined) {
arrays[i][j] = null;
}
}
}
return data;
}
export function addNullToFirstHistogram(
data: AlignedData,
bucketSize: number,
): void {
const histograms = data as (number | null)[][];
if (
histograms.length > 0 &&
histograms[0].length > 0 &&
histograms[0][0] !== null
) {
histograms[0].unshift(histograms[0][0] - bucketSize);
for (let i = 1; i < histograms.length; i++) {
histograms[i].unshift(null);
}
}
}

View File

@@ -1,93 +0,0 @@
.forgot-password-title {
font-family: var(--label-large-600-font-family);
font-size: var(--label-large-600-font-size);
font-weight: var(--label-large-600-font-weight);
letter-spacing: var(--label-large-600-letter-spacing);
line-height: 1.45;
color: var(--l1-foreground);
margin: 0;
}
.forgot-password-description {
font-family: var(--paragraph-base-400-font-family);
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
line-height: var(--paragraph-base-400-line-height);
letter-spacing: -0.065px;
color: var(--l2-foreground);
margin: 0;
text-align: center;
max-width: 317px;
}
.forgot-password-form {
width: 100%;
// Label styling
.forgot-password-label {
font-family: Inter, sans-serif;
font-size: 13px;
font-weight: 600;
line-height: 1;
letter-spacing: -0.065px;
color: var(--l1-foreground);
margin-bottom: 12px;
display: block;
.lightMode & {
color: var(--text-ink-500);
}
}
// Parent container for fields
.forgot-password-field {
width: 100%;
display: flex;
flex-direction: column;
}
&.ant-form {
display: flex;
flex-direction: column;
align-items: flex-start;
.ant-form-item {
margin-bottom: 0px;
width: 100%;
}
}
}
.forgot-password-actions {
display: flex;
gap: 12px;
width: 100%;
> .forgot-password-back-button,
> .login-submit-btn {
flex: 1 1 0%;
}
}
.forgot-password-back-button {
height: 32px;
padding: 10px 16px;
border-radius: 2px;
font-family: Inter, sans-serif;
font-size: 11px;
font-weight: 500;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
background: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l1-foreground);
&:hover:not(:disabled) {
background: var(--l3-border);
border-color: var(--l3-border);
opacity: 0.9;
}
}

View File

@@ -1,41 +0,0 @@
import { Button } from '@signozhq/button';
import { ArrowLeft, Mail } from '@signozhq/icons';
interface SuccessScreenProps {
onBackToLogin: () => void;
}
function SuccessScreen({ onBackToLogin }: SuccessScreenProps): JSX.Element {
return (
<div className="login-form-container">
<div className="forgot-password-form">
<div className="login-form-header">
<div className="login-form-emoji">
<Mail size={32} />
</div>
<h4 className="forgot-password-title">Check your email</h4>
<p className="forgot-password-description">
We&apos;ve sent a password reset link to your email. Please check your
inbox and follow the instructions to reset your password.
</p>
</div>
<div className="login-form-actions forgot-password-actions">
<Button
variant="solid"
color="primary"
type="button"
data-testid="back-to-login"
className="login-submit-btn"
onClick={onBackToLogin}
prefixIcon={<ArrowLeft size={12} />}
>
Back to login
</Button>
</div>
</div>
</div>
);
}
export default SuccessScreen;

View File

@@ -1,402 +0,0 @@
import ROUTES from 'constants/routes';
import history from 'lib/history';
import {
createErrorResponse,
handleInternalServerError,
rest,
server,
} from 'mocks-server/server';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import { OrgSessionContext } from 'types/api/v2/sessions/context/get';
import ForgotPassword, { ForgotPasswordRouteState } from '../index';
// Mock dependencies
jest.mock('lib/history', () => ({
__esModule: true,
default: {
push: jest.fn(),
location: {
search: '',
},
},
}));
const mockHistoryPush = history.push as jest.MockedFunction<
typeof history.push
>;
const FORGOT_PASSWORD_ENDPOINT = '*/api/v2/factor_password/forgot';
// Mock data
const mockSingleOrg: OrgSessionContext[] = [
{
id: 'org-1',
name: 'Test Organization',
authNSupport: {
password: [{ provider: 'email_password' }],
callback: [],
},
},
];
const mockMultipleOrgs: OrgSessionContext[] = [
{
id: 'org-1',
name: 'Organization One',
authNSupport: {
password: [{ provider: 'email_password' }],
callback: [],
},
},
{
id: 'org-2',
name: 'Organization Two',
authNSupport: {
password: [{ provider: 'email_password' }],
callback: [],
},
},
];
const TEST_EMAIL = 'jest.test@signoz.io';
const defaultProps: ForgotPasswordRouteState = {
email: TEST_EMAIL,
orgs: mockSingleOrg,
};
const multiOrgProps: ForgotPasswordRouteState = {
email: TEST_EMAIL,
orgs: mockMultipleOrgs,
};
describe('ForgotPassword Component', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
server.resetHandlers();
});
describe('Initial Render', () => {
it('renders forgot password form with all required elements', () => {
render(<ForgotPassword {...defaultProps} />);
expect(screen.getByText(/forgot your password\?/i)).toBeInTheDocument();
expect(
screen.getByText(/send a reset link to your inbox/i),
).toBeInTheDocument();
expect(screen.getByTestId('email')).toBeInTheDocument();
expect(screen.getByTestId('forgot-password-submit')).toBeInTheDocument();
expect(screen.getByTestId('forgot-password-back')).toBeInTheDocument();
});
it('pre-fills email from props', () => {
render(<ForgotPassword {...defaultProps} />);
const emailInput = screen.getByTestId('email');
expect(emailInput).toHaveValue(TEST_EMAIL);
});
it('disables email input field', () => {
render(<ForgotPassword {...defaultProps} />);
const emailInput = screen.getByTestId('email');
expect(emailInput).toBeDisabled();
});
it('does not show organization dropdown for single org', () => {
render(<ForgotPassword {...defaultProps} />);
expect(screen.queryByTestId('orgId')).not.toBeInTheDocument();
expect(screen.queryByText('Organization Name')).not.toBeInTheDocument();
});
it('enables submit button when email is provided with single org', () => {
render(<ForgotPassword {...defaultProps} />);
const submitButton = screen.getByTestId('forgot-password-submit');
expect(submitButton).not.toBeDisabled();
});
});
describe('Multiple Organizations', () => {
it('shows organization dropdown when multiple orgs exist', () => {
render(<ForgotPassword {...multiOrgProps} />);
expect(screen.getByTestId('orgId')).toBeInTheDocument();
expect(screen.getByText('Organization Name')).toBeInTheDocument();
});
it('disables submit button when org is not selected', () => {
const propsWithoutOrgId: ForgotPasswordRouteState = {
email: TEST_EMAIL,
orgs: mockMultipleOrgs,
};
render(<ForgotPassword {...propsWithoutOrgId} />);
const submitButton = screen.getByTestId('forgot-password-submit');
expect(submitButton).toBeDisabled();
});
it('enables submit button after selecting an organization', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<ForgotPassword {...multiOrgProps} />);
const submitButton = screen.getByTestId('forgot-password-submit');
expect(submitButton).toBeDisabled();
// Click on the dropdown to reveal the options
await user.click(screen.getByRole('combobox'));
await user.click(screen.getByText('Organization One'));
await waitFor(() => {
expect(submitButton).not.toBeDisabled();
});
});
it('pre-selects organization when orgId is provided', () => {
const propsWithOrgId: ForgotPasswordRouteState = {
email: TEST_EMAIL,
orgId: 'org-1',
orgs: mockMultipleOrgs,
};
render(<ForgotPassword {...propsWithOrgId} />);
const submitButton = screen.getByTestId('forgot-password-submit');
expect(submitButton).not.toBeDisabled();
});
});
describe('Form Submission - Success', () => {
it('successfully submits forgot password request and shows success screen', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) =>
res(ctx.status(200), ctx.json({ status: 'success' })),
),
);
render(<ForgotPassword {...defaultProps} />);
const submitButton = screen.getByTestId('forgot-password-submit');
await user.click(submitButton);
expect(await screen.findByText(/check your email/i)).toBeInTheDocument();
expect(
screen.getByText(/we've sent a password reset link/i),
).toBeInTheDocument();
});
it('shows back to login button on success screen', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) =>
res(ctx.status(200), ctx.json({ status: 'success' })),
),
);
render(<ForgotPassword {...defaultProps} />);
const submitButton = screen.getByTestId('forgot-password-submit');
await user.click(submitButton);
expect(await screen.findByTestId('back-to-login')).toBeInTheDocument();
});
it('redirects to login when clicking back to login on success screen', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) =>
res(ctx.status(200), ctx.json({ status: 'success' })),
),
);
render(<ForgotPassword {...defaultProps} />);
const submitButton = screen.getByTestId('forgot-password-submit');
await user.click(submitButton);
expect(await screen.findByTestId('back-to-login')).toBeInTheDocument();
const backToLoginButton = screen.getByTestId('back-to-login');
await user.click(backToLoginButton);
expect(mockHistoryPush).toHaveBeenCalledWith(ROUTES.LOGIN);
});
});
describe('Form Submission - Error Handling', () => {
it('displays error message when forgot password API fails', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(
rest.post(
FORGOT_PASSWORD_ENDPOINT,
createErrorResponse(400, 'USER_NOT_FOUND', 'User not found'),
),
);
render(<ForgotPassword {...defaultProps} />);
const submitButton = screen.getByTestId('forgot-password-submit');
await user.click(submitButton);
expect(await screen.findByText(/user not found/i)).toBeInTheDocument();
});
it('displays error message when API returns server error', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(rest.post(FORGOT_PASSWORD_ENDPOINT, handleInternalServerError));
render(<ForgotPassword {...defaultProps} />);
const submitButton = screen.getByTestId('forgot-password-submit');
await user.click(submitButton);
expect(
await screen.findByText(/internal server error occurred/i),
).toBeInTheDocument();
});
it('clears error message on new submission attempt', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
let requestCount = 0;
server.use(
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) => {
requestCount += 1;
if (requestCount === 1) {
return res(
ctx.status(400),
ctx.json({
error: {
code: 'USER_NOT_FOUND',
message: 'User not found',
},
}),
);
}
return res(ctx.status(200), ctx.json({ status: 'success' }));
}),
);
render(<ForgotPassword {...defaultProps} />);
const submitButton = screen.getByTestId('forgot-password-submit');
await user.click(submitButton);
expect(await screen.findByText(/user not found/i)).toBeInTheDocument();
// Click submit again
await user.click(submitButton);
await waitFor(() => {
expect(screen.queryByText(/user not found/i)).not.toBeInTheDocument();
});
expect(await screen.findByText(/check your email/i)).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('redirects to login when clicking back button on form', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<ForgotPassword {...defaultProps} />);
const backButton = screen.getByTestId('forgot-password-back');
await user.click(backButton);
expect(mockHistoryPush).toHaveBeenCalledWith(ROUTES.LOGIN);
});
});
describe('Loading States', () => {
it('shows loading state during API call', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) =>
res(ctx.delay(100), ctx.status(200), ctx.json({ status: 'success' })),
),
);
render(<ForgotPassword {...defaultProps} />);
const submitButton = screen.getByTestId('forgot-password-submit');
await user.click(submitButton);
// Button should show loading state
expect(await screen.findByText(/sending\.\.\./i)).toBeInTheDocument();
});
it('disables submit button during loading', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(
rest.post(FORGOT_PASSWORD_ENDPOINT, (_req, res, ctx) =>
res(ctx.delay(100), ctx.status(200), ctx.json({ status: 'success' })),
),
);
render(<ForgotPassword {...defaultProps} />);
const submitButton = screen.getByTestId('forgot-password-submit');
await user.click(submitButton);
await waitFor(() => {
expect(submitButton).toBeDisabled();
});
});
});
describe('Edge Cases', () => {
it('handles empty email gracefully', () => {
const propsWithEmptyEmail: ForgotPasswordRouteState = {
email: '',
orgs: mockSingleOrg,
};
render(<ForgotPassword {...propsWithEmptyEmail} />);
const submitButton = screen.getByTestId('forgot-password-submit');
expect(submitButton).toBeDisabled();
});
it('handles whitespace-only email', () => {
const propsWithWhitespaceEmail: ForgotPasswordRouteState = {
email: ' ',
orgs: mockSingleOrg,
};
render(<ForgotPassword {...propsWithWhitespaceEmail} />);
const submitButton = screen.getByTestId('forgot-password-submit');
expect(submitButton).toBeDisabled();
});
it('handles empty orgs array by disabling submission', () => {
const propsWithNoOrgs: ForgotPasswordRouteState = {
email: TEST_EMAIL,
orgs: [],
};
render(<ForgotPassword {...propsWithNoOrgs} />);
// Should not show org dropdown
expect(screen.queryByTestId('orgId')).not.toBeInTheDocument();
// Submit should be disabled because no orgId can be determined
const submitButton = screen.getByTestId('forgot-password-submit');
expect(submitButton).toBeDisabled();
});
});
});

View File

@@ -1,217 +0,0 @@
import { useCallback, useEffect, useMemo } from 'react';
import { Button } from '@signozhq/button';
import { ArrowLeft, ArrowRight } from '@signozhq/icons';
import { Input } from '@signozhq/input';
import { Form, Select } from 'antd';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { useForgotPassword } from 'api/generated/services/users';
import { AxiosError } from 'axios';
import AuthError from 'components/AuthError/AuthError';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { ErrorV2Resp } from 'types/api';
import APIError from 'types/api/error';
import { OrgSessionContext } from 'types/api/v2/sessions/context/get';
import SuccessScreen from './SuccessScreen';
import './ForgotPassword.styles.scss';
import 'container/Login/Login.styles.scss';
type FormValues = {
email: string;
orgId: string;
};
export type ForgotPasswordRouteState = {
email: string;
orgId?: string;
orgs: OrgSessionContext[];
};
function ForgotPassword({
email,
orgId,
orgs,
}: ForgotPasswordRouteState): JSX.Element {
const [form] = Form.useForm<FormValues>();
const {
mutate: forgotPasswordMutate,
isLoading,
isSuccess,
error: mutationError,
} = useForgotPassword();
const errorMessage = useMemo(() => {
if (!mutationError) {
return undefined;
}
try {
ErrorResponseHandlerV2(mutationError as AxiosError<ErrorV2Resp>);
} catch (apiError) {
return apiError as APIError;
}
}, [mutationError]);
const initialOrgId = useMemo((): string | undefined => {
if (orgId) {
return orgId;
}
if (orgs.length === 1) {
return orgs[0]?.id;
}
return undefined;
}, [orgId, orgs]);
const watchedEmail = Form.useWatch('email', form);
const selectedOrgId = Form.useWatch('orgId', form);
useEffect(() => {
form.setFieldsValue({
email,
orgId: initialOrgId,
});
}, [email, form, initialOrgId]);
const hasMultipleOrgs = orgs.length > 1;
const isSubmitEnabled = useMemo((): boolean => {
if (isLoading) {
return false;
}
if (!watchedEmail?.trim()) {
return false;
}
// Ensure we have an orgId (either selected from dropdown or the initial one)
const currentOrgId = hasMultipleOrgs ? selectedOrgId : initialOrgId;
return Boolean(currentOrgId);
}, [watchedEmail, selectedOrgId, isLoading, initialOrgId, hasMultipleOrgs]);
const handleSubmit = useCallback((): void => {
const values = form.getFieldsValue();
const currentOrgId = hasMultipleOrgs ? values.orgId : initialOrgId;
if (!currentOrgId) {
return;
}
// Call the forgot password API
forgotPasswordMutate({
data: {
email: values.email,
orgId: currentOrgId,
frontendBaseURL: window.location.origin,
},
});
}, [form, forgotPasswordMutate, initialOrgId, hasMultipleOrgs]);
const handleBackToLogin = useCallback((): void => {
history.push(ROUTES.LOGIN);
}, []);
// Success screen
if (isSuccess) {
return <SuccessScreen onBackToLogin={handleBackToLogin} />;
}
// Form screen
return (
<div className="login-form-container">
<Form
form={form}
onFinish={handleSubmit}
className="forgot-password-form"
initialValues={{
email,
orgId: initialOrgId,
}}
>
<div className="login-form-header">
<div className="login-form-emoji">
<img src="/svgs/tv.svg" alt="TV" width="32" height="32" />
</div>
<h4 className="forgot-password-title">Forgot your password?</h4>
<p className="forgot-password-description">
Send a reset link to your inbox and get back to monitoring.
</p>
</div>
<div className="login-form-card">
<div className="forgot-password-field">
<label className="forgot-password-label" htmlFor="forgotPasswordEmail">
Email address
</label>
<Form.Item name="email">
<Input
type="email"
id="forgotPasswordEmail"
data-testid="email"
required
disabled
className="login-form-input"
/>
</Form.Item>
</div>
{hasMultipleOrgs && (
<div className="forgot-password-field">
<label className="forgot-password-label" htmlFor="orgId">
Organization Name
</label>
<Form.Item
name="orgId"
rules={[{ required: true, message: 'Please select your organization' }]}
>
<Select
id="orgId"
data-testid="orgId"
className="login-form-input login-form-select-no-border"
placeholder="Select your organization"
options={orgs.map((org) => ({
value: org.id,
label: org.name || 'default',
}))}
/>
</Form.Item>
</div>
)}
</div>
{errorMessage && <AuthError error={errorMessage} />}
<div className="login-form-actions forgot-password-actions">
<Button
variant="solid"
type="button"
data-testid="forgot-password-back"
className="forgot-password-back-button"
onClick={handleBackToLogin}
prefixIcon={<ArrowLeft size={12} />}
>
Back to login
</Button>
<Button
disabled={!isSubmitEnabled}
loading={isLoading}
variant="solid"
color="primary"
type="submit"
data-testid="forgot-password-submit"
className="login-submit-btn"
suffixIcon={<ArrowRight size={12} />}
>
{isLoading ? 'Sending...' : 'Send reset link'}
</Button>
</div>
</Form>
</div>
);
}
export default ForgotPassword;

View File

@@ -2,6 +2,7 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation } from 'react-query';
import { useHistory } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { Color } from '@signozhq/design-tokens';
@@ -26,20 +27,12 @@ import {
} from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
import { CollapseProps } from 'antd/lib';
import {
useCreateIngestionKey,
useCreateIngestionKeyLimit,
useDeleteIngestionKey,
useDeleteIngestionKeyLimit,
useGetIngestionKeys,
useSearchIngestionKeys,
useUpdateIngestionKey,
useUpdateIngestionKeyLimit,
} from 'api/generated/services/gateway';
import {
GatewaytypesIngestionKeyDTO,
RenderErrorResponseDTO,
} from 'api/generated/services/sigNoz.schemas';
import createIngestionKeyApi from 'api/IngestionKeys/createIngestionKey';
import deleteIngestionKey from 'api/IngestionKeys/deleteIngestionKey';
import createLimitForIngestionKeyApi from 'api/IngestionKeys/limits/createLimitsForKey';
import deleteLimitsForIngestionKey from 'api/IngestionKeys/limits/deleteLimitsForIngestionKey';
import updateLimitForIngestionKeyApi from 'api/IngestionKeys/limits/updateLimitsForIngestionKey';
import updateIngestionKey from 'api/IngestionKeys/updateIngestionKey';
import { AxiosError } from 'axios';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import Tags from 'components/Tags/Tags';
@@ -51,6 +44,7 @@ import ROUTES from 'constants/routes';
import { INITIAL_ALERT_THRESHOLD_STATE } from 'container/CreateAlertV2/context/constants';
import dayjs from 'dayjs';
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { useNotifications } from 'hooks/useNotifications';
import { cloneDeep, isNil, isUndefined } from 'lodash-es';
@@ -72,12 +66,16 @@ import {
} from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useTimezone } from 'providers/Timezone';
import { ErrorResponse } from 'types/api';
import {
AddLimitProps,
LimitProps,
UpdateLimitProps,
} from 'types/api/ingestionKeys/limits/types';
import { PaginationProps } from 'types/api/ingestionKeys/types';
import {
IngestionKeyProps,
PaginationProps,
} from 'types/api/ingestionKeys/types';
import { MeterAggregateOperator } from 'types/common/queryBuilder';
import { USER_ROLES } from 'types/roles';
import { getDaysUntilExpiry } from 'utils/timeUtils';
@@ -88,10 +86,6 @@ const { Option } = Select;
const BYTES = 1073741824;
const INITIAL_PAGE_SIZE = 10;
const SEARCH_PAGE_SIZE = 100;
const FIRST_PAGE = 1;
const COUNT_MULTIPLIER = {
thousand: 1000,
million: 1000000,
@@ -117,8 +111,6 @@ export const showErrorNotification = (
): void => {
notifications.error({
message: err.message || SOMETHING_WENT_WRONG,
description: (err as AxiosError<RenderErrorResponseDTO>).response?.data?.error
?.message,
});
};
@@ -171,20 +163,15 @@ function MultiIngestionSettings(): JSX.Element {
const [updatedTags, setUpdatedTags] = useState<string[]>([]);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [isEditAddLimitOpen, setIsEditAddLimitOpen] = useState(false);
const [
activeAPIKey,
setActiveAPIKey,
] = useState<GatewaytypesIngestionKeyDTO | null>(null);
const [activeAPIKey, setActiveAPIKey] = useState<IngestionKeyProps | null>();
const [activeSignal, setActiveSignal] = useState<LimitProps | null>(null);
const [searchValue, setSearchValue] = useState<string>('');
const [searchText, setSearchText] = useState<string>('');
const [dataSource, setDataSource] = useState<GatewaytypesIngestionKeyDTO[]>(
[],
);
const [dataSource, setDataSource] = useState<IngestionKeyProps[]>([]);
const [paginationParams, setPaginationParams] = useState<PaginationProps>({
page: FIRST_PAGE,
per_page: INITIAL_PAGE_SIZE,
page: 1,
per_page: 10,
});
const [totalIngestionKeys, setTotalIngestionKeys] = useState(0);
@@ -199,7 +186,7 @@ function MultiIngestionSettings(): JSX.Element {
const [
createLimitForIngestionKeyError,
setCreateLimitForIngestionKeyError,
] = useState<string | null>(null);
] = useState<ErrorResponse | null>(null);
const [
hasUpdateLimitForIngestionKeyError,
@@ -209,7 +196,7 @@ function MultiIngestionSettings(): JSX.Element {
const [
updateLimitForIngestionKeyError,
setUpdateLimitForIngestionKeyError,
] = useState<string | null>(null);
] = useState<ErrorResponse | null>(null);
const { t } = useTranslation(['ingestionKeys']);
@@ -229,11 +216,7 @@ function MultiIngestionSettings(): JSX.Element {
handleFormReset();
};
const showDeleteModal = (apiKey: GatewaytypesIngestionKeyDTO): void => {
setHasCreateLimitForIngestionKeyError(false);
setCreateLimitForIngestionKeyError(null);
setHasUpdateLimitForIngestionKeyError(false);
setUpdateLimitForIngestionKeyError(null);
const showDeleteModal = (apiKey: IngestionKeyProps): void => {
setActiveAPIKey(apiKey);
setIsDeleteModalOpen(true);
};
@@ -250,11 +233,7 @@ function MultiIngestionSettings(): JSX.Element {
setIsAddModalOpen(false);
};
const showEditModal = (apiKey: GatewaytypesIngestionKeyDTO): void => {
setHasCreateLimitForIngestionKeyError(false);
setCreateLimitForIngestionKeyError(null);
setHasUpdateLimitForIngestionKeyError(false);
setUpdateLimitForIngestionKeyError(null);
const showEditModal = (apiKey: IngestionKeyProps): void => {
setActiveAPIKey(apiKey);
handleFormReset();
setUpdatedTags(apiKey.tags || []);
@@ -269,10 +248,6 @@ function MultiIngestionSettings(): JSX.Element {
};
const showAddModal = (): void => {
setHasCreateLimitForIngestionKeyError(false);
setCreateLimitForIngestionKeyError(null);
setHasUpdateLimitForIngestionKeyError(false);
setUpdateLimitForIngestionKeyError(null);
setUpdatedTags([]);
setActiveAPIKey(null);
setIsAddModalOpen(true);
@@ -283,62 +258,27 @@ function MultiIngestionSettings(): JSX.Element {
setActiveSignal(null);
};
// Use search API when searchText is present, otherwise use normal get API
const isSearching = searchText.length > 0;
const {
data: ingestionKeysData,
isLoading: isLoadingGet,
isRefetching: isRefetchingGet,
refetch: refetchGetAPIKeys,
error: getError,
isError: isGetError,
} = useGetIngestionKeys(
{
...paginationParams,
},
{
query: {
enabled: !isSearching,
},
},
);
const {
data: searchIngestionKeysData,
isLoading: isLoadingSearch,
isRefetching: isRefetchingSearch,
refetch: refetchSearchAPIKeys,
error: searchError,
isError: isSearchError,
} = useSearchIngestionKeys(
{
page: FIRST_PAGE,
per_page: SEARCH_PAGE_SIZE,
name: searchText,
},
{
query: {
enabled: isSearching,
},
},
);
// Use the appropriate data based on which API is active
const ingestionKeys = isSearching
? searchIngestionKeysData
: ingestionKeysData;
const isLoading = isSearching ? isLoadingSearch : isLoadingGet;
const isRefetching = isSearching ? isRefetchingSearch : isRefetchingGet;
const refetchAPIKeys = isSearching ? refetchSearchAPIKeys : refetchGetAPIKeys;
const error = isSearching ? searchError : getError;
const isError = isSearching ? isSearchError : isGetError;
data: IngestionKeys,
isLoading,
isRefetching,
refetch: refetchAPIKeys,
error,
isError,
} = useGetAllIngestionsKeys({
search: searchText,
...paginationParams,
});
useEffect(() => {
setDataSource(ingestionKeys?.data.data?.keys || []);
setTotalIngestionKeys(ingestionKeys?.data?.data?._pagination?.total || 0);
setActiveAPIKey(IngestionKeys?.data.data[0]);
}, [IngestionKeys]);
useEffect(() => {
setDataSource(IngestionKeys?.data.data || []);
setTotalIngestionKeys(IngestionKeys?.data?._pagination?.total || 0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ingestionKeys?.data?.data]);
}, [IngestionKeys?.data?.data]);
useEffect(() => {
if (isError) {
@@ -357,7 +297,6 @@ function MultiIngestionSettings(): JSX.Element {
const clearSearch = (): void => {
setSearchValue('');
setSearchText('');
};
const {
@@ -370,54 +309,101 @@ function MultiIngestionSettings(): JSX.Element {
const {
mutate: createIngestionKey,
isLoading: isLoadingCreateAPIKey,
} = useCreateIngestionKey<AxiosError<RenderErrorResponseDTO>>();
} = useMutation(createIngestionKeyApi, {
onSuccess: (data) => {
setActiveAPIKey(data.payload);
setUpdatedTags([]);
hideAddViewModal();
refetchAPIKeys();
},
onError: (error) => {
showErrorNotification(notifications, error as AxiosError);
},
});
const {
mutate: updateAPIKey,
isLoading: isLoadingUpdateAPIKey,
} = useUpdateIngestionKey<AxiosError<RenderErrorResponseDTO>>();
const { mutate: updateAPIKey, isLoading: isLoadingUpdateAPIKey } = useMutation(
updateIngestionKey,
{
onSuccess: () => {
refetchAPIKeys();
setIsEditModalOpen(false);
},
onError: (error) => {
showErrorNotification(notifications, error as AxiosError);
},
},
);
const {
mutate: deleteAPIKey,
isLoading: isDeleteingAPIKey,
} = useDeleteIngestionKey<AxiosError<RenderErrorResponseDTO>>();
const { mutate: deleteAPIKey, isLoading: isDeleteingAPIKey } = useMutation(
deleteIngestionKey,
{
onSuccess: () => {
refetchAPIKeys();
setIsDeleteModalOpen(false);
},
onError: (error) => {
showErrorNotification(notifications, error as AxiosError);
},
},
);
const {
mutate: createLimitForIngestionKey,
isLoading: isLoadingLimitForKey,
} = useCreateIngestionKeyLimit<AxiosError<RenderErrorResponseDTO>>();
} = useMutation(createLimitForIngestionKeyApi, {
onSuccess: () => {
setActiveSignal(null);
setActiveAPIKey(null);
setIsEditAddLimitOpen(false);
setUpdatedTags([]);
hideAddViewModal();
refetchAPIKeys();
setHasCreateLimitForIngestionKeyError(false);
},
onError: (error: ErrorResponse) => {
setHasCreateLimitForIngestionKeyError(true);
setCreateLimitForIngestionKeyError(error);
},
});
const {
mutate: updateLimitForIngestionKey,
isLoading: isLoadingUpdatedLimitForKey,
} = useUpdateIngestionKeyLimit<AxiosError<RenderErrorResponseDTO>>();
} = useMutation(updateLimitForIngestionKeyApi, {
onSuccess: () => {
setActiveSignal(null);
setActiveAPIKey(null);
setIsEditAddLimitOpen(false);
setUpdatedTags([]);
hideAddViewModal();
refetchAPIKeys();
setHasUpdateLimitForIngestionKeyError(false);
},
onError: (error: ErrorResponse) => {
setHasUpdateLimitForIngestionKeyError(true);
setUpdateLimitForIngestionKeyError(error);
},
});
const {
mutate: deleteLimitForKey,
isLoading: isDeletingLimit,
} = useDeleteIngestionKeyLimit<AxiosError<RenderErrorResponseDTO>>();
const { mutate: deleteLimitForKey, isLoading: isDeletingLimit } = useMutation(
deleteLimitsForIngestionKey,
{
onSuccess: () => {
setIsDeleteModalOpen(false);
setIsDeleteLimitModalOpen(false);
refetchAPIKeys();
},
onError: (error) => {
showErrorNotification(notifications, error as AxiosError);
},
},
);
const onDeleteHandler = (): void => {
clearSearch();
if (activeAPIKey && activeAPIKey.id) {
deleteAPIKey(
{
pathParams: { keyId: activeAPIKey.id },
},
{
onSuccess: () => {
notifications.success({
message: 'Ingestion key deleted successfully',
});
refetchAPIKeys();
setIsDeleteModalOpen(false);
},
onError: (error) => {
showErrorNotification(notifications, error as AxiosError);
},
},
);
if (activeAPIKey) {
deleteAPIKey(activeAPIKey.id);
}
};
@@ -425,31 +411,15 @@ function MultiIngestionSettings(): JSX.Element {
editForm
.validateFields()
.then((values) => {
if (activeAPIKey && activeAPIKey.id) {
updateAPIKey(
{
pathParams: { keyId: activeAPIKey.id },
data: {
name: values.name,
tags: updatedTags,
expires_at: new Date(
dayjs(values.expires_at).endOf('day').toISOString(),
),
},
if (activeAPIKey) {
updateAPIKey({
id: activeAPIKey.id,
data: {
name: values.name,
tags: updatedTags,
expires_at: dayjs(values.expires_at).endOf('day').toISOString(),
},
{
onSuccess: () => {
notifications.success({
message: 'Ingestion key updated successfully',
});
refetchAPIKeys();
setIsEditModalOpen(false);
},
onError: (error) => {
showErrorNotification(notifications, error as AxiosError);
},
},
);
});
}
})
.catch((errorInfo) => {
@@ -465,30 +435,10 @@ function MultiIngestionSettings(): JSX.Element {
const requestPayload = {
name: values.name,
tags: updatedTags,
expires_at: new Date(dayjs(values.expires_at).endOf('day').toISOString()),
expires_at: dayjs(values.expires_at).endOf('day').toISOString(),
};
createIngestionKey(
{
data: requestPayload,
},
{
onSuccess: (_data) => {
notifications.success({
message: 'Ingestion key created successfully',
});
// The new API returns GatewaytypesGettableCreatedIngestionKeyDTO with only id and value
// We rely on refetchAPIKeys to get the full key object
setActiveAPIKey(null);
setUpdatedTags([]);
hideAddViewModal();
refetchAPIKeys();
},
onError: (error) => {
showErrorNotification(notifications, error as AxiosError);
},
},
);
createIngestionKey(requestPayload);
}
})
.catch((errorInfo) => {
@@ -515,7 +465,7 @@ function MultiIngestionSettings(): JSX.Element {
formatTimezoneAdjustedTimestamp(date, DATE_TIME_FORMATS.UTC_MONTH_COMPACT);
const showDeleteLimitModal = (
APIKey: GatewaytypesIngestionKeyDTO,
APIKey: IngestionKeyProps,
limit: LimitProps,
): void => {
setActiveAPIKey(APIKey);
@@ -539,17 +489,9 @@ function MultiIngestionSettings(): JSX.Element {
/* eslint-disable sonarjs/cognitive-complexity */
const handleAddLimit = (
APIKey: GatewaytypesIngestionKeyDTO,
APIKey: IngestionKeyProps,
signalName: string,
): void => {
if (!APIKey.id) {
notifications.error({
message: 'Invalid ingestion key',
description: 'Cannot create limit for ingestion key without a valid ID',
});
return;
}
const {
dailyLimit,
secondsLimit,
@@ -634,49 +576,13 @@ function MultiIngestionSettings(): JSX.Element {
return;
}
createLimitForIngestionKey(
{
pathParams: { keyId: payload.keyID },
data: {
signal: payload.signal,
config: payload.config,
},
},
{
onSuccess: () => {
notifications.success({
message: 'Limit created successfully',
});
setActiveSignal(null);
setActiveAPIKey(null);
setIsEditAddLimitOpen(false);
setUpdatedTags([]);
hideAddViewModal();
refetchAPIKeys();
setHasCreateLimitForIngestionKeyError(false);
},
onError: (error: AxiosError<RenderErrorResponseDTO>) => {
setHasCreateLimitForIngestionKeyError(true);
setCreateLimitForIngestionKeyError(
error.response?.data?.error?.message || 'Failed to create limit',
);
},
},
);
createLimitForIngestionKey(payload);
};
const handleUpdateLimit = (
APIKey: GatewaytypesIngestionKeyDTO,
APIKey: IngestionKeyProps,
signal: LimitProps,
): void => {
if (!signal.id) {
notifications.error({
message: 'Invalid limit',
description: 'Cannot update limit without a valid ID',
});
return;
}
const {
dailyLimit,
secondsLimit,
@@ -738,34 +644,7 @@ function MultiIngestionSettings(): JSX.Element {
}
}
updateLimitForIngestionKey(
{
pathParams: { limitId: payload.limitID },
data: {
config: payload.config,
},
},
{
onSuccess: () => {
notifications.success({
message: 'Limit updated successfully',
});
setActiveSignal(null);
setActiveAPIKey(null);
setIsEditAddLimitOpen(false);
setUpdatedTags([]);
hideAddViewModal();
refetchAPIKeys();
setHasUpdateLimitForIngestionKeyError(false);
},
onError: (error: AxiosError<RenderErrorResponseDTO>) => {
setHasUpdateLimitForIngestionKeyError(true);
setUpdateLimitForIngestionKeyError(
error.response?.data?.error?.message || 'Failed to update limit',
);
},
},
);
updateLimitForIngestionKey(payload);
};
/* eslint-enable sonarjs/cognitive-complexity */
@@ -777,7 +656,7 @@ function MultiIngestionSettings(): JSX.Element {
};
const enableEditLimitMode = (
APIKey: GatewaytypesIngestionKeyDTO,
APIKey: IngestionKeyProps,
signal: LimitProps,
): void => {
const dayCount = signal?.config?.day?.count;
@@ -786,11 +665,6 @@ function MultiIngestionSettings(): JSX.Element {
const dayCountConverted = countToUnit(dayCount || 0);
const secondCountConverted = countToUnit(secondCount || 0);
setHasCreateLimitForIngestionKeyError(false);
setCreateLimitForIngestionKeyError(null);
setHasUpdateLimitForIngestionKeyError(false);
setUpdateLimitForIngestionKeyError(null);
setActiveAPIKey(APIKey);
setActiveSignal({
...signal,
@@ -829,31 +703,14 @@ function MultiIngestionSettings(): JSX.Element {
const onDeleteLimitHandler = (): void => {
if (activeSignal && activeSignal.id) {
deleteLimitForKey(
{
pathParams: { limitId: activeSignal.id },
},
{
onSuccess: () => {
notifications.success({
message: 'Limit deleted successfully',
});
setIsDeleteModalOpen(false);
setIsDeleteLimitModalOpen(false);
refetchAPIKeys();
},
onError: (error) => {
showErrorNotification(notifications, error as AxiosError);
},
},
);
deleteLimitForKey(activeSignal.id);
}
};
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const handleCreateAlert = (
APIKey: GatewaytypesIngestionKeyDTO,
APIKey: IngestionKeyProps,
signal: LimitProps,
): void => {
let metricName = '';
@@ -914,61 +771,31 @@ function MultiIngestionSettings(): JSX.Element {
history.push(URL);
};
const columns: AntDTableProps<GatewaytypesIngestionKeyDTO>['columns'] = [
const columns: AntDTableProps<IngestionKeyProps>['columns'] = [
{
title: 'Ingestion Key',
key: 'ingestion-key',
// eslint-disable-next-line sonarjs/cognitive-complexity
render: (APIKey: GatewaytypesIngestionKeyDTO): JSX.Element => {
const createdOn = APIKey?.created_at
? getFormattedTime(
dayjs(APIKey.created_at).toISOString(),
formatTimezoneAdjustedTimestamp,
)
: '';
render: (APIKey: IngestionKeyProps): JSX.Element => {
const createdOn = getFormattedTime(
APIKey.created_at,
formatTimezoneAdjustedTimestamp,
);
const expiresOn =
!APIKey?.expires_at ||
dayjs(APIKey?.expires_at).toISOString() === '0001-01-01T00:00:00.000Z'
!APIKey?.expires_at || APIKey?.expires_at === '0001-01-01T00:00:00Z'
? 'No Expiry'
: getFormattedTime(
dayjs(APIKey?.expires_at).toISOString(),
formatTimezoneAdjustedTimestamp,
);
: getFormattedTime(APIKey?.expires_at, formatTimezoneAdjustedTimestamp);
const updatedOn = APIKey?.updated_at
? getFormattedTime(
dayjs(APIKey.updated_at).toISOString(),
formatTimezoneAdjustedTimestamp,
)
: '';
const onCopyKey = (e: React.MouseEvent): void => {
e.stopPropagation();
e.preventDefault();
if (APIKey?.value) {
handleCopyKey(APIKey.value);
}
};
const onEditKey = (e: React.MouseEvent): void => {
e.stopPropagation();
e.preventDefault();
showEditModal(APIKey);
};
const onDeleteKey = (e: React.MouseEvent): void => {
e.stopPropagation();
e.preventDefault();
showDeleteModal(APIKey);
};
const updatedOn = getFormattedTime(
APIKey?.updated_at,
formatTimezoneAdjustedTimestamp,
);
// Convert array of limits to a dictionary for quick access
const limitsDict: Record<string, LimitProps> = {};
APIKey.limits?.forEach((limitItem) => {
if (limitItem.signal && limitItem.id) {
limitsDict[limitItem.signal] = limitItem as LimitProps;
}
APIKey.limits?.forEach((limitItem: LimitProps) => {
limitsDict[limitItem.signal] = limitItem;
});
const hasLimits = (signalName: string): boolean => !!limitsDict[signalName];
@@ -985,25 +812,39 @@ function MultiIngestionSettings(): JSX.Element {
<div className="ingestion-key-value">
<Typography.Text>
{APIKey?.value?.substring(0, 2)}********
{APIKey?.value
?.substring(APIKey?.value?.length ? APIKey.value.length - 2 : 0)
?.trim()}
{APIKey?.value.substring(0, 2)}********
{APIKey?.value.substring(APIKey.value.length - 2).trim()}
</Typography.Text>
<Copy className="copy-key-btn" size={12} onClick={onCopyKey} />
<Copy
className="copy-key-btn"
size={12}
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
handleCopyKey(APIKey.value);
}}
/>
</div>
</div>
<div className="action-btn">
<Button
className="periscope-btn ghost"
icon={<PenLine size={14} />}
onClick={onEditKey}
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
showEditModal(APIKey);
}}
/>
<Button
className="periscope-btn ghost"
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
onClick={onDeleteKey}
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
showDeleteModal(APIKey);
}}
/>
</div>
</div>
@@ -1013,7 +854,7 @@ function MultiIngestionSettings(): JSX.Element {
<Row>
<Col span={6}> ID </Col>
<Col span={12}>
<Typography.Text>{APIKey?.id}</Typography.Text>
<Typography.Text>{APIKey.id}</Typography.Text>
</Col>
</Row>
@@ -1065,39 +906,6 @@ function MultiIngestionSettings(): JSX.Element {
limit?.config?.second?.size !== undefined ||
limit?.config?.second?.count !== undefined;
const onEditSignalLimit = (e: React.MouseEvent): void => {
e.stopPropagation();
e.preventDefault();
enableEditLimitMode(APIKey, limit);
};
const onDeleteSignalLimit = (e: React.MouseEvent): void => {
e.stopPropagation();
e.preventDefault();
showDeleteLimitModal(APIKey, limit);
};
const onAddSignalLimit = (e: React.MouseEvent): void => {
e.stopPropagation();
e.preventDefault();
enableEditLimitMode(APIKey, {
id: signalName,
signal: signalName,
config: {},
});
};
const onSaveSignalLimit = (): void => {
if (!hasLimits(signalName)) {
handleAddLimit(APIKey, signalName);
} else {
handleUpdateLimit(APIKey, limitsDict[signalName]);
}
};
const onCreateSignalAlert = (): void =>
handleCreateAlert(APIKey, limitsDict[signalName]);
return (
<div className="signal" key={signalName}>
<div className="header">
@@ -1108,18 +916,22 @@ function MultiIngestionSettings(): JSX.Element {
<Button
className="periscope-btn ghost"
icon={<PenLine size={14} />}
disabled={
!!(activeAPIKey?.id === APIKey?.id && activeSignal)
}
onClick={onEditSignalLimit}
disabled={!!(activeAPIKey?.id === APIKey.id && activeSignal)}
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
enableEditLimitMode(APIKey, limit);
}}
/>
<Button
className="periscope-btn ghost"
icon={<Trash2 color={Color.BG_CHERRY_500} size={14} />}
disabled={
!!(activeAPIKey?.id === APIKey?.id && activeSignal)
}
onClick={onDeleteSignalLimit}
disabled={!!(activeAPIKey?.id === APIKey.id && activeSignal)}
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
showDeleteLimitModal(APIKey, limit);
}}
/>
</>
) : (
@@ -1128,8 +940,16 @@ function MultiIngestionSettings(): JSX.Element {
size="small"
shape="round"
icon={<PlusIcon size={14} />}
disabled={!!(activeAPIKey?.id === APIKey?.id && activeSignal)}
onClick={onAddSignalLimit}
disabled={!!(activeAPIKey?.id === APIKey.id && activeSignal)}
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
enableEditLimitMode(APIKey, {
id: signalName,
signal: signalName,
config: {},
});
}}
>
Limits
</Button>
@@ -1138,7 +958,7 @@ function MultiIngestionSettings(): JSX.Element {
</div>
<div className="signal-limit-values">
{activeAPIKey?.id === APIKey?.id &&
{activeAPIKey?.id === APIKey.id &&
activeSignal?.signal === signalName &&
isEditAddLimitOpen ? (
<Form
@@ -1334,27 +1154,27 @@ function MultiIngestionSettings(): JSX.Element {
</div>
</div>
{activeAPIKey?.id === APIKey?.id &&
{activeAPIKey?.id === APIKey.id &&
activeSignal.signal === signalName &&
!isLoadingLimitForKey &&
hasCreateLimitForIngestionKeyError &&
createLimitForIngestionKeyError && (
createLimitForIngestionKeyError?.error && (
<div className="error">
{createLimitForIngestionKeyError}
{createLimitForIngestionKeyError?.error}
</div>
)}
{activeAPIKey?.id === APIKey?.id &&
{activeAPIKey?.id === APIKey.id &&
activeSignal.signal === signalName &&
!isLoadingLimitForKey &&
hasUpdateLimitForIngestionKeyError &&
updateLimitForIngestionKeyError && (
updateLimitForIngestionKeyError?.error && (
<div className="error">
{updateLimitForIngestionKeyError}
{updateLimitForIngestionKeyError?.error}
</div>
)}
{activeAPIKey?.id === APIKey?.id &&
{activeAPIKey?.id === APIKey.id &&
activeSignal.signal === signalName &&
isEditAddLimitOpen && (
<div className="signal-limit-save-discard">
@@ -1368,7 +1188,13 @@ function MultiIngestionSettings(): JSX.Element {
loading={
isLoadingLimitForKey || isLoadingUpdatedLimitForKey
}
onClick={onSaveSignalLimit}
onClick={(): void => {
if (!hasLimits(signalName)) {
handleAddLimit(APIKey, signalName);
} else {
handleUpdateLimit(APIKey, limitsDict[signalName]);
}
}}
>
Save
</Button>
@@ -1449,7 +1275,9 @@ function MultiIngestionSettings(): JSX.Element {
className="set-alert-btn periscope-btn ghost"
type="text"
data-testid={`set-alert-btn-${signalName}`}
onClick={onCreateSignalAlert}
onClick={(): void =>
handleCreateAlert(APIKey, limitsDict[signalName])
}
/>
</Tooltip>
)}
@@ -1564,7 +1392,7 @@ function MultiIngestionSettings(): JSX.Element {
const handleTableChange = (pagination: TablePaginationConfig): void => {
setPaginationParams({
page: pagination?.current || 1,
per_page: INITIAL_PAGE_SIZE,
per_page: 10,
});
};
@@ -1662,7 +1490,7 @@ function MultiIngestionSettings(): JSX.Element {
showHeader={false}
onChange={handleTableChange}
pagination={{
pageSize: isSearching ? SEARCH_PAGE_SIZE : paginationParams?.per_page,
pageSize: paginationParams?.per_page,
hideOnSinglePage: true,
showTotal: (total: number, range: number[]): string =>
`${range[0]}-${range[1]} of ${total} Ingestion keys`,

View File

@@ -1,4 +1,3 @@
import { GatewaytypesGettableIngestionKeysDTO } from 'api/generated/services/sigNoz.schemas';
import { QueryParams } from 'constants/query';
import { rest, server } from 'mocks-server/server';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
@@ -19,12 +18,6 @@ interface TestAllIngestionKeyProps extends Omit<AllIngestionKeyProps, 'data'> {
data: TestIngestionKeyProps[];
}
// Gateway API response type (uses actual schema types for contract safety)
interface TestGatewayIngestionKeysResponse {
status: string;
data: GatewaytypesGettableIngestionKeysDTO;
}
// Mock useHistory.push to capture navigation URL used by MultiIngestionSettings
const mockPush = jest.fn() as jest.MockedFunction<(path: string) => void>;
jest.mock('react-router-dom', () => {
@@ -93,34 +86,32 @@ describe('MultiIngestionSettings Page', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
// Arrange API response with a metrics daily count limit so the alert button is visible
const response: TestGatewayIngestionKeysResponse = {
const response: TestAllIngestionKeyProps = {
status: 'success',
data: {
keys: [
{
name: 'Key One',
expires_at: new Date(TEST_EXPIRES_AT),
value: 'secret',
workspace_id: TEST_WORKSPACE_ID,
id: 'k1',
created_at: new Date(TEST_CREATED_UPDATED),
updated_at: new Date(TEST_CREATED_UPDATED),
tags: [],
limits: [
{
id: 'l1',
signal: 'metrics',
config: { day: { count: 1000 } },
},
],
},
],
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
},
data: [
{
name: 'Key One',
expires_at: TEST_EXPIRES_AT,
value: 'secret',
workspace_id: TEST_WORKSPACE_ID,
id: 'k1',
created_at: TEST_CREATED_UPDATED,
updated_at: TEST_CREATED_UPDATED,
tags: [],
limits: [
{
id: 'l1',
signal: 'metrics',
config: { day: { count: 1000 } },
},
],
},
],
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
};
server.use(
rest.get('*/api/v2/gateway/ingestion_keys*', (_req, res, ctx) =>
rest.get('*/workspaces/me/keys*', (_req, res, ctx) =>
res(ctx.status(200), ctx.json(response)),
),
);
@@ -266,95 +257,4 @@ describe('MultiIngestionSettings Page', () => {
'signoz.meter.log.size',
);
});
it('switches to search API when search text is entered', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
const getResponse: TestGatewayIngestionKeysResponse = {
status: 'success',
data: {
keys: [
{
name: 'Key Regular',
expires_at: new Date(TEST_EXPIRES_AT),
value: 'secret1',
workspace_id: TEST_WORKSPACE_ID,
id: 'k1',
created_at: new Date(TEST_CREATED_UPDATED),
updated_at: new Date(TEST_CREATED_UPDATED),
tags: [],
limits: [],
},
],
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
},
};
const searchResponse: TestGatewayIngestionKeysResponse = {
status: 'success',
data: {
keys: [
{
name: 'Key Search Result',
expires_at: new Date(TEST_EXPIRES_AT),
value: 'secret2',
workspace_id: TEST_WORKSPACE_ID,
id: 'k2',
created_at: new Date(TEST_CREATED_UPDATED),
updated_at: new Date(TEST_CREATED_UPDATED),
tags: [],
limits: [],
},
],
_pagination: { page: 1, per_page: 10, pages: 1, total: 1 },
},
};
const getHandler = jest.fn();
const searchHandler = jest.fn();
server.use(
rest.get('*/api/v2/gateway/ingestion_keys', (req, res, ctx) => {
if (req.url.pathname.endsWith('/search')) {
return undefined;
}
getHandler();
return res(ctx.status(200), ctx.json(getResponse));
}),
rest.get('*/api/v2/gateway/ingestion_keys/search', (_req, res, ctx) => {
searchHandler();
return res(ctx.status(200), ctx.json(searchResponse));
}),
);
render(<MultiIngestionSettings />, undefined, {
initialRoute: INGESTION_SETTINGS_ROUTE,
});
await screen.findByText('Key Regular');
expect(getHandler).toHaveBeenCalled();
expect(searchHandler).not.toHaveBeenCalled();
// Reset getHandler count to verify it's not called again during search
getHandler.mockClear();
// Type in search box
const searchInput = screen.getByPlaceholderText(
'Search for ingestion key...',
);
await user.type(searchInput, 'test');
await screen.findByText('Key Search Result');
expect(searchHandler).toHaveBeenCalled();
expect(getHandler).not.toHaveBeenCalled();
// Clear search
searchHandler.mockClear();
getHandler.mockClear();
await user.clear(searchInput);
await screen.findByText('Key Regular');
// Search API should be disabled when not searching
expect(searchHandler).not.toHaveBeenCalled();
});
});

View File

@@ -35,10 +35,10 @@
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
padding: 0px;
.more-filter-actions {
.group-by-clause {
display: flex;
align-items: center;
gap: 8px;
gap: 4px;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
@@ -53,7 +53,7 @@
}
}
.more-filter-actions:hover {
.group-by-clause:hover {
background-color: unset !important;
}
}
@@ -65,7 +65,7 @@
border: 1px solid var(--bg-vanilla-400);
background: var(--bg-vanilla-100) !important;
.more-filter-actions {
.group-by-clause {
color: var(--bg-ink-400);
}
}

View File

@@ -1,5 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/cognitive-complexity */
import React, { useCallback, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { Color } from '@signozhq/design-tokens';
@@ -17,12 +16,7 @@ import { MetricsType } from 'container/MetricsApplication/constant';
import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange';
import {
ArrowDownToDot,
ArrowUpFromDot,
Ellipsis,
RefreshCw,
} from 'lucide-react';
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
import { ExplorerViews } from 'pages/LogsExplorer/utils';
import { useTimezone } from 'providers/Timezone';
import {
@@ -211,70 +205,6 @@ export default function TableViewActions(
viewName,
]);
const handleReplaceFilter = useCallback((): void => {
if (!stagedQuery) {
return;
}
const normalizedDataType: DataTypes | undefined =
dataType && Object.values(DataTypes).includes(dataType as DataTypes)
? (dataType as DataTypes)
: undefined;
const updatedQuery = updateQueriesData(
stagedQuery,
'queryData',
(item, index) => {
// Only replace filters for index 0
if (index === 0) {
const newFilterItem: BaseAutocompleteData = {
key: fieldFilterKey,
type: fieldType || '',
dataType: normalizedDataType,
};
// Create new filter items array with single IN filter
const newFilters = {
items: [
{
id: '',
key: newFilterItem,
op: OPERATORS.IN,
value: [parseFieldValue(fieldData.value)],
},
],
op: 'AND',
};
// Clear the expression and update filters
return {
...item,
filters: newFilters,
filter: { expression: '' },
};
}
return item;
},
);
const queryData: ICurrentQueryData = {
name: viewName,
id: updatedQuery.id,
query: updatedQuery,
};
handleChangeSelectedView?.(ExplorerViews.LIST, queryData);
}, [
stagedQuery,
updateQueriesData,
fieldFilterKey,
fieldType,
dataType,
fieldData,
handleChangeSelectedView,
viewName,
]);
// Memoize textToCopy computation
const textToCopy = useMemo(() => {
let text = fieldData.value;
@@ -397,21 +327,13 @@ export default function TableViewActions(
content={
<div>
<Button
className="more-filter-actions"
className="group-by-clause"
type="text"
icon={<GroupByIcon />}
onClick={handleGroupByAttribute}
>
Group By Attribute
</Button>
<Button
className="more-filter-actions"
type="text"
icon={<RefreshCw size={14} />}
onClick={handleReplaceFilter}
>
Replace filters with this value
</Button>
</div>
}
rootClassName="table-view-actions-content"
@@ -483,21 +405,13 @@ export default function TableViewActions(
content={
<div>
<Button
className="more-filter-actions"
className="group-by-clause"
type="text"
icon={<GroupByIcon />}
onClick={handleGroupByAttribute}
>
Group By Attribute
</Button>
<Button
className="more-filter-actions"
type="text"
icon={<RefreshCw size={14} />}
onClick={handleReplaceFilter}
>
Replace filters with this value
</Button>
</div>
}
rootClassName="table-view-actions-content"

View File

@@ -407,10 +407,6 @@
color: var(--text-neutral-light-200) !important;
}
.ant-select-selection-item {
color: var(--text-ink-500) !important;
}
&:hover .ant-select-selector {
border-color: var(--bg-vanilla-300) !important;
}

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { Button } from '@signozhq/button';
import { Form, Input, Select, Typography } from 'antd';
import { Form, Input, Select, Tooltip, Typography } from 'antd';
import getVersion from 'api/v1/version/get';
import get from 'api/v2/sessions/context/get';
import post from 'api/v2/sessions/email_password/post';
@@ -220,20 +220,6 @@ function Login(): JSX.Element {
}
};
const handleForgotPasswordClick = useCallback((): void => {
const email = form.getFieldValue('email');
if (!email || !sessionsContext || !sessionsContext?.orgs?.length) {
return;
}
history.push(ROUTES.FORGOT_PASSWORD, {
email,
orgId: sessionsOrgId,
orgs: sessionsContext.orgs,
});
}, [form, sessionsContext, sessionsOrgId]);
useEffect(() => {
if (callbackAuthError) {
setErrorMessage(
@@ -359,16 +345,11 @@ function Login(): JSX.Element {
<ParentContainer>
<div className="password-label-container">
<Label htmlFor="Password">Password</Label>
<Typography.Link
className="forgot-password-link"
href="#"
onClick={(event): void => {
event.preventDefault();
handleForgotPasswordClick();
}}
>
Forgot password?
</Typography.Link>
<Tooltip title="Ask your admin to reset your password and send you a new invite link">
<Typography.Link className="forgot-password-link">
Forgot password?
</Typography.Link>
</Tooltip>
</div>
<FormContainer.Item name="password">
<Input.Password

View File

@@ -2,16 +2,13 @@ import { useEffect, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Button, Skeleton, Tooltip, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { useGetIngestionKeys } from 'api/generated/services/gateway';
import {
GatewaytypesIngestionKeyDTO,
RenderErrorResponseDTO,
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { DOCS_BASE_URL } from 'constants/app';
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys';
import { useNotifications } from 'hooks/useNotifications';
import { ArrowUpRight, Copy, Info, Key, TriangleAlert } from 'lucide-react';
import { IngestionKeyProps } from 'types/api/ingestionKeys/types';
import './IngestionDetails.styles.scss';
@@ -42,17 +39,17 @@ export default function OnboardingIngestionDetails(): JSX.Element {
const { notifications } = useNotifications();
const [, handleCopyToClipboard] = useCopyToClipboard();
const [
firstIngestionKey,
setFirstIngestionKey,
] = useState<GatewaytypesIngestionKeyDTO>({} as GatewaytypesIngestionKeyDTO);
const [firstIngestionKey, setFirstIngestionKey] = useState<IngestionKeyProps>(
{} as IngestionKeyProps,
);
const {
data: ingestionKeys,
isLoading: isIngestionKeysLoading,
error,
isError,
} = useGetIngestionKeys({
} = useGetAllIngestionsKeys({
search: '',
page: 1,
per_page: 10,
});
@@ -72,11 +69,8 @@ export default function OnboardingIngestionDetails(): JSX.Element {
};
useEffect(() => {
if (
ingestionKeys?.data?.data?.keys &&
ingestionKeys?.data.data.keys.length > 0
) {
setFirstIngestionKey(ingestionKeys?.data.data.keys[0]);
if (ingestionKeys?.data.data && ingestionKeys?.data.data.length > 0) {
setFirstIngestionKey(ingestionKeys?.data.data[0]);
}
}, [ingestionKeys]);
@@ -86,10 +80,7 @@ export default function OnboardingIngestionDetails(): JSX.Element {
<div className="ingestion-endpoint-section-error-container">
<Typography.Text className="ingestion-endpoint-section-error-text error">
<TriangleAlert size={14} />{' '}
{(error as AxiosError<RenderErrorResponseDTO>)?.response?.data?.error
?.message ||
(error as AxiosError)?.message ||
'Something went wrong'}
{(error as AxiosError)?.message || 'Something went wrong'}
</Typography.Text>
<div className="ingestion-setup-details-links">
@@ -185,7 +176,7 @@ export default function OnboardingIngestionDetails(): JSX.Element {
</Typography.Text>
<Typography.Text className="ingestion-key-value-copy">
{maskKey(firstIngestionKey?.value || '')}
{maskKey(firstIngestionKey?.value)}
<Copy
size={14}
@@ -195,9 +186,7 @@ export default function OnboardingIngestionDetails(): JSX.Element {
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INGESTION_KEY_COPIED}`,
{},
);
if (firstIngestionKey?.value) {
handleCopyKey(firstIngestionKey.value);
}
handleCopyKey(firstIngestionKey?.value);
}}
/>
</Typography.Text>

View File

@@ -32,7 +32,6 @@ export const routeConfig: Record<string, QueryParams[]> = {
[ROUTES.LIST_ALL_ALERT]: [QueryParams.resourceAttributes],
[ROUTES.LIST_LICENSES]: [QueryParams.resourceAttributes],
[ROUTES.LOGIN]: [QueryParams.resourceAttributes],
[ROUTES.FORGOT_PASSWORD]: [QueryParams.resourceAttributes],
[ROUTES.LOGS]: [QueryParams.resourceAttributes],
[ROUTES.LOGS_BASE]: [QueryParams.resourceAttributes],
[ROUTES.MY_SETTINGS]: [QueryParams.resourceAttributes],

View File

@@ -1,31 +0,0 @@
import { useMemo } from 'react';
import { BarTooltipProps, TooltipContentItem } from '../types';
import Tooltip from './Tooltip';
import { buildTooltipContent } from './utils';
export default function BarChartTooltip(props: BarTooltipProps): JSX.Element {
const content = useMemo(
(): TooltipContentItem[] =>
buildTooltipContent({
data: props.uPlotInstance.data,
series: props.uPlotInstance.series,
dataIndexes: props.dataIndexes,
activeSeriesIndex: props.seriesIndex,
uPlotInstance: props.uPlotInstance,
yAxisUnit: props.yAxisUnit ?? '',
decimalPrecision: props.decimalPrecision,
isStackedBarChart: props.isStackedBarChart,
}),
[
props.uPlotInstance,
props.seriesIndex,
props.dataIndexes,
props.yAxisUnit,
props.decimalPrecision,
props.isStackedBarChart,
],
);
return <Tooltip {...props} content={content} />;
}

View File

@@ -1,8 +0,0 @@
import { HistogramTooltipProps } from '../types';
import Tooltip from './Tooltip';
export default function HistogramTooltip(
props: HistogramTooltipProps,
): JSX.Element {
return <Tooltip {...props} showTooltipHeader={false} />;
}

View File

@@ -16,16 +16,12 @@ export default function Tooltip({
uPlotInstance,
timezone,
content,
showTooltipHeader = true,
}: TooltipProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const tooltipContent = content ?? [];
const headerTitle = useMemo(() => {
if (!showTooltipHeader) {
return null;
}
const data = uPlotInstance.data;
const cursorIdx = uPlotInstance.cursor.idx;
if (cursorIdx == null) {
@@ -34,12 +30,7 @@ export default function Tooltip({
return dayjs(data[0][cursorIdx] * 1000)
.tz(timezone)
.format(DATE_TIME_FORMATS.MONTH_DATETIME_SECONDS);
}, [
timezone,
uPlotInstance.data,
uPlotInstance.cursor.idx,
showTooltipHeader,
]);
}, [timezone, uPlotInstance.data, uPlotInstance.cursor.idx]);
return (
<div
@@ -48,11 +39,9 @@ export default function Tooltip({
isDarkMode ? 'darkMode' : 'lightMode',
)}
>
{showTooltipHeader && (
<div className="uplot-tooltip-header">
<span>{headerTitle}</span>
</div>
)}
<div className="uplot-tooltip-header">
<span>{headerTitle}</span>
</div>
<div
style={{
height: Math.min(

View File

@@ -25,28 +25,16 @@ export function getTooltipBaseValue({
index,
dataIndex,
isStackedBarChart,
series,
}: {
data: AlignedData;
index: number;
dataIndex: number;
isStackedBarChart?: boolean;
series?: Series[];
}): number | null {
let baseValue = data[index][dataIndex] ?? null;
// Top-down stacking (first series at top): raw = stacked[i] - stacked[nextVisible].
// When series are hidden, we must use the next *visible* series, not index+1,
// since hidden series keep raw values and would produce negative/wrong results.
if (isStackedBarChart && baseValue !== null && series) {
let nextVisibleIdx = -1;
for (let j = index + 1; j < series.length; j++) {
if (series[j]?.show) {
nextVisibleIdx = j;
break;
}
}
if (nextVisibleIdx >= 1) {
const nextValue = data[nextVisibleIdx][dataIndex] ?? 0;
if (isStackedBarChart && index + 1 < data.length && baseValue !== null) {
const nextValue = data[index + 1][dataIndex] ?? null;
if (nextValue !== null) {
baseValue = baseValue - nextValue;
}
}
@@ -92,7 +80,6 @@ export function buildTooltipContent({
index,
dataIndex,
isStackedBarChart,
series,
});
const isActive = index === activeSeriesIndex;

View File

@@ -60,7 +60,6 @@ export interface TooltipRenderArgs {
}
export interface BaseTooltipProps {
showTooltipHeader?: boolean;
timezone: string;
yAxisUnit?: string;
decimalPrecision?: PrecisionOption;
@@ -75,14 +74,7 @@ export interface BarTooltipProps extends BaseTooltipProps, TooltipRenderArgs {
isStackedBarChart?: boolean;
}
export interface HistogramTooltipProps
extends BaseTooltipProps,
TooltipRenderArgs {}
export type TooltipProps =
| TimeSeriesTooltipProps
| BarTooltipProps
| HistogramTooltipProps;
export type TooltipProps = TimeSeriesTooltipProps | BarTooltipProps;
export enum LegendPosition {
BOTTOM = 'bottom',

View File

@@ -1,10 +1,8 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
import uPlot, { Series } from 'uplot';
import {
BarAlignment,
ConfigBuilder,
DrawStyle,
LineInterpolation,
@@ -20,10 +18,15 @@ import {
export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
private buildLineConfig({
lineColor,
lineWidth,
lineStyle,
lineCap,
}: {
lineColor: string;
lineWidth?: number;
lineStyle?: LineStyle;
lineCap?: Series.Cap;
}): Partial<Series> {
const { lineWidth, lineStyle, lineCap } = this.props;
const lineConfig: Partial<Series> = {
stroke: lineColor,
width: lineWidth ?? 2,
@@ -36,28 +39,21 @@ export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
if (lineCap) {
lineConfig.cap = lineCap;
}
if (this.props.panelType === PANEL_TYPES.BAR) {
lineConfig.fill = lineColor;
} else if (this.props.panelType === PANEL_TYPES.HISTOGRAM) {
lineConfig.fill = `${lineColor}40`;
}
return lineConfig;
}
/**
* Build path configuration
*/
private buildPathConfig(): Partial<Series> {
const {
pathBuilder,
drawStyle,
lineInterpolation,
barAlignment,
barMaxWidth,
barWidthFactor,
} = this.props;
private buildPathConfig({
pathBuilder,
drawStyle,
lineInterpolation,
}: {
pathBuilder?: Series.PathBuilder | null;
drawStyle: DrawStyle;
lineInterpolation?: LineInterpolation;
}): Partial<Series> {
if (pathBuilder) {
return { paths: pathBuilder };
}
@@ -74,13 +70,7 @@ export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
idx0: number,
idx1: number,
): Series.Paths | null => {
const pathsBuilder = getPathBuilder({
drawStyle,
lineInterpolation,
barAlignment,
barMaxWidth,
barWidthFactor,
});
const pathsBuilder = getPathBuilder(drawStyle, lineInterpolation);
return pathsBuilder(self, seriesIdx, idx0, idx1);
},
@@ -95,17 +85,21 @@ export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
*/
private buildPointsConfig({
lineColor,
lineWidth,
pointSize,
pointsBuilder,
pointsFilter,
drawStyle,
showPoints,
}: {
lineColor: string;
lineWidth?: number;
pointSize?: number;
pointsBuilder: Series.Points.Show | null;
pointsFilter: Series.Points.Filter | null;
drawStyle: DrawStyle;
showPoints?: VisibilityMode;
}): Partial<Series.Points> {
const {
lineWidth,
pointSize,
pointsBuilder,
pointsFilter,
drawStyle,
showPoints,
} = this.props;
const pointsConfig: Partial<Series.Points> = {
stroke: lineColor,
fill: lineColor,
@@ -142,16 +136,44 @@ export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
}
getConfig(): Series {
const { scaleKey, label, spanGaps, show = true } = this.props;
const {
drawStyle,
pathBuilder,
pointsBuilder,
pointsFilter,
lineInterpolation,
lineWidth,
lineStyle,
lineCap,
showPoints,
pointSize,
scaleKey,
label,
spanGaps,
show = true,
} = this.props;
const lineColor = this.getLineColor();
const lineConfig = this.buildLineConfig({
lineColor,
lineWidth,
lineStyle,
lineCap,
});
const pathConfig = this.buildPathConfig({
pathBuilder,
drawStyle,
lineInterpolation,
});
const pathConfig = this.buildPathConfig();
const pointsConfig = this.buildPointsConfig({
lineColor,
lineWidth,
pointSize,
pointsBuilder: pointsBuilder ?? null,
pointsFilter: pointsFilter ?? null,
drawStyle,
showPoints,
});
return {
@@ -181,19 +203,10 @@ let builders: PathBuilders | null = null;
/**
* Get path builder based on draw style and interpolation
*/
function getPathBuilder({
drawStyle,
lineInterpolation,
barAlignment = BarAlignment.Center,
barWidthFactor = 0.6,
barMaxWidth = 200,
}: {
drawStyle: DrawStyle;
lineInterpolation?: LineInterpolation;
barAlignment?: BarAlignment;
barMaxWidth?: number;
barWidthFactor?: number;
}): Series.PathBuilder {
function getPathBuilder(
style: DrawStyle,
lineInterpolation?: LineInterpolation,
): Series.PathBuilder {
const pathBuilders = uPlot.paths;
if (!builders) {
@@ -213,18 +226,7 @@ function getPathBuilder({
};
}
if (drawStyle === DrawStyle.Bar) {
const barsCfgKey = `bars|${barAlignment}|${barWidthFactor}|${barMaxWidth}`;
if (!builders[barsCfgKey] && pathBuilders.bars) {
builders[barsCfgKey] = pathBuilders.bars({
size: [barWidthFactor, barMaxWidth],
align: barAlignment,
});
}
return builders[barsCfgKey];
}
if (drawStyle === DrawStyle.Line) {
if (style === DrawStyle.Line) {
if (lineInterpolation === LineInterpolation.StepBefore) {
return builders.stepBefore;
}

View File

@@ -126,45 +126,7 @@ export enum VisibilityMode {
Never = 'never',
}
/**
* Props for configuring lines
*/
export interface LineConfig {
lineColor?: string;
lineInterpolation?: LineInterpolation;
lineStyle?: LineStyle;
lineWidth?: number;
lineCap?: Series.Cap;
}
/**
* Alignment of bars
*/
export enum BarAlignment {
After = 1,
Before = -1,
Center = 0,
}
/**
* Props for configuring bars
*/
export interface BarConfig {
barAlignment?: BarAlignment;
barMaxWidth?: number;
barWidthFactor?: number;
}
/**
* Props for configuring points
*/
export interface PointsConfig {
pointColor?: string;
pointSize?: number;
showPoints?: VisibilityMode;
}
export interface SeriesProps extends LineConfig, PointsConfig, BarConfig {
export interface SeriesProps {
scaleKey: string;
label?: string;
panelType: PANEL_TYPES;
@@ -175,7 +137,20 @@ export interface SeriesProps extends LineConfig, PointsConfig, BarConfig {
pointsBuilder?: Series.Points.Show;
show?: boolean;
spanGaps?: boolean;
isDarkMode?: boolean;
// Line config
lineColor?: string;
lineInterpolation?: LineInterpolation;
lineStyle?: LineStyle;
lineWidth?: number;
lineCap?: Series.Cap;
// Points config
pointColor?: string;
pointSize?: number;
showPoints?: VisibilityMode;
}
export interface LegendItem {

View File

@@ -7,6 +7,4 @@ import { handlers } from './handlers';
// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers);
export * from './utils';
export { rest };

View File

@@ -1,26 +0,0 @@
import { ResponseResolver, restContext, RestRequest } from 'msw';
export const createErrorResponse = (
status: number,
code: string,
message: string,
): ResponseResolver<RestRequest, typeof restContext> => (
_req,
res,
ctx,
): ReturnType<ResponseResolver<RestRequest, typeof restContext>> =>
res(
ctx.status(status),
ctx.json({
error: {
code,
message,
},
}),
);
export const handleInternalServerError = createErrorResponse(
500,
'INTERNAL_SERVER_ERROR',
'Internal server error occurred',
);

View File

@@ -1,46 +0,0 @@
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { render, waitFor } from 'tests/test-utils';
import ForgotPassword from '../index';
// Mock dependencies
jest.mock('lib/history', () => ({
__esModule: true,
default: {
push: jest.fn(),
location: {
search: '',
},
},
}));
const mockHistoryPush = history.push as jest.MockedFunction<
typeof history.push
>;
describe('ForgotPassword Page', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Route State Handling', () => {
it('redirects to login when route state is missing', async () => {
render(<ForgotPassword />, undefined, {
initialRoute: '/forgot-password',
});
await waitFor(() => {
expect(mockHistoryPush).toHaveBeenCalledWith(ROUTES.LOGIN);
});
});
it('returns null when route state is missing', () => {
const { container } = render(<ForgotPassword />, undefined, {
initialRoute: '/forgot-password',
});
expect(container.firstChild).toBeNull();
});
});
});

View File

@@ -1,39 +0,0 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import AuthPageContainer from 'components/AuthPageContainer';
import ROUTES from 'constants/routes';
import ForgotPasswordContainer, {
ForgotPasswordRouteState,
} from 'container/ForgotPassword';
import history from 'lib/history';
import '../Login/Login.styles.scss';
function ForgotPassword(): JSX.Element | null {
const location = useLocation<ForgotPasswordRouteState | undefined>();
const routeState = location.state;
useEffect(() => {
if (!routeState?.email) {
history.push(ROUTES.LOGIN);
}
}, [routeState]);
if (!routeState?.email) {
return null;
}
return (
<AuthPageContainer>
<div className="auth-form-card">
<ForgotPasswordContainer
email={routeState.email}
orgId={routeState.orgId}
orgs={routeState.orgs}
/>
</div>
</AuthPageContainer>
);
}
export default ForgotPassword;

View File

@@ -6,8 +6,6 @@ const DOCLINKS = {
'https://signoz.io/docs/product-features/trace-explorer/?utm_source=product&utm_medium=traces-explorer-trace-tab#traces-view',
METRICS_EXPLORER_EMPTY_STATE:
'https://signoz.io/docs/userguide/send-metrics-cloud/',
EXTERNAL_API_MONITORING:
'https://signoz.io/docs/external-api-monitoring/overview/',
};
export default DOCLINKS;

View File

@@ -68,7 +68,6 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
ALERT_HISTORY: ['ADMIN', 'EDITOR', 'VIEWER'],
ALERT_OVERVIEW: ['ADMIN', 'EDITOR', 'VIEWER'],
LOGIN: ['ADMIN', 'EDITOR', 'VIEWER'],
FORGOT_PASSWORD: ['ADMIN', 'EDITOR', 'VIEWER'],
NOT_FOUND: ['ADMIN', 'VIEWER', 'EDITOR'],
PASSWORD_RESET: ['ADMIN', 'EDITOR', 'VIEWER'],
SERVICE_METRICS: ['ADMIN', 'EDITOR', 'VIEWER'],

View File

@@ -5038,7 +5038,7 @@
resolved "https://registry.yarnpkg.com/@signozhq/design-tokens/-/design-tokens-2.1.1.tgz#9c36d433fd264410713cc0c5ebdd75ce0ebecba3"
integrity sha512-SdziCHg5Lwj+6oY6IRUPplaKZ+kTHjbrlhNj//UoAJ8aQLnRdR2F/miPzfSi4vrYw88LtXxNA9J9iJyacCp37A==
"@signozhq/icons@0.1.0", "@signozhq/icons@^0.1.0":
"@signozhq/icons@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@signozhq/icons/-/icons-0.1.0.tgz#00dfb430dbac423bfff715876f91a7b8a72509e4"
integrity sha512-kGWDhCpQkFWaNwyWfy88AIbg902wBbgTFTBAtmo6DkHyLGoqWAf0Jcq8BX+7brFqJF9PnLoSJDj1lvCpUsI/Ig==

View File

@@ -57,7 +57,7 @@ func Error(rw http.ResponseWriter, cause error) {
case errors.TypeUnauthenticated:
httpCode = http.StatusUnauthorized
case errors.TypeUnsupported:
httpCode = http.StatusNotImplemented
httpCode = http.StatusUnprocessableEntity
case errors.TypeForbidden:
httpCode = http.StatusForbidden
case errors.TypeCanceled:

View File

@@ -6,18 +6,20 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
"github.com/SigNoz/signoz/pkg/modules/rootuser"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
type setter struct {
store types.OrganizationStore
alertmanager alertmanager.Alertmanager
quickfilter quickfilter.Module
store types.OrganizationStore
alertmanager alertmanager.Alertmanager
quickfilter quickfilter.Module
rootUserReconciler rootuser.Reconciler
}
func NewSetter(store types.OrganizationStore, alertmanager alertmanager.Alertmanager, quickfilter quickfilter.Module) organization.Setter {
return &setter{store: store, alertmanager: alertmanager, quickfilter: quickfilter}
func NewSetter(store types.OrganizationStore, alertmanager alertmanager.Alertmanager, quickfilter quickfilter.Module, rootUserReconciler rootuser.Reconciler) organization.Setter {
return &setter{store: store, alertmanager: alertmanager, quickfilter: quickfilter, rootUserReconciler: rootUserReconciler}
}
func (module *setter) Create(ctx context.Context, organization *types.Organization, createManagedRoles func(context.Context, valuer.UUID) error) error {
@@ -37,6 +39,10 @@ func (module *setter) Create(ctx context.Context, organization *types.Organizati
return err
}
if err := module.rootUserReconciler.ReconcileForOrg(ctx, organization); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,72 @@
package implrootuser
import (
"context"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/rootuser"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type module struct {
store types.RootUserStore
settings factory.ScopedProviderSettings
config user.RootUserConfig
authz authz.AuthZ
}
func NewModule(store types.RootUserStore, providerSettings factory.ProviderSettings, config user.RootUserConfig, authz authz.AuthZ) rootuser.Module {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/rootuser/implrootuser")
return &module{
store: store,
settings: settings,
config: config,
authz: authz,
}
}
func (m *module) Authenticate(ctx context.Context, orgID valuer.UUID, email valuer.Email, password string) (*authtypes.Identity, error) {
// get the root user by email and org id
rootUser, err := m.store.GetByEmailAndOrgID(ctx, orgID, email)
if err != nil {
return nil, err
}
// verify the password
if !rootUser.VerifyPassword(password) {
return nil, errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "invalid email or password")
}
// create a root user identity
identity := authtypes.NewRootIdentity(rootUser.ID, orgID, rootUser.Email)
// make sure the returning identity has admin role
err = m.authz.Grant(ctx, orgID, roletypes.SigNozAdminRoleName, authtypes.MustNewSubject(authtypes.TypeableUser, rootUser.ID.StringValue(), rootUser.OrgID, nil))
if err != nil {
return nil, err
}
return identity, nil
}
func (m *module) ExistsByOrgID(ctx context.Context, orgID valuer.UUID) (bool, error) {
return m.store.ExistsByOrgID(ctx, orgID)
}
func (m *module) GetByEmailAndOrgID(ctx context.Context, orgID valuer.UUID, email valuer.Email) (*types.RootUser, error) {
return m.store.GetByEmailAndOrgID(ctx, orgID, email)
}
func (m *module) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.RootUser, error) {
return m.store.GetByOrgIDAndID(ctx, orgID, id)
}
func (m *module) GetByEmailAndOrgIDs(ctx context.Context, orgIDs []valuer.UUID, email valuer.Email) ([]*types.RootUser, error) {
return m.store.GetByEmailAndOrgIDs(ctx, orgIDs, email)
}

View File

@@ -0,0 +1,165 @@
package implrootuser
import (
"context"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rootuser"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
type reconciler struct {
store types.RootUserStore
settings factory.ScopedProviderSettings
orgGetter organization.Getter
config user.RootUserConfig
}
func NewReconciler(store types.RootUserStore, settings factory.ProviderSettings, orgGetter organization.Getter, config user.RootUserConfig) rootuser.Reconciler {
scopedSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/rootuser/implrootuser/reconciler")
return &reconciler{
store: store,
settings: scopedSettings,
orgGetter: orgGetter,
config: config,
}
}
func (r *reconciler) Reconcile(ctx context.Context) error {
if !r.config.IsConfigured() {
r.settings.Logger().InfoContext(ctx, "reconciler: root user is not configured, skipping reconciliation")
return nil
}
r.settings.Logger().InfoContext(ctx, "reconciler: reconciling root user(s)")
// get the organizations that are owned by this instance of signoz
orgs, err := r.orgGetter.ListByOwnedKeyRange(ctx)
if err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "failed to get list of organizations owned by this instance of signoz")
}
if len(orgs) == 0 {
r.settings.Logger().InfoContext(ctx, "reconciler: no organizations owned by this instance of signoz, skipping reconciliation")
return nil
}
for _, org := range orgs {
r.settings.Logger().InfoContext(ctx, "reconciler: reconciling root user for organization", "organization_id", org.ID, "organization_name", org.Name)
err := r.reconcileRootUserForOrg(ctx, org)
if err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "reconciler: failed to reconcile root user for organization %s (%s)", org.Name, org.ID)
}
r.settings.Logger().InfoContext(ctx, "reconciler: root user reconciled for organization", "organization_id", org.ID, "organization_name", org.Name)
}
r.settings.Logger().InfoContext(ctx, "reconciler: reconciliation complete")
return nil
}
func (r *reconciler) ReconcileForOrg(ctx context.Context, org *types.Organization) error {
if !r.config.IsConfigured() {
r.settings.Logger().InfoContext(ctx, "reconciler: root user is not configured, skipping reconciliation")
return nil
}
r.settings.Logger().InfoContext(ctx, "reconciler: reconciling root user for organization", "organization_id", org.ID, "organization_name", org.Name)
err := r.reconcileRootUserForOrg(ctx, org)
if err != nil {
return errors.WrapInternalf(err, errors.CodeInternal, "reconciler: failed to reconcile root user for organization %s (%s)", org.Name, org.ID)
}
r.settings.Logger().InfoContext(ctx, "reconciler: root user reconciled for organization", "organization_id", org.ID, "organization_name", org.Name)
return nil
}
func (r *reconciler) reconcileRootUserForOrg(ctx context.Context, org *types.Organization) error {
// try creating the user optimisitically
err := r.createRootUserForOrg(ctx, org.ID)
if err == nil {
// success - yay
return nil
}
// if error is not "alredy exists", something really went wrong
if !errors.Asc(err, types.ErrCodeRootUserAlreadyExists) {
return err
}
// here means the root user already exists - just make sure it is configured correctly
// this could be the case where the root user was created by some other means
// either previously or by some other instance of signoz
r.settings.Logger().InfoContext(ctx, "reconciler: root user already exists for organization", "organization_id", org.ID, "organization_name", org.Name)
// check if the root user already exists for the org
existingRootUser, err := r.store.GetByOrgID(ctx, org.ID)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return err
}
// make updates to the existing root user if needed
return r.updateRootUserForOrg(ctx, org.ID, existingRootUser)
}
func (r *reconciler) createRootUserForOrg(ctx context.Context, orgID valuer.UUID) error {
rootUser, err := types.NewRootUser(
valuer.MustNewEmail(r.config.Email),
r.config.Password,
orgID,
)
if err != nil {
return err
}
r.settings.Logger().InfoContext(ctx, "reconciler: creating new root user for organization", "organization_id", orgID, "email", r.config.Email)
err = r.store.Create(ctx, rootUser)
if err != nil {
return err
}
r.settings.Logger().InfoContext(ctx, "reconciler: root user created for organization", "organization_id", orgID, "email", r.config.Email)
return nil
}
func (r *reconciler) updateRootUserForOrg(ctx context.Context, orgID valuer.UUID, rootUser *types.RootUser) error {
needsUpdate := false
if rootUser.Email != valuer.MustNewEmail(r.config.Email) {
rootUser.Email = valuer.MustNewEmail(r.config.Email)
needsUpdate = true
}
if !rootUser.VerifyPassword(r.config.Password) {
passwordHash, err := types.NewHashedPassword(r.config.Password)
if err != nil {
return err
}
rootUser.PasswordHash = passwordHash
needsUpdate = true
}
if needsUpdate {
r.settings.Logger().InfoContext(ctx, "reconciler: updating root user for organization", "organization_id", orgID, "email", r.config.Email)
err := r.store.Update(ctx, orgID, rootUser.ID, rootUser)
if err != nil {
return err
}
r.settings.Logger().InfoContext(ctx, "reconciler: root user updated for organization", "organization_id", orgID, "email", r.config.Email)
return nil
}
return nil
}

View File

@@ -0,0 +1,126 @@
package implrootuser
import (
"context"
"time"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
type store struct {
sqlstore sqlstore.SQLStore
settings factory.ProviderSettings
}
func NewStore(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) types.RootUserStore {
return &store{
sqlstore: sqlstore,
settings: settings,
}
}
func (store *store) Create(ctx context.Context, rootUser *types.RootUser) error {
_, err := store.sqlstore.BunDBCtx(ctx).
NewInsert().
Model(rootUser).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrCodeRootUserAlreadyExists, "root user with email %s already exists in org %s", rootUser.Email, rootUser.OrgID)
}
return nil
}
func (store *store) GetByOrgID(ctx context.Context, orgID valuer.UUID) (*types.RootUser, error) {
rootUser := new(types.RootUser)
err := store.sqlstore.BunDBCtx(ctx).
NewSelect().
Model(rootUser).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeRootUserNotFound, "root user with org_id %s does not exist", orgID)
}
return rootUser, nil
}
func (store *store) GetByEmailAndOrgID(ctx context.Context, orgID valuer.UUID, email valuer.Email) (*types.RootUser, error) {
rootUser := new(types.RootUser)
err := store.sqlstore.BunDBCtx(ctx).
NewSelect().
Model(rootUser).
Where("email = ?", email).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeRootUserNotFound, "root user with email %s does not exist in org %s", email, orgID)
}
return rootUser, nil
}
func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.RootUser, error) {
rootUser := new(types.RootUser)
err := store.sqlstore.BunDBCtx(ctx).
NewSelect().
Model(rootUser).
Where("id = ?", id).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeRootUserNotFound, "root user with id %s does not exist in org %s", id, orgID)
}
return rootUser, nil
}
func (store *store) GetByEmailAndOrgIDs(ctx context.Context, orgIDs []valuer.UUID, email valuer.Email) ([]*types.RootUser, error) {
rootUsers := []*types.RootUser{}
err := store.sqlstore.BunDBCtx(ctx).
NewSelect().
Model(&rootUsers).
Where("email = ?", email).
Where("org_id IN (?)", bun.In(orgIDs)).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeRootUserNotFound, "root user with email %s does not exist in orgs %s", email, orgIDs)
}
return rootUsers, nil
}
func (store *store) ExistsByOrgID(ctx context.Context, orgID valuer.UUID) (bool, error) {
exists, err := store.sqlstore.BunDBCtx(ctx).
NewSelect().
Model(new(types.RootUser)).
Where("org_id = ?", orgID).
Exists(ctx)
if err != nil {
return false, err
}
return exists, nil
}
func (store *store) Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, rootUser *types.RootUser) error {
rootUser.UpdatedAt = time.Now()
_, err := store.sqlstore.BunDBCtx(ctx).
NewUpdate().
Model(rootUser).
Column("email").
Column("password_hash").
Column("updated_at").
Where("id = ?", id).
Where("org_id = ?", orgID).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeRootUserNotFound, "root user with id %s does not exist", id)
}
return nil
}

View File

@@ -0,0 +1,34 @@
package rootuser
import (
"context"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Module interface {
// Authenticate a root user by email and password
Authenticate(ctx context.Context, orgID valuer.UUID, email valuer.Email, password string) (*authtypes.Identity, error)
// Get the root user by email and orgID.
GetByEmailAndOrgID(ctx context.Context, orgID valuer.UUID, email valuer.Email) (*types.RootUser, error)
// Get the root user by orgID and ID.
GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.RootUser, error)
// Get the root users by email and org IDs.
GetByEmailAndOrgIDs(ctx context.Context, orgIDs []valuer.UUID, email valuer.Email) ([]*types.RootUser, error)
// Checks if a root user exists for an organization
ExistsByOrgID(ctx context.Context, orgID valuer.UUID) (bool, error)
}
type Reconciler interface {
// Reconcile the root users.
Reconcile(ctx context.Context) error
// Reconcile the root user for the given org.
ReconcileForOrg(ctx context.Context, org *types.Organization) error
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/authdomain"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rootuser"
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/tokenizer"
@@ -21,24 +22,26 @@ import (
)
type module struct {
settings factory.ScopedProviderSettings
authNs map[authtypes.AuthNProvider]authn.AuthN
user user.Module
userGetter user.Getter
authDomain authdomain.Module
tokenizer tokenizer.Tokenizer
orgGetter organization.Getter
settings factory.ScopedProviderSettings
authNs map[authtypes.AuthNProvider]authn.AuthN
user user.Module
userGetter user.Getter
authDomain authdomain.Module
tokenizer tokenizer.Tokenizer
orgGetter organization.Getter
rootUserModule rootuser.Module
}
func NewModule(providerSettings factory.ProviderSettings, authNs map[authtypes.AuthNProvider]authn.AuthN, user user.Module, userGetter user.Getter, authDomain authdomain.Module, tokenizer tokenizer.Tokenizer, orgGetter organization.Getter) session.Module {
func NewModule(providerSettings factory.ProviderSettings, authNs map[authtypes.AuthNProvider]authn.AuthN, user user.Module, userGetter user.Getter, authDomain authdomain.Module, tokenizer tokenizer.Tokenizer, orgGetter organization.Getter, rootUserModule rootuser.Module) session.Module {
return &module{
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/session/implsession"),
authNs: authNs,
user: user,
userGetter: userGetter,
authDomain: authDomain,
tokenizer: tokenizer,
orgGetter: orgGetter,
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/session/implsession"),
authNs: authNs,
user: user,
userGetter: userGetter,
authDomain: authDomain,
tokenizer: tokenizer,
orgGetter: orgGetter,
rootUserModule: rootUserModule,
}
}
@@ -60,6 +63,19 @@ func (module *module) GetSessionContext(ctx context.Context, email valuer.Email,
orgIDs = append(orgIDs, org.ID)
}
// ROOT USER
// if this email is a root user email, we will only allow password authentication
if module.rootUserModule != nil {
rootUserContexts, err := module.getRootUserSessionContext(ctx, orgs, orgIDs, email)
if err != nil {
return nil, err
}
if rootUserContexts.Exists {
return rootUserContexts, nil
}
}
// REGULAR USER
users, err := module.userGetter.ListUsersByEmailAndOrgIDs(ctx, email, orgIDs)
if err != nil {
return nil, err
@@ -108,6 +124,22 @@ func (module *module) GetSessionContext(ctx context.Context, email valuer.Email,
}
func (module *module) CreatePasswordAuthNSession(ctx context.Context, authNProvider authtypes.AuthNProvider, email valuer.Email, password string, orgID valuer.UUID) (*authtypes.Token, error) {
// Root User Authentication
if module.rootUserModule != nil {
// Ignore root user authentication errors and continue with regular user authentication.
// This error can be either not found or incorrect password, in both cases we continue with regular user authentication.
identity, err := module.rootUserModule.Authenticate(ctx, orgID, email, password)
if err != nil && !errors.Asc(err, types.ErrCodeRootUserNotFound) && !errors.Ast(err, errors.TypeUnauthenticated) {
// something else went wrong, we should report back to the caller
return nil, err
}
if identity != nil {
// root user authentication successful
return module.tokenizer.CreateToken(ctx, identity, map[string]string{})
}
}
// Regular User Authentication
passwordAuthN, err := getProvider[authn.PasswordAuthN](authNProvider, module.authNs)
if err != nil {
return nil, err
@@ -215,3 +247,31 @@ func getProvider[T authn.AuthN](authNProvider authtypes.AuthNProvider, authNs ma
return provider, nil
}
func (module *module) getRootUserSessionContext(ctx context.Context, orgs []*types.Organization, orgIDs []valuer.UUID, email valuer.Email) (*authtypes.SessionContext, error) {
context := authtypes.NewSessionContext()
rootUsers, err := module.rootUserModule.GetByEmailAndOrgIDs(ctx, orgIDs, email)
if err != nil && !errors.Asc(err, types.ErrCodeRootUserNotFound) {
// something else went wrong, report back to the caller
return nil, err
}
for _, rootUser := range rootUsers {
idx := slices.IndexFunc(orgs, func(org *types.Organization) bool {
return org.ID == rootUser.OrgID
})
if idx == -1 {
continue
}
org := orgs[idx]
context.Exists = true
orgContext := authtypes.NewOrgSessionContext(org.ID, org.Name).AddPasswordAuthNSupport(authtypes.AuthNProviderEmailPassword)
context = context.AddOrgContext(orgContext)
}
return context, nil
}

View File

@@ -5,15 +5,26 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
minRootUserPasswordLength = 12
)
type Config struct {
Password PasswordConfig `mapstructure:"password"`
Password PasswordConfig `mapstructure:"password"`
RootUserConfig RootUserConfig `mapstructure:"root"`
}
type PasswordConfig struct {
Reset ResetConfig `mapstructure:"reset"`
}
type RootUserConfig struct {
Email string `mapstructure:"email"`
Password string `mapstructure:"password"`
}
type ResetConfig struct {
AllowSelf bool `mapstructure:"allow_self"`
MaxTokenLifetime time.Duration `mapstructure:"max_token_lifetime"`
@@ -31,6 +42,10 @@ func newConfig() factory.Config {
MaxTokenLifetime: 6 * time.Hour,
},
},
RootUserConfig: RootUserConfig{
Email: "",
Password: "",
},
}
}
@@ -39,5 +54,40 @@ func (c Config) Validate() error {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::password::reset::max_token_lifetime must be positive")
}
if err := c.RootUserConfig.Validate(); err != nil {
return err
}
return nil
}
func (r RootUserConfig) Validate() error {
if (r.Email == "") != (r.Password == "") {
// all or nothing case
return errors.Newf(
errors.TypeInvalidInput,
errors.CodeInvalidInput,
"user::root requires both email and password to be set, or neither",
)
}
// nothing case
if !r.IsConfigured() {
return nil
}
_, err := valuer.NewEmail(r.Email)
if err != nil {
return errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "invalid user::root::email %s", r.Email)
}
if len(r.Password) < minRootUserPasswordLength {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::password must be at least %d characters long", minRootUserPasswordLength)
}
return nil
}
func (r RootUserConfig) IsConfigured() bool {
return r.Email != "" && r.Password != ""
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/binding"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/rootuser"
root "github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
@@ -18,12 +19,13 @@ import (
)
type handler struct {
module root.Module
getter root.Getter
module root.Module
getter root.Getter
rootUserModule rootuser.Module
}
func NewHandler(module root.Module, getter root.Getter) root.Handler {
return &handler{module: module, getter: getter}
func NewHandler(module root.Module, getter root.Getter, rootUserModule rootuser.Module) root.Handler {
return &handler{module: module, getter: getter, rootUserModule: rootUserModule}
}
func (h *handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
@@ -61,6 +63,23 @@ func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
return
}
// ROOT USER CHECK - START
// if the to-be-invited email is one of the root users, we forbid this operation
if h.rootUserModule != nil {
rootUser, err := h.rootUserModule.GetByEmailAndOrgID(ctx, valuer.MustNewUUID(claims.OrgID), req.Email)
if err != nil && !errors.Asc(err, types.ErrCodeRootUserNotFound) {
// something else went wrong, report back to UI
render.Error(rw, err)
return
}
if rootUser != nil {
render.Error(rw, errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot invite this email id"))
return
}
}
// ROOT USER CHECK - END
invites, err := h.module.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), &types.PostableBulkInviteRequest{
Invites: []types.PostableInvite{req},
})
@@ -94,6 +113,25 @@ func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
return
}
// ROOT USER CHECK - START
// if the to-be-invited email is one of the root users, we forbid this operation
if h.rootUserModule != nil {
for _, invite := range req.Invites {
rootUser, err := h.rootUserModule.GetByEmailAndOrgID(ctx, valuer.MustNewUUID(claims.OrgID), invite.Email)
if err != nil && !errors.Asc(err, types.ErrCodeRootUserNotFound) {
// something else went wrong, report back to UI
render.Error(rw, err)
return
}
if rootUser != nil {
render.Error(rw, errors.New(errors.TypeForbidden, errors.CodeForbidden, "reserved email(s) found, failed to invite users"))
return
}
}
}
// ROOT USER CHECK - END
_, err = h.module.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), &req)
if err != nil {
render.Error(rw, err)
@@ -192,6 +230,37 @@ func (h *handler) GetMyUser(w http.ResponseWriter, r *http.Request) {
return
}
// ROOT USER
if h.rootUserModule != nil {
rootUser, err := h.rootUserModule.GetByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID))
if err != nil && !errors.Asc(err, types.ErrCodeRootUserNotFound) {
// something else is wrong report back in UI
render.Error(w, err)
return
}
if rootUser != nil {
// root user detected
rUser := types.User{
Identifiable: types.Identifiable{
ID: rootUser.ID,
},
DisplayName: "Root User",
Email: rootUser.Email,
Role: types.RoleAdmin,
OrgID: rootUser.OrgID,
TimeAuditable: types.TimeAuditable{
CreatedAt: rootUser.CreatedAt,
UpdatedAt: rootUser.UpdatedAt,
},
}
render.Success(w, http.StatusOK, rUser)
return
}
}
// NORMAL USER
user, err := h.getter.GetByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID))
if err != nil {
render.Error(w, err)
@@ -259,6 +328,11 @@ func (h *handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
return
}
if claims.UserID == id {
render.Error(w, errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "users cannot delete themselves"))
return
}
if err := h.module.DeleteUser(ctx, valuer.MustNewUUID(claims.OrgID), id, claims.UserID); err != nil {
render.Error(w, err)
return

View File

@@ -278,7 +278,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
var metricTemporality map[string]metrictypes.Temporality
if len(metricNames) > 0 {
var err error
metricTemporality, err = q.metadataStore.FetchTemporalityMulti(ctx, req.Start, req.End, metricNames...)
metricTemporality, err = q.metadataStore.FetchTemporalityMulti(ctx, metricNames...)
if err != nil {
q.logger.WarnContext(ctx, "failed to fetch metric temporality", "error", err, "metrics", metricNames)
// Continue without temporality - statement builder will handle unspecified

View File

@@ -13,6 +13,8 @@ import (
"github.com/SigNoz/signoz/pkg/factory/factorytest"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/rootuser/implrootuser"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sharder/noopsharder"
@@ -40,7 +42,8 @@ func TestNewHandlers(t *testing.T) {
queryParser := queryparser.New(providerSettings)
require.NoError(t, err)
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule)
rootUserReconciler := implrootuser.NewReconciler(implrootuser.NewStore(sqlstore, providerSettings), providerSettings, orgGetter, user.RootUserConfig{})
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, rootUserReconciler)
handlers := NewHandlers(modules, providerSettings, nil, nil, nil, nil, nil, nil, nil)
reflectVal := reflect.ValueOf(handlers)

View File

@@ -25,6 +25,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter"
"github.com/SigNoz/signoz/pkg/modules/rawdataexport"
"github.com/SigNoz/signoz/pkg/modules/rawdataexport/implrawdataexport"
"github.com/SigNoz/signoz/pkg/modules/rootuser"
"github.com/SigNoz/signoz/pkg/modules/rootuser/implrootuser"
"github.com/SigNoz/signoz/pkg/modules/savedview"
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
"github.com/SigNoz/signoz/pkg/modules/services"
@@ -66,6 +68,7 @@ type Modules struct {
SpanPercentile spanpercentile.Module
MetricsExplorer metricsexplorer.Module
Promote promote.Module
RootUser rootuser.Module
}
func NewModules(
@@ -85,13 +88,16 @@ func NewModules(
queryParser queryparser.QueryParser,
config Config,
dashboard dashboard.Module,
rootUserReconciler rootuser.Reconciler,
) Modules {
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter, rootUserReconciler)
user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), tokenizer, emailing, providerSettings, orgSetter, authz, analytics, config.User)
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings))
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
rootUser := implrootuser.NewModule(implrootuser.NewStore(sqlstore, providerSettings), providerSettings, config.User.RootUserConfig, authz)
return Modules{
OrgGetter: orgGetter,
OrgSetter: orgSetter,
@@ -105,10 +111,11 @@ func NewModules(
TraceFunnel: impltracefunnel.NewModule(impltracefunnel.NewStore(sqlstore)),
RawDataExport: implrawdataexport.NewModule(querier),
AuthDomain: implauthdomain.NewModule(implauthdomain.NewStore(sqlstore), authNs),
Session: implsession.NewModule(providerSettings, authNs, user, userGetter, implauthdomain.NewModule(implauthdomain.NewStore(sqlstore), authNs), tokenizer, orgGetter),
Session: implsession.NewModule(providerSettings, authNs, user, userGetter, implauthdomain.NewModule(implauthdomain.NewStore(sqlstore), authNs), tokenizer, orgGetter, rootUser),
SpanPercentile: implspanpercentile.NewModule(querier, providerSettings),
Services: implservices.NewModule(querier, telemetryStore),
MetricsExplorer: implmetricsexplorer.NewModule(telemetryStore, telemetryMetadataStore, cache, ruleStore, dashboard, providerSettings, config.MetricsExplorer),
Promote: implpromote.NewModule(telemetryMetadataStore, telemetryStore),
RootUser: rootUser,
}
}

View File

@@ -13,6 +13,8 @@ import (
"github.com/SigNoz/signoz/pkg/factory/factorytest"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/rootuser/implrootuser"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sharder/noopsharder"
@@ -40,7 +42,8 @@ func TestNewModules(t *testing.T) {
queryParser := queryparser.New(providerSettings)
require.NoError(t, err)
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule)
rootUserReconciler := implrootuser.NewReconciler(implrootuser.NewStore(sqlstore, providerSettings), providerSettings, orgGetter, user.RootUserConfig{})
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, rootUserReconciler)
reflectVal := reflect.ValueOf(modules)
for i := 0; i < reflectVal.NumField(); i++ {

View File

@@ -167,6 +167,7 @@ func NewSQLMigrationProviderFactories(
sqlmigration.NewMigrateRbacToAuthzFactory(sqlstore),
sqlmigration.NewMigratePublicDashboardsFactory(sqlstore),
sqlmigration.NewAddAnonymousPublicDashboardTransactionFactory(sqlstore),
sqlmigration.NewAddRootUserFactory(sqlstore, sqlschema),
)
}
@@ -237,7 +238,7 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
orgGetter,
authz,
implorganization.NewHandler(modules.OrgGetter, modules.OrgSetter),
impluser.NewHandler(modules.User, modules.UserGetter),
impluser.NewHandler(modules.User, modules.UserGetter, modules.RootUser),
implsession.NewHandler(modules.Session),
implauthdomain.NewHandler(modules.AuthDomain),
implpreference.NewHandler(modules.Preference),

View File

@@ -21,6 +21,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/rootuser/implrootuser"
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
@@ -266,6 +267,10 @@ func New(
// Initialize organization getter
orgGetter := implorganization.NewGetter(implorganization.NewStore(sqlstore), sharder)
// Initialize and run the root user reconciler
rootUserStore := implrootuser.NewStore(sqlstore, providerSettings)
rootUserReconciler := implrootuser.NewReconciler(rootUserStore, providerSettings, orgGetter, config.User.RootUserConfig)
// Initialize tokenizer from the available tokenizer provider factories
tokenizer, err := factory.NewProviderFromNamedMap(
ctx,
@@ -387,7 +392,7 @@ func New(
}
// Initialize all modules
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, rootUserReconciler)
// Initialize all handlers for the modules
handlers := NewHandlers(modules, providerSettings, querier, licensing, global, flagger, gateway, telemetryMetadataStore, authz)
@@ -443,6 +448,12 @@ func New(
return nil, err
}
err = rootUserReconciler.Reconcile(ctx)
if err != nil {
// Question: Should we fail the startup if the root user reconciliation fails?
return nil, err
}
return &SigNoz{
Registry: registry,
Analytics: analytics,

View File

@@ -0,0 +1,103 @@
package sqlmigration
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
type addRootUser struct {
sqlStore sqlstore.SQLStore
sqlSchema sqlschema.SQLSchema
}
func NewAddRootUserFactory(sqlStore sqlstore.SQLStore, sqlSchema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(
factory.MustNewName("add_root_user"),
func(ctx context.Context, settings factory.ProviderSettings, config Config) (SQLMigration, error) {
return newAddRootUser(ctx, settings, config, sqlStore, sqlSchema)
},
)
}
func newAddRootUser(_ context.Context, _ factory.ProviderSettings, _ Config, sqlStore sqlstore.SQLStore, sqlSchema sqlschema.SQLSchema) (SQLMigration, error) {
return &addRootUser{sqlStore: sqlStore, sqlSchema: sqlSchema}, nil
}
func (migration *addRootUser) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addRootUser) Up(ctx context.Context, db *bun.DB) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
sqls := [][]byte{}
// create root_users table sqls
tableSQLs := migration.sqlSchema.Operator().CreateTable(
&sqlschema.Table{
Name: "root_users",
Columns: []*sqlschema.Column{
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "email", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "password_hash", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "org_id", DataType: sqlschema.DataTypeText, Nullable: false},
},
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
ColumnNames: []sqlschema.ColumnName{"id"},
},
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
{
ReferencingColumnName: sqlschema.ColumnName("org_id"),
ReferencedTableName: sqlschema.TableName("organizations"),
ReferencedColumnName: sqlschema.ColumnName("id"),
},
},
},
)
sqls = append(sqls, tableSQLs...)
// create index sqls
indexSQLs := migration.sqlSchema.Operator().CreateIndex(
&sqlschema.UniqueIndex{
TableName: sqlschema.TableName("root_users"),
ColumnNames: []sqlschema.ColumnName{"org_id"},
},
)
sqls = append(sqls, indexSQLs...)
for _, sqlStmt := range sqls {
if _, err := tx.ExecContext(ctx, string(sqlStmt)); err != nil {
return err
}
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func (migration *addRootUser) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -1597,12 +1597,12 @@ func (t *telemetryMetaStore) GetAllValues(ctx context.Context, fieldValueSelecto
return values, complete, nil
}
func (t *telemetryMetaStore) FetchTemporality(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricName string) (metrictypes.Temporality, error) {
func (t *telemetryMetaStore) FetchTemporality(ctx context.Context, metricName string) (metrictypes.Temporality, error) {
if metricName == "" {
return metrictypes.Unknown, errors.Newf(errors.TypeInternal, errors.CodeInternal, "metric name cannot be empty")
}
temporalityMap, err := t.FetchTemporalityMulti(ctx, queryTimeRangeStartTs, queryTimeRangeEndTs, metricName)
temporalityMap, err := t.FetchTemporalityMulti(ctx, metricName)
if err != nil {
return metrictypes.Unknown, err
}
@@ -1615,13 +1615,13 @@ func (t *telemetryMetaStore) FetchTemporality(ctx context.Context, queryTimeRang
return temporality, nil
}
func (t *telemetryMetaStore) FetchTemporalityMulti(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricNames ...string) (map[string]metrictypes.Temporality, error) {
func (t *telemetryMetaStore) FetchTemporalityMulti(ctx context.Context, metricNames ...string) (map[string]metrictypes.Temporality, error) {
if len(metricNames) == 0 {
return make(map[string]metrictypes.Temporality), nil
}
result := make(map[string]metrictypes.Temporality)
metricsTemporality, err := t.fetchMetricsTemporality(ctx, queryTimeRangeStartTs, queryTimeRangeEndTs, metricNames...)
metricsTemporality, err := t.fetchMetricsTemporality(ctx, metricNames...)
if err != nil {
return nil, err
}
@@ -1630,12 +1630,8 @@ func (t *telemetryMetaStore) FetchTemporalityMulti(ctx context.Context, queryTim
// For metrics not found in the database, set to Unknown
for _, metricName := range metricNames {
if temporality, exists := metricsTemporality[metricName]; exists && len(temporality) > 0 {
if len(temporality) > 1 {
result[metricName] = metrictypes.Multiple
} else {
result[metricName] = temporality[0]
}
if temporality, exists := metricsTemporality[metricName]; exists {
result[metricName] = temporality
continue
}
if temporality, exists := meterMetricsTemporality[metricName]; exists {
@@ -1648,10 +1644,8 @@ func (t *telemetryMetaStore) FetchTemporalityMulti(ctx context.Context, queryTim
return result, nil
}
func (t *telemetryMetaStore) fetchMetricsTemporality(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricNames ...string) (map[string][]metrictypes.Temporality, error) {
result := make(map[string][]metrictypes.Temporality)
adjustedStartTs, adjustedEndTs, tsTableName, _ := telemetrymetrics.WhichTSTableToUse(queryTimeRangeStartTs, queryTimeRangeEndTs, nil)
func (t *telemetryMetaStore) fetchMetricsTemporality(ctx context.Context, metricNames ...string) (map[string]metrictypes.Temporality, error) {
result := make(map[string]metrictypes.Temporality)
// Build query to fetch temporality for all metrics
// We use attr_string_value where attr_name = '__temporality__'
@@ -1659,18 +1653,14 @@ func (t *telemetryMetaStore) fetchMetricsTemporality(ctx context.Context, queryT
// and metric_name column contains temporality value, so we use the correct mapping
sb := sqlbuilder.Select(
"metric_name",
"temporality",
).
From(t.metricsDBName + "." + tsTableName)
"argMax(temporality, last_reported_unix_milli) as temporality",
).From(t.metricsDBName + "." + t.metricsFieldsTblName)
// Filter by metric names (in the temporality column due to data mix-up)
sb.Where(
sb.In("metric_name", metricNames),
sb.GTE("unix_milli", adjustedStartTs),
sb.LT("unix_milli", adjustedEndTs),
)
sb.Where(sb.In("metric_name", metricNames))
sb.GroupBy("metric_name", "temporality")
// Group by metric name to get one temporality per metric
sb.GroupBy("metric_name")
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
@@ -1702,12 +1692,8 @@ func (t *telemetryMetaStore) fetchMetricsTemporality(ctx context.Context, queryT
// Unknown or empty temporality
temporality = metrictypes.Unknown
}
if temporality != metrictypes.Unknown {
result[metricName] = append(result[metricName], temporality)
}
}
if err := rows.Err(); err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error iterating over metrics temporality rows")
result[metricName] = temporality
}
return result, nil

View File

@@ -21,10 +21,6 @@ const (
RateWithoutNegative = `If((per_series_value - lagInFrame(per_series_value, 1, 0) OVER rate_window) < 0, per_series_value / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window), (per_series_value - lagInFrame(per_series_value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window))`
IncreaseWithoutNegative = `If((per_series_value - lagInFrame(per_series_value, 1, 0) OVER rate_window) < 0, per_series_value, ((per_series_value - lagInFrame(per_series_value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window)) * (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window))`
RateWithoutNegativeMultiTemporality = `IF(LOWER(temporality) LIKE LOWER('delta'), %s, IF((%s - lagInFrame(%s, 1, 0) OVER rate_window) < 0, %s / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window), (%s - lagInFrame(%s, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window))) AS per_series_value`
IncreaseWithoutNegativeMultiTemporality = `IF(LOWER(temporality) LIKE LOWER('delta'), %s, IF((%s - lagInFrame(%s, 1, 0) OVER rate_window) < 0, %s, ((%s - lagInFrame(%s, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window)) * (ts - lagInFrame(ts, 1, toDateTime(fromUnixTimestamp64Milli(%d))) OVER rate_window))) AS per_series_value`
OthersMultiTemporality = `IF(LOWER(temporality) LIKE LOWER('delta'), %s, %s) AS per_series_value`
RateWithInterpolation = `
CASE
WHEN row_number() OVER rate_window = 1 THEN
@@ -381,7 +377,7 @@ func (b *MetricQueryStatementBuilder) buildTimeSeriesCTE(
sb.LTE("unix_milli", end),
)
if query.Aggregations[0].Temporality != metrictypes.Multiple && query.Aggregations[0].Temporality != metrictypes.Unknown {
if query.Aggregations[0].Temporality != metrictypes.Unknown {
sb.Where(sb.ILike("temporality", query.Aggregations[0].Temporality.StringValue()))
}
@@ -411,10 +407,8 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggregationCTE(
) (string, []any, error) {
if query.Aggregations[0].Temporality == metrictypes.Delta {
return b.buildTemporalAggDelta(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
} else if query.Aggregations[0].Temporality != metrictypes.Multiple {
return b.buildTemporalAggCumulativeOrUnspecified(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
}
return b.buildTemporalAggForMultipleTemporalities(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
return b.buildTemporalAggCumulativeOrUnspecified(ctx, start, end, query, timeSeriesCTE, timeSeriesCTEArgs)
}
func (b *MetricQueryStatementBuilder) buildTemporalAggDelta(
@@ -534,58 +528,6 @@ func (b *MetricQueryStatementBuilder) buildTemporalAggCumulativeOrUnspecified(
}
}
// because RateInterpolation is not enabled anywhere due to some gaps in the logic wrt cache handling, it hasn't been considered for the multi temporality
func (b *MetricQueryStatementBuilder) buildTemporalAggForMultipleTemporalities(
_ context.Context,
start, end uint64,
query qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation],
timeSeriesCTE string,
timeSeriesCTEArgs []any,
) (string, []any, error) {
stepSec := int64(query.StepInterval.Seconds())
sb := sqlbuilder.NewSelectBuilder()
sb.SelectMore(fmt.Sprintf(
"toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), toIntervalSecond(%d)) AS ts",
stepSec,
))
for _, g := range query.GroupBy {
sb.SelectMore(fmt.Sprintf("`%s`", g.TelemetryFieldKey.Name))
}
aggForDeltaTemporality := AggregationColumnForSamplesTable(start, end, query.Aggregations[0].Type, metrictypes.Delta, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
aggForCumulativeTemporality := AggregationColumnForSamplesTable(start, end, query.Aggregations[0].Type, metrictypes.Cumulative, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
if query.Aggregations[0].TimeAggregation == metrictypes.TimeAggregationRate {
aggForDeltaTemporality = fmt.Sprintf("%s/%d", aggForDeltaTemporality, stepSec)
}
switch query.Aggregations[0].TimeAggregation {
case metrictypes.TimeAggregationRate:
rateExpr := fmt.Sprintf(RateWithoutNegativeMultiTemporality, aggForDeltaTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, start, aggForCumulativeTemporality, aggForCumulativeTemporality, start)
sb.SelectMore(rateExpr)
case metrictypes.TimeAggregationIncrease:
increaseExpr := fmt.Sprintf(IncreaseWithoutNegativeMultiTemporality, aggForDeltaTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, aggForCumulativeTemporality, start, start)
sb.SelectMore(increaseExpr)
default:
expr := fmt.Sprintf(OthersMultiTemporality, aggForDeltaTemporality, aggForCumulativeTemporality)
sb.SelectMore(expr)
}
tbl := WhichSamplesTableToUse(start, end, query.Aggregations[0].Type, query.Aggregations[0].TimeAggregation, query.Aggregations[0].TableHints)
sb.From(fmt.Sprintf("%s.%s AS points", DBName, tbl))
sb.JoinWithOption(sqlbuilder.InnerJoin, timeSeriesCTE, "points.fingerprint = filtered_time_series.fingerprint")
sb.Where(
sb.In("metric_name", query.Aggregations[0].MetricName),
sb.GTE("unix_milli", start),
sb.LT("unix_milli", end),
)
sb.GroupBy("fingerprint", "ts", "temporality")
sb.GroupBy(querybuilder.GroupByKeys(query.GroupBy)...)
queryWithoutWindow, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse, timeSeriesCTEArgs...)
queryWithWindowAndOrder := queryWithoutWindow + " WINDOW rate_window AS (PARTITION BY fingerprint ORDER BY fingerprint ASC, ts ASC) ORDER BY ts"
return fmt.Sprintf("__temporal_aggregation_cte AS (%s)", queryWithWindowAndOrder), args, nil
}
func (b *MetricQueryStatementBuilder) buildSpatialAggregationCTE(
_ context.Context,
_ uint64,

View File

@@ -2,6 +2,7 @@ package sqltokenizerstore
import (
"context"
"database/sql"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
@@ -34,6 +35,7 @@ func (store *store) Create(ctx context.Context, token *authtypes.StorableToken)
}
func (store *store) GetIdentityByUserID(ctx context.Context, userID valuer.UUID) (*authtypes.Identity, error) {
// try to get the user from the user table - this will be most common case
user := new(types.User)
err := store.
@@ -43,11 +45,36 @@ func (store *store) GetIdentityByUserID(ctx context.Context, userID valuer.UUID)
Model(user).
Where("id = ?", userID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id: %s does not exist", userID)
// if err != nil {
// return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id: %s does not exist", userID)
// }
if err == nil {
// we found the user, return the identity
return authtypes.NewIdentity(userID, user.OrgID, user.Email, types.Role(user.Role)), nil
}
return authtypes.NewIdentity(userID, user.OrgID, user.Email, types.Role(user.Role)), nil
if err != sql.ErrNoRows {
// this is not a not found error, return the error, something else went wrong
return nil, err
}
// if the user not found, try to find that in root_user table
rootUser := new(types.RootUser)
err = store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(rootUser).
Where("id = ?", userID).
Scan(ctx)
if err == nil {
return authtypes.NewRootIdentity(userID, rootUser.OrgID, rootUser.Email), nil
}
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id: %s does not exist", userID)
}
func (store *store) GetByAccessToken(ctx context.Context, accessToken string) (*authtypes.StorableToken, error) {
@@ -130,6 +157,24 @@ func (store *store) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*auth
return nil, err
}
// ROOT USER TOKENS
rootUserTokens := make([]*authtypes.StorableToken, 0)
err = store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(&rootUserTokens).
Join("JOIN root_users").
JoinOn("root_users.id = auth_token.user_id").
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, err
}
tokens = append(tokens, rootUserTokens...)
return tokens, nil
}
@@ -151,6 +196,26 @@ func (store *store) ListByOrgIDs(ctx context.Context, orgIDs []valuer.UUID) ([]*
return nil, err
}
// ROOT USER TOKENS
rootUserTokens := make([]*authtypes.StorableToken, 0)
err = store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(&rootUserTokens).
Join("JOIN root_users").
JoinOn("root_users.id = auth_token.user_id").
Join("JOIN organizations").
JoinOn("organizations.id = root_users.org_id").
Where("organizations.id IN (?)", bun.In(orgIDs)).
Scan(ctx)
if err != nil {
return nil, err
}
tokens = append(tokens, rootUserTokens...)
return tokens, nil
}

View File

@@ -29,6 +29,7 @@ type Identity struct {
OrgID valuer.UUID `json:"orgId"`
Email valuer.Email `json:"email"`
Role types.Role `json:"role"`
IsRoot bool `json:"isRoot"`
}
type CallbackIdentity struct {
@@ -84,6 +85,17 @@ func NewIdentity(userID valuer.UUID, orgID valuer.UUID, email valuer.Email, role
OrgID: orgID,
Email: email,
Role: role,
IsRoot: false,
}
}
func NewRootIdentity(userID valuer.UUID, orgID valuer.UUID, email valuer.Email) *Identity {
return &Identity{
UserID: userID,
OrgID: orgID,
Email: email,
Role: types.RoleAdmin,
IsRoot: true,
}
}

View File

@@ -151,8 +151,7 @@ func (c *AccountConfig) Value() (driver.Value, error) {
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't serialize cloud account config to JSON")
}
// Return as string instead of []byte to ensure PostgreSQL stores as text, not bytea
return string(serialized), nil
return serialized, nil
}
type AgentReport struct {
@@ -187,8 +186,7 @@ func (r *AgentReport) Value() (driver.Value, error) {
err, errors.CodeInternal, "couldn't serialize agent report to JSON",
)
}
// Return as string instead of []byte to ensure PostgreSQL stores as text, not bytea
return string(serialized), nil
return serialized, nil
}
type CloudIntegrationService struct {
@@ -242,6 +240,5 @@ func (c *CloudServiceConfig) Value() (driver.Value, error) {
err, errors.CodeInternal, "couldn't serialize cloud service config to JSON",
)
}
// Return as string instead of []byte to ensure PostgreSQL stores as text, not bytea
return string(serialized), nil
return serialized, nil
}

View File

@@ -19,7 +19,6 @@ var (
Cumulative = Temporality{valuer.NewString("cumulative")}
Unspecified = Temporality{valuer.NewString("unspecified")}
Unknown = Temporality{valuer.NewString("")}
Multiple = Temporality{valuer.NewString("__multiple__")}
)
func (t Temporality) Value() (driver.Value, error) {

73
pkg/types/rootuser.go Normal file
View File

@@ -0,0 +1,73 @@
package types
import (
"context"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
"golang.org/x/crypto/bcrypt"
)
var (
ErrCodeRootUserAlreadyExists = errors.MustNewCode("root_user_already_exists")
ErrCodeRootUserNotFound = errors.MustNewCode("root_user_not_found")
)
type RootUser struct {
bun.BaseModel `bun:"table:root_users"`
Identifiable // gives ID field
Email valuer.Email `bun:"email,type:text" json:"email"`
PasswordHash string `bun:"password_hash,type:text" json:"-"`
OrgID valuer.UUID `bun:"org_id,type:text" json:"orgId"`
TimeAuditable // gives CreatedAt and UpdatedAt fields
}
func NewRootUser(email valuer.Email, password string, orgID valuer.UUID) (*RootUser, error) {
passwordHash, err := NewHashedPassword(password)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "failed to generate password hash")
}
return &RootUser{
Identifiable: Identifiable{
ID: valuer.GenerateUUID(),
},
Email: email,
PasswordHash: string(passwordHash),
OrgID: orgID,
TimeAuditable: TimeAuditable{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
}, nil
}
func (r *RootUser) VerifyPassword(password string) bool {
return bcrypt.CompareHashAndPassword([]byte(r.PasswordHash), []byte(password)) == nil
}
type RootUserStore interface {
// Creates a new root user. Returns ErrCodeRootUserAlreadyExists if a root user already exists for the organization.
Create(ctx context.Context, rootUser *RootUser) error
// Gets the root user by organization ID. Returns ErrCodeRootUserNotFound if a root user does not exist.
GetByOrgID(ctx context.Context, orgID valuer.UUID) (*RootUser, error)
// Gets a root user by email and organization ID. Returns ErrCodeRootUserNotFound if a root user does not exist.
GetByEmailAndOrgID(ctx context.Context, orgID valuer.UUID, email valuer.Email) (*RootUser, error)
// Gets a root user by organization ID and ID. Returns ErrCodeRootUserNotFound if a root user does not exist.
GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*RootUser, error)
// Gets all root users by email and organization IDs. Returns ErrCodeRootUserNotFound if a root user does not exist.
GetByEmailAndOrgIDs(ctx context.Context, orgIDs []valuer.UUID, email valuer.Email) ([]*RootUser, error)
// Updates the password of a root user. Returns ErrCodeRootUserNotFound if a root user does not exist.
Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, rootUser *RootUser) error
// Checks if a root user exists for an organization. Returns true if a root user exists, false otherwise.
ExistsByOrgID(ctx context.Context, orgID valuer.UUID) (bool, error)
}

View File

@@ -27,10 +27,10 @@ type MetadataStore interface {
GetAllValues(ctx context.Context, fieldValueSelector *FieldValueSelector) (*TelemetryFieldValues, bool, error)
// FetchTemporality fetches the temporality for metric
FetchTemporality(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricName string) (metrictypes.Temporality, error)
FetchTemporality(ctx context.Context, metricName string) (metrictypes.Temporality, error)
// FetchTemporalityMulti fetches the temporality for multiple metrics
FetchTemporalityMulti(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricNames ...string) (map[string]metrictypes.Temporality, error)
FetchTemporalityMulti(ctx context.Context, metricNames ...string) (map[string]metrictypes.Temporality, error)
// ListLogsJSONIndexes lists the JSON indexes for the logs table.
ListLogsJSONIndexes(ctx context.Context, filters ...string) (map[string][]schemamigrator.Index, error)

View File

@@ -265,7 +265,7 @@ func (m *MockMetadataStore) SetAllValues(lookupKey string, values *telemetrytype
}
// FetchTemporality fetches the temporality for a metric
func (m *MockMetadataStore) FetchTemporality(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricName string) (metrictypes.Temporality, error) {
func (m *MockMetadataStore) FetchTemporality(ctx context.Context, metricName string) (metrictypes.Temporality, error) {
if temporality, exists := m.TemporalityMap[metricName]; exists {
return temporality, nil
}
@@ -273,7 +273,7 @@ func (m *MockMetadataStore) FetchTemporality(ctx context.Context, queryTimeRange
}
// FetchTemporalityMulti fetches the temporality for multiple metrics
func (m *MockMetadataStore) FetchTemporalityMulti(ctx context.Context, queryTimeRangeStartTs, queryTimeRangeEndTs uint64, metricNames ...string) (map[string]metrictypes.Temporality, error) {
func (m *MockMetadataStore) FetchTemporalityMulti(ctx context.Context, metricNames ...string) (map[string]metrictypes.Temporality, error) {
result := make(map[string]metrictypes.Temporality)
for _, metricName := range metricNames {

View File

@@ -20,7 +20,6 @@ pytest_plugins = [
"fixtures.idputils",
"fixtures.notification_channel",
"fixtures.alerts",
"fixtures.cloudintegrations",
]

View File

@@ -1,88 +0,0 @@
"""Fixtures for cloud integration tests."""
from typing import Callable, Optional
from http import HTTPStatus
import pytest
import requests
from fixtures import types
from fixtures.logger import setup_logger
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
logger = setup_logger(__name__)
@pytest.fixture(scope="function")
def create_cloud_integration_account(
request: pytest.FixtureRequest,
signoz: types.SigNoz,
) -> Callable[[str, str], dict]:
created_account_id: Optional[str] = None
cloud_provider_used: Optional[str] = None
def _create(
admin_token: str,
cloud_provider: str = "aws",
) -> dict:
nonlocal created_account_id, cloud_provider_used
endpoint = (
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/generate-connection-url"
)
request_payload = {
"account_config": {"regions": ["us-east-1"]},
"agent_config": {
"region": "us-east-1",
"ingestion_url": "https://ingest.test.signoz.cloud",
"ingestion_key": "test-ingestion-key-123456",
"signoz_api_url": "https://test-deployment.test.signoz.cloud",
"signoz_api_key": "test-api-key-789",
"version": "v0.0.8",
},
}
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=request_payload,
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Failed to create test account: {response.status_code}"
data = response.json().get("data", response.json())
created_account_id = data.get("account_id")
cloud_provider_used = cloud_provider
return data
def _disconnect(admin_token: str, cloud_provider: str) -> requests.Response:
assert created_account_id
disconnect_endpoint = (
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{created_account_id}/disconnect"
)
return requests.post(
signoz.self.host_configs["8080"].get(disconnect_endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
# Yield factory to the test
yield _create
# Post-test cleanup: generate admin token and disconnect the created account
if created_account_id and cloud_provider_used:
get_token = request.getfixturevalue("get_token")
try:
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
r = _disconnect(admin_token, cloud_provider_used)
if r.status_code != HTTPStatus.OK:
logger.info(
"Disconnect cleanup returned %s for account %s",
r.status_code,
created_account_id,
)
logger.info("Cleaned up test account: %s", created_account_id)
except Exception as exc: # pylint: disable=broad-except
logger.info("Post-test disconnect cleanup failed: %s", exc)

View File

@@ -1,46 +0,0 @@
"""Fixtures for cloud integration tests."""
from http import HTTPStatus
import requests
from fixtures import types
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
def simulate_agent_checkin(
signoz: types.SigNoz,
admin_token: str,
cloud_provider: str,
account_id: str,
cloud_account_id: str,
) -> dict:
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/agent-check-in"
checkin_payload = {
"account_id": account_id,
"cloud_account_id": cloud_account_id,
"data": {},
}
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=checkin_payload,
timeout=10,
)
if response.status_code != HTTPStatus.OK:
logger.error(
"Agent check-in failed: %s, response: %s",
response.status_code,
response.text,
)
assert (
response.status_code == HTTPStatus.OK
), f"Agent check-in failed: {response.status_code}"
response_data = response.json()
return response_data.get("data", response_data)

View File

@@ -52,7 +52,7 @@ def build_builder_query(
time_aggregation: str,
space_aggregation: str,
*,
temporality: Optional[str] = None,
temporality: str = "cumulative",
step_interval: int = DEFAULT_STEP_INTERVAL,
group_by: Optional[List[str]] = None,
filter_expression: Optional[str] = None,
@@ -65,6 +65,7 @@ def build_builder_query(
"aggregations": [
{
"metricName": metric_name,
"temporality": temporality,
"timeAggregation": time_aggregation,
"spaceAggregation": space_aggregation,
}
@@ -72,8 +73,6 @@ def build_builder_query(
"stepInterval": step_interval,
"disabled": disabled,
}
if temporality:
spec["aggregations"][0]["temporality"] = temporality
if group_by:
spec["groupBy"] = [

View File

@@ -1,143 +0,0 @@
from http import HTTPStatus
from typing import Callable
import pytest
import requests
from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
def test_generate_connection_url(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test to generate connection URL for AWS CloudFormation stack deployment."""
# Get authentication token for admin user
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts/generate-connection-url"
# Prepare request payload
request_payload = {
"account_config": {"regions": ["us-east-1", "us-west-2"]},
"agent_config": {
"region": "us-east-1",
"ingestion_url": "https://ingest.test.signoz.cloud",
"ingestion_key": "test-ingestion-key-123456",
"signoz_api_url": "https://test-deployment.test.signoz.cloud",
"signoz_api_key": "test-api-key-789",
"version": "v0.0.8",
},
}
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=request_payload,
timeout=10,
)
# Assert successful response
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}: {response.text}"
# Parse response JSON
response_data = response.json()
# Assert response structure contains expected data
assert "data" in response_data, "Response should contain 'data' field"
# Assert required fields in the response data
expected_fields = ["account_id", "connection_url"]
for field in expected_fields:
assert (
field in response_data["data"]
), f"Response data should contain '{field}' field"
data = response_data["data"]
# Assert account_id is a valid UUID format
assert (
len(data["account_id"]) > 0
), "account_id should be a non-empty string (UUID)"
# Assert connection_url contains expected CloudFormation parameters
connection_url = data["connection_url"]
# Verify it's an AWS CloudFormation URL
assert (
"console.aws.amazon.com/cloudformation" in connection_url
), "connection_url should be an AWS CloudFormation URL"
# Verify region is included
assert (
"region=us-east-1" in connection_url
), "connection_url should contain the specified region"
# Verify required parameters are in the URL
required_params = [
"param_SigNozIntegrationAgentVersion=v0.0.8",
"param_SigNozApiUrl=https%3A%2F%2Ftest-deployment.test.signoz.cloud",
"param_SigNozApiKey=test-api-key-789",
"param_SigNozAccountId=", # Will be a UUID
"param_IngestionUrl=https%3A%2F%2Fingest.test.signoz.cloud",
"param_IngestionKey=test-ingestion-key-123456",
"stackName=signoz-integration",
"templateURL=https%3A%2F%2Fsignoz-integrations.s3.us-east-1.amazonaws.com%2Faws-quickcreate-template-v0.0.8.json",
]
for param in required_params:
assert (
param in connection_url
), f"connection_url should contain parameter: {param}"
def test_generate_connection_url_unsupported_provider(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test that unsupported cloud providers return an error."""
# Get authentication token for admin user
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Try with GCP (unsupported)
cloud_provider = "gcp"
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts/generate-connection-url"
request_payload = {
"account_config": {"regions": ["us-central1"]},
"agent_config": {
"region": "us-central1",
"ingestion_url": "https://ingest.test.signoz.cloud",
"ingestion_key": "test-ingestion-key-123456",
"signoz_api_url": "https://test-deployment.test.signoz.cloud",
"signoz_api_key": "test-api-key-789",
},
}
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=request_payload,
timeout=10,
)
# Should return Bad Request for unsupported provider
assert (
response.status_code == HTTPStatus.BAD_REQUEST
), f"Expected 400 for unsupported provider, got {response.status_code}"
response_data = response.json()
assert "error" in response_data, "Response should contain 'error' field"
assert (
"unsupported cloud provider" in response_data["error"].lower()
), "Error message should indicate unsupported provider"

View File

@@ -1,316 +0,0 @@
from http import HTTPStatus
from typing import Callable
import uuid
import pytest
import requests
from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
from fixtures.cloudintegrations import (
create_cloud_integration_account,
)
from fixtures.cloudintegrationsutils import simulate_agent_checkin
logger = setup_logger(__name__)
def test_list_connected_accounts_empty(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test listing connected accounts when there are none."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
response_data = response.json()
data = response_data.get("data", response_data)
assert "accounts" in data, "Response should contain 'accounts' field"
assert isinstance(data["accounts"], list), "Accounts should be a list"
assert len(data["accounts"]) == 0, "Accounts list should be empty when no accounts are connected"
def test_list_connected_accounts_with_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test listing connected accounts after creating one."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account
cloud_provider = "aws"
account_data = create_cloud_integration_account(admin_token, cloud_provider)
account_id = account_data["account_id"]
# Simulate agent check-in to mark as connected
cloud_account_id = str(uuid.uuid4())
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
# List accounts
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
response_data = response.json()
data = response_data.get("data", response_data)
assert "accounts" in data, "Response should contain 'accounts' field"
assert isinstance(data["accounts"], list), "Accounts should be a list"
# Find our account in the list (there may be leftover accounts from previous test runs)
account = next((a for a in data["accounts"] if a["id"] == account_id), None)
assert account is not None, f"Account {account_id} should be found in list"
assert account["id"] == account_id, "Account ID should match"
assert "config" in account, "Account should have config field"
assert "status" in account, "Account should have status field"
def test_get_account_status(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test getting the status of a specific account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account (no check-in needed for status check)
cloud_provider = "aws"
account_data = create_cloud_integration_account(admin_token, cloud_provider)
account_id = account_data["account_id"]
# Get account status
endpoint = (
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{account_id}/status"
)
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
response_data = response.json()
data = response_data.get("data", response_data)
assert "id" in data, "Response should contain 'id' field"
assert data["id"] == account_id, "Account ID should match"
assert "status" in data, "Response should contain 'status' field"
assert "integration" in data["status"], "Status should contain 'integration' field"
def test_get_account_status_not_found(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test getting status for a non-existent account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
fake_account_id = "00000000-0000-0000-0000-000000000000"
endpoint = (
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{fake_account_id}/status"
)
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NOT_FOUND
), f"Expected 404, got {response.status_code}"
def test_update_account_config(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test updating account configuration."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account
cloud_provider = "aws"
account_data = create_cloud_integration_account(admin_token, cloud_provider)
account_id = account_data["account_id"]
# Simulate agent check-in to mark as connected
cloud_account_id = str(uuid.uuid4())
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
# Update account configuration
endpoint = (
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{account_id}/config"
)
updated_config = {
"config": {"regions": ["us-east-1", "us-west-2", "eu-west-1"]}
}
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=updated_config,
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
response_data = response.json()
data = response_data.get("data", response_data)
assert "id" in data, "Response should contain 'id' field"
assert data["id"] == account_id, "Account ID should match"
# Verify the update by listing accounts
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
list_response = requests.get(
signoz.self.host_configs["8080"].get(list_endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
list_response_data = list_response.json()
list_data = list_response_data.get("data", list_response_data)
account = next((a for a in list_data["accounts"] if a["id"] == account_id), None)
assert account is not None, "Account should be found in list"
assert "config" in account, "Account should have config"
assert "regions" in account["config"], "Config should have regions"
assert len(account["config"]["regions"]) == 3, "Should have 3 regions"
assert set(account["config"]["regions"]) == {
"us-east-1",
"us-west-2",
"eu-west-1",
}, "Regions should match updated config"
def test_disconnect_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test disconnecting an account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account
cloud_provider = "aws"
account_data = create_cloud_integration_account(admin_token, cloud_provider)
account_id = account_data["account_id"]
# Simulate agent check-in to mark as connected
cloud_account_id = str(uuid.uuid4())
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
# Disconnect the account
endpoint = (
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{account_id}/disconnect"
)
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
# Verify our specific account is no longer in the connected list
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
list_response = requests.get(
signoz.self.host_configs["8080"].get(list_endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
list_response_data = list_response.json()
list_data = list_response_data.get("data", list_response_data)
# Check that our specific account is not in the list
disconnected_account = next(
(a for a in list_data["accounts"] if a["id"] == account_id), None
)
assert disconnected_account is None, f"Account {account_id} should be removed from connected accounts"
def test_disconnect_account_not_found(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test disconnecting a non-existent account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
fake_account_id = "00000000-0000-0000-0000-000000000000"
endpoint = (
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/{fake_account_id}/disconnect"
)
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NOT_FOUND
), f"Expected 404, got {response.status_code}"
def test_list_accounts_unsupported_provider(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test listing accounts for an unsupported cloud provider."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "gcp" # Unsupported provider
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.BAD_REQUEST
), f"Expected 400, got {response.status_code}"

View File

@@ -1,468 +0,0 @@
from http import HTTPStatus
from typing import Callable
import uuid
import pytest
import requests
from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
from fixtures.cloudintegrations import (
create_cloud_integration_account,
)
from fixtures.cloudintegrationsutils import simulate_agent_checkin
logger = setup_logger(__name__)
def test_list_services_without_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test listing available services without specifying an account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
response_data = response.json()
data = response_data.get("data", response_data)
assert "services" in data, "Response should contain 'services' field"
assert isinstance(data["services"], list), "Services should be a list"
assert len(data["services"]) > 0, "Should have at least one service available"
# Verify service structure
service = data["services"][0]
assert "id" in service, "Service should have 'id' field"
assert "title" in service, "Service should have 'title' field"
assert "icon" in service, "Service should have 'icon' field"
def test_list_services_with_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test listing services for a specific connected account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in
cloud_provider = "aws"
account_data = create_cloud_integration_account(admin_token, cloud_provider)
account_id = account_data["account_id"]
cloud_account_id = str(uuid.uuid4())
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
# List services for the account
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services?cloud_account_id={cloud_account_id}"
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
response_data = response.json()
data = response_data.get("data", response_data)
assert "services" in data, "Response should contain 'services' field"
assert isinstance(data["services"], list), "Services should be a list"
assert len(data["services"]) > 0, "Should have at least one service available"
# Services should include config field (may be null if not configured)
service = data["services"][0]
assert "id" in service, "Service should have 'id' field"
assert "title" in service, "Service should have 'title' field"
assert "icon" in service, "Service should have 'icon' field"
def test_get_service_details_without_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test getting service details without specifying an account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
# First get the list of services to get a valid service ID
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
list_response = requests.get(
signoz.self.host_configs["8080"].get(list_endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
list_data = list_response.json().get("data", list_response.json())
assert len(list_data["services"]) > 0, "Should have at least one service"
service_id = list_data["services"][0]["id"]
# Get service details
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{service_id}"
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
response_data = response.json()
data = response_data.get("data", response_data)
# Verify service details structure
assert "id" in data, "Service details should have 'id' field"
assert data["id"] == service_id, "Service ID should match requested ID"
assert "title" in data, "Service details should have 'title' field"
assert "overview" in data, "Service details should have 'overview' field"
# assert assets to had list of dashboards
assert "assets" in data, "Service details should have 'assets' field"
assert isinstance(data["assets"], dict), "Assets should be a dictionary"
def test_get_service_details_with_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test getting service details for a specific connected account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in
cloud_provider = "aws"
account_data = create_cloud_integration_account(admin_token, cloud_provider)
account_id = account_data["account_id"]
cloud_account_id = str(uuid.uuid4())
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
# Get list of services first
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
list_response = requests.get(
signoz.self.host_configs["8080"].get(list_endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
list_data = list_response.json().get("data", list_response.json())
assert len(list_data["services"]) > 0, "Should have at least one service"
service_id = list_data["services"][0]["id"]
# Get service details with account
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{service_id}?cloud_account_id={cloud_account_id}"
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
response_data = response.json()
data = response_data.get("data", response_data)
# Verify service details structure
assert "id" in data, "Service details should have 'id' field"
assert data["id"] == service_id, "Service ID should match requested ID"
assert "title" in data, "Service details should have 'title' field"
assert "overview" in data, "Service details should have 'overview' field"
assert "assets" in data, "Service details should have 'assets' field"
assert "config" in data, "Service details should have 'config' field"
assert "status" in data, "Config should have 'status' field"
def test_get_service_details_invalid_service(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test getting details for a non-existent service."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
fake_service_id = "non-existent-service"
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{fake_service_id}"
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.NOT_FOUND
), f"Expected 404, got {response.status_code}"
def test_list_services_unsupported_provider(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test listing services for an unsupported cloud provider."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "gcp" # Unsupported provider
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
response = requests.get(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
assert (
response.status_code == HTTPStatus.BAD_REQUEST
), f"Expected 400, got {response.status_code}"
def test_update_service_config(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test updating service configuration for a connected account."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in
cloud_provider = "aws"
account_data = create_cloud_integration_account(admin_token, cloud_provider)
account_id = account_data["account_id"]
cloud_account_id = str(uuid.uuid4())
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
# Get list of services to pick a valid service ID
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
list_response = requests.get(
signoz.self.host_configs["8080"].get(list_endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
list_data = list_response.json().get("data", list_response.json())
assert len(list_data["services"]) > 0, "Should have at least one service"
service_id = list_data["services"][0]["id"]
# Update service configuration
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{service_id}/config"
config_payload = {
"cloud_account_id": cloud_account_id,
"config": {
"metrics": {"enabled": True},
"logs": {"enabled": True},
},
}
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=config_payload,
timeout=10,
)
assert (
response.status_code == HTTPStatus.OK
), f"Expected 200, got {response.status_code}"
response_data = response.json()
data = response_data.get("data", response_data)
# Verify response structure
assert "id" in data, "Response should contain 'id' field"
assert data["id"] == service_id, "Service ID should match"
assert "config" in data, "Response should contain 'config' field"
assert "metrics" in data["config"], "Config should contain 'metrics' field"
assert "logs" in data["config"], "Config should contain 'logs' field"
def test_update_service_config_without_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test updating service config without a connected account should fail."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
# Get a valid service ID
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
list_response = requests.get(
signoz.self.host_configs["8080"].get(list_endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
list_data = list_response.json().get("data", list_response.json())
service_id = list_data["services"][0]["id"]
# Try to update config with non-existent account
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{service_id}/config"
fake_cloud_account_id = str(uuid.uuid4())
config_payload = {
"cloud_account_id": fake_cloud_account_id,
"config": {
"metrics": {"enabled": True},
},
}
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=config_payload,
timeout=10,
)
assert (
response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
), f"Expected 500 for non-existent account, got {response.status_code}"
def test_update_service_config_invalid_service(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test updating config for a non-existent service should fail."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in
cloud_provider = "aws"
account_data = create_cloud_integration_account(admin_token, cloud_provider)
account_id = account_data["account_id"]
cloud_account_id = str(uuid.uuid4())
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
# Try to update config for invalid service
fake_service_id = "non-existent-service"
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{fake_service_id}/config"
config_payload = {
"cloud_account_id": cloud_account_id,
"config": {
"metrics": {"enabled": True},
},
}
response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=config_payload,
timeout=10,
)
assert (
response.status_code == HTTPStatus.NOT_FOUND
), f"Expected 404 for invalid service, got {response.status_code}"
def test_update_service_config_disable_service(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test disabling a service by updating config with enabled=false."""
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in
cloud_provider = "aws"
account_data = create_cloud_integration_account(admin_token, cloud_provider)
account_id = account_data["account_id"]
cloud_account_id = str(uuid.uuid4())
simulate_agent_checkin(signoz, admin_token, cloud_provider, account_id, cloud_account_id)
# Get a valid service
list_endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
list_response = requests.get(
signoz.self.host_configs["8080"].get(list_endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
timeout=10,
)
list_data = list_response.json().get("data", list_response.json())
service_id = list_data["services"][0]["id"]
# First enable the service
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services/{service_id}/config"
enable_payload = {
"cloud_account_id": cloud_account_id,
"config": {
"metrics": {"enabled": True},
},
}
enable_response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=enable_payload,
timeout=10,
)
assert enable_response.status_code == HTTPStatus.OK, "Failed to enable service"
# Now disable the service
disable_payload = {
"cloud_account_id": cloud_account_id,
"config": {
"metrics": {"enabled": False},
"logs": {"enabled": False},
},
}
disable_response = requests.post(
signoz.self.host_configs["8080"].get(endpoint),
headers={"Authorization": f"Bearer {admin_token}"},
json=disable_payload,
timeout=10,
)
assert (
disable_response.status_code == HTTPStatus.OK
), f"Expected 200, got {disable_response.status_code}"
response_data = disable_response.json()
data = response_data.get("data", response_data)
# Verify service is disabled
assert data["config"]["metrics"]["enabled"] is False, "Metrics should be disabled"
assert data["config"]["logs"]["enabled"] is False, "Logs should be disabled"

View File

@@ -1,399 +0,0 @@
"""
Look at the multi_temporality_counters_1h.jsonl file for the relevant data
"""
import os
from datetime import datetime, timedelta, timezone
from http import HTTPStatus
import random
from typing import Any, Callable, List
import pytest
from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.metrics import Metrics
from fixtures.querier import (
build_builder_query,
get_all_series,
get_series_values,
make_query_request,
)
from fixtures.utils import get_testdata_file_path
MULTI_TEMPORALITY_FILE = get_testdata_file_path("multi_temporality_counters_1h.jsonl")
MULTI_TEMPORALITY_FILE_10h = get_testdata_file_path("multi_temporality_counters_10h.jsonl")
MULTI_TEMPORALITY_FILE_24h = get_testdata_file_path("multi_temporality_counters_24h.jsonl")
@pytest.mark.parametrize(
"time_aggregation, expected_value_at_31st_minute, expected_value_at_32nd_minute, steady_value",
[
("rate", 0.0167, 0.133, 0.0833),
("increase", 2, 8, 5),
],
)
def test_with_steady_values_and_reset(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
time_aggregation: str,
expected_value_at_31st_minute: float,
expected_value_at_32nd_minute: float,
steady_value: float
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
metric_name = f"test_{time_aggregation}_stale"
metrics = Metrics.load_from_file(
MULTI_TEMPORALITY_FILE,
base_time=now - timedelta(minutes=60),
metric_name_override=metric_name,
)
insert_metrics(metrics)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,
time_aggregation,
"sum",
filter_expression='endpoint = "/orders"',
)
response = make_query_request(signoz, token, start_ms, end_ms, [query])
assert response.status_code == HTTPStatus.OK
data = response.json()
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
assert len(result_values) >= 59
# the counter reset happened at 31st minute
assert (
result_values[30]["value"] == expected_value_at_31st_minute
)
assert (
result_values[31]["value"] == expected_value_at_32nd_minute
)
assert (
result_values[39]["value"] == steady_value
) # 39th minute is when cumulative shifts to delta
count_of_steady_rate = sum(1 for v in result_values if v["value"] == steady_value)
assert (
count_of_steady_rate >= 56
) # 59 - (1 reset + 1 high rate + 1 at the beginning)
# All rates should be non-negative (stale periods = 0 rate)
for v in result_values:
assert v["value"] >= 0, f"{time_aggregation} should not be negative: {v['value']}"
@pytest.mark.parametrize(
"time_aggregation, stable_health_value, stable_products_value, stable_checkout_value, spike_checkout_value, stable_orders_value, spike_users_value",
[
("rate", 0.167, 0.333, 0.0167, 0.833, 0.0833, 0.0167),
("increase", 10, 20, 1, 50, 5, 1),
],
)
def test_group_by_endpoint(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
time_aggregation: str,
stable_health_value: float,
stable_products_value: float,
stable_checkout_value: float,
spike_checkout_value: float,
stable_orders_value: float,
spike_users_value: float,
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
metric_name = f"test_{time_aggregation}_groupby"
metrics = Metrics.load_from_file(
MULTI_TEMPORALITY_FILE,
base_time=now - timedelta(minutes=60),
metric_name_override=metric_name,
)
insert_metrics(metrics)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,
time_aggregation,
"sum",
group_by=["endpoint"],
)
response = make_query_request(signoz, token, start_ms, end_ms, [query])
assert response.status_code == HTTPStatus.OK
data = response.json()
all_series = get_all_series(data, "A")
# Should have 5 different endpoints
assert (
len(all_series) == 5
), f"Expected 5 series for 5 endpoints, got {len(all_series)}"
# endpoint -> values
endpoint_values = {}
for series in all_series:
endpoint = series.get("labels", [{}])[0].get("value", "unknown")
values = sorted(series.get("values", []), key=lambda x: x["timestamp"])
endpoint_values[endpoint] = values
expected_endpoints = {"/products", "/health", "/checkout", "/orders", "/users"}
assert (
set(endpoint_values.keys()) == expected_endpoints
), f"Expected endpoints {expected_endpoints}, got {set(endpoint_values.keys())}"
# at no point rate should be negative
for endpoint, values in endpoint_values.items():
for v in values:
assert (
v["value"] >= 0
), f"Rate for {endpoint} should not be negative: {v['value']}"
# /health: 60 data points (t01-t60), steady +10/min
health_values = endpoint_values["/health"]
assert (
len(health_values) >= 58
), f"Expected >= 58 values for /health, got {len(health_values)}"
count_steady_health = sum(1 for v in health_values if v["value"] == stable_health_value)
assert (
count_steady_health >= 57
), f"Expected >= 57 steady rate values ({stable_health_value}) for /health, got {count_steady_health}"
# all /health rates should be state except possibly first/last due to boundaries
for v in health_values[1:-1]:
assert v["value"] == stable_health_value, f"Expected /health rate {stable_health_value}, got {v['value']}"
# /products: 51 data points with 10-minute gap (t20-t29 missing), steady +20/min
products_values = endpoint_values["/products"]
assert (
len(products_values) >= 49
), f"Expected >= 49 values for /products, got {len(products_values)}"
count_steady_products = sum(1 for v in products_values if v["value"] == stable_products_value)
# most values should be stable, some boundary values differ due to 10-min gap
assert (
count_steady_products >= 46
), f"Expected >= 46 steady rate values ({stable_products_value}) for /products, got {count_steady_products}"
# check that non-stable values are due to gap averaging (should be lower)
gap_boundary_values = [v["value"] for v in products_values if v["value"] != stable_products_value]
for val in gap_boundary_values:
assert (
0 < val < stable_products_value
), f"Gap boundary values should be between 0 and {stable_products_value}, got {val}"
# /checkout: 61 data points (t00-t60), +1/min normal, +50/min spike at t40-t44
checkout_values = endpoint_values["/checkout"]
assert (
len(checkout_values) >= 59
), f"Expected >= 59 values for /checkout, got {len(checkout_values)}"
count_steady_checkout = sum(1 for v in checkout_values if v["value"] == stable_checkout_value)
assert (
count_steady_checkout >= 53
), f"Expected >= 53 steady {time_aggregation} values ({stable_checkout_value}) for /checkout, got {count_steady_checkout}"
# check that spike values exist (traffic spike +50/min at t40-t44)
count_spike_checkout = sum(1 for v in checkout_values if v["value"] == spike_checkout_value)
assert (
count_spike_checkout >= 4
), f"Expected >= 4 spike {time_aggregation} values ({spike_checkout_value}) for /checkout, got {count_spike_checkout}"
# spike values should be consecutive
spike_indices = [
i for i, v in enumerate(checkout_values) if v["value"] == spike_checkout_value
]
assert len(spike_indices) >= 4, f"Expected >= 4 spike indices, got {spike_indices}"
# consecutiveness
for i in range(1, len(spike_indices)):
assert (
spike_indices[i] == spike_indices[i - 1] + 1
), f"Spike indices should be consecutive, got {spike_indices}"
# /orders: 60 data points (t00-t60) with gap at t30, counter reset at t31 (150->2)
# reset at t31 causes: rate/increase at t30 includes gap (lower), t31 has high rate after reset
orders_values = endpoint_values["/orders"]
assert (
len(orders_values) >= 58
), f"Expected >= 58 values for /orders, got {len(orders_values)}"
count_steady_orders = sum(1 for v in orders_values if v["value"] == stable_orders_value)
assert (
count_steady_orders >= 55
), f"Expected >= 55 steady {time_aggregation} values ({stable_orders_value}) for /orders, got {count_steady_orders}"
# check for counter reset effects - there should be some non-standard values
non_standard_orders = [v["value"] for v in orders_values if v["value"] != stable_orders_value]
assert (
len(non_standard_orders) >= 2
), f"Expected >= 2 non-standard values due to counter reset, got {non_standard_orders}"
# post-reset value should be higher (new counter value / interval)
high_rate_orders = [v for v in non_standard_orders if v > stable_orders_value]
assert (
len(high_rate_orders) >= 1
), f"Expected at least one high {time_aggregation} value after counter reset, got {non_standard_orders}"
# /users: 56 data points (t05-t60), sparse +1 every 5 minutes
users_values = endpoint_values["/users"]
assert (
len(users_values) >= 54
), f"Expected >= 54 values for /users, got {len(users_values)}"
count_zero_users = sum(1 for v in users_values if v["value"] == 0)
# most values should be 0 (flat periods between increments)
assert (
count_zero_users >= 40
), f"Expected >= 40 zero {time_aggregation} values for /users (sparse data), got {count_zero_users}"
# non-zero values should be 0.0167 (1/60 increment rate)
non_zero_users = [v["value"] for v in users_values if v["value"] != 0]
count_increment_rate = sum(1 for v in non_zero_users if v == spike_users_value)
assert (
count_increment_rate >= 8
), f"Expected >= 8 increment {time_aggregation} values ({spike_users_value}) for /users, got {count_increment_rate}"
@pytest.mark.parametrize(
"time_aggregation, expected_value_at_30th_minute, expected_value_at_31st_minute, value_at_switch",
[
("rate", 0.183, 0.183, 0.25),
("increase", 11, 12, 15),
],
)
def test_for_service_with_switch(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
time_aggregation: str,
expected_value_at_30th_minute: float,
expected_value_at_31st_minute: float,
value_at_switch: float
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
metric_name = f"test_{time_aggregation}_count"
metrics = Metrics.load_from_file(
MULTI_TEMPORALITY_FILE,
base_time=now - timedelta(minutes=60),
metric_name_override=metric_name,
)
insert_metrics(metrics)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,
time_aggregation,
"sum",
filter_expression='service = "api"',
)
response = make_query_request(signoz, token, start_ms, end_ms, [query])
assert response.status_code == HTTPStatus.OK
data = response.json()
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
assert len(result_values) >= 60
assert (
result_values[30]["value"] == expected_value_at_30th_minute #0.183
)
assert (
result_values[31]["value"] == expected_value_at_31st_minute # 0.183
)
assert (
result_values[38]["value"] == value_at_switch # 0.25
)
assert (
result_values[39]["value"] == value_at_switch # 0.25
) # 39th minute is when cumulative shifts to delta
# All rates should be non-negative (stale periods = 0 rate)
for v in result_values:
assert v["value"] >= 0, f"{time_aggregation} should not be negative: {v['value']}"
@pytest.mark.parametrize(
"time_aggregation, expected_value",
[
("rate", 0.0122),
("increase", 22),
],
)
def test_for_week_long_time_range(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
time_aggregation: str,
expected_value: float,
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
start_ms = int((now - timedelta(days=7)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
metric_name = f"test_{time_aggregation}_" + hex(random.getrandbits(32))[2:]
metrics = Metrics.load_from_file(
MULTI_TEMPORALITY_FILE_10h,
base_time=now - timedelta(minutes=600),
metric_name_override=metric_name,
)
insert_metrics(metrics)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,
time_aggregation,
"sum",
)
response = make_query_request(signoz, token, start_ms, end_ms, [query])
assert response.status_code == HTTPStatus.OK
data = response.json()
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
# the zeroth value may not cover the whole time range
for value in result_values[1:]:
assert value["value"] == expected_value
@pytest.mark.parametrize(
"time_aggregation, expected_value",
[
("rate", 0.0122),
("increase", 110),
],
)
def test_for_month_long_time_range(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
time_aggregation: str,
expected_value: float,
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
start_ms = int((now - timedelta(days=31)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
metric_name = f"test_{time_aggregation}_" + hex(random.getrandbits(32))[2:]
metrics = Metrics.load_from_file(
MULTI_TEMPORALITY_FILE_24h,
base_time=now - timedelta(minutes=1441),
metric_name_override=metric_name,
)
insert_metrics(metrics)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,
time_aggregation,
"sum",
)
response = make_query_request(signoz, token, start_ms, end_ms, [query])
assert response.status_code == HTTPStatus.OK
data = response.json()
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
## the zeroth and last values may not cover the whole 9000 seconds
for value in result_values[1:-1]:
assert value["value"] == expected_value

View File

@@ -1,84 +0,0 @@
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:30:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T11:00:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T11:30:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T12:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T12:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T13:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T13:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T14:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T14:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T15:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T15:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T16:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T16:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T17:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T17:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T18:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T18:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T19:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T19:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T20:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":50,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":70,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":110,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":130,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":150,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":160,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":170,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":180,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":190,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":200,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":210,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":8,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":9,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":11,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":12,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":13,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":14,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":16,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":17,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":18,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":19,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":21,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}

View File

@@ -1,288 +0,0 @@
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:01:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:02:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:03:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:04:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:05:00+00:00","value":50,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:06:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:07:00+00:00","value":70,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:08:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:09:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:10:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:11:00+00:00","value":110,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:12:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:13:00+00:00","value":130,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:14:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:15:00+00:00","value":150,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:16:00+00:00","value":160,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:17:00+00:00","value":170,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:18:00+00:00","value":180,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:19:00+00:00","value":190,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:20:00+00:00","value":200,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:21:00+00:00","value":210,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:22:00+00:00","value":220,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:23:00+00:00","value":230,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:24:00+00:00","value":240,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:25:00+00:00","value":250,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:26:00+00:00","value":260,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:27:00+00:00","value":270,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:28:00+00:00","value":280,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:29:00+00:00","value":290,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":300,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:31:00+00:00","value":310,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:32:00+00:00","value":320,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:33:00+00:00","value":330,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:34:00+00:00","value":340,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:35:00+00:00","value":350,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:36:00+00:00","value":360,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:37:00+00:00","value":370,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:38:00+00:00","value":380,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:39:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:40:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:41:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:42:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:43:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:44:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:45:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:46:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:47:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:48:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:49:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:50:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:51:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:52:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:53:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:54:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:55:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:56:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:57:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:58:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:59:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:05:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:06:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:07:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:08:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:09:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:10:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:11:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:12:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:13:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:14:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:15:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:16:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:17:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:18:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:19:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:20:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:21:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:22:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:23:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:24:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:25:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:26:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:27:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:28:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:29:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:30:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:31:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:32:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:33:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:34:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:35:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:36:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:37:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:38:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:39:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:40:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:41:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:42:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:43:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:44:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:45:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:46:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:47:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:48:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:49:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:50:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:51:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:52:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:53:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:54:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:55:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:56:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:57:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:58:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:59:00+00:00","value":0,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T11:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:01:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:02:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:03:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:04:00+00:00","value":25,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:05:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:06:00+00:00","value":35,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:07:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:08:00+00:00","value":45,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:09:00+00:00","value":50,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:10:00+00:00","value":55,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:11:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:12:00+00:00","value":65,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:13:00+00:00","value":70,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:14:00+00:00","value":75,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:15:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:16:00+00:00","value":85,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:17:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:18:00+00:00","value":95,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:19:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:20:00+00:00","value":105,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:21:00+00:00","value":110,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:22:00+00:00","value":115,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:23:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:24:00+00:00","value":125,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:25:00+00:00","value":130,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:26:00+00:00","value":135,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:27:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:28:00+00:00","value":145,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:29:00+00:00","value":150,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:31:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:32:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:33:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:34:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:35:00+00:00","value":25,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:36:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:37:00+00:00","value":35,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:38:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:39:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:40:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:41:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:42:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:43:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:44:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:45:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:46:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:47:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:48:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:49:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:50:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:51:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:52:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:53:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:54:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:55:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:56:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:57:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:58:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T10:59:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/orders","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":5,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:01:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:02:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:03:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:04:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:05:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:06:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:07:00+00:00","value":160,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:08:00+00:00","value":180,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:09:00+00:00","value":200,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:10:00+00:00","value":220,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:11:00+00:00","value":240,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:12:00+00:00","value":260,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:13:00+00:00","value":280,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:14:00+00:00","value":300,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:15:00+00:00","value":320,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:16:00+00:00","value":340,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:17:00+00:00","value":360,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:18:00+00:00","value":380,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:19:00+00:00","value":400,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":420,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:31:00+00:00","value":440,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:32:00+00:00","value":460,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:33:00+00:00","value":480,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:34:00+00:00","value":500,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:35:00+00:00","value":520,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:36:00+00:00","value":540,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:37:00+00:00","value":560,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:38:00+00:00","value":580,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:39:00+00:00","value":600,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:40:00+00:00","value":620,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:41:00+00:00","value":640,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:42:00+00:00","value":660,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:43:00+00:00","value":680,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:44:00+00:00","value":700,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:45:00+00:00","value":720,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:46:00+00:00","value":740,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:47:00+00:00","value":760,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:48:00+00:00","value":780,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:49:00+00:00","value":800,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:50:00+00:00","value":820,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:51:00+00:00","value":840,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:52:00+00:00","value":860,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:53:00+00:00","value":880,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:54:00+00:00","value":900,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:55:00+00:00","value":920,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:56:00+00:00","value":940,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:57:00+00:00","value":960,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:58:00+00:00","value":980,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:59:00+00:00","value":1000,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":1020,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:00:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:01:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:02:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:03:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:04:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:05:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:06:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:07:00+00:00","value":8,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:08:00+00:00","value":9,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:09:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:10:00+00:00","value":11,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:11:00+00:00","value":12,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:12:00+00:00","value":13,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:13:00+00:00","value":14,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:14:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:15:00+00:00","value":16,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:16:00+00:00","value":17,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:17:00+00:00","value":18,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:18:00+00:00","value":19,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:19:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:20:00+00:00","value":21,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:21:00+00:00","value":22,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:22:00+00:00","value":23,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:23:00+00:00","value":24,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:24:00+00:00","value":25,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:25:00+00:00","value":26,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:26:00+00:00","value":27,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:27:00+00:00","value":28,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:28:00+00:00","value":29,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:29:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:30:00+00:00","value":31,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:31:00+00:00","value":32,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:32:00+00:00","value":33,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:33:00+00:00","value":34,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:34:00+00:00","value":35,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:35:00+00:00","value":36,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:36:00+00:00","value":37,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:37:00+00:00","value":38,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:38:00+00:00","value":39,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:39:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:40:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:41:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:42:00+00:00","value":190,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:43:00+00:00","value":240,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:44:00+00:00","value":290,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:45:00+00:00","value":291,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:46:00+00:00","value":292,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:47:00+00:00","value":293,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:48:00+00:00","value":294,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:49:00+00:00","value":295,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:50:00+00:00","value":296,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:51:00+00:00","value":297,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:52:00+00:00","value":298,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:53:00+00:00","value":299,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:54:00+00:00","value":300,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:55:00+00:00","value":301,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:56:00+00:00","value":302,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:57:00+00:00","value":303,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:58:00+00:00","value":304,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T10:59:00+00:00","value":305,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"429"},"timestamp":"2025-01-10T11:00:00+00:00","value":306,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}

View File

@@ -1,196 +0,0 @@
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T00:00:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T00:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T01:00:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T01:30:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T02:00:00+00:00","value":50,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T02:30:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T03:00:00+00:00","value":70,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T03:30:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T04:00:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T04:30:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T05:00:00+00:00","value":110,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T05:30:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T06:00:00+00:00","value":130,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T06:30:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T07:00:00+00:00","value":150,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T07:30:00+00:00","value":160,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T08:00:00+00:00","value":170,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T08:30:00+00:00","value":180,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T09:00:00+00:00","value":190,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T09:30:00+00:00","value":200,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":210,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":220,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":230,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":240,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T20:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T21:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T21:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T22:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T22:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T23:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-10T23:30:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/health","status_code":"200"},"timestamp":"2025-01-11T00:00:00+00:00","value":10,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T00:00:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T00:30:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T01:00:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T01:30:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T02:00:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T02:30:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T03:00:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T03:30:00+00:00","value":8,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T04:00:00+00:00","value":9,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T04:30:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T05:00:00+00:00","value":11,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T05:30:00+00:00","value":12,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T06:00:00+00:00","value":13,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T06:30:00+00:00","value":14,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T07:00:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T07:30:00+00:00","value":16,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T08:00:00+00:00","value":17,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T08:30:00+00:00","value":18,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T09:00:00+00:00","value":19,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T09:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:00:00+00:00","value":21,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T10:30:00+00:00","value":22,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T11:00:00+00:00","value":23,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T11:30:00+00:00","value":24,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T12:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T12:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T13:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T13:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T14:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T14:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T15:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T15:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T16:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T16:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T17:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T17:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T18:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T18:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T19:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T19:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T20:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T20:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T21:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T21:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T22:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T22:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T23:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-10T23:30:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Delta","service":"api","endpoint":"/users","status_code":"500"},"timestamp":"2025-01-11T00:00:00+00:00","value":1,"temporality":"Delta","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T00:00:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T00:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T01:00:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T01:30:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T02:00:00+00:00","value":50,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T02:30:00+00:00","value":60,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T03:00:00+00:00","value":70,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T03:30:00+00:00","value":80,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T04:00:00+00:00","value":90,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T04:30:00+00:00","value":100,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T05:00:00+00:00","value":110,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T05:30:00+00:00","value":120,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T06:00:00+00:00","value":130,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T06:30:00+00:00","value":140,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T07:00:00+00:00","value":150,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T07:30:00+00:00","value":160,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T08:00:00+00:00","value":170,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T08:30:00+00:00","value":180,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T09:00:00+00:00","value":190,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T09:30:00+00:00","value":200,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":210,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":220,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":230,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":240,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":250,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":260,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":270,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":280,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":290,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":300,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":310,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":320,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":330,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":340,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":350,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":360,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":370,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":380,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":390,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":400,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":410,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T20:30:00+00:00","value":420,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T21:00:00+00:00","value":430,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T21:30:00+00:00","value":440,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T22:00:00+00:00","value":450,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T22:30:00+00:00","value":460,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T23:00:00+00:00","value":470,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-10T23:30:00+00:00","value":480,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/products","status_code":"200"},"timestamp":"2025-01-11T00:00:00+00:00","value":490,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T00:00:00+00:00","value":1,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T00:30:00+00:00","value":2,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T01:00:00+00:00","value":3,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T01:30:00+00:00","value":4,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T02:00:00+00:00","value":5,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T02:30:00+00:00","value":6,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T03:00:00+00:00","value":7,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T03:30:00+00:00","value":8,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T04:00:00+00:00","value":9,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T04:30:00+00:00","value":10,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T05:00:00+00:00","value":11,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T05:30:00+00:00","value":12,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T06:00:00+00:00","value":13,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T06:30:00+00:00","value":14,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T07:00:00+00:00","value":15,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T07:30:00+00:00","value":16,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T08:00:00+00:00","value":17,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T08:30:00+00:00","value":18,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T09:00:00+00:00","value":19,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T09:30:00+00:00","value":20,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T10:00:00+00:00","value":21,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T10:30:00+00:00","value":22,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T11:00:00+00:00","value":23,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T11:30:00+00:00","value":24,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T12:00:00+00:00","value":25,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T12:30:00+00:00","value":26,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T13:00:00+00:00","value":27,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T13:30:00+00:00","value":28,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T14:00:00+00:00","value":29,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T14:30:00+00:00","value":30,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T15:00:00+00:00","value":31,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T15:30:00+00:00","value":32,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T16:00:00+00:00","value":33,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T16:30:00+00:00","value":34,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T17:00:00+00:00","value":35,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T17:30:00+00:00","value":36,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T18:00:00+00:00","value":37,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T18:30:00+00:00","value":38,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T19:00:00+00:00","value":39,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T19:30:00+00:00","value":40,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T20:00:00+00:00","value":41,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T20:30:00+00:00","value":42,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T21:00:00+00:00","value":43,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T21:30:00+00:00","value":44,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T22:00:00+00:00","value":45,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T22:30:00+00:00","value":46,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T23:00:00+00:00","value":47,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-10T23:30:00+00:00","value":48,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}
{"metric_name":"http.request.count","labels":{"__temporality__":"Cumulative","service":"web","endpoint":"/checkout","status_code":"200"},"timestamp":"2025-01-11T00:00:00+00:00","value":49,"temporality":"Cumulative","type_":"Sum","is_monotonic":true,"flags":0,"description":"","unit":"","env":"default","resource_attrs":{},"scope_attrs":{}}