Compare commits

..

3 Commits

Author SHA1 Message Date
Gaurav Tewari
55d5ddb9ba feat: ai trace view cloumn 2026-08-04 16:14:18 +05:30
Gaurav Tewari
fead012459 chore: initial commit 2026-08-04 16:08:42 +05:30
Cursor Agent
5f50dcd349 poc(ai-o11y): Trace View column registry + client-side visibility
Hardcode a Trace View column whitelist (AI O11y TDD shape), own visibility
in a separate localStorage key, and wire Options → Edit columns via
FieldsSelector.availableFields so toggling is instant with no selectFields
refetch. Demoed on existing Trace View.

Co-authored-by: Gaurav Tewari <gauravtewari111@gmail.com>
2026-08-04 09:42:31 +00:00
18 changed files with 515 additions and 117 deletions

View File

@@ -31,6 +31,13 @@ interface FieldsSelectorProps {
width?: number;
height?: number;
defaultPosition?: { x: number; y: number };
/**
* Caller-supplied field list. When provided, Other Fields offers exactly
* these and key discovery is skipped — use it when the set of selectable
* fields is known up front rather than fetched. Omit to discover keys from
* the API for `signal`.
*/
availableFields?: TelemetryFieldKey[];
}
type FieldsSelectorContentProps = Omit<FieldsSelectorProps, 'isOpen'>;
@@ -49,6 +56,7 @@ function FieldsSelectorContent({
width = DEFAULT_PANEL_WIDTH,
height,
defaultPosition,
availableFields,
}: FieldsSelectorContentProps): JSX.Element {
const resolvedHeight =
height ?? window.innerHeight - DEFAULT_PANEL_HEIGHT_OFFSET;
@@ -153,6 +161,7 @@ function FieldsSelectorContent({
addedFields={draftFields}
onAdd={handleAdd}
isAtLimit={isAtLimit}
availableFields={availableFields}
/>
{hasUnsavedChanges && (

View File

@@ -21,6 +21,11 @@ interface OtherFieldsProps {
addedFields: TelemetryFieldKey[];
onAdd: (field: TelemetryFieldKey) => void;
isAtLimit: boolean;
/**
* Caller-supplied field list. When provided, key discovery is skipped and
* these are filtered locally by the search term instead.
*/
availableFields?: TelemetryFieldKey[];
}
function OtherFields({
@@ -29,7 +34,10 @@ function OtherFields({
addedFields,
onAdd,
isAtLimit,
availableFields,
}: OtherFieldsProps): JSX.Element {
const useRegistry = Boolean(availableFields);
const { data, isFetching } = useGetQueryKeySuggestions(
{
signal,
@@ -41,11 +49,34 @@ function OtherFields({
signal,
debouncedInputValue,
],
enabled: true,
enabled: !useRegistry,
},
);
const otherFields: TelemetryFieldKey[] = useMemo(() => {
const addedIds = new Set(
addedFields.map((f) => f.key ?? buildCompositeKey(f.name, f.fieldContext)),
);
if (useRegistry && availableFields) {
const search = debouncedInputValue.trim().toLowerCase();
return availableFields
.filter((attr) => {
const id = attr.key ?? buildCompositeKey(attr.name, attr.fieldContext);
if (addedIds.has(id)) {
return false;
}
if (!search) {
return true;
}
return attr.name.toLowerCase().includes(search);
})
.map((attr) => ({
...attr,
key: attr.key ?? buildCompositeKey(attr.name, attr.fieldContext),
}));
}
const suggestions = Object.values(data?.data.data.keys || {}).flat();
// Normalize: synthesize `key` once so downstream reads can trust it.
const normalizedSuggestions: TelemetryFieldKey[] = suggestions.map(
@@ -57,15 +88,12 @@ function OtherFields({
fieldDataType: attr.fieldDataType,
}),
);
const addedIds = new Set(
addedFields.map((f) => f.key ?? buildCompositeKey(f.name, f.fieldContext)),
);
return normalizedSuggestions.filter(
(attr) => !addedIds.has(attr.key as string),
);
}, [data, addedFields]);
}, [data, addedFields, availableFields, debouncedInputValue, useRegistry]);
if (isFetching) {
if (!useRegistry && isFetching) {
return (
<div className={cx(styles.section, styles.sectionOther)}>
<div className={styles.sectionHeader}>OTHER FIELDS</div>

View File

@@ -11,6 +11,8 @@ export enum LOCALSTORAGE {
TRACES_LIST_OPTIONS = 'TRACES_LIST_OPTIONS',
GRAPH_VISIBILITY_STATES = 'GRAPH_VISIBILITY_STATES',
TRACES_LIST_COLUMNS = 'TRACES_LIST_COLUMNS',
/** AI Observability Trace View column visibility. */
AI_TRACE_VIEW_COLUMNS = 'AI_TRACE_VIEW_COLUMNS',
LOGS_LIST_COLUMNS = 'LOGS_LIST_COLUMNS',
LOGS_LIST_COLUMN_SIZING = 'LOGS_LIST_COLUMN_SIZING',
LOGGED_IN_USER_NAME = 'LOGGED_IN_USER_NAME',

View File

@@ -0,0 +1,178 @@
import { Typography } from '@signozhq/ui/typography';
import { LOCALSTORAGE } from 'constants/localStorage';
import {
BASE_TRACE_VIEW_COLUMNS,
renderTraceDurationCell,
TraceViewColumn,
} from 'container/TracesExplorer/TracesView/configs';
import { TraceViewColumnSelection } from 'container/TracesExplorer/TracesView/useTraceViewColumns';
function isBlank(value: unknown): boolean {
return value === undefined || value === null || value === '';
}
function renderCountCell(value: unknown): JSX.Element {
if (isBlank(value)) {
return <Typography></Typography>;
}
const count = Number(value);
return (
<Typography>
{Number.isFinite(count) ? count.toLocaleString() : String(value)}
</Typography>
);
}
function renderCostCell(value: unknown): JSX.Element {
if (isBlank(value)) {
return <Typography></Typography>;
}
const cost = Number(value);
return (
<Typography>
{Number.isFinite(cost) ? `$${cost.toFixed(4)}` : String(value)}
</Typography>
);
}
/**
* Trace-level gen_ai aggregates. The query-range response does not carry these
* yet, so they render as em dashes until the AI trace API lands — visible but
* empty is intentional, it lets the column set be reviewed ahead of the data.
*/
const AI_ONLY_COLUMNS: TraceViewColumn[] = [
{
field: {
name: 'input_tokens',
fieldContext: 'trace',
fieldDataType: 'int64',
},
title: 'Input Tokens',
render: renderCountCell,
},
{
field: {
name: 'output_tokens',
fieldContext: 'trace',
fieldDataType: 'int64',
},
title: 'Output Tokens',
render: renderCountCell,
},
{
field: {
name: 'total_tokens',
fieldContext: 'trace',
fieldDataType: 'int64',
},
title: 'Total Tokens',
render: renderCountCell,
},
{
field: {
name: 'llm_call_count',
fieldContext: 'trace',
fieldDataType: 'int64',
},
title: 'LLM Calls',
render: renderCountCell,
},
{
field: {
name: 'tool_call_count',
fieldContext: 'trace',
fieldDataType: 'int64',
},
title: 'Tool Calls',
render: renderCountCell,
},
{
field: {
name: 'distinct_tool_count',
fieldContext: 'trace',
fieldDataType: 'int64',
},
title: 'Distinct Tools',
render: renderCountCell,
},
{
field: {
name: 'estimated_cost_usd',
fieldContext: 'trace',
fieldDataType: 'float64',
},
title: 'Est. Cost (USD)',
render: renderCostCell,
},
{
field: {
name: 'max_llm_latency_ns',
fieldContext: 'trace',
fieldDataType: 'int64',
},
title: 'Max LLM Latency',
render: renderTraceDurationCell,
},
{
field: {
name: 'last_activity_time',
fieldContext: 'trace',
fieldDataType: 'int64',
},
title: 'Last Activity',
},
{
field: { name: 'start_time', fieldContext: 'trace', fieldDataType: 'int64' },
title: 'Start Time',
},
{
field: { name: 'end_time', fieldContext: 'trace', fieldDataType: 'int64' },
title: 'End Time',
},
{
field: {
name: 'trace_duration_nano',
fieldContext: 'trace',
fieldDataType: 'int64',
},
title: 'Trace Duration',
render: renderTraceDurationCell,
},
{
field: { name: 'error_count', fieldContext: 'trace', fieldDataType: 'int64' },
title: 'Errors',
render: renderCountCell,
},
{
field: {
name: 'root_span_name',
fieldContext: 'trace',
fieldDataType: 'string',
},
title: 'Root Span Name',
},
{
field: { name: 'input', fieldContext: 'trace', fieldDataType: 'string' },
title: 'Input',
},
{
field: { name: 'output', fieldContext: 'trace', fieldDataType: 'string' },
title: 'Output',
},
];
export const AI_TRACE_VIEW_COLUMNS: TraceViewColumn[] = [
...BASE_TRACE_VIEW_COLUMNS,
...AI_ONLY_COLUMNS,
];
/**
* Hand this to `<TracesView columnSelection={…} />` to get the AI column set
* plus the Options → Edit columns picker. Module-level so its identity is
* stable across renders.
*/
export const AI_TRACE_VIEW_COLUMN_SELECTION: TraceViewColumnSelection = {
columns: AI_TRACE_VIEW_COLUMNS,
storageKey: LOCALSTORAGE.AI_TRACE_VIEW_COLUMNS,
defaultVisible: BASE_TRACE_VIEW_COLUMNS.map((column) => column.field.name),
};

View File

@@ -11,4 +11,12 @@
align-items: center;
gap: 4px;
cursor: pointer;
// Rendered as a <button> for keyboard access — strip the native chrome so it
// still reads as the inline text trigger it was.
padding: 0;
border: none;
background: none;
color: inherit;
font: inherit;
}

View File

@@ -5,6 +5,7 @@ import FieldsSelector from 'components/FieldsSelector';
import Controls, { ControlsProps } from 'container/Controls';
import { OptionsMenuConfig } from 'container/OptionsMenu/types';
import useQueryPagination from 'hooks/queryPagination/useQueryPagination';
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
import { DataSource } from 'types/common/queryBuilder';
import styles from './Controls.module.scss';
@@ -15,6 +16,7 @@ function TraceExplorerControls({
perPageOptions,
config,
showSizeChanger = true,
availableFields,
}: TraceExplorerControlsProps): JSX.Element | null {
const { t } = useTranslation(['trace']);
const [isFieldsSelectorOpen, setIsFieldsSelectorOpen] = useState(false);
@@ -30,13 +32,15 @@ function TraceExplorerControls({
<div className={styles.container}>
{config?.fieldsSelector && (
<>
<div
<button
type="button"
className={styles.optionsTrigger}
onClick={(): void => setIsFieldsSelectorOpen(true)}
data-testid="trace-view-options-trigger"
>
{t('options_menu.options')}
<Settings size="md" />
</div>
</button>
<FieldsSelector
isOpen={isFieldsSelectorOpen}
title="Edit columns"
@@ -44,6 +48,7 @@ function TraceExplorerControls({
onFieldsChange={config.fieldsSelector.onFieldsChange}
onClose={(): void => setIsFieldsSelectorOpen(false)}
signal={DataSource.TRACES}
availableFields={availableFields}
/>
</>
)}
@@ -63,20 +68,20 @@ function TraceExplorerControls({
);
}
TraceExplorerControls.defaultProps = {
config: null,
};
type TraceExplorerControlsProps = Pick<
ControlsProps,
'isLoading' | 'totalCount' | 'perPageOptions'
> & {
config?: OptionsMenuConfig | null;
showSizeChanger?: boolean;
/** Forwarded to FieldsSelector — see `availableFields` there. */
availableFields?: TelemetryFieldKey[];
};
TraceExplorerControls.defaultProps = {
config: null,
showSizeChanger: true,
availableFields: undefined,
};
export default memo(TraceExplorerControls);

View File

@@ -4,47 +4,105 @@ import { Typography } from '@signozhq/ui/typography';
import ROUTES from 'constants/routes';
import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util';
import { DEFAULT_PER_PAGE_OPTIONS } from 'hooks/queryPagination';
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
import { ListItem } from 'types/api/widgets/getQuery';
export const PER_PAGE_OPTIONS: number[] = [10, ...DEFAULT_PER_PAGE_OPTIONS];
export const columns: ColumnsType<ListItem['data']> = [
/**
* One Trace View column: the telemetry field it reads, its header, and how the
* cell renders. Callers own their column sets — Trace View has no knowledge of
* any particular product's columns.
*/
export interface TraceViewColumn {
field: TelemetryFieldKey;
title: string;
/** Defaults to `renderTraceCellValue`. */
render?: (value: unknown) => JSX.Element;
}
function isBlank(value: unknown): boolean {
return value === undefined || value === null || value === '';
}
/** Fallback cell: em dash when empty, otherwise stringified. */
export function renderTraceCellValue(value: unknown): JSX.Element {
if (isBlank(value)) {
return <Typography></Typography>;
}
return <Typography>{String(value)}</Typography>;
}
/** Nanosecond duration rendered as milliseconds. */
export function renderTraceDurationCell(value: unknown): JSX.Element {
if (isBlank(value)) {
return <Typography></Typography>;
}
return <Typography>{getMs(String(value))}ms</Typography>;
}
function renderTraceIdCell(value: unknown): JSX.Element {
if (isBlank(value)) {
return <Typography></Typography>;
}
return (
<Link
to={generatePath(ROUTES.TRACE_DETAIL, {
id: String(value),
})}
data-testid="trace-id"
>
{String(value)}
</Link>
);
}
/**
* The root-span columns Trace View renders when the caller configures no
* column selection. Matches the pre-selection behaviour exactly.
*/
export const BASE_TRACE_VIEW_COLUMNS: TraceViewColumn[] = [
{
field: {
name: 'service.name',
fieldContext: 'resource',
fieldDataType: 'string',
},
title: 'Root Service Name',
dataIndex: 'service.name',
key: 'serviceName',
},
{
field: { name: 'name', fieldContext: 'span', fieldDataType: 'string' },
title: 'Root Operation Name',
dataIndex: 'name',
key: 'name',
},
{
field: {
name: 'duration_nano',
fieldContext: 'span',
fieldDataType: 'int64',
},
title: 'Root Duration (in ms)',
dataIndex: 'duration_nano',
key: 'durationNano',
render: (duration: number): JSX.Element => (
<Typography>{getMs(String(duration))}ms</Typography>
),
render: renderTraceDurationCell,
},
{
field: { name: 'span_count', fieldContext: 'trace', fieldDataType: 'int64' },
title: 'No of Spans',
dataIndex: 'span_count',
key: 'span_count',
},
{
field: { name: 'trace_id', fieldContext: 'span', fieldDataType: 'string' },
title: 'TraceID',
dataIndex: 'trace_id',
key: 'traceID',
render: (traceID: string): JSX.Element => (
<Link
to={generatePath(ROUTES.TRACE_DETAIL, {
id: traceID,
})}
data-testid="trace-id"
>
{traceID}
</Link>
),
render: renderTraceIdCell,
},
];
/** Build antd columns, preserving the order given. */
export function buildTraceViewColumns(
columns: TraceViewColumn[],
): ColumnsType<ListItem['data']> {
return columns.map(({ field, title, render }) => ({
title,
dataIndex: field.name,
key: field.name,
render: (value: unknown): JSX.Element =>
(render ?? renderTraceCellValue)(value),
}));
}

View File

@@ -34,14 +34,26 @@ import DOCLINKS from 'utils/docLinks';
import TraceExplorerControls from '../Controls';
import { TracesLoading } from '../TraceLoading/TraceLoading';
import { columns, PER_PAGE_OPTIONS } from './configs';
import {
BASE_TRACE_VIEW_COLUMNS,
buildTraceViewColumns,
PER_PAGE_OPTIONS,
} from './configs';
import { ActionsContainer, Container } from './styles';
import useTraceViewColumns, {
TraceViewColumnSelection,
} from './useTraceViewColumns';
interface TracesViewProps {
isFilterApplied: boolean;
setWarning: Dispatch<SetStateAction<Warning | undefined>>;
setIsLoadingQueries: Dispatch<SetStateAction<boolean>>;
queryKeyRef?: MutableRefObject<any>;
/**
* Opt in to user-editable columns. Omit for the base root-span columns with
* no Options → Edit columns picker.
*/
columnSelection?: TraceViewColumnSelection;
}
function TracesView({
@@ -49,6 +61,7 @@ function TracesView({
setWarning,
setIsLoadingQueries,
queryKeyRef,
columnSelection,
}: TracesViewProps): JSX.Element {
const { stagedQuery, panelType } = useQueryBuilder();
@@ -62,6 +75,32 @@ function TracesView({
QueryParams.pagination,
);
// Column visibility is owned here rather than by useOptionsMenu, whose
// TRACES_LIST_OPTIONS storage is already claimed by List View.
const { visibleColumns, selectedFields, availableFields, onFieldsChange } =
useTraceViewColumns(columnSelection);
const fieldsSelectorConfig = useMemo(
() =>
columnSelection
? {
fieldsSelector: {
value: selectedFields,
onFieldsChange,
},
}
: null,
[columnSelection, selectedFields, onFieldsChange],
);
const tableColumns = useMemo(
() =>
buildTraceViewColumns(
columnSelection ? visibleColumns : BASE_TRACE_VIEW_COLUMNS,
),
[columnSelection, visibleColumns],
);
const transformedQuery = useMemo(
() => getListViewQuery(stagedQuery || initialQueriesMap.traces),
[stagedQuery],
@@ -76,6 +115,8 @@ function TracesView({
stagedQuery,
panelType,
paginationQueryData,
// Column visibility is deliberately absent: it is client-side only, and
// this array doubles as the parent's cancelQueries handle.
],
[
globalSelectedTime,
@@ -100,6 +141,8 @@ function TracesView({
params: {
dataSource: 'traces',
},
// No selectColumns: the backend returns all columns and visibility is
// resolved client-side, so toggling a column is refetch-free.
tableParams: {
pagination: paginationQueryData,
},
@@ -162,6 +205,8 @@ function TracesView({
isLoading={isLoading}
totalCount={responseData?.length || 0}
perPageOptions={PER_PAGE_OPTIONS}
config={fieldsSelectorConfig}
availableFields={columnSelection ? availableFields : undefined}
/>
</div>
</ActionsContainer>
@@ -190,7 +235,7 @@ function TracesView({
{(tableData || []).length !== 0 && (
<ResizeTable
loading={isLoading}
columns={columns}
columns={tableColumns}
tableLayout="fixed"
dataSource={tableData}
scroll={{ x: true }}
@@ -203,6 +248,7 @@ function TracesView({
TracesView.defaultProps = {
queryKeyRef: undefined,
columnSelection: undefined,
};
export default memo(TracesView);

View File

@@ -0,0 +1,123 @@
import { useCallback, useMemo, useState } from 'react';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
import { TraceViewColumn } from './configs';
/**
* Opt-in column selection for Trace View. Passing one enables the Options →
* Edit columns picker; omitting it leaves Trace View on its base columns with
* no picker at all.
*
* Pass a module-level constant, not an inline literal — a fresh object each
* render invalidates the memoised column set on every pass.
*/
export interface TraceViewColumnSelection {
/** Every column offered, in default display order. */
columns: TraceViewColumn[];
/** Where this caller persists visibility. Must be unique per view. */
storageKey: LOCALSTORAGE;
/** Field names visible before the user customises anything. */
defaultVisible: string[];
}
interface UseTraceViewColumnsReturn {
/** Columns to render, in the user's persisted order. */
visibleColumns: TraceViewColumn[];
/** Visible fields, for the picker's "added" list. */
selectedFields: TelemetryFieldKey[];
/** Every offered field, for the picker's "other" list. */
availableFields: TelemetryFieldKey[];
onFieldsChange: (fields: TelemetryFieldKey[]) => void;
}
function readStoredKeys(
selection: TraceViewColumnSelection | undefined,
): string[] {
if (!selection) {
return [];
}
const { storageKey, columns, defaultVisible } = selection;
const raw = getLocalStorageKey(storageKey);
if (!raw) {
return defaultVisible;
}
try {
const parsed = JSON.parse(raw) as string[];
if (!Array.isArray(parsed) || parsed.length === 0) {
return defaultVisible;
}
// Drop anything the caller no longer offers, so a stale localStorage
// entry from an earlier column set self-heals instead of rendering blank.
const allowed = new Set(columns.map((column) => column.field.name));
const filtered = parsed.filter((key) => allowed.has(key));
return filtered.length > 0 ? filtered : defaultVisible;
} catch {
return defaultVisible;
}
}
/**
* Owns Trace View column visibility client-side: toggling reorders or hides
* columns without touching the query, so it never triggers a refetch.
*/
function useTraceViewColumns(
selection?: TraceViewColumnSelection,
): UseTraceViewColumnsReturn {
const [visibleKeys, setVisibleKeys] = useState<string[]>(() =>
readStoredKeys(selection),
);
const offeredColumns = selection?.columns;
const columnsByName = useMemo(
() =>
new Map((offeredColumns ?? []).map((column) => [column.field.name, column])),
[offeredColumns],
);
// Ordered by visibleKeys, so reordering in the picker moves the column.
const visibleColumns = useMemo(
() =>
visibleKeys
.map((key) => columnsByName.get(key))
.filter((column): column is TraceViewColumn => Boolean(column)),
[columnsByName, visibleKeys],
);
const selectedFields = useMemo(
() => visibleColumns.map((column) => column.field),
[visibleColumns],
);
const availableFields = useMemo(
() => (offeredColumns ?? []).map((column) => column.field),
[offeredColumns],
);
const onFieldsChange = useCallback(
(fields: TelemetryFieldKey[]): void => {
if (!selection) {
return;
}
const nextKeys = fields.map((field) => field.name);
const keys = nextKeys.length > 0 ? nextKeys : selection.defaultVisible;
setVisibleKeys(keys);
setLocalStorageKey(selection.storageKey, JSON.stringify(keys));
},
[selection],
);
return {
visibleColumns,
selectedFields,
availableFields,
onFieldsChange,
};
}
export default useTraceViewColumns;

View File

@@ -114,23 +114,6 @@ describe('getCaretContext — stage detection', () => {
expect(ctx.partial).toBe('');
});
it('never replaces past the caret when it sits before the operator', () => {
const ctx = getCaretContext("env = 'prod'", 4);
expect(ctx.stage).toBe('operator');
expect(ctx.partial).toBe('');
expect(ctx.replaceStart).toBe(4);
expect(ctx.replaceEnd).toBe(4);
});
it('never replaces past the caret when it sits before the value', () => {
const ctx = getCaretContext("env = 'prod'", 6);
expect(ctx.stage).toBe('value');
expect(ctx.operator).toBe('=');
expect(ctx.partial).toBe('');
expect(ctx.replaceStart).toBe(6);
expect(ctx.replaceEnd).toBe(6);
});
it('detects the stage of the term under a mid-string caret', () => {
const q = "env = AND team = 'core'";
// caret right after the first `env ` (index 4) is the operator stage
@@ -159,13 +142,6 @@ describe('spliceAtCaret', () => {
expect(next).toBe("env = 'prod'");
});
it('inserts (without duplicating text) at a caret parked before a token', () => {
const q = "env = 'prod'";
const ctx = getCaretContext(q, 4);
const { next } = spliceAtCaret(q, ctx, '!= ');
expect(next).toBe("env != = 'prod'");
});
it('preserves text after the caret', () => {
const q = "env AND team = 'core'";
const ctx = getCaretContext(q, 4); // operator gap after `env`

View File

@@ -328,7 +328,7 @@ export const getCaretContext = (query: string, caret: number): CaretContext => {
fieldKey: scan.key ? scan.key.text : '',
operator: slot.operator,
partial: slot.partial,
replaceStart: Math.min(term.start + slot.replaceStartRel, pos),
replaceStart: term.start + slot.replaceStartRel,
replaceEnd: pos,
};
};

View File

@@ -17,6 +17,7 @@ import { usePageActions } from 'container/AIAssistant/pageActions/usePageActions
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
import { useOptionsMenu } from 'container/OptionsMenu';
import LeftToolbarActions from 'container/QueryBuilder/components/ToolbarActions/LeftToolbarActions';
import { AI_TRACE_VIEW_COLUMN_SELECTION } from 'container/AIObservability/TraceView/aiTraceViewColumns';
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
import Toolbar from 'container/Toolbar/Toolbar';
import {
@@ -330,6 +331,10 @@ function TracesExplorer(): JSX.Element {
setWarning={setWarning}
setIsLoadingQueries={setIsLoadingQueries}
queryKeyRef={listQueryKeyRef}
// DEMO ONLY — surfaces the AI Observability column set here because
// the AI Explorer does not exist yet. Drop this prop (and the import)
// to return Trace View to its five base columns.
columnSelection={AI_TRACE_VIEW_COLUMN_SELECTION}
/>
</div>
)}

View File

@@ -1,40 +0,0 @@
package implcloudintegration
import (
"context"
"testing"
citypes "github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestServiceDefinitionsAreValid(t *testing.T) {
store := NewServiceDefinitionStore()
for _, provider := range []citypes.CloudProviderType{
citypes.CloudProviderTypeAWS,
citypes.CloudProviderTypeAzure,
citypes.CloudProviderTypeGCP,
} {
t.Run(provider.StringValue(), func(t *testing.T) {
defs, err := store.List(context.Background(), provider)
require.NoError(t, err, "all embedded definitions must load and validate")
require.NotEmpty(t, defs, "provider should ship at least one service definition")
for _, def := range defs {
assert.NotEmpty(t, def.ID, "service definition must have an id")
assert.NotEmpty(t, def.Title, "service %q must have a title", def.ID)
// Get() must agree with List() for every service it advertises.
serviceID, err := citypes.NewServiceID(provider, def.ID)
if !assert.NoError(t, err, "service id %q must be registered in serviceid.go", def.ID) {
continue
}
got, err := store.Get(context.Background(), provider, serviceID)
require.NoError(t, err, "service %q listed but not gettable", def.ID)
assert.Equal(t, def.ID, got.ID)
}
})
}
}

View File

@@ -621,7 +621,7 @@
{
"metricName": "cloudsql.googleapis.com/database/postgresql/deadlock_count",
"temporality": "",
"timeAggregation": "rate",
"timeAggregation": "max",
"spaceAggregation": "sum",
"reduceTo": "avg"
}
@@ -882,7 +882,7 @@
{
"metricName": "cloudsql.googleapis.com/database/postgresql/transaction_count",
"temporality": "",
"timeAggregation": "rate",
"timeAggregation": "max",
"spaceAggregation": "sum",
"reduceTo": "avg"
}

View File

@@ -54,13 +54,13 @@
{
"name": "cloudsql.googleapis.com/database/postgresql/transaction_count",
"unit": "Count",
"type": "Sum",
"type": "Gauge",
"description": ""
},
{
"name": "cloudsql.googleapis.com/database/postgresql/deadlock_count",
"unit": "Count",
"type": "Sum",
"type": "Gauge",
"description": ""
},
{

View File

@@ -925,7 +925,7 @@
"metricName": "compute.googleapis.com/instance/disk/average_io_latency",
"temporality": "",
"timeAggregation": "avg",
"spaceAggregation": "max",
"spaceAggregation": "sum",
"reduceTo": "avg"
}
],

View File

@@ -1207,8 +1207,8 @@
{
"metricName": "kubernetes.io/container/restart_count",
"temporality": "",
"timeAggregation": "increase",
"spaceAggregation": "sum",
"timeAggregation": "max",
"spaceAggregation": "max",
"reduceTo": "avg"
}
],

View File

@@ -601,8 +601,8 @@
{
"metricName": "redis.googleapis.com/stats/cache_hit_ratio",
"temporality": "",
"timeAggregation": "min",
"spaceAggregation": "min",
"timeAggregation": "max",
"spaceAggregation": "sum",
"reduceTo": "avg"
}
],
@@ -788,7 +788,7 @@
"metricName": "redis.googleapis.com/commands/usec_per_call",
"temporality": "",
"timeAggregation": "max",
"spaceAggregation": "max",
"spaceAggregation": "sum",
"reduceTo": "avg"
}
],
@@ -945,7 +945,7 @@
{
"metricName": "redis.googleapis.com/commands/calls",
"temporality": "",
"timeAggregation": "rate",
"timeAggregation": "max",
"spaceAggregation": "sum",
"reduceTo": "avg"
}
@@ -1044,8 +1044,8 @@
{
"metricName": "redis.googleapis.com/stats/reject_connections_count",
"temporality": "",
"timeAggregation": "rate",
"spaceAggregation": "sum",
"timeAggregation": "avg",
"spaceAggregation": "avg",
"reduceTo": "avg"
}
],
@@ -1213,4 +1213,4 @@
"refreshInterval": "",
"links": []
}
}
}